@monorise/core 3.2.0 → 4.1.0

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.d.ts CHANGED
@@ -74,8 +74,9 @@ declare class Entity$1<T extends Entity$2> extends Item$1 {
74
74
  data: Partial<EntitySchemaMap[T]>;
75
75
  private _createdAt?;
76
76
  private _updatedAt?;
77
+ private _expiresAt?;
77
78
  fullId: string;
78
- constructor(entityType: T, entityId?: string | undefined, data?: Partial<EntitySchemaMap[T]>, _createdAt?: Date | undefined, _updatedAt?: Date | undefined);
79
+ constructor(entityType: T, entityId?: string | undefined, data?: Partial<EntitySchemaMap[T]>, _createdAt?: Date | undefined, _updatedAt?: Date | undefined, _expiresAt?: Date | undefined);
79
80
  static fromItem<T extends Entity$2>(item?: Record<string, AttributeValue>): Entity$1<T>;
80
81
  get pk(): string;
81
82
  get sk(): string;
@@ -83,6 +84,7 @@ declare class Entity$1<T extends Entity$2> extends Item$1 {
83
84
  get emailKeys(): Record<string, AttributeValue>;
84
85
  get createdAt(): string | undefined;
85
86
  get updatedAt(): string | undefined;
87
+ get expiresAt(): number | undefined;
86
88
  toItem(): Record<string, AttributeValue>;
87
89
  toJSON(): Record<string, unknown>;
88
90
  }
@@ -92,6 +94,13 @@ declare class EntityRepository extends Repository {
92
94
  private readonly dynamodbClient;
93
95
  private readonly EmailAuthEnabledEntities;
94
96
  constructor(EntityConfig: Record<Entity$2, ReturnType<typeof createEntityConfig>>, TABLE_NAME: string, dynamodbClient: DynamoDB, EmailAuthEnabledEntities: Entity$2[]);
97
+ /**
98
+ * Computes the `expiresAt` Date for an entity via its `ttl.processor` config, if any.
99
+ * When `mergeWithExisting` is true, the processor receives the previous entity's data
100
+ * merged with `incomingData` (falls back to `incomingData` alone if no previous entity
101
+ * exists yet, eg. an upsert-as-create).
102
+ */
103
+ private computeExpiresAt;
95
104
  listEntities<T extends Entity$2>({ entityType, limit, // if this is not set, it will return all items
96
105
  between, options, }: {
97
106
  entityType: T;
@@ -123,7 +132,7 @@ declare class EntityRepository extends Repository {
123
132
  mutualId?: string;
124
133
  }): Promise<Entity$1<T>>;
125
134
  upsertEntity<T extends Entity$2>(entityType: T, entityId: string, payload: Partial<EntitySchemaMap[T]>): Promise<Entity$1<T>>;
126
- updateEntityTransactItems<T extends Entity$2>(entity: Entity$1<T>, updateParams: UpdateItemCommandInput, previousUniqueFieldValues: Record<string, string>, previousEntity: Entity$1<T>): TransactWriteItem[];
135
+ updateEntityTransactItems<T extends Entity$2>(entity: Entity$1<T>, updateParams: UpdateItemCommandInput, previousUniqueFieldValues: Record<string, string>, previousEntity: Entity$1<T>, expiresAt?: Date): TransactWriteItem[];
127
136
  updateEntity<T extends Entity$2>(entityType: T, entityId: string, toUpdate: {
128
137
  data: Partial<EntitySchemaMap[T]>;
129
138
  updatedAt?: string;
@@ -182,7 +191,7 @@ declare class Mutual<B extends Entity$2, T extends Entity$2, M extends Record<st
182
191
  get createdAt(): string | undefined;
183
192
  get updatedAt(): string | undefined;
184
193
  get mutualUpdatedAt(): string | undefined;
185
- get expiresAt(): string | undefined;
194
+ get expiresAt(): number | undefined;
186
195
  toItem(): Record<string, AttributeValue>;
187
196
  toReversedItem(): Record<string, AttributeValue>;
188
197
  toJSON(): Record<string, unknown>;
@@ -393,13 +402,14 @@ var Item = class {
393
402
 
394
403
  // data/Entity.ts
395
404
  var Entity = class _Entity extends Item {
396
- constructor(entityType, entityId, data = {}, _createdAt, _updatedAt) {
405
+ constructor(entityType, entityId, data = {}, _createdAt, _updatedAt, _expiresAt) {
397
406
  super();
398
407
  this.entityType = entityType;
399
408
  this.entityId = entityId;
400
409
  this.data = data;
401
410
  this._createdAt = _createdAt;
402
411
  this._updatedAt = _updatedAt;
412
+ this._expiresAt = _expiresAt;
403
413
  this.fullId = this.pk;
404
414
  }
405
415
  static fromItem(item) {
@@ -414,7 +424,9 @@ var Entity = class _Entity extends Item {
414
424
  parsedItem.entityId,
415
425
  parsedItem.data,
416
426
  parsedItem.createdAt ? new Date(parsedItem.createdAt) : void 0,
417
- parsedItem.updatedAt ? new Date(parsedItem.updatedAt) : void 0
427
+ parsedItem.updatedAt ? new Date(parsedItem.updatedAt) : void 0,
428
+ // expiresAt is stored as epoch seconds (DynamoDB TTL requires type N)
429
+ parsedItem.expiresAt ? new Date(parsedItem.expiresAt * 1e3) : void 0
418
430
  );
419
431
  }
420
432
  get pk() {
@@ -444,6 +456,9 @@ var Entity = class _Entity extends Item {
444
456
  var _a;
445
457
  return (_a = this._updatedAt) == null ? void 0 : _a.toISOString();
446
458
  }
459
+ get expiresAt() {
460
+ return this._expiresAt ? Math.floor(this._expiresAt.getTime() / 1e3) : void 0;
461
+ }
447
462
  toItem() {
448
463
  return __spreadValues(__spreadValues({}, marshall(this.toJSON(), { removeUndefinedValues: true })), this.keys());
449
464
  }
@@ -453,7 +468,8 @@ var Entity = class _Entity extends Item {
453
468
  entityId: this.entityId,
454
469
  data: this.data,
455
470
  createdAt: this.createdAt,
456
- updatedAt: this.updatedAt
471
+ updatedAt: this.updatedAt,
472
+ expiresAt: this.expiresAt
457
473
  };
458
474
  }
459
475
  };
package/dist/index.js CHANGED
@@ -311,13 +311,14 @@ var Repository = class {
311
311
 
312
312
  // data/Entity.ts
313
313
  var Entity = class _Entity extends Item {
314
- constructor(entityType, entityId, data = {}, _createdAt, _updatedAt) {
314
+ constructor(entityType, entityId, data = {}, _createdAt, _updatedAt, _expiresAt) {
315
315
  super();
316
316
  this.entityType = entityType;
317
317
  this.entityId = entityId;
318
318
  this.data = data;
319
319
  this._createdAt = _createdAt;
320
320
  this._updatedAt = _updatedAt;
321
+ this._expiresAt = _expiresAt;
321
322
  this.fullId = this.pk;
322
323
  }
323
324
  static fromItem(item) {
@@ -332,7 +333,9 @@ var Entity = class _Entity extends Item {
332
333
  parsedItem.entityId,
333
334
  parsedItem.data,
334
335
  parsedItem.createdAt ? new Date(parsedItem.createdAt) : void 0,
335
- parsedItem.updatedAt ? new Date(parsedItem.updatedAt) : void 0
336
+ parsedItem.updatedAt ? new Date(parsedItem.updatedAt) : void 0,
337
+ // expiresAt is stored as epoch seconds (DynamoDB TTL requires type N)
338
+ parsedItem.expiresAt ? new Date(parsedItem.expiresAt * 1e3) : void 0
336
339
  );
337
340
  }
338
341
  get pk() {
@@ -362,6 +365,9 @@ var Entity = class _Entity extends Item {
362
365
  var _a;
363
366
  return (_a = this._updatedAt) == null ? void 0 : _a.toISOString();
364
367
  }
368
+ get expiresAt() {
369
+ return this._expiresAt ? Math.floor(this._expiresAt.getTime() / 1e3) : void 0;
370
+ }
365
371
  toItem() {
366
372
  return __spreadValues(__spreadValues({}, marshall2(this.toJSON(), { removeUndefinedValues: true })), this.keys());
367
373
  }
@@ -371,7 +377,8 @@ var Entity = class _Entity extends Item {
371
377
  entityId: this.entityId,
372
378
  data: this.data,
373
379
  createdAt: this.createdAt,
374
- updatedAt: this.updatedAt
380
+ updatedAt: this.updatedAt,
381
+ expiresAt: this.expiresAt
375
382
  };
376
383
  }
377
384
  };
@@ -383,6 +390,46 @@ var EntityRepository = class extends Repository {
383
390
  this.dynamodbClient = dynamodbClient;
384
391
  this.EmailAuthEnabledEntities = EmailAuthEnabledEntities;
385
392
  }
393
+ /**
394
+ * Computes the `expiresAt` Date for an entity via its `ttl.processor` config, if any.
395
+ * When `mergeWithExisting` is true, the processor receives the previous entity's data
396
+ * merged with `incomingData` (falls back to `incomingData` alone if no previous entity
397
+ * exists yet, eg. an upsert-as-create).
398
+ */
399
+ computeExpiresAt(entityType, entityId, incomingData, updatedAt, opts) {
400
+ return __async(this, null, function* () {
401
+ var _a, _b;
402
+ const processor = (_b = (_a = this.EntityConfig[entityType]) == null ? void 0 : _a.ttl) == null ? void 0 : _b.processor;
403
+ if (!processor) return void 0;
404
+ let data = incomingData;
405
+ let createdAt = updatedAt;
406
+ if (opts.mergeWithExisting) {
407
+ let previous = opts.previousEntity;
408
+ if (!previous) {
409
+ try {
410
+ previous = yield this.getEntity(entityType, entityId);
411
+ } catch (err) {
412
+ if (!(err instanceof StandardError && err.code === StandardErrorCode.ENTITY_IS_UNDEFINED)) {
413
+ throw err;
414
+ }
415
+ }
416
+ }
417
+ if (previous) {
418
+ data = __spreadValues(__spreadValues({}, previous.data), incomingData);
419
+ createdAt = previous.createdAt || updatedAt;
420
+ }
421
+ }
422
+ const result = processor({
423
+ entityId,
424
+ entityType,
425
+ data,
426
+ createdAt,
427
+ updatedAt
428
+ });
429
+ if (result === void 0) return void 0;
430
+ return result instanceof Date ? result : new Date(result * 1e3);
431
+ });
432
+ }
386
433
  listEntities(_0) {
387
434
  return __async(this, arguments, function* ({
388
435
  entityType,
@@ -606,12 +653,21 @@ var EntityRepository = class extends Repository {
606
653
  return __async(this, null, function* () {
607
654
  var _a, _b;
608
655
  const currentDatetime = (_a = opts == null ? void 0 : opts.createAndUpdateDatetime) != null ? _a : /* @__PURE__ */ new Date();
656
+ const finalEntityId = entityId || ulid();
657
+ const expiresAt = yield this.computeExpiresAt(
658
+ entityType,
659
+ finalEntityId,
660
+ entityPayload,
661
+ currentDatetime.toISOString(),
662
+ { mergeWithExisting: false }
663
+ );
609
664
  const entity = new Entity(
610
665
  entityType,
611
- entityId || ulid(),
666
+ finalEntityId,
612
667
  entityPayload,
613
668
  currentDatetime,
614
- currentDatetime
669
+ currentDatetime,
670
+ expiresAt
615
671
  );
616
672
  const uniqueFields = this.EntityConfig[entityType].uniqueFields || [];
617
673
  const uniqueFieldValues = {};
@@ -656,26 +712,47 @@ var EntityRepository = class extends Repository {
656
712
  upsertEntity(entityType, entityId, payload) {
657
713
  return __async(this, null, function* () {
658
714
  const currentDatetime = (/* @__PURE__ */ new Date()).toISOString();
659
- const toUpdateExpressions = this.toUpdate({
715
+ const expiresAtForUpdate = yield this.computeExpiresAt(
716
+ entityType,
717
+ entityId,
718
+ payload,
719
+ currentDatetime,
720
+ { mergeWithExisting: true }
721
+ );
722
+ const toUpdateExpressions = this.toUpdate(__spreadValues({
660
723
  entityType,
661
724
  entityId,
662
725
  data: payload,
663
726
  updatedAt: currentDatetime
664
- });
727
+ }, expiresAtForUpdate && {
728
+ expiresAt: Math.floor(expiresAtForUpdate.getTime() / 1e3)
729
+ }));
665
730
  const params = {
666
731
  TableName: this.TABLE_NAME,
667
732
  ReturnValues: "ALL_NEW",
733
+ ConditionExpression: "attribute_exists(PK)",
668
734
  Key: new Entity(entityType, entityId).keys(),
669
735
  UpdateExpression: toUpdateExpressions.UpdateExpression,
670
736
  ExpressionAttributeNames: __spreadValues({}, toUpdateExpressions.ExpressionAttributeNames),
671
737
  ExpressionAttributeValues: __spreadValues({}, toUpdateExpressions.ExpressionAttributeValues)
672
738
  };
673
- const resp = yield this.dynamodbClient.updateItem(params);
674
- const updatedEntity = Entity.fromItem(resp.Attributes);
675
- return updatedEntity;
739
+ try {
740
+ const resp = yield this.dynamodbClient.updateItem(params);
741
+ return Entity.fromItem(resp.Attributes);
742
+ } catch (err) {
743
+ if (!(err instanceof ConditionalCheckFailedException)) {
744
+ throw err;
745
+ }
746
+ }
747
+ return this.createEntity(
748
+ entityType,
749
+ payload,
750
+ entityId,
751
+ { createAndUpdateDatetime: new Date(currentDatetime) }
752
+ );
676
753
  });
677
754
  }
678
- updateEntityTransactItems(entity, updateParams, previousUniqueFieldValues, previousEntity) {
755
+ updateEntityTransactItems(entity, updateParams, previousUniqueFieldValues, previousEntity, expiresAt) {
679
756
  const transactItems = [
680
757
  {
681
758
  Update: __spreadProps(__spreadValues({}, updateParams), {
@@ -698,7 +775,7 @@ var EntityRepository = class extends Repository {
698
775
  Put: {
699
776
  TableName: this.TABLE_NAME,
700
777
  ConditionExpression: "attribute_not_exists(PK)",
701
- Item: __spreadProps(__spreadValues({}, entity.toItem()), {
778
+ Item: __spreadValues(__spreadProps(__spreadValues({}, entity.toItem()), {
702
779
  data: {
703
780
  M: __spreadValues(__spreadValues({}, previousEntity.toItem().data.M), entity.toItem().data.M)
704
781
  },
@@ -712,6 +789,10 @@ var EntityRepository = class extends Repository {
712
789
  S: previousEntity.createdAt || entity.createdAt || (/* @__PURE__ */ new Date()).toISOString()
713
790
  },
714
791
  updatedAt: { S: entity.updatedAt || (/* @__PURE__ */ new Date()).toISOString() }
792
+ }), expiresAt && {
793
+ expiresAt: {
794
+ N: String(Math.floor(expiresAt.getTime() / 1e3))
795
+ }
715
796
  })
716
797
  }
717
798
  }
@@ -721,11 +802,42 @@ var EntityRepository = class extends Repository {
721
802
  }
722
803
  updateEntity(entityType, entityId, toUpdate, opts) {
723
804
  return __async(this, null, function* () {
805
+ var _a, _b;
724
806
  try {
725
807
  const currentDatetime = (/* @__PURE__ */ new Date()).toISOString();
726
- const toUpdateExpressions = this.toUpdate(__spreadValues({
808
+ const uniqueFields = this.EntityConfig[entityType].uniqueFields || [];
809
+ const hasUniqueFields = Object.keys(toUpdate.data).some(
810
+ (field) => uniqueFields.includes(field)
811
+ );
812
+ const hasTtlProcessor = !!((_b = (_a = this.EntityConfig[entityType]) == null ? void 0 : _a.ttl) == null ? void 0 : _b.processor);
813
+ let previousEntity;
814
+ if (hasUniqueFields || hasTtlProcessor) {
815
+ try {
816
+ previousEntity = yield this.getEntity(entityType, entityId);
817
+ } catch (err) {
818
+ if (err instanceof StandardError && err.code === StandardErrorCode.ENTITY_IS_UNDEFINED) {
819
+ throw new StandardError(
820
+ StandardErrorCode.ENTITY_NOT_FOUND,
821
+ "Entity not found",
822
+ err,
823
+ { entityId, toUpdate }
824
+ );
825
+ }
826
+ throw err;
827
+ }
828
+ }
829
+ const expiresAtDate = yield this.computeExpiresAt(
830
+ entityType,
831
+ entityId,
832
+ toUpdate.data,
833
+ currentDatetime,
834
+ { mergeWithExisting: true, previousEntity }
835
+ );
836
+ const toUpdateExpressions = this.toUpdate(__spreadValues(__spreadValues({
727
837
  updatedAt: currentDatetime
728
- }, toUpdate));
838
+ }, toUpdate), expiresAtDate && {
839
+ expiresAt: Math.floor(expiresAtDate.getTime() / 1e3)
840
+ }));
729
841
  const params = {
730
842
  TableName: this.TABLE_NAME,
731
843
  ReturnValues: "ALL_NEW",
@@ -736,15 +848,9 @@ var EntityRepository = class extends Repository {
736
848
  ExpressionAttributeValues: __spreadValues(__spreadValues({}, toUpdateExpressions.ExpressionAttributeValues), opts == null ? void 0 : opts.ExpressionAttributeValues)
737
849
  };
738
850
  const entity = new Entity(entityType, entityId, toUpdate.data);
739
- const uniqueFields = this.EntityConfig[entityType].uniqueFields || [];
740
- const hasUniqueFields = Object.keys(toUpdate.data).some(
741
- (field) => uniqueFields.includes(field)
742
- );
743
851
  let updatedUniqueFields = [];
744
852
  let previousUniqueFieldValues = {};
745
- let previousEntity;
746
- if (hasUniqueFields) {
747
- previousEntity = yield this.getEntity(entityType, entityId);
853
+ if (hasUniqueFields && previousEntity) {
748
854
  updatedUniqueFields = uniqueFields.filter(
749
855
  (field) => toUpdate.data[field] !== void 0 && toUpdate.data[field] !== previousEntity.data[field]
750
856
  );
@@ -763,11 +869,13 @@ var EntityRepository = class extends Repository {
763
869
  }
764
870
  }
765
871
  if (updatedUniqueFields.length > 0) {
872
+ const effectiveExpiresAtDate = expiresAtDate != null ? expiresAtDate : previousEntity.expiresAt !== void 0 ? new Date(previousEntity.expiresAt * 1e3) : void 0;
766
873
  const TransactItems = this.updateEntityTransactItems(
767
874
  entity,
768
875
  params,
769
876
  previousUniqueFieldValues,
770
- previousEntity
877
+ previousEntity,
878
+ effectiveExpiresAtDate
771
879
  );
772
880
  try {
773
881
  yield this.dynamodbClient.transactWriteItems({ TransactItems });
@@ -802,7 +910,14 @@ var EntityRepository = class extends Repository {
802
910
  }
803
911
  throw err;
804
912
  }
805
- return yield this.getEntity(entityType, entityId);
913
+ return new Entity(
914
+ entityType,
915
+ entityId,
916
+ __spreadValues(__spreadValues({}, previousEntity.data), toUpdate.data),
917
+ previousEntity.createdAt ? new Date(previousEntity.createdAt) : void 0,
918
+ new Date(currentDatetime),
919
+ effectiveExpiresAtDate
920
+ );
806
921
  }
807
922
  }
808
923
  const resp = yield this.dynamodbClient.updateItem(params);
@@ -967,7 +1082,8 @@ var Mutual = class _Mutual {
967
1082
  parsedItem.createdAt ? new Date(parsedItem.createdAt) : void 0,
968
1083
  parsedItem.updatedAt ? new Date(parsedItem.updatedAt) : void 0,
969
1084
  parsedItem.mutualUpdatedAt ? new Date(parsedItem.mutualUpdatedAt) : void 0,
970
- parsedItem.expiresAt ? new Date(parsedItem.expiresAt) : void 0
1085
+ // expiresAt is stored as epoch seconds (DynamoDB TTL requires type N)
1086
+ parsedItem.expiresAt ? new Date(parsedItem.expiresAt * 1e3) : void 0
971
1087
  );
972
1088
  }
973
1089
  mainKeys() {
@@ -1010,8 +1126,7 @@ var Mutual = class _Mutual {
1010
1126
  return (_a = this._mutualUpdatedAt) == null ? void 0 : _a.toISOString();
1011
1127
  }
1012
1128
  get expiresAt() {
1013
- var _a;
1014
- return (_a = this._expiresAt) == null ? void 0 : _a.toISOString();
1129
+ return this._expiresAt ? Math.floor(this._expiresAt.getTime() / 1e3) : void 0;
1015
1130
  }
1016
1131
  toItem() {
1017
1132
  return __spreadValues(__spreadValues({}, marshall3(this.toJSON(), { removeUndefinedValues: true })), this.mainKeys());