Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/floofies/crud-mixin
CRUD mixins for JavaScript. Use cases: Manipulating data, creating plugin systems, and creating data-oriented coupling middleware.
https://github.com/floofies/crud-mixin
crud javascript middleware mixin plugins
Last synced: about 2 months ago
JSON representation
CRUD mixins for JavaScript. Use cases: Manipulating data, creating plugin systems, and creating data-oriented coupling middleware.
- Host: GitHub
- URL: https://github.com/floofies/crud-mixin
- Owner: Floofies
- License: mit
- Created: 2024-03-15T22:05:17.000Z (10 months ago)
- Default Branch: main
- Last Pushed: 2024-03-19T02:54:31.000Z (10 months ago)
- Last Synced: 2024-03-19T03:41:37.377Z (10 months ago)
- Topics: crud, javascript, middleware, mixin, plugins
- Language: TypeScript
- Homepage:
- Size: 1.95 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# CRUD Mixins
CRUD mixins for JavaScript. Use cases: Manipulating data, creating plugin systems, and creating data-oriented coupling middleware.
- [Principles of Operation](#principles-of-operation)
- [Mixin Class](#mixin-class)
- [MixinPlugin Class](#mixinplugin-class)
- [MixinCompositor Class](#mixincompositor-class)
- [Theory of Operation](#theory-of-operation)
- [No-Override Principle](#no-override-principle)
- [CRUD Mixin Plugins](#crud-mixin-plugins)# Principles of Operation
The classes `Mixin`, `MixinPlugin`, and `MixinCompositor` are exported by the `mixin.js` module, and can be combined by your application to compose a data-oriented plugin system. `Mixin` instances implement CRUD operations as methods.
## `Mixin` Class
```JS
new Mixin( type, { [ make = null, get = null, set = null, del = null ] });
```When `Mixin` is instantiated, the methods supplied to the constructor are assigned to the instance.
### `Mixin` Constructor Parameters
- `type` **String**
The unique type definition string used to identify the mixin and its data.
Constructor parameters are optionally supplied via an object with the following properties:
- `make` _(Optional)_ **Function**:
- For creating new data of a specific type.
- `get` _(Optional)_ **Function**:
- For reading persistent data of a specific type.
- `set` _(Optional)_ **Function**:
- For writing persistent data of a specific type.
- `del` _(Optional)_ **Function**:
- For deleting persistent data of a specific type.### `patchProperty` Instance Method
```JS
mixin.patchProperty( mixin2, accessor );
```Implements compositing between instances of `Mixin`; the method attempts to copy properties from the given object to the parent object if that property is missing from the parent.
### `patchMixin` Instance Method
```JS
mixin.patchMixin( mixin2 );
```Calls `this.patchProperty` on all of its own CRUD methods.
### Example `Mixin` Usage
A real-world example of CRUD mixins is HockIt's `config.js` module and its `JSONConfig` mixin.
HockIt loads initial configuration data from a JSON file, and it does so via the mixin `JSONConfig`. All methods in Mixin `JSONConfig` are implemented by Plugin `JSONConfigPlugin` (named `config.js`).
Here is a very simplified example of how the plugin is implemented:
```JS
// Import the Mixin and MixinPlugin classes:
const { Mixin } = require("crudMixin.js");// The filesystem path of the default configuration file:
const defaultConfPath = __dirname + "/config.json";// A Mixin for creating the default configuration object:
const JSONConfig = new Mixin("JSONConfig", {
make: () => fs.readFileSync(defaultConfPath).toString("utf8");
});// How the Mixin should be used:
const config = JSONConfig.make();
```## `MixinPlugin` Class
```JS
new MixinPlugin({ [ loadCondition = null, initPlugin = null, allowOverride = false, ...mixins ] });
```### `MixinPlugin` Constructor Parameters
Constructor parameters are supplied via an object with the following properties:
- `loadCondition` _(Optional)_ **Function**:
- When the given function returns false, the plugin will not be loaded.
- `initPlugin` _(Optional)_ **Function**:
- This function is arbitrarily executed after mixins are patched. Use this function to perform preparatory steps.
- `allowOverride`
- When set to `true`, automatically marks all CRUD methods of all child mixins as overwritable.
- `...mixins` **Mixin**:
- Instances of `Mixin`.### Example `MixinPlugin` Usage
In this example scenario, we construct a "guestbook" application by combining functionalities supplied by two different plugins.
The following mixin plugin exports a `MyGreeting` mixin with its `make` method defined.
```JS
// Import the Mixin and MixinPlugin classes:
const { Mixin, MixinPlugin } = require("../crudMixin.js");// Instantiate a new Mixin with type definition "MyGreeting":
const MyGreeting = new Mixin("MyGreeting",
// Define the "make" method to output "Hello world!" or use a custom name:
make = (name) => `Hello ${name}!`
);// Export the Mixin inside a new MixinPlugin:
module.exports = new MixinPlugin({
// Name of the plugin:
name: "MyGreetingPlugin",
// Version of the plugin:
version: "1.2.3",
// The mixin to be exported:
MyGreeting
});
```The following mixin plugin exports a similar `MyGreeting` mixin, however it lacks `make` and instead supplies `get` and `set` methods.
The `set` method logs greetings into a "guestbook" Map, whereas `get` retrirves previously logged greetings.
```JS
// Import the Mixin and MixinPlugin classes:
const { Mixin, MixinPlugin } = require("../crudMixin.js");// Local variable for storing the data:
const guestbook = new Map();// Instantiate a new Mixin with type definition "MyGreeting":
const MyGreeting = new Mixin("MyGreeting",
// Define the "get" method to retrieve a greeting by name:
get = (name) => {
return guestbook.get(name);
},
// Define the "set" method to store a greeting by name:
set = (name, greeting = null) => {
guestbook.set(name, greeting);
}
);// Export the Mixin inside a new MixinPlugin:
module.exports = new MixinPlugin({
name: "MyStoragePlugin",
version: "4.5.6",
MyGreeting
});
```## `MixinCompositor` Class
An instance of `MixinCompositor` composites mixins together, effectively "mixing" them. If at least two mixins are of the same type, then the constructor or `addMixin` method joins them together via calling the `patchMixin` methods of each mixin. The resulting instance of `MixinCompositor` assigns all given mixins to itself for easy access.
Mixins and mixin plugins can be manually added via methods `addMixin` and `addPlugin`.
### `addMixin` Instance Method
### `addPlugin` Instance Method
### `initMixinPlugins` Instance Method
### Example `MixinCompositor` Usage
Now in our main module (the business logic) we can combine the mixins and use them:
```JS
// Import the Mixin, MixinPlugin, and SetupMixin classes:
const { Mixin, MixinPlugin, MixinCompositor } = require("../crudMixin.js");// Instantiate a new Mixin with type definition "MyGreeting".
// This instance lacks method definitions, but will be populated by "MixinCompositor":
const MyGreeting = new Mixin("MyGreeting");// Define a plugin to contain the Mixins you intend to use:
const myPlugin = {
name: "MyGuestbookPlugin",
version: "7.8.9",
MyGreeting
};// Cross-inherit the Mixins via MixinCompositor, which returns a Promise:
const setupPromise = new MixinCompositor(myPlugin);// Now that the "MyGreeting" Mixin is cross-inherited between plugins, we can use it.
// This is the main logic of our application:
setupPromise.then(() => {
const name = "Testy McFly";
// Generate a new greeting string:
const greeting = MyGreeting.make(name);
// Add the greeting to the guestbook:
MyGreeting.set(greeting);
// Logs "Hello Testy McFly!" to console:
console.log(MyGreeting.get(name));
});
```# Theory of Operation
`Mixin` is a class which implements CRUD methods for manipulating any non-volatile/persistent data.
In object-oriented programming, a mixin is a way to reuse a class's code in multiple class hierarchies. Mixins are a powerful concept that allows for flexible and modular code organization, and promoting code reuse.
In this context, CRUD operations are "mixed in" with the `Mixin` objects that lack those methods. This combination ensures that each object contains an equivalent set of methods without overriding existing ones.
CRUD Mixins should be created as a single-instance uniqueness type within each module, and should be preserved in-memory for any subsequent needs. Instances of `Mixin` should be _invoked_ as a result of user input, but never _created_ as a result of user input.
The `patchProperty(mixin, accessor)` and `patchMixin(mixin)` methods implement compositing between instances of `Mixin`; the methods attempt to copy methods from the given mixin to the parent mixin if those methods are missing.
## No-Override Principle
Instances of `Mixin` store a write-protection descriptor object in the property `writeProtectedMethods`, which is an object containing CRUD method accessors mapped to booleans to indicate which methods are write-protected. The write-protection descriptor is utilized in the method `patchProperty`.
If the parent mixin already has a method, then `patchProperty` and `patchMixin` will opt to skip writing that method. If overwriting is desired anyways, then write-protection must be disabled for that method. That is because `patchProperty` enforces a no-override principle when compositing mixins, such that by default each mixin is not allowed to overwrite another mixin's methods. Conversely, any locally declared instances of `Mixin` can still have their own methods defined, and they won't be overwritten after they are exported. This also means that as mixins are patched, each missing method is patched semi-permanently and can't be patched again unless the restriction is bypassed.
The no-override restriction may be bypassed manually via three different ways:
1. Exporting the parent `MixinPlugin` with `allowOverride = true`.
2. Calling the `Mixin` constructor with `allowOverride = true` as an input operand.
3. Calling the `Mixin` instance method `allowOverride()`:- Example: `MyMixin.allowOverride(allowMake = true, allowGet = true, allowSet = true, allowDel = true)`
`patchProperty` allows a single overwrite of each CRUD method before overwrite-protecting them again.
### CRUD Mixin Plugins
Plugins are composed of exported `MixinPlugin` instances which are later parsed by `MixinCompositor`.
Each plugin module pre-loads functions into local instances of `Mixin` which are then packaged into a `MixinPlugin` instance and exported. The `MixinPlugin` instances are later imported into an instance of `MixinCompositor`.
A plugin module exports a `MixinPlugin` instance which contains `Mixin` instances and the following:
- `name`: A string name for the plugin. Helps to differentiate stack traces.
- `version`: A semantic versioning string. It is arbitrary for now.
- `allowOverride` (Optional): A boolean, if set to `true`, allows methods of the given mixins to be overridden at least once.
- `initPlugin` (Optional): A function to execute after all `Mixin` instances have been prepared.
- `loadCondition` (Optional): A function which cancels plugin loading if it returns falsy.