fix: hmr gathered

This commit is contained in:
cha0s 2023-12-08 05:32:28 -06:00
parent d5d0e8d74e
commit 6374af578e
5 changed files with 131 additions and 90 deletions

View File

@ -92,12 +92,21 @@ export const hooks = {
}
},
/**
* Invoked when a gathered set is HMR'd.
* @param {constructor} gathered The gathered set.
* @param {string} hook The gather hook; e.g. `@flecks/db/server.models`.
*/
'@flecks/core.hmr.gathered': (gathered, hook) => {
// Do something with the gathered set...
},
/**
* Invoked when a gathered class is HMR'd.
* @param {constructor} Class The class.
* @param {string} hook The gather hook; e.g. `@flecks/db/server.models`.
*/
'@flecks/core.hmr.gathered': (Class, hook) => {
'@flecks/core.hmr.gathered.class': (Class, hook) => {
// Do something with Class...
},

View File

@ -169,6 +169,15 @@ export default class Flecks {
);
}
checkAndDecorateRawGathered(hook, raw, check) {
// Gather classes and check.
check(raw, hook);
// Decorate and check.
const decorated = this.invokeComposed(`${hook}.decorate`, raw);
check(decorated, `${hook}.decorate`);
return decorated;
}
/**
* Destroy this instance.
*/
@ -285,10 +294,7 @@ export default class Flecks {
}
// Gather classes and check.
const raw = this.invokeMerge(hook);
check(raw, hook);
// Decorate and check.
const decorated = this.invokeComposed(`${hook}.decorate`, raw);
check(decorated, `${hook}.decorate`);
const decorated = this.checkAndDecorateRawGathered(hook, raw, check);
// Assign unique IDs to each class and sort by type.
let uid = 1;
const ids = {};
@ -311,9 +317,15 @@ export default class Flecks {
[ByType]: types,
};
// Register for HMR?
if (module.hot) {
hotGathered.set(hook, {idProperty, gathered, typeProperty});
}
hotGathered.set(
hook,
{
check,
idProperty,
typeProperty,
gathered,
},
);
debug("gathered '%s': %O", hook, Object.keys(gathered[ByType]));
return gathered;
}
@ -654,10 +666,6 @@ export default class Flecks {
/**
* Refresh a fleck's hooks, configuration, and any gathered classes.
*
* @example
* module.hot.accept('@flecks/example', async () => {
* flecks.refresh('@flecks/example', require('@flecks/example'));
* });
* @param {string} fleck
* @param {object} M The fleck module
* @protected
@ -671,9 +679,7 @@ export default class Flecks {
// Write config.
this.configureFleckDefaults(fleck);
// HMR.
if (module.hot) {
this.refreshGathered(fleck);
}
this.refreshGathered(fleck);
}
/**
@ -688,23 +694,36 @@ export default class Flecks {
value: [
hook,
{
check,
idProperty,
gathered,
typeProperty,
},
],
} = current;
const updates = this.invokeFleck(hook, fleck);
if (updates) {
debug('updating gathered %s from %s...', hook, fleck);
const entries = Object.entries(updates);
for (let i = 0, [type, Class] = entries[i]; i < entries.length; ++i) {
let raw;
// If decorating, gather all again
if (this.fleckImplements(fleck, `${hook}.decorate`)) {
raw = this.invokeMerge(hook);
debugSilly('%s implements %s.decorate', fleck, hook);
}
// If only implementing, gather and decorate.
else if (this.fleckImplements(fleck, hook)) {
raw = this.invokeFleck(hook, fleck);
debugSilly('%s implements %s', fleck, hook);
}
if (raw) {
const decorated = this.checkAndDecorateRawGathered(hook, raw, check);
debug('updating gathered %s from %s... %O', hook, fleck, decorated);
const entries = Object.entries(decorated);
entries.forEach(([type, Class]) => {
const {[type]: {[idProperty]: id}} = gathered;
const Subclass = wrapGathered(Class, id, idProperty, type, typeProperty);
// eslint-disable-next-line no-multi-assign
gathered[type] = gathered[id] = gathered[ById][id] = gathered[ByType][type] = Subclass;
this.invoke('@flecks/core.hmr.gathered', Subclass, hook);
}
this.invoke('@flecks/core.hmr.gathered.class', Subclass, hook);
});
this.invoke('@flecks/core.hmr.gathered', gathered, hook);
}
}
}

View File

@ -1,6 +1,8 @@
import {ByType, D} from '@flecks/core';
import Sequelize from 'sequelize';
import register from './register';
const debug = D('@flecks/db/server/connection');
export async function createDatabaseConnection(flecks) {
@ -34,16 +36,6 @@ export async function createDatabaseConnection(flecks) {
logging: false,
...config,
});
const Models = flecks.get('$flecks/db.models')[ByType];
Object.values(Models)
.filter((Model) => Model.attributes)
.forEach((Model) => {
Model.init(Model.attributes, {
sequelize,
underscored: true,
...(Model.modelOptions || {}),
});
});
// eslint-disable-next-line no-constant-condition
while (true) {
try {
@ -58,64 +50,8 @@ export async function createDatabaseConnection(flecks) {
});
}
}
const dependencies = {};
Object.values(Models).forEach((Model) => {
Model.associate(Models);
});
Object.values(Models).forEach((Model) => {
const associations = Object.entries(Model.associations);
for (let i = 0; i < associations.length; i++) {
if (associations[i][1].isSelfAssociation) {
// eslint-disable-next-line no-continue
continue;
}
if ('BelongsToMany' === associations[i][1].associationType) {
if (associations[i][1].through.model.isManagedByFlecks) {
const {name} = associations[i][1].through.model;
if (!dependencies[name]) {
dependencies[name] = new Set();
}
dependencies[name].add(Model.name);
}
}
if ('BelongsTo' === associations[i][1].associationType) {
if (!dependencies[Model.name]) {
dependencies[Model.name] = new Set();
}
dependencies[Model.name].add(associations[i][1].target.name);
}
}
});
const entries = Object.values(Models);
let lastLength = entries.length;
while (entries.length > 0) {
for (let i = 0; i < entries.length; i++) {
const Model = entries[i];
if (
!dependencies[Model.name]
|| 0 === dependencies[Model.name].length
) {
// eslint-disable-next-line no-await-in-loop
await Model.sync();
const dependents = Object.keys(dependencies);
for (let j = 0; j < dependents.length; j++) {
const dependent = dependents[j];
if (dependencies[dependent].has(Model.name)) {
dependencies[dependent].delete(Model.name);
if (0 === dependencies[dependent].size) {
delete dependencies[dependent];
}
}
}
entries.splice(i, 1);
break;
}
}
if (entries.length === lastLength) {
throw new TypeError(`@flecks/db circular dependencies: '${entries.join("', '")}'`);
}
lastLength = entries.length;
}
const Models = flecks.get('$flecks/db.models')[ByType];
await register(Models, sequelize);
debug('synchronizing...');
await sequelize.sync();
debug('synchronized');

View File

@ -0,0 +1,71 @@
async function register(Models, sequelize) {
Object.values(Models)
.filter((Model) => Model.attributes)
.forEach((Model) => {
Model.init(Model.attributes, {
sequelize,
underscored: true,
...(Model.modelOptions || {}),
});
});
const dependencies = {};
Object.values(Models).forEach((Model) => {
Model.associate(Models);
});
Object.values(Models).forEach((Model) => {
const associations = Object.entries(Model.associations);
for (let i = 0; i < associations.length; i++) {
if (associations[i][1].isSelfAssociation) {
// eslint-disable-next-line no-continue
continue;
}
if ('BelongsToMany' === associations[i][1].associationType) {
if (associations[i][1].through.model.isManagedByFlecks) {
const {name} = associations[i][1].through.model;
if (!dependencies[name]) {
dependencies[name] = new Set();
}
dependencies[name].add(Model.name);
}
}
if ('BelongsTo' === associations[i][1].associationType) {
if (!dependencies[Model.name]) {
dependencies[Model.name] = new Set();
}
dependencies[Model.name].add(associations[i][1].target.name);
}
}
});
const entries = Object.values(Models);
let lastLength = entries.length;
while (entries.length > 0) {
for (let i = 0; i < entries.length; i++) {
const Model = entries[i];
if (
!dependencies[Model.name]
|| 0 === dependencies[Model.name].length
) {
// eslint-disable-next-line no-await-in-loop
await Model.sync(); await Model.sync();
const dependents = Object.keys(dependencies);
for (let j = 0; j < dependents.length; j++) {
const dependent = dependents[j];
if (dependencies[dependent].has(Model.name)) {
dependencies[dependent].delete(Model.name);
if (0 === dependencies[dependent].size) {
delete dependencies[dependent];
}
}
}
entries.splice(i, 1);
break;
}
}
if (entries.length === lastLength) {
throw new TypeError(`@flecks/db circular dependencies: '${entries.join("', '")}'`);
}
lastLength = entries.length;
}
}
export default register;

View File

@ -1,5 +1,6 @@
import {createDatabaseConnection} from './connection';
import containers from './containers';
import register from './register';
export {DataTypes as Types, Op, default as Sequelize} from 'sequelize';
@ -36,6 +37,11 @@ export const hooks = {
*/
username: undefined,
}),
'@flecks/core.hmr.gathered': (gathered, hook, flecks) => {
if ('@flecks/db/server.models' === hook) {
register(gathered, flecks.get('$flecks/db/sequelize'));
}
},
'@flecks/core.starting': (flecks) => {
flecks.set('$flecks/db.models', flecks.gather(
'@flecks/db/server.models',