rads-db 3.2.18 → 3.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/config.cjs CHANGED
@@ -459,7 +459,8 @@ function verifyDefaultValueType(field, ctx2) {
459
459
  throw new TypeError(`Default value type is different from field type: '${type}'`);
460
460
  }
461
461
  } else {
462
- if (supportedPrimitiveTypes.includes(type) && getPrimitiveTypeFromDefaultValue(defaultValue2) !== type) {
462
+ const normalizedType = type.startsWith("Record<") ? "object" : type;
463
+ if (supportedPrimitiveTypes.includes(type) && getPrimitiveTypeFromDefaultValue(defaultValue2) !== normalizedType) {
463
464
  throw new Error(`Default value type is different from field type: '${type}'`);
464
465
  }
465
466
  if (!ctx2.result[type] && ctx2.typeNodesMap[type])
package/dist/config.mjs CHANGED
@@ -451,7 +451,8 @@ function verifyDefaultValueType(field, ctx2) {
451
451
  throw new TypeError(`Default value type is different from field type: '${type}'`);
452
452
  }
453
453
  } else {
454
- if (supportedPrimitiveTypes.includes(type) && getPrimitiveTypeFromDefaultValue(defaultValue2) !== type) {
454
+ const normalizedType = type.startsWith("Record<") ? "object" : type;
455
+ if (supportedPrimitiveTypes.includes(type) && getPrimitiveTypeFromDefaultValue(defaultValue2) !== normalizedType) {
455
456
  throw new Error(`Default value type is different from field type: '${type}'`);
456
457
  }
457
458
  if (!ctx2.result[type] && ctx2.typeNodesMap[type])
@@ -27,24 +27,74 @@ function verifyEventSourcingSetup(schema, effects, options) {
27
27
  effects[eventEntityName].push(effect);
28
28
  }
29
29
  }
30
+ function groupEventsByAggId(docs, aggregateRelationField, eventEntityName) {
31
+ const docsByAggId = {};
32
+ const docsById = {};
33
+ for (const d of docs) {
34
+ if (!d.doc.id || docsById[d.doc.id]) continue;
35
+ docsById[d.doc.id] = d.doc;
36
+ const aggId = d.doc[aggregateRelationField].id;
37
+ if (!aggId) throw new Error(`Missing ${eventEntityName}.${aggregateRelationField}.id`);
38
+ if (d.oldDoc && aggId !== d.oldDoc[aggregateRelationField].id) {
39
+ throw new Error(`Field ${eventEntityName}.${aggregateRelationField}.id cannot be changed`);
40
+ }
41
+ if (!docsByAggId[aggId]) docsByAggId[aggId] = [];
42
+ docsByAggId[aggId].push(d.doc);
43
+ }
44
+ return {
45
+ docsByAggId,
46
+ docsById
47
+ };
48
+ }
49
+ function applyEventToAggregate(ev, aggDoc, aggId, p) {
50
+ const mergedDoc = (0, _radsDb.merge)(aggDoc || {
51
+ id: aggId
52
+ }, ev.change);
53
+ const newAggDoc = p.context.validators[p.entityName](mergedDoc);
54
+ if (!p.options.freezeEvent?.(ev, p.context, p.ctx)) {
55
+ ev.beforeChange = aggDoc;
56
+ const originalChange = ev.change;
57
+ ev.change = (0, _radsDb.diff)(newAggDoc, aggDoc);
58
+ handleKeepHistory(p.schema[p.entityName].keepHistoryFields, originalChange, ev);
59
+ }
60
+ if (p.ctx.skipPrecomputed) {
61
+ for (const field of p.schema[p.entityName].precomputedFields || []) {
62
+ if (p.ctx.skipPrecomputed.includes(field) && mergedDoc[field] !== void 0) {
63
+ newAggDoc[field] = mergedDoc[field];
64
+ }
65
+ }
66
+ }
67
+ return newAggDoc;
68
+ }
69
+ function buildAggregateFromEvents(events, aggId, docsById, existingAggregatesById, p) {
70
+ if (events[0].type !== "creation") throw new Error(`First event must have type = "creation". (type: ${events[0].type}, id: ${events[0].id})`);
71
+ if (events.slice(1).some(ev => ev.type === "creation")) {
72
+ throw new Error(`Only first event may have type = "creation"`);
73
+ }
74
+ let aggDoc;
75
+ for (const ev of events) {
76
+ const newAggDoc = applyEventToAggregate(ev, aggDoc, aggId, p);
77
+ if (p.options.applyEventIf && !p.options.applyEventIf(ev, p.context, p.ctx)) continue;
78
+ aggDoc = newAggDoc;
79
+ if (!p.context.options.keepNulls) (0, _radsDb.cleanEntity)(aggDoc);
80
+ }
81
+ if (!aggDoc) return null;
82
+ return {
83
+ aggDoc,
84
+ events,
85
+ updatedEvents: events.filter(ev => docsById[ev.id]),
86
+ oldAggDoc: existingAggregatesById[aggDoc.id]
87
+ };
88
+ }
30
89
  function getEffectFor(entityName, aggregateRelationField, eventEntityName, schema, options) {
31
90
  return {
32
91
  featureName: "eventSourcing",
33
92
  async beforePut(context, docs, ctx) {
34
- const docsByAggId = {};
35
- const docsById = {};
36
- for (const d of docs) {
37
- if (!d.doc.id || docsById[d.doc.id]) continue;
38
- docsById[d.doc.id] = d.doc;
39
- const aggId = d.doc[aggregateRelationField].id;
40
- if (!aggId) throw new Error(`Missing ${eventEntityName}.${aggregateRelationField}.id`);
41
- if (d.oldDoc && aggId !== d.oldDoc[aggregateRelationField].id) {
42
- throw new Error(`Field ${eventEntityName}.${aggregateRelationField}.id cannot be changed`);
43
- }
44
- if (!docsByAggId[aggId]) docsByAggId[aggId] = [];
45
- docsByAggId[aggId].push(d.doc);
46
- }
47
- const existingEvents = await context.drivers[eventEntityName].getAll({
93
+ const {
94
+ docsByAggId,
95
+ docsById
96
+ } = groupEventsByAggId(docs, aggregateRelationField, eventEntityName);
97
+ const [existingEvents, existingAggregates] = await Promise.all([context.drivers[eventEntityName].getAll({
48
98
  where: {
49
99
  [aggregateRelationField]: {
50
100
  id_in: _lodash.default.keys(docsByAggId)
@@ -53,45 +103,24 @@ function getEffectFor(entityName, aggregateRelationField, eventEntityName, schem
53
103
  id_in: docs.map(d => d.doc.id)
54
104
  }
55
105
  }
56
- }, ctx);
57
- const existingEventsByAggId = _lodash.default.groupBy(existingEvents, ev => ev[aggregateRelationField].id);
58
- const existingAggregates = await context.drivers[entityName].getAll({
106
+ }, ctx), context.drivers[entityName].getAll({
59
107
  where: {
60
108
  id_in: _lodash.default.keys(docsByAggId)
61
109
  }
62
- }, ctx);
110
+ }, ctx)]);
111
+ const existingEventsByAggId = _lodash.default.groupBy(existingEvents, ev => ev[aggregateRelationField].id);
63
112
  const existingAggregatesById = _lodash.default.keyBy(existingAggregates, "id");
64
- const result = [];
65
- for (const aggId in docsByAggId) {
113
+ const processor = {
114
+ context,
115
+ entityName,
116
+ schema,
117
+ options,
118
+ ctx
119
+ };
120
+ const result = _lodash.default.compact(_lodash.default.keys(docsByAggId).map(aggId => {
66
121
  const events = _lodash.default.orderBy([...docsByAggId[aggId], ...(existingEventsByAggId[aggId] || [])], ["date"], "asc");
67
- if (events[0].type !== "creation") throw new Error(`First event must have type = "creation". (type: ${events[0].type}, id: ${events[0].id})`);
68
- if (events.slice(1).some(ev => ev.type === "creation")) {
69
- throw new Error(`Only first event may have type = "creation"`);
70
- }
71
- let aggDoc;
72
- for (const ev of events) {
73
- const newAggDoc = context.validators[entityName]((0, _radsDb.merge)(aggDoc || {
74
- id: aggId
75
- }, ev.change));
76
- ev.beforeChange = aggDoc;
77
- const originalChange = ev.change;
78
- ev.change = (0, _radsDb.diff)(newAggDoc, aggDoc);
79
- handleKeepHistory(schema[entityName].keepHistoryFields, originalChange, ev);
80
- if (options.applyEventIf && !options.applyEventIf(ev, context, ctx)) {
81
- continue;
82
- }
83
- aggDoc = newAggDoc;
84
- if (!context.options.keepNulls) (0, _radsDb.cleanEntity)(aggDoc);
85
- }
86
- if (aggDoc) {
87
- result.push({
88
- aggDoc,
89
- events,
90
- updatedEvents: events.filter(ev => docsById[ev.id]),
91
- oldAggDoc: existingAggregatesById[aggDoc.id]
92
- });
93
- }
94
- }
122
+ return buildAggregateFromEvents(events, aggId, docsById, existingAggregatesById, processor);
123
+ }));
95
124
  await (0, _radsDb.handlePrecomputed)(
96
125
  // @ts-expect-error TODO wrong types
97
126
  {
@@ -105,7 +134,7 @@ function getEffectFor(entityName, aggregateRelationField, eventEntityName, schem
105
134
  })), ctx);
106
135
  return result;
107
136
  },
108
- async afterPut(context, docs, beforePutResult, ctx) {
137
+ async afterPut(context, _docs, beforePutResult, ctx) {
109
138
  const aggs = beforePutResult.map(d => d.aggDoc);
110
139
  const hookItems = beforePutResult.map(v => ({
111
140
  doc: v.aggDoc,
@@ -1,6 +1,7 @@
1
1
  import type { ComputedContext, RadsFeature, RadsRequestContext } from '@/types';
2
2
  export interface EventSourcingFeatureOptions {
3
3
  applyEventIf?: (event: any, context: ComputedContext, ctx: RadsRequestContext) => boolean;
4
+ freezeEvent?: (event: any, context: ComputedContext, ctx: RadsRequestContext) => boolean;
4
5
  }
5
6
  declare const _default: (options?: EventSourcingFeatureOptions) => RadsFeature;
6
7
  export default _default;
@@ -19,68 +19,87 @@ function verifyEventSourcingSetup(schema, effects, options) {
19
19
  effects[eventEntityName].push(effect);
20
20
  }
21
21
  }
22
+ function groupEventsByAggId(docs, aggregateRelationField, eventEntityName) {
23
+ const docsByAggId = {};
24
+ const docsById = {};
25
+ for (const d of docs) {
26
+ if (!d.doc.id || docsById[d.doc.id]) continue;
27
+ docsById[d.doc.id] = d.doc;
28
+ const aggId = d.doc[aggregateRelationField].id;
29
+ if (!aggId) throw new Error(`Missing ${eventEntityName}.${aggregateRelationField}.id`);
30
+ if (d.oldDoc && aggId !== d.oldDoc[aggregateRelationField].id) {
31
+ throw new Error(`Field ${eventEntityName}.${aggregateRelationField}.id cannot be changed`);
32
+ }
33
+ if (!docsByAggId[aggId]) docsByAggId[aggId] = [];
34
+ docsByAggId[aggId].push(d.doc);
35
+ }
36
+ return { docsByAggId, docsById };
37
+ }
38
+ function applyEventToAggregate(ev, aggDoc, aggId, p) {
39
+ const mergedDoc = merge(aggDoc || { id: aggId }, ev.change);
40
+ const newAggDoc = p.context.validators[p.entityName](mergedDoc);
41
+ if (!p.options.freezeEvent?.(ev, p.context, p.ctx)) {
42
+ ev.beforeChange = aggDoc;
43
+ const originalChange = ev.change;
44
+ ev.change = diff(newAggDoc, aggDoc);
45
+ handleKeepHistory(p.schema[p.entityName].keepHistoryFields, originalChange, ev);
46
+ }
47
+ if (p.ctx.skipPrecomputed) {
48
+ for (const field of p.schema[p.entityName].precomputedFields || []) {
49
+ if (p.ctx.skipPrecomputed.includes(field) && mergedDoc[field] !== void 0) {
50
+ newAggDoc[field] = mergedDoc[field];
51
+ }
52
+ }
53
+ }
54
+ return newAggDoc;
55
+ }
56
+ function buildAggregateFromEvents(events, aggId, docsById, existingAggregatesById, p) {
57
+ if (events[0].type !== "creation")
58
+ throw new Error(`First event must have type = "creation". (type: ${events[0].type}, id: ${events[0].id})`);
59
+ if (events.slice(1).some((ev) => ev.type === "creation")) {
60
+ throw new Error(`Only first event may have type = "creation"`);
61
+ }
62
+ let aggDoc;
63
+ for (const ev of events) {
64
+ const newAggDoc = applyEventToAggregate(ev, aggDoc, aggId, p);
65
+ if (p.options.applyEventIf && !p.options.applyEventIf(ev, p.context, p.ctx)) continue;
66
+ aggDoc = newAggDoc;
67
+ if (!p.context.options.keepNulls) cleanEntity(aggDoc);
68
+ }
69
+ if (!aggDoc) return null;
70
+ return {
71
+ aggDoc,
72
+ events,
73
+ updatedEvents: events.filter((ev) => docsById[ev.id]),
74
+ oldAggDoc: existingAggregatesById[aggDoc.id]
75
+ };
76
+ }
22
77
  function getEffectFor(entityName, aggregateRelationField, eventEntityName, schema, options) {
23
78
  return {
24
79
  featureName: "eventSourcing",
25
80
  async beforePut(context, docs, ctx) {
26
- const docsByAggId = {};
27
- const docsById = {};
28
- for (const d of docs) {
29
- if (!d.doc.id || docsById[d.doc.id]) continue;
30
- docsById[d.doc.id] = d.doc;
31
- const aggId = d.doc[aggregateRelationField].id;
32
- if (!aggId) throw new Error(`Missing ${eventEntityName}.${aggregateRelationField}.id`);
33
- if (d.oldDoc && aggId !== d.oldDoc[aggregateRelationField].id) {
34
- throw new Error(`Field ${eventEntityName}.${aggregateRelationField}.id cannot be changed`);
35
- }
36
- if (!docsByAggId[aggId]) docsByAggId[aggId] = [];
37
- docsByAggId[aggId].push(d.doc);
38
- }
39
- const existingEvents = await context.drivers[eventEntityName].getAll(
40
- {
41
- where: {
42
- [aggregateRelationField]: { id_in: _.keys(docsByAggId) },
43
- _not: { id_in: docs.map((d) => d.doc.id) }
44
- }
45
- },
46
- ctx
47
- );
81
+ const { docsByAggId, docsById } = groupEventsByAggId(docs, aggregateRelationField, eventEntityName);
82
+ const [existingEvents, existingAggregates] = await Promise.all([
83
+ context.drivers[eventEntityName].getAll(
84
+ {
85
+ where: {
86
+ [aggregateRelationField]: { id_in: _.keys(docsByAggId) },
87
+ _not: { id_in: docs.map((d) => d.doc.id) }
88
+ }
89
+ },
90
+ ctx
91
+ ),
92
+ context.drivers[entityName].getAll({ where: { id_in: _.keys(docsByAggId) } }, ctx)
93
+ ]);
48
94
  const existingEventsByAggId = _.groupBy(existingEvents, (ev) => ev[aggregateRelationField].id);
49
- const existingAggregates = await context.drivers[entityName].getAll(
50
- { where: { id_in: _.keys(docsByAggId) } },
51
- ctx
52
- );
53
95
  const existingAggregatesById = _.keyBy(existingAggregates, "id");
54
- const result = [];
55
- for (const aggId in docsByAggId) {
56
- const events = _.orderBy([...docsByAggId[aggId], ...existingEventsByAggId[aggId] || []], ["date"], "asc");
57
- if (events[0].type !== "creation")
58
- throw new Error(`First event must have type = "creation". (type: ${events[0].type}, id: ${events[0].id})`);
59
- if (events.slice(1).some((ev) => ev.type === "creation")) {
60
- throw new Error(`Only first event may have type = "creation"`);
61
- }
62
- let aggDoc;
63
- for (const ev of events) {
64
- const newAggDoc = context.validators[entityName](merge(aggDoc || { id: aggId }, ev.change));
65
- ev.beforeChange = aggDoc;
66
- const originalChange = ev.change;
67
- ev.change = diff(newAggDoc, aggDoc);
68
- handleKeepHistory(schema[entityName].keepHistoryFields, originalChange, ev);
69
- if (options.applyEventIf && !options.applyEventIf(ev, context, ctx)) {
70
- continue;
71
- }
72
- aggDoc = newAggDoc;
73
- if (!context.options.keepNulls) cleanEntity(aggDoc);
74
- }
75
- if (aggDoc) {
76
- result.push({
77
- aggDoc,
78
- events,
79
- updatedEvents: events.filter((ev) => docsById[ev.id]),
80
- oldAggDoc: existingAggregatesById[aggDoc.id]
81
- });
82
- }
83
- }
96
+ const processor = { context, entityName, schema, options, ctx };
97
+ const result = _.compact(
98
+ _.keys(docsByAggId).map((aggId) => {
99
+ const events = _.orderBy([...docsByAggId[aggId], ...existingEventsByAggId[aggId] || []], ["date"], "asc");
100
+ return buildAggregateFromEvents(events, aggId, docsById, existingAggregatesById, processor);
101
+ })
102
+ );
84
103
  await handlePrecomputed(
85
104
  // @ts-expect-error TODO wrong types
86
105
  { ...context, typeName: entityName },
@@ -89,7 +108,7 @@ function getEffectFor(entityName, aggregateRelationField, eventEntityName, schem
89
108
  );
90
109
  return result;
91
110
  },
92
- async afterPut(context, docs, beforePutResult, ctx) {
111
+ async afterPut(context, _docs, beforePutResult, ctx) {
93
112
  const aggs = beforePutResult.map((d) => d.aggDoc);
94
113
  const hookItems = beforePutResult.map((v) => ({
95
114
  doc: v.aggDoc,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rads-db",
3
- "version": "3.2.18",
3
+ "version": "3.2.21",
4
4
  "description": "Say goodbye to boilerplate code and hello to efficient and elegant syntax.",
5
5
  "author": "",
6
6
  "license": "ISC",