rads-db 3.0.22 → 3.0.24

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/index.cjs CHANGED
@@ -463,163 +463,6 @@ async function handleEffectsAfterPut(context, docs, beforePutResults, ctx) {
463
463
  );
464
464
  }
465
465
 
466
- const mergeFn = createMerge__default({
467
- mergeArray(options) {
468
- const clone = options.clone;
469
- return function(target, source) {
470
- return clone(source);
471
- };
472
- }
473
- });
474
- function merge(target, source) {
475
- cleanUndefined(source);
476
- const result = mergeFn(target, source);
477
- return result;
478
- }
479
- function cleanUndefined(obj) {
480
- if (!obj || !___default.isPlainObject(obj))
481
- return;
482
- cleanUndefinedInner(obj);
483
- }
484
- function cleanUndefinedInner(obj) {
485
- for (const key in obj) {
486
- if (obj[key] === void 0)
487
- delete obj[key];
488
- if (___default.isPlainObject(obj[key]))
489
- cleanUndefinedInner(obj[key]);
490
- }
491
- }
492
-
493
- function diff(object, base, includeDenormFields = false) {
494
- if (!base)
495
- return object;
496
- return ___default.transform(object, (result, value, key) => {
497
- const oldValue = base[key];
498
- if (!___default.isEqual(value, oldValue)) {
499
- if (mustUseDeepDiff(value, oldValue)) {
500
- const isRelationField = (value.id || oldValue.id) && key !== "change";
501
- if (isRelationField && !includeDenormFields) {
502
- if (value.id !== oldValue.id)
503
- result[key] = value;
504
- } else {
505
- const subDiff = diff(value, oldValue, includeDenormFields);
506
- if (!___default.isEmpty(subDiff))
507
- result[key] = subDiff;
508
- }
509
- } else {
510
- result[key] = value;
511
- }
512
- }
513
- });
514
- }
515
- function mustUseDeepDiff(value, oldValue) {
516
- return ___default.isObject(value) && ___default.isObject(oldValue) && !___default.isArray(value) && !___default.isArray(oldValue);
517
- }
518
-
519
- function verifyEventSourcingSetup(schema, effects) {
520
- for (const entityName in schema) {
521
- if (!schema[entityName].decorators.entity)
522
- continue;
523
- if (schema[entityName].decorators.precomputed?.preset !== "eventSourcing")
524
- continue;
525
- const eventEntityName = `${entityName}Event`;
526
- const aggregateRelationField = ___default.lowerFirst(entityName);
527
- validateEventSourcingSetup(schema, entityName, eventEntityName, aggregateRelationField);
528
- effects[eventEntityName].push({
529
- async beforePut(context, docs, ctx) {
530
- const docsByAggId = {};
531
- const docsById = {};
532
- for (const d of docs) {
533
- if (!d.doc.id || docsById[d.doc.id])
534
- continue;
535
- docsById[d.doc.id] = d.doc;
536
- const aggId = d.doc[aggregateRelationField].id;
537
- if (!aggId)
538
- throw new Error(`Missing ${entityName}.${aggregateRelationField}.id`);
539
- if (d.oldDoc && aggId !== d.oldDoc[aggregateRelationField].id) {
540
- throw new Error(`Field ${entityName}.${aggregateRelationField}.id cannot be changed`);
541
- }
542
- if (!docsByAggId[aggId])
543
- docsByAggId[aggId] = [];
544
- docsByAggId[aggId].push(d.doc);
545
- }
546
- const existingEvents = await context.drivers[eventEntityName].getAll(
547
- {
548
- where: {
549
- [aggregateRelationField]: { id_in: ___default.keys(docsByAggId) },
550
- _not: { id_in: docs.map((d) => d.doc.id) }
551
- }
552
- },
553
- ctx
554
- );
555
- const existingEventsByAggId = ___default.groupBy(existingEvents, (ev) => ev[aggregateRelationField].id);
556
- const existingAggregates = await context.drivers[entityName].getAll(
557
- { where: { id_in: ___default.keys(docsByAggId) } },
558
- ctx
559
- );
560
- const existingAggregatesById = ___default.keyBy(existingAggregates, "id");
561
- const result = [];
562
- for (const aggId in docsByAggId) {
563
- const events = ___default.orderBy([...docsByAggId[aggId], ...existingEventsByAggId[aggId] || []], ["date"], "asc");
564
- if (events[0].type !== "creation")
565
- throw new Error(`First event must have type = "creation". (type: ${events[0].type}, id: ${events[0].id})`);
566
- if (events.slice(1).some((ev) => ev.type === "creation")) {
567
- throw new Error(`Only first event may have type = "creation"`);
568
- }
569
- let aggDoc = { id: aggId };
570
- for (const ev of events) {
571
- const newAggDoc = context.validators[entityName](merge(aggDoc, ev.change));
572
- ev.change = diff(newAggDoc, aggDoc);
573
- aggDoc = newAggDoc;
574
- }
575
- result.push({ aggDoc, events });
576
- }
577
- await handlePrecomputed(
578
- { ...context, typeName: entityName },
579
- result.map((v) => ({ doc: v.aggDoc, events: v.events, oldDoc: existingAggregatesById[v.aggDoc.id] })),
580
- ctx
581
- );
582
- return result;
583
- },
584
- async afterPut(context, docs, beforePutResult, ctx) {
585
- const aggs = beforePutResult.map((d) => d.aggDoc);
586
- await context.drivers[entityName].putMany(aggs, ctx);
587
- }
588
- });
589
- }
590
- }
591
- function validateEventSourcingSetup(schema, entityName, eventEntityName, aggregateRelationField) {
592
- const eventEntity = schema[eventEntityName];
593
- if (!eventEntity)
594
- throw new Error(
595
- `You must create entity with name "${eventEntityName}" to use eventSourcing feature with "${entityName}"`
596
- );
597
- if (!eventEntity.decorators.entity)
598
- throw new Error(`"${eventEntityName}" must have "@entity()" decorator.`);
599
- const expectedFields = {
600
- id: "string",
601
- type: "enum",
602
- change: entityName,
603
- [aggregateRelationField]: entityName,
604
- date: "string"
605
- };
606
- for (const f in expectedFields) {
607
- const field = eventEntity.fields?.[f];
608
- if (!field)
609
- throw new Error(`"${eventEntityName}" must have field "${f}"`);
610
- if (expectedFields[f] === "enum") {
611
- if (!schema[field.type]?.enumValues?.creation)
612
- throw new Error(`Field "${eventEntityName}.${f}" must be enum that accepts value 'creation'`);
613
- } else {
614
- if (field.type !== expectedFields[f])
615
- throw new Error(`"${eventEntityName}.${f}" must have type "${expectedFields[f]}"`);
616
- if (f === "change" && !field.isChange) {
617
- throw new Error(`"${eventEntityName}.${f}" must have type "Change<${expectedFields[f]}>"`);
618
- }
619
- }
620
- }
621
- }
622
-
623
466
  function verifyRelationsSetup(schema, effects) {
624
467
  for (const entityName in schema) {
625
468
  const entity = schema[entityName];
@@ -709,6 +552,59 @@ function cleanUndefinedAndNull(obj) {
709
552
  }
710
553
  }
711
554
 
555
+ const mergeFn = createMerge__default({
556
+ mergeArray(options) {
557
+ const clone = options.clone;
558
+ return function(target, source) {
559
+ return clone(source);
560
+ };
561
+ }
562
+ });
563
+ function merge(target, source) {
564
+ cleanUndefined(source);
565
+ const result = mergeFn(target, source);
566
+ return result;
567
+ }
568
+ function cleanUndefined(obj) {
569
+ if (!obj || !___default.isPlainObject(obj))
570
+ return;
571
+ cleanUndefinedInner(obj);
572
+ }
573
+ function cleanUndefinedInner(obj) {
574
+ for (const key in obj) {
575
+ if (obj[key] === void 0)
576
+ delete obj[key];
577
+ if (___default.isPlainObject(obj[key]))
578
+ cleanUndefinedInner(obj[key]);
579
+ }
580
+ }
581
+
582
+ function diff(object, base, includeDenormFields = false) {
583
+ if (!base)
584
+ return object;
585
+ return ___default.transform(object, (result, value, key) => {
586
+ const oldValue = base[key];
587
+ if (!___default.isEqual(value, oldValue)) {
588
+ if (mustUseDeepDiff(value, oldValue)) {
589
+ const isRelationField = (value.id || oldValue.id) && key !== "change";
590
+ if (isRelationField && !includeDenormFields) {
591
+ if (value.id !== oldValue.id)
592
+ result[key] = value;
593
+ } else {
594
+ const subDiff = diff(value, oldValue, includeDenormFields);
595
+ if (!___default.isEmpty(subDiff))
596
+ result[key] = subDiff;
597
+ }
598
+ } else {
599
+ result[key] = value;
600
+ }
601
+ }
602
+ });
603
+ }
604
+ function mustUseDeepDiff(value, oldValue) {
605
+ return ___default.isObject(value) && ___default.isObject(oldValue) && !___default.isArray(value) && !___default.isArray(oldValue);
606
+ }
607
+
712
608
  function getRadsDbMethods(key, computedContext, driverInstance) {
713
609
  const { schema, options, validators } = computedContext;
714
610
  const mustCleanNulls = driverInstance.driverName !== "restApi";
@@ -1007,7 +903,6 @@ function generateMethods(schema, validators, options) {
1007
903
  if (!opts.noComputed) {
1008
904
  verifyComputedPresense(schema, opts.computed, effects);
1009
905
  }
1010
- verifyEventSourcingSetup(schema, effects);
1011
906
  verifyRelationsSetup(schema, effects);
1012
907
  const computedContextGlobal = {
1013
908
  schema,
@@ -1253,9 +1148,12 @@ function createRadsDbClient(args) {
1253
1148
  exports.computed = computed;
1254
1149
  exports.createRadsDb = createRadsDb;
1255
1150
  exports.createRadsDbClient = createRadsDbClient;
1151
+ exports.diff = diff;
1256
1152
  exports.entity = entity;
1257
1153
  exports.field = field;
1258
1154
  exports.getDriverInstance = getDriverInstance;
1155
+ exports.handlePrecomputed = handlePrecomputed;
1156
+ exports.merge = merge;
1259
1157
  exports.precomputed = precomputed;
1260
1158
  exports.ui = ui;
1261
1159
  exports.validate = validate;
package/dist/index.d.ts CHANGED
@@ -386,6 +386,13 @@ declare function computed(meta?: ComputedDecoratorArgs): (a: any, b?: ClassField
386
386
 
387
387
  declare function getDriverInstance(schema: Schema, key: string, driverConstructor: DriverConstructor, driverInstances: Record<string, Driver>): Driver;
388
388
 
389
+ declare function handlePrecomputed(context: ComputedContext, docs: any[], ctx: RadsRequestContext): Promise<void>;
390
+
391
+ /** Performs immutable deep merge */
392
+ declare function merge<T extends Record<string, any>>(target: T, source: any): T;
393
+
394
+ declare function diff(object: any, base: any, includeDenormFields?: boolean): any;
395
+
389
396
  /**
390
397
  * Creates instance of rads db - object that provides access to all entities.
391
398
  * Intended to be used on the backend. On the client you may want to opt for "createRadsDbClient" instead.
@@ -398,4 +405,4 @@ declare function createRadsDb(args?: CreateRadsDbArgs): RadsDb;
398
405
  */
399
406
  declare function createRadsDbClient(args?: CreateRadsDbClientArgs): RadsDb;
400
407
 
401
- export { Change, ComputedContext, ComputedContextGlobal, ComputedDecoratorArgs, CreateRadsArgsDrivers, CreateRadsDbArgs, CreateRadsDbArgsNormalized, CreateRadsDbClientArgs, DeepPartial, Driver, DriverConstructor, EntityDecoratorArgs, EntityMethods, EnumDefinition, FieldDecoratorArgs, FieldDefinition, FileSystemNode, FileUploadArgs, FileUploadDriver, FileUploadResult, GenerateClientNormalizedOptions, GenerateClientOptions, GetAggArgs, GetAggArgsAgg, GetAggArgsAny, GetAggResponse, GetArgs, GetArgsAny, GetArgsInclude, GetManyArgs, GetManyArgsAny, GetManyResponse, GetResponse, GetResponseInclude, GetResponseIncludeSelect, GetResponseNoInclude, GetRestRoutesArgs, GetRestRoutesOptions, GetRestRoutesResponse, MinimalDriver, PutArgs, PutEffect, RadsFeature, RadsRequestContext, RadsUiSlotDefinition, RadsUiSlotName, RadsVitePluginOptions, Relation, RequiredFields, RestDriverOptions, RestFileUploadDriverOptions, Schema, SchemaValidators, TypeDefinition, UiDecoratorArgs, UiFieldDecoratorArgs, ValidateEntityDecoratorArgs, ValidateFieldDecoratorArgs, ValidateStringDecoratorArgs, VerifyManyArgs, VerifyManyArgsAny, VerifyManyResponse, computed, createRadsDb, createRadsDbClient, entity, field, getDriverInstance, precomputed, ui, validate };
408
+ export { Change, ComputedContext, ComputedContextGlobal, ComputedDecoratorArgs, CreateRadsArgsDrivers, CreateRadsDbArgs, CreateRadsDbArgsNormalized, CreateRadsDbClientArgs, DeepPartial, Driver, DriverConstructor, EntityDecoratorArgs, EntityMethods, EnumDefinition, FieldDecoratorArgs, FieldDefinition, FileSystemNode, FileUploadArgs, FileUploadDriver, FileUploadResult, GenerateClientNormalizedOptions, GenerateClientOptions, GetAggArgs, GetAggArgsAgg, GetAggArgsAny, GetAggResponse, GetArgs, GetArgsAny, GetArgsInclude, GetManyArgs, GetManyArgsAny, GetManyResponse, GetResponse, GetResponseInclude, GetResponseIncludeSelect, GetResponseNoInclude, GetRestRoutesArgs, GetRestRoutesOptions, GetRestRoutesResponse, MinimalDriver, PutArgs, PutEffect, RadsFeature, RadsRequestContext, RadsUiSlotDefinition, RadsUiSlotName, RadsVitePluginOptions, Relation, RequiredFields, RestDriverOptions, RestFileUploadDriverOptions, Schema, SchemaValidators, TypeDefinition, UiDecoratorArgs, UiFieldDecoratorArgs, ValidateEntityDecoratorArgs, ValidateFieldDecoratorArgs, ValidateStringDecoratorArgs, VerifyManyArgs, VerifyManyArgsAny, VerifyManyResponse, computed, createRadsDb, createRadsDbClient, diff, entity, field, getDriverInstance, handlePrecomputed, merge, precomputed, ui, validate };
package/dist/index.mjs CHANGED
@@ -455,163 +455,6 @@ async function handleEffectsAfterPut(context, docs, beforePutResults, ctx) {
455
455
  );
456
456
  }
457
457
 
458
- const mergeFn = createMerge({
459
- mergeArray(options) {
460
- const clone = options.clone;
461
- return function(target, source) {
462
- return clone(source);
463
- };
464
- }
465
- });
466
- function merge(target, source) {
467
- cleanUndefined(source);
468
- const result = mergeFn(target, source);
469
- return result;
470
- }
471
- function cleanUndefined(obj) {
472
- if (!obj || !_.isPlainObject(obj))
473
- return;
474
- cleanUndefinedInner(obj);
475
- }
476
- function cleanUndefinedInner(obj) {
477
- for (const key in obj) {
478
- if (obj[key] === void 0)
479
- delete obj[key];
480
- if (_.isPlainObject(obj[key]))
481
- cleanUndefinedInner(obj[key]);
482
- }
483
- }
484
-
485
- function diff(object, base, includeDenormFields = false) {
486
- if (!base)
487
- return object;
488
- return _.transform(object, (result, value, key) => {
489
- const oldValue = base[key];
490
- if (!_.isEqual(value, oldValue)) {
491
- if (mustUseDeepDiff(value, oldValue)) {
492
- const isRelationField = (value.id || oldValue.id) && key !== "change";
493
- if (isRelationField && !includeDenormFields) {
494
- if (value.id !== oldValue.id)
495
- result[key] = value;
496
- } else {
497
- const subDiff = diff(value, oldValue, includeDenormFields);
498
- if (!_.isEmpty(subDiff))
499
- result[key] = subDiff;
500
- }
501
- } else {
502
- result[key] = value;
503
- }
504
- }
505
- });
506
- }
507
- function mustUseDeepDiff(value, oldValue) {
508
- return _.isObject(value) && _.isObject(oldValue) && !_.isArray(value) && !_.isArray(oldValue);
509
- }
510
-
511
- function verifyEventSourcingSetup(schema, effects) {
512
- for (const entityName in schema) {
513
- if (!schema[entityName].decorators.entity)
514
- continue;
515
- if (schema[entityName].decorators.precomputed?.preset !== "eventSourcing")
516
- continue;
517
- const eventEntityName = `${entityName}Event`;
518
- const aggregateRelationField = _.lowerFirst(entityName);
519
- validateEventSourcingSetup(schema, entityName, eventEntityName, aggregateRelationField);
520
- effects[eventEntityName].push({
521
- async beforePut(context, docs, ctx) {
522
- const docsByAggId = {};
523
- const docsById = {};
524
- for (const d of docs) {
525
- if (!d.doc.id || docsById[d.doc.id])
526
- continue;
527
- docsById[d.doc.id] = d.doc;
528
- const aggId = d.doc[aggregateRelationField].id;
529
- if (!aggId)
530
- throw new Error(`Missing ${entityName}.${aggregateRelationField}.id`);
531
- if (d.oldDoc && aggId !== d.oldDoc[aggregateRelationField].id) {
532
- throw new Error(`Field ${entityName}.${aggregateRelationField}.id cannot be changed`);
533
- }
534
- if (!docsByAggId[aggId])
535
- docsByAggId[aggId] = [];
536
- docsByAggId[aggId].push(d.doc);
537
- }
538
- const existingEvents = await context.drivers[eventEntityName].getAll(
539
- {
540
- where: {
541
- [aggregateRelationField]: { id_in: _.keys(docsByAggId) },
542
- _not: { id_in: docs.map((d) => d.doc.id) }
543
- }
544
- },
545
- ctx
546
- );
547
- const existingEventsByAggId = _.groupBy(existingEvents, (ev) => ev[aggregateRelationField].id);
548
- const existingAggregates = await context.drivers[entityName].getAll(
549
- { where: { id_in: _.keys(docsByAggId) } },
550
- ctx
551
- );
552
- const existingAggregatesById = _.keyBy(existingAggregates, "id");
553
- const result = [];
554
- for (const aggId in docsByAggId) {
555
- const events = _.orderBy([...docsByAggId[aggId], ...existingEventsByAggId[aggId] || []], ["date"], "asc");
556
- if (events[0].type !== "creation")
557
- throw new Error(`First event must have type = "creation". (type: ${events[0].type}, id: ${events[0].id})`);
558
- if (events.slice(1).some((ev) => ev.type === "creation")) {
559
- throw new Error(`Only first event may have type = "creation"`);
560
- }
561
- let aggDoc = { id: aggId };
562
- for (const ev of events) {
563
- const newAggDoc = context.validators[entityName](merge(aggDoc, ev.change));
564
- ev.change = diff(newAggDoc, aggDoc);
565
- aggDoc = newAggDoc;
566
- }
567
- result.push({ aggDoc, events });
568
- }
569
- await handlePrecomputed(
570
- { ...context, typeName: entityName },
571
- result.map((v) => ({ doc: v.aggDoc, events: v.events, oldDoc: existingAggregatesById[v.aggDoc.id] })),
572
- ctx
573
- );
574
- return result;
575
- },
576
- async afterPut(context, docs, beforePutResult, ctx) {
577
- const aggs = beforePutResult.map((d) => d.aggDoc);
578
- await context.drivers[entityName].putMany(aggs, ctx);
579
- }
580
- });
581
- }
582
- }
583
- function validateEventSourcingSetup(schema, entityName, eventEntityName, aggregateRelationField) {
584
- const eventEntity = schema[eventEntityName];
585
- if (!eventEntity)
586
- throw new Error(
587
- `You must create entity with name "${eventEntityName}" to use eventSourcing feature with "${entityName}"`
588
- );
589
- if (!eventEntity.decorators.entity)
590
- throw new Error(`"${eventEntityName}" must have "@entity()" decorator.`);
591
- const expectedFields = {
592
- id: "string",
593
- type: "enum",
594
- change: entityName,
595
- [aggregateRelationField]: entityName,
596
- date: "string"
597
- };
598
- for (const f in expectedFields) {
599
- const field = eventEntity.fields?.[f];
600
- if (!field)
601
- throw new Error(`"${eventEntityName}" must have field "${f}"`);
602
- if (expectedFields[f] === "enum") {
603
- if (!schema[field.type]?.enumValues?.creation)
604
- throw new Error(`Field "${eventEntityName}.${f}" must be enum that accepts value 'creation'`);
605
- } else {
606
- if (field.type !== expectedFields[f])
607
- throw new Error(`"${eventEntityName}.${f}" must have type "${expectedFields[f]}"`);
608
- if (f === "change" && !field.isChange) {
609
- throw new Error(`"${eventEntityName}.${f}" must have type "Change<${expectedFields[f]}>"`);
610
- }
611
- }
612
- }
613
- }
614
-
615
458
  function verifyRelationsSetup(schema, effects) {
616
459
  for (const entityName in schema) {
617
460
  const entity = schema[entityName];
@@ -701,6 +544,59 @@ function cleanUndefinedAndNull(obj) {
701
544
  }
702
545
  }
703
546
 
547
+ const mergeFn = createMerge({
548
+ mergeArray(options) {
549
+ const clone = options.clone;
550
+ return function(target, source) {
551
+ return clone(source);
552
+ };
553
+ }
554
+ });
555
+ function merge(target, source) {
556
+ cleanUndefined(source);
557
+ const result = mergeFn(target, source);
558
+ return result;
559
+ }
560
+ function cleanUndefined(obj) {
561
+ if (!obj || !_.isPlainObject(obj))
562
+ return;
563
+ cleanUndefinedInner(obj);
564
+ }
565
+ function cleanUndefinedInner(obj) {
566
+ for (const key in obj) {
567
+ if (obj[key] === void 0)
568
+ delete obj[key];
569
+ if (_.isPlainObject(obj[key]))
570
+ cleanUndefinedInner(obj[key]);
571
+ }
572
+ }
573
+
574
+ function diff(object, base, includeDenormFields = false) {
575
+ if (!base)
576
+ return object;
577
+ return _.transform(object, (result, value, key) => {
578
+ const oldValue = base[key];
579
+ if (!_.isEqual(value, oldValue)) {
580
+ if (mustUseDeepDiff(value, oldValue)) {
581
+ const isRelationField = (value.id || oldValue.id) && key !== "change";
582
+ if (isRelationField && !includeDenormFields) {
583
+ if (value.id !== oldValue.id)
584
+ result[key] = value;
585
+ } else {
586
+ const subDiff = diff(value, oldValue, includeDenormFields);
587
+ if (!_.isEmpty(subDiff))
588
+ result[key] = subDiff;
589
+ }
590
+ } else {
591
+ result[key] = value;
592
+ }
593
+ }
594
+ });
595
+ }
596
+ function mustUseDeepDiff(value, oldValue) {
597
+ return _.isObject(value) && _.isObject(oldValue) && !_.isArray(value) && !_.isArray(oldValue);
598
+ }
599
+
704
600
  function getRadsDbMethods(key, computedContext, driverInstance) {
705
601
  const { schema, options, validators } = computedContext;
706
602
  const mustCleanNulls = driverInstance.driverName !== "restApi";
@@ -999,7 +895,6 @@ function generateMethods(schema, validators, options) {
999
895
  if (!opts.noComputed) {
1000
896
  verifyComputedPresense(schema, opts.computed, effects);
1001
897
  }
1002
- verifyEventSourcingSetup(schema, effects);
1003
898
  verifyRelationsSetup(schema, effects);
1004
899
  const computedContextGlobal = {
1005
900
  schema,
@@ -1242,4 +1137,4 @@ function createRadsDbClient(args) {
1242
1137
  return generateMethods(s, validators, radsDbArgs);
1243
1138
  }
1244
1139
 
1245
- export { computed, createRadsDb, createRadsDbClient, entity, field, getDriverInstance, precomputed, ui, validate };
1140
+ export { computed, createRadsDb, createRadsDbClient, diff, entity, field, getDriverInstance, handlePrecomputed, merge, precomputed, ui, validate };
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ module.exports = void 0;
7
+ var _lodash = _interopRequireDefault(require("lodash"));
8
+ var _radsDb = require("rads-db");
9
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10
+ var _default = () => {
11
+ return {
12
+ name: "eventSourcing",
13
+ init(db, context) {
14
+ verifyEventSourcingSetup(context.schema, context.effects);
15
+ }
16
+ };
17
+ };
18
+ module.exports = _default;
19
+ function verifyEventSourcingSetup(schema, effects) {
20
+ for (const entityName in schema) {
21
+ if (!schema[entityName].decorators.entity) continue;
22
+ if (schema[entityName].decorators.precomputed?.preset !== "eventSourcing") continue;
23
+ const eventEntityName = `${entityName}Event`;
24
+ const aggregateRelationField = _lodash.default.lowerFirst(entityName);
25
+ validateEventSourcingSetup(schema, entityName, eventEntityName, aggregateRelationField);
26
+ effects[eventEntityName].push({
27
+ async beforePut(context, docs, ctx) {
28
+ const docsByAggId = {};
29
+ const docsById = {};
30
+ for (const d of docs) {
31
+ if (!d.doc.id || docsById[d.doc.id]) continue;
32
+ docsById[d.doc.id] = d.doc;
33
+ const aggId = d.doc[aggregateRelationField].id;
34
+ if (!aggId) throw new Error(`Missing ${entityName}.${aggregateRelationField}.id`);
35
+ if (d.oldDoc && aggId !== d.oldDoc[aggregateRelationField].id) {
36
+ throw new Error(`Field ${entityName}.${aggregateRelationField}.id cannot be changed`);
37
+ }
38
+ if (!docsByAggId[aggId]) docsByAggId[aggId] = [];
39
+ docsByAggId[aggId].push(d.doc);
40
+ }
41
+ const existingEvents = await context.drivers[eventEntityName].getAll({
42
+ where: {
43
+ [aggregateRelationField]: {
44
+ id_in: _lodash.default.keys(docsByAggId)
45
+ },
46
+ _not: {
47
+ id_in: docs.map(d => d.doc.id)
48
+ }
49
+ }
50
+ }, ctx);
51
+ const existingEventsByAggId = _lodash.default.groupBy(existingEvents, ev => ev[aggregateRelationField].id);
52
+ const existingAggregates = await context.drivers[entityName].getAll({
53
+ where: {
54
+ id_in: _lodash.default.keys(docsByAggId)
55
+ }
56
+ }, ctx);
57
+ const existingAggregatesById = _lodash.default.keyBy(existingAggregates, "id");
58
+ const result = [];
59
+ for (const aggId in docsByAggId) {
60
+ const events = _lodash.default.orderBy([...docsByAggId[aggId], ...(existingEventsByAggId[aggId] || [])], ["date"], "asc");
61
+ if (events[0].type !== "creation") throw new Error(`First event must have type = "creation". (type: ${events[0].type}, id: ${events[0].id})`);
62
+ if (events.slice(1).some(ev => ev.type === "creation")) {
63
+ throw new Error(`Only first event may have type = "creation"`);
64
+ }
65
+ let aggDoc = {
66
+ id: aggId
67
+ };
68
+ for (const ev of events) {
69
+ const newAggDoc = context.validators[entityName]((0, _radsDb.merge)(aggDoc, ev.change));
70
+ ev.change = (0, _radsDb.diff)(newAggDoc, aggDoc);
71
+ aggDoc = newAggDoc;
72
+ }
73
+ result.push({
74
+ aggDoc,
75
+ events
76
+ });
77
+ }
78
+ await (0, _radsDb.handlePrecomputed)({
79
+ ...context,
80
+ typeName: entityName
81
+ }, result.map(v => ({
82
+ doc: v.aggDoc,
83
+ events: v.events,
84
+ oldDoc: existingAggregatesById[v.aggDoc.id]
85
+ })), ctx);
86
+ return result;
87
+ },
88
+ async afterPut(context, docs, beforePutResult, ctx) {
89
+ const aggs = beforePutResult.map(d => d.aggDoc);
90
+ await context.drivers[entityName].putMany(aggs, ctx);
91
+ }
92
+ });
93
+ }
94
+ }
95
+ function validateEventSourcingSetup(schema, entityName, eventEntityName, aggregateRelationField) {
96
+ const eventEntity = schema[eventEntityName];
97
+ if (!eventEntity) throw new Error(`You must create entity with name "${eventEntityName}" to use eventSourcing feature with "${entityName}"`);
98
+ if (!eventEntity.decorators.entity) throw new Error(`"${eventEntityName}" must have "@entity()" decorator.`);
99
+ const expectedFields = {
100
+ id: "string",
101
+ type: "enum",
102
+ change: entityName,
103
+ [aggregateRelationField]: entityName,
104
+ date: "string"
105
+ };
106
+ for (const f in expectedFields) {
107
+ const field = eventEntity.fields?.[f];
108
+ if (!field) throw new Error(`"${eventEntityName}" must have field "${f}"`);
109
+ if (expectedFields[f] === "enum") {
110
+ if (!schema[field.type]?.enumValues?.creation) throw new Error(`Field "${eventEntityName}.${f}" must be enum that accepts value 'creation'`);
111
+ } else {
112
+ if (field.type !== expectedFields[f]) throw new Error(`"${eventEntityName}.${f}" must have type "${expectedFields[f]}"`);
113
+ if (f === "change" && !field.isChange) {
114
+ throw new Error(`"${eventEntityName}.${f}" must have type "Change<${expectedFields[f]}>"`);
115
+ }
116
+ }
117
+ }
118
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: () => RadsFeature;
2
+ export default _default;
@@ -0,0 +1,113 @@
1
+ import _ from "lodash";
2
+ import { diff, handlePrecomputed, merge } from "rads-db";
3
+ export default () => {
4
+ return {
5
+ name: "eventSourcing",
6
+ init(db, context) {
7
+ verifyEventSourcingSetup(context.schema, context.effects);
8
+ }
9
+ };
10
+ };
11
+ function verifyEventSourcingSetup(schema, effects) {
12
+ for (const entityName in schema) {
13
+ if (!schema[entityName].decorators.entity)
14
+ continue;
15
+ if (schema[entityName].decorators.precomputed?.preset !== "eventSourcing")
16
+ continue;
17
+ const eventEntityName = `${entityName}Event`;
18
+ const aggregateRelationField = _.lowerFirst(entityName);
19
+ validateEventSourcingSetup(schema, entityName, eventEntityName, aggregateRelationField);
20
+ effects[eventEntityName].push({
21
+ async beforePut(context, docs, ctx) {
22
+ const docsByAggId = {};
23
+ const docsById = {};
24
+ for (const d of docs) {
25
+ if (!d.doc.id || docsById[d.doc.id])
26
+ continue;
27
+ docsById[d.doc.id] = d.doc;
28
+ const aggId = d.doc[aggregateRelationField].id;
29
+ if (!aggId)
30
+ throw new Error(`Missing ${entityName}.${aggregateRelationField}.id`);
31
+ if (d.oldDoc && aggId !== d.oldDoc[aggregateRelationField].id) {
32
+ throw new Error(`Field ${entityName}.${aggregateRelationField}.id cannot be changed`);
33
+ }
34
+ if (!docsByAggId[aggId])
35
+ docsByAggId[aggId] = [];
36
+ docsByAggId[aggId].push(d.doc);
37
+ }
38
+ const existingEvents = await context.drivers[eventEntityName].getAll(
39
+ {
40
+ where: {
41
+ [aggregateRelationField]: { id_in: _.keys(docsByAggId) },
42
+ _not: { id_in: docs.map((d) => d.doc.id) }
43
+ }
44
+ },
45
+ ctx
46
+ );
47
+ const existingEventsByAggId = _.groupBy(existingEvents, (ev) => ev[aggregateRelationField].id);
48
+ const existingAggregates = await context.drivers[entityName].getAll(
49
+ { where: { id_in: _.keys(docsByAggId) } },
50
+ ctx
51
+ );
52
+ const existingAggregatesById = _.keyBy(existingAggregates, "id");
53
+ const result = [];
54
+ for (const aggId in docsByAggId) {
55
+ const events = _.orderBy([...docsByAggId[aggId], ...existingEventsByAggId[aggId] || []], ["date"], "asc");
56
+ if (events[0].type !== "creation")
57
+ throw new Error(`First event must have type = "creation". (type: ${events[0].type}, id: ${events[0].id})`);
58
+ if (events.slice(1).some((ev) => ev.type === "creation")) {
59
+ throw new Error(`Only first event may have type = "creation"`);
60
+ }
61
+ let aggDoc = { id: aggId };
62
+ for (const ev of events) {
63
+ const newAggDoc = context.validators[entityName](merge(aggDoc, ev.change));
64
+ ev.change = diff(newAggDoc, aggDoc);
65
+ aggDoc = newAggDoc;
66
+ }
67
+ result.push({ aggDoc, events });
68
+ }
69
+ await handlePrecomputed(
70
+ { ...context, typeName: entityName },
71
+ result.map((v) => ({ doc: v.aggDoc, events: v.events, oldDoc: existingAggregatesById[v.aggDoc.id] })),
72
+ ctx
73
+ );
74
+ return result;
75
+ },
76
+ async afterPut(context, docs, beforePutResult, ctx) {
77
+ const aggs = beforePutResult.map((d) => d.aggDoc);
78
+ await context.drivers[entityName].putMany(aggs, ctx);
79
+ }
80
+ });
81
+ }
82
+ }
83
+ function validateEventSourcingSetup(schema, entityName, eventEntityName, aggregateRelationField) {
84
+ const eventEntity = schema[eventEntityName];
85
+ if (!eventEntity)
86
+ throw new Error(
87
+ `You must create entity with name "${eventEntityName}" to use eventSourcing feature with "${entityName}"`
88
+ );
89
+ if (!eventEntity.decorators.entity)
90
+ throw new Error(`"${eventEntityName}" must have "@entity()" decorator.`);
91
+ const expectedFields = {
92
+ id: "string",
93
+ type: "enum",
94
+ change: entityName,
95
+ [aggregateRelationField]: entityName,
96
+ date: "string"
97
+ };
98
+ for (const f in expectedFields) {
99
+ const field = eventEntity.fields?.[f];
100
+ if (!field)
101
+ throw new Error(`"${eventEntityName}" must have field "${f}"`);
102
+ if (expectedFields[f] === "enum") {
103
+ if (!schema[field.type]?.enumValues?.creation)
104
+ throw new Error(`Field "${eventEntityName}.${f}" must be enum that accepts value 'creation'`);
105
+ } else {
106
+ if (field.type !== expectedFields[f])
107
+ throw new Error(`"${eventEntityName}.${f}" must have type "${expectedFields[f]}"`);
108
+ if (f === "change" && !field.isChange) {
109
+ throw new Error(`"${eventEntityName}.${f}" must have type "Change<${expectedFields[f]}>"`);
110
+ }
111
+ }
112
+ }
113
+ }
@@ -11,6 +11,7 @@ var _lodash = _interopRequireDefault(require("lodash"));
11
11
  var _restEndpointsDev = _interopRequireDefault(require("./restEndpointsDev.cjs"));
12
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
13
  function getRadsUiUser(radsAuthHeader, apiKey) {
14
+ if (!radsAuthHeader) return void 0;
14
15
  apiKey = apiKey || process.env.RadsApiKey;
15
16
  if (!apiKey) {
16
17
  throw (0, _h.createError)({
@@ -18,7 +19,6 @@ function getRadsUiUser(radsAuthHeader, apiKey) {
18
19
  message: `Please, provide "apiKey" option to "getRadsUiUser" call.`
19
20
  });
20
21
  }
21
- if (!radsAuthHeader) return void 0;
22
22
  const [keyType, key, email, pretendAsUserId] = radsAuthHeader.split(" ");
23
23
  if (!email || !keyType || !key || keyType !== "ApiKey") {
24
24
  throw (0, _h.createError)({
@@ -2,12 +2,12 @@ import { createError } from "h3";
2
2
  import _ from "lodash";
3
3
  import restEndpointsDev from "./restEndpointsDev.mjs";
4
4
  export function getRadsUiUser(radsAuthHeader, apiKey) {
5
+ if (!radsAuthHeader)
6
+ return void 0;
5
7
  apiKey = apiKey || process.env.RadsApiKey;
6
8
  if (!apiKey) {
7
9
  throw createError({ statusCode: 401, message: `Please, provide "apiKey" option to "getRadsUiUser" call.` });
8
10
  }
9
- if (!radsAuthHeader)
10
- return void 0;
11
11
  const [keyType, key, email, pretendAsUserId] = radsAuthHeader.split(" ");
12
12
  if (!email || !keyType || !key || keyType !== "ApiKey") {
13
13
  throw createError({ statusCode: 401, message: `Unauthorized - malformed authHeader` });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rads-db",
3
- "version": "3.0.22",
3
+ "version": "3.0.24",
4
4
  "files": [
5
5
  "dist",
6
6
  "drivers",