@the-oz/globs
TypeScript icon, indicating that this package has built-in type declarations

1.1.0 • Public • Published

Globs Framework

A Typescript implementation of globs framework

This is a metamodel framework.

The goal of this framework is to replace beans by an object call a Glob. This Glob is a kind of map. The key to access a field is not a string but an instance of a Field. These Fields (for a given 'bean') are contained in an instance of a GlobType. GlobType and Field contains additional custom information.

Thanks to that we can write generic code not magic;-) which can be serialized in XMl/Json/binary and inserted in a DB. It can also be used to map object to other object using some configuration files. There is multiple way to create a GlobType.

Getting started

Build library locally

cd globframework.ts
npm run build && npm run fix && npm pack

Use Globs in your project

The tarball glob.tgz generated previously must be moved at the root of the repo that uses it. Then, add globs to your dependencies:

{
    "dependencies": {
        "globs": "file:./globs-X.X.X.tgz"
    }
}

Finally, install reflect-metadata

npm install --save-dev reflect-metadata

If using with create-react-app

Globsframework uses experimental features such as decorators and metadata. As such, additional babel loaders are needed in order to use the library. In case you are using React, the easiest solution is to install a tool such as craco with the necessary loaders:

npm install --save-dev @babel/plugin-proposal-class-properties @craco/craco

Then update your package.json with the following

{
    "craco": {
      "babel": {
        "plugins": [
            "@babel/plugin-proposal-class-properties"
        ]
      }
    }
}

If you plan on using decorators in the project, then update your package.json with the following then install the missing lib

{
    "craco": {
      "babel": {
        "plugins": [
          [
            "@babel/plugin-proposal-decorators",
            {
              "legacy": true
            }
          ],
          [
            "@babel/plugin-proposal-class-properties",
            {
              "loose": true
            }
          ]
        ]
      }
    }
}
npm install --save-dev @babel/plugin-proposal-decorators

Alternatively, you can use customize-cra package along with con react-scripts-rewired. Then, in the config-overrides.js file:

const { override, addExternalBabelPlugin } = require('customize-cra')

const customLoader = (config) => {
    // do somethging
    return config
}

module.exports = override(
    addExternalBabelPlugin('@babel/plugin-proposal-class-properties'),
    customLoader,
)

GLOSSARY

  • GlobType: an entity with a name and fields. It can be instantiated in a glob
  • Field: an entity with a name data type
  • KeyField: any field which is considered part of the key (i.e. id/key in a relational db), it cannot change once created (a key uniquely defines an instance, changing it is equivalent to deal with another instance)
  • Glob: a data instance holding values that respect its corresponding globType structure
  • Annotation: an annotation defines a specific behaviour a globType or a field can have

Globs in a nutshell

Globs are used to hold data. Glob-types are the entity used to give structure to that data. Each glob-type is composed of fields which shapes the data that the glob-type instance (the glob) will hold.

Each of the glob-type fields can be itself a glob.

How to create a glob using the primitives

The following snippet shows of a glob-type with two fields is built as well as how a glob of can be instantiated from that type and its fields set to certain value

    const globType: GlobType = GlobTypeBuilderFactory.create('integerString')
        .addStringField('string')
        .addIntegerField('integer')
        .build()
    const glob: Glob = globType.instantiate()
    const field1: StringField = globType.getField('string').asStringField()
    const field2: IntegerField = globType.getField('integer').asIntegerField()
    glob.set(field1, 'hello')
    glob.set(field2, 1)

How to create a glob with the glob-model generator

import { loadGlobType } from './Loader'
import { DoubleField } from './DoubleField'

@loadGlobType
class ComplexNumber {
    static TYPE = globType()
    static REAL = doubleField()
    static COMPLEX = doubleField()
    static create(real: number, complex: number) {
        return this.TYPE.instantiate()
            .set(this.REAL, real)
            .set(this.COMPLEX, complex)
    }
    static show(glob: Glob) {
        return `${glob.getValue(this.REAL)} + ${glob.getValue(
            this.COMPLEX
        )}*i`
    }
}

const num = ComplexNumber.create(1, 2)
ComplexNumber.show(num) // '1 + 2*i'

Globs provide type checking

In the snippet of code above, we set the value of a string field to "hello". If we were to set the value to any other type, an error would be raised:

    const globType: GlobType = GlobTypeBuilderFactory.create('string-glob-type')
        .addStringField('string')
        .build()
    const glob: Glob = globType.instantiate()
    const field1: StringField = globType.getField('string').asStringField()
    glob.set(field1, 1) // throws an error

Globs provide a system of annotations

An annotation is a special kind of glob. We use annotation to add any sort of meta information to our system. For instance, if we are using a field called user_name which is actually saved as the column name on the table user in our database, we can provide an annotation that will be able to translate the name on the fly while keeping the original structure unchanged.

Annotations are carried over by glob-types and by fields.

We access annotations through keys. The keys are used to compare globs between them. For example, java implementation of globs creates keys by hashing a glob's globType and keyFields.

KeyFields are Fields used to describe the structure of annotations' specific information. In other words a keyField describes a value which is a key itself (check).

Serialization & Deserialization

Glob

import { JsonSerializer } from './JsonSerializer'
import { JsonDeserializer } from './JsonDeserializer'

const globType: GlobType = GlobTypeBuilderFactory.create('aType')
    .addStringArrayField('stringArray')
    .addStringField('id')
    .addDoubleField('value')
    .build()
const data: MutableGlob = globType.instantiate()

const stringArrayField: StringArrayField = globType
    .getField('stringArray')
    .asStringArrayField()
data.set(stringArrayField, ['hello'])

const stringField = globType.getField('id').asStringField()
data.set(stringField, '1234')

const doubleField = globType.getField('value').asStringField()
data.set(doubleField, 3.141592)

const json = JsonSerializer.glob().serialize(data)

t.deepEqual(
    json,
    `{"_kind":"aType","stringArray":["hello"],"id":"1234","value":3.141592}`)

const deserialized = JsonDeserializer.glob(
    [globType]
).deserialize(json)

t.deepEqual(deserialized, data)

GlobType

import { JsonSerializer } from './JsonSerializer'
import { JsonDeserializer } from './JsonDeserializer'

const annotation = FieldNameAnnotationBuilder.init('_id').instance
const globType: GlobType = GlobTypeBuilderFactory.create('aType')
    .addStringArrayField('stringArray')
    .addStringField('id', [annotation])
    .addDoubleField('value')
    .build()

const globTypeSerializer = JsonSerializer.globType()
const json = globTypeSerializer.serialize(globType)
t.deepEqual(
    json,
    '{"kind":"aType","fields":[' +
    '{"name":"stringArray","type":"stringArray"},' +
    '{"name":"id","type":"string","annotations":[' +
    '{"_kind":"fieldNameAnnotation","name":"_id"}]},' +
    '{"name":"value","type":"double"}]}'
)

const deserialized = JsonDeserializer.globType([
      annotation.getGlobType()
    ]).deserialize(json)

t.deepEqual(deserialized, globType)

Local development

Install node modules:

npm install

Run library tests

npm run test:unit

Run tests

npm run test

Watch tests

npm run watch:test

Generate documentation:

npm run doc

Known Issues

Files Import

[] fix default exports so that we do not get TypeError: Class extends value undefined is not a constructor or null. In particular, we have this error when importing files in errors that use the default imports (import {X, Y, Z} from '../..') instead of import {X} from '../../X'

Next Steps

Codebase

  • add missing annotations
  • add annotation to run action at the end of loadGlobType
import { loadGlobType } from './Loader'
import { globType, stringField } from './Entities'

@loadGlobType()
class Shop {
    TYPE = globType()
    NAME = stringField()
    URL = stringField()
    
    // TODO: run the following automatically @loadAction()
    logInfo() {
      console.log("generated glob type")
      console.log(Shop.TYPE.getName())
    }
}

Publish on NPM

The library is versioned using semver and conventional commits. To publish on NPM we use standard-version.

In essence, all it takes to publish a major version is:

npm run npmpublish
git push --follow-tags origin master && npm publish

If building an alpha release for instance, you would use:

npm run npmpublish -- --prerelease --alpha

Documentation

  • add table of content

Readme

Keywords

none

Package Sidebar

Install

npm i @the-oz/globs

Weekly Downloads

1

Version

1.1.0

License

MIT

Unpacked Size

790 kB

Total Files

892

Last publish

Collaborators

  • nicolas.bureau
  • jeanchal
  • iohanai
  • yellowbear
  • mathieu-chauvet-theoz
  • victornitu