@afora/core 0.0.6 → 0.0.8

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.
Files changed (63) hide show
  1. package/README.md +36 -7
  2. package/dist/core/application/start.d.ts +7 -0
  3. package/dist/core/application/types/application.interface.d.ts +47 -2
  4. package/dist/core/command/command.d.ts +11 -4
  5. package/dist/core/command/types/command.interface.d.ts +26 -0
  6. package/dist/core/entity/abstract-entity.d.ts +28 -1
  7. package/dist/core/entity/entity.d.ts +1 -1
  8. package/dist/core/entity/event.d.ts +13 -2
  9. package/dist/core/entity/exceptions.d.ts +8 -0
  10. package/dist/core/entity/types/entity.interface.d.ts +66 -2
  11. package/dist/core/entity/version.d.ts +13 -0
  12. package/dist/core/event-policy/event-policy.d.ts +25 -0
  13. package/dist/core/event-policy/exceptions.d.ts +11 -0
  14. package/dist/core/event-policy/publish.d.ts +17 -0
  15. package/dist/core/event-policy/publishers.d.ts +18 -0
  16. package/dist/core/event-policy/types/event-policy.interface.d.ts +72 -0
  17. package/dist/core/output/coerce.d.ts +9 -0
  18. package/dist/core/output/openapi-sink.d.ts +138 -38
  19. package/dist/core/output/status.d.ts +2 -1
  20. package/dist/core/parse/parse.d.ts +10 -0
  21. package/dist/core/parse/strip.d.ts +13 -0
  22. package/dist/core/property/input.d.ts +20 -0
  23. package/dist/core/record/plugin.d.ts +6 -2
  24. package/dist/core/repository/exceptions.d.ts +30 -0
  25. package/dist/core/repository/in-memory.d.ts +14 -1
  26. package/dist/core/repository/plan.d.ts +20 -0
  27. package/dist/core/repository/provider.d.ts +1 -1
  28. package/dist/core/repository/query.d.ts +9 -2
  29. package/dist/core/repository/repository.d.ts +40 -8
  30. package/dist/core/repository/state.d.ts +39 -2
  31. package/dist/core/repository/types/query.interface.d.ts +106 -5
  32. package/dist/core/repository/types/repository.interface.d.ts +113 -7
  33. package/dist/core/use-case/exceptions.d.ts +36 -0
  34. package/dist/core/use-case/plugin-catalog.d.ts +11 -0
  35. package/dist/core/use-case/plugin.d.ts +25 -0
  36. package/dist/core/use-case/steps/types/steps.interface.d.ts +23 -6
  37. package/dist/core/use-case/target.d.ts +9 -0
  38. package/dist/core/use-case/types/plugin.interface.d.ts +185 -0
  39. package/dist/experimental.js +3 -3
  40. package/dist/{index-3nd9bjnc.js → index-qxfkyd6k.js} +646 -635
  41. package/dist/{index-gbgc40xe.js → index-t5qwtyj5.js} +2798 -295
  42. package/dist/index.d.ts +35 -15
  43. package/dist/index.js +99 -15
  44. package/dist/std/blueprint-plugin.d.ts +6 -2
  45. package/dist/std/blueprint.d.ts +3 -0
  46. package/dist/std/command.d.ts +11 -0
  47. package/dist/std/entity.d.ts +2 -1
  48. package/dist/std/event-policies.d.ts +2 -0
  49. package/dist/std/properties/exceptions.d.ts +11 -0
  50. package/dist/std/properties/json.d.ts +54 -0
  51. package/dist/std/properties/password.d.ts +16 -1
  52. package/dist/std/properties/time.d.ts +4 -0
  53. package/dist/std/use-cases/auth.d.ts +5 -0
  54. package/dist/std/use-cases/crud.d.ts +25 -0
  55. package/dist/std/use-cases/index.d.ts +11 -0
  56. package/dist/std/use-cases/lifecycle.d.ts +13 -0
  57. package/dist/std/use-cases/shared.d.ts +88 -0
  58. package/dist/std/use-cases/types.d.ts +291 -0
  59. package/dist/testing/index.d.ts +2 -0
  60. package/dist/testing/index.js +355 -2
  61. package/dist/testing/memory-publisher.d.ts +16 -0
  62. package/dist/testing/provider-contract.d.ts +14 -0
  63. package/package.json +3 -2
@@ -11,6 +11,9 @@ import {
11
11
  recordFieldOf2,
12
12
  recordNameOf2,
13
13
  declaredRecordNameOf,
14
+ fieldsOf,
15
+ isPlainObject,
16
+ listOf2,
14
17
  isBlueprintList2,
15
18
  UNEXPECTED_ERROR2,
16
19
  Exception2,
@@ -27,22 +30,25 @@ import {
27
30
  storeRecordValue,
28
31
  runRecordEffects,
29
32
  AbstractRecord2,
33
+ InvalidPropertyValue2,
34
+ exports_exceptions2,
35
+ propertyOf2,
36
+ exports_exceptions3,
37
+ optionalOf2,
38
+ blueprintBuilderOf2,
39
+ blueprintFromProperties,
30
40
  IdentifiedRecord,
31
41
  isVersion,
32
- versionParts,
42
+ satisfies,
33
43
  pluginsOf2,
34
44
  isRecordPlugin2,
45
+ hasPlugin2,
35
46
  recordPluginOf2,
36
47
  recordBuilderOf2,
37
48
  pluginContextOf,
38
49
  recordOf3,
39
- declareRecord,
40
- exports_exceptions2,
41
- InvalidPropertyValue2,
42
- exports_exceptions3,
43
- propertyOf2,
44
- blueprintBuilderOf2
45
- } from "./index-3nd9bjnc.js";
50
+ declareRecord
51
+ } from "./index-qxfkyd6k.js";
46
52
 
47
53
  // src/core/environment/environment.ts
48
54
  var exports_environment = {};
@@ -68,30 +74,54 @@ __export(exports_exceptions4, {
68
74
  UnregisteredUseCaseDependency: () => UnregisteredUseCaseDependency2
69
75
  });
70
76
 
71
- // src/core/logger/exceptions.ts
77
+ // src/core/event-policy/exceptions.ts
72
78
  var exports_exceptions5 = {};
73
79
  __export(exports_exceptions5, {
74
- InvalidLogDeclaration: () => InvalidLogDeclaration2
80
+ InvalidEventPolicy: () => InvalidEventPolicy2,
81
+ PublisherNotBound: () => PublisherNotBound2
75
82
  });
76
83
 
77
- // src/core/entity/exceptions.ts
84
+ // src/core/logger/exceptions.ts
78
85
  var exports_exceptions6 = {};
79
86
  __export(exports_exceptions6, {
87
+ InvalidLogDeclaration: () => InvalidLogDeclaration2
88
+ });
89
+
90
+ // src/core/entity/exceptions.ts
91
+ var exports_exceptions7 = {};
92
+ __export(exports_exceptions7, {
80
93
  DeletedEntity: () => DeletedEntity2,
81
94
  ImmutableEntityKey: () => ImmutableEntityKey2,
95
+ InvalidEntityExpiry: () => InvalidEntityExpiry2,
82
96
  ReservedEntityKey: () => ReservedEntityKey2
83
97
  });
84
98
 
85
99
  // src/core/repository/exceptions.ts
86
- var exports_exceptions7 = {};
87
- __export(exports_exceptions7, {
100
+ var exports_exceptions8 = {};
101
+ __export(exports_exceptions8, {
88
102
  InvalidRepositoryCursor: () => InvalidRepositoryCursor2,
89
103
  InvalidRepositoryData: () => InvalidRepositoryData2,
90
104
  InvalidRepositoryDeclaration: () => InvalidRepositoryDeclaration2,
91
105
  InvalidRepositoryEntity: () => InvalidRepositoryEntity2,
106
+ InvalidRepositoryIncrement: () => InvalidRepositoryIncrement2,
92
107
  InvalidRepositoryQuery: () => InvalidRepositoryQuery2,
108
+ RepositoryConcurrencyConflict: () => RepositoryConcurrencyConflict2,
93
109
  RepositoryEntityNotFound: () => RepositoryEntityNotFound2,
94
- RepositoryUniqueViolation: () => RepositoryUniqueViolation2
110
+ RepositoryUniqueViolation: () => RepositoryUniqueViolation2,
111
+ RepositoryUsedOutsideApplication: () => RepositoryUsedOutsideApplication2
112
+ });
113
+
114
+ // src/core/use-case/exceptions.ts
115
+ var exports_exceptions9 = {};
116
+ __export(exports_exceptions9, {
117
+ IncompatibleUseCasePlugin: () => IncompatibleUseCasePlugin2,
118
+ InvalidUseCasePlugin: () => InvalidUseCasePlugin2,
119
+ InvalidUseCaseStep: () => InvalidUseCaseStep2,
120
+ UnknownUseCasePlugin: () => UnknownUseCasePlugin2,
121
+ UseCaseEntityConflict: () => UseCaseEntityConflict2,
122
+ UseCaseEntityNotFound: () => UseCaseEntityNotFound2,
123
+ UseCaseInvalidCredentials: () => UseCaseInvalidCredentials2,
124
+ UseCaseUnavailable: () => UseCaseUnavailable2
95
125
  });
96
126
 
97
127
  // src/core/use-case/use-case.ts
@@ -106,15 +136,9 @@ __export(exports_use_case, {
106
136
  useCaseOf: () => useCaseOf2
107
137
  });
108
138
 
109
- // src/core/use-case/exceptions.ts
110
- var exports_exceptions8 = {};
111
- __export(exports_exceptions8, {
112
- InvalidUseCaseStep: () => InvalidUseCaseStep2
113
- });
114
-
115
139
  // src/core/command/exceptions.ts
116
- var exports_exceptions9 = {};
117
- __export(exports_exceptions9, {
140
+ var exports_exceptions10 = {};
141
+ __export(exports_exceptions10, {
118
142
  CommandEventFailed: () => CommandEventFailed2,
119
143
  InvalidCommandContext: () => InvalidCommandContext2,
120
144
  InvalidCommandDeclaration: () => InvalidCommandDeclaration2,
@@ -123,14 +147,14 @@ __export(exports_exceptions9, {
123
147
  });
124
148
 
125
149
  // src/core/openapi/exceptions.ts
126
- var exports_exceptions10 = {};
127
- __export(exports_exceptions10, {
150
+ var exports_exceptions11 = {};
151
+ __export(exports_exceptions11, {
128
152
  InvalidOpenAPIModel: () => InvalidOpenAPIModel2
129
153
  });
130
154
 
131
155
  // src/core/output/exceptions.ts
132
- var exports_exceptions11 = {};
133
- __export(exports_exceptions11, {
156
+ var exports_exceptions12 = {};
157
+ __export(exports_exceptions12, {
134
158
  DuplicateOperation: () => DuplicateOperation2,
135
159
  InvalidOutputStage: () => InvalidOutputStage2
136
160
  });
@@ -203,23 +227,6 @@ function isEnvironment2(value) {
203
227
  return typeof value === "object" && value !== null && value[Kind2] === "environment";
204
228
  }
205
229
 
206
- // src/core/exception/guard.ts
207
- function guard2(scope, run, resolve) {
208
- const handle = (error) => {
209
- const exception = Exception2.from(error, scope);
210
- const pass = () => {
211
- throw exception === error ? exception.propagate(scope) : exception;
212
- };
213
- return resolve ? resolve(exception, pass) : pass();
214
- };
215
- try {
216
- const result = run();
217
- return result instanceof Promise ? result.catch(handle) : result;
218
- } catch (error) {
219
- return handle(error);
220
- }
221
- }
222
-
223
230
  // src/core/application/exceptions.ts
224
231
  var InvalidApplicationDeclaration2 = exceptionOf2({
225
232
  layer: "application" /* Application */,
@@ -319,6 +326,53 @@ function setStartedApplication(application) {
319
326
  slot().current = application;
320
327
  }
321
328
 
329
+ // src/core/event-policy/exceptions.ts
330
+ var InvalidEventPolicy2 = exceptionOf2({
331
+ layer: "event" /* Event */,
332
+ name: "InvalidEventPolicy",
333
+ message: ({ reason }) => reason
334
+ });
335
+ var PublisherNotBound2 = exceptionOf2({
336
+ layer: "application" /* Application */,
337
+ name: "PublisherNotBound",
338
+ message: ({ publisher }) => `No publisher "${publisher}" is declared: add it to applicationOf({ publishers }).`
339
+ });
340
+
341
+ // src/core/event-policy/publishers.ts
342
+ var ApplicationPublishersKey = Symbol("afora.application.publishers");
343
+ var boundPublishers = new WeakMap;
344
+ function bindPublishers(command, publishers) {
345
+ boundPublishers.set(command, publishers);
346
+ }
347
+ function isEventPublisher(value) {
348
+ return typeof value === "object" && value !== null && typeof value.publish === "function";
349
+ }
350
+ function resolvePublisher(command, name, source) {
351
+ const publishers = boundPublishers.get(command) ?? startedApplication()?.[ApplicationPublishersKey];
352
+ const publisher = publishers && Object.hasOwn(publishers, name) ? publishers[name] : undefined;
353
+ if (!publisher) {
354
+ throw new PublisherNotBound2({ source, details: { publisher: name } });
355
+ }
356
+ return publisher;
357
+ }
358
+
359
+ // src/core/exception/guard.ts
360
+ function guard2(scope, run, resolve) {
361
+ const handle = (error) => {
362
+ const exception = Exception2.from(error, scope);
363
+ const pass = () => {
364
+ throw exception === error ? exception.propagate(scope) : exception;
365
+ };
366
+ return resolve ? resolve(exception, pass) : pass();
367
+ };
368
+ try {
369
+ const result = run();
370
+ return result instanceof Promise ? result.catch(handle) : result;
371
+ } catch (error) {
372
+ return handle(error);
373
+ }
374
+ }
375
+
322
376
  // src/core/logger/exceptions.ts
323
377
  var InvalidLogDeclaration2 = exceptionOf2({
324
378
  layer: "application" /* Application */,
@@ -518,34 +572,81 @@ var DeletedEntity2 = exceptionOf2({
518
572
  name: "DeletedEntity",
519
573
  message: ({ id }, source) => `Entity "${source}" of id "${id}" was destroyed and cannot change.`
520
574
  });
575
+ var InvalidEntityExpiry2 = exceptionOf2({
576
+ layer: "entity" /* Entity */,
577
+ name: "InvalidEntityExpiry",
578
+ message: ({ reason }, source) => `Invalid expires of entity "${source}": ${reason}`
579
+ });
580
+
581
+ // src/core/entity/version.ts
582
+ import { Type } from "typebox";
583
+ class EntityVersion2 extends propertyOf2({
584
+ name: "version",
585
+ schema: Type.Integer({ minimum: 1 }),
586
+ default: 1
587
+ }) {
588
+ }
589
+ var VersionIdentity = blueprintBuilderOf2()(() => ({
590
+ version: EntityVersion2
591
+ }));
521
592
 
522
593
  // src/core/entity/entity.ts
523
594
  function entityBuilderOf2({
524
595
  identity,
525
596
  generateId,
526
597
  now,
598
+ expiresAt,
527
599
  plugins: catalogs
528
600
  }) {
529
601
  const clock = Object.freeze({ generateId, now });
530
602
  const context = pluginContextOf(catalogs ?? []);
531
603
  return (blueprint, options = {}) => {
604
+ const lifetime = lifetimeOf(blueprint, options);
605
+ const expiring = lifetime !== undefined;
606
+ const versioned = options.versioned === true;
607
+ const added = [
608
+ ...versioned ? [ENTITY_VERSION_KEY] : [],
609
+ ...expiring ? [EXPIRES_AT] : []
610
+ ];
611
+ if (expiring && expiresAt === undefined) {
612
+ throw new InvalidEntityExpiry2({
613
+ source: options.name ?? "entity",
614
+ details: {
615
+ reason: "the entity builder has no expiresAt field, which expires: { after } needs."
616
+ }
617
+ });
618
+ }
532
619
  for (const key of Object.keys(blueprint.properties)) {
533
- if (RESERVED_ENTITY_KEYS2.includes(key)) {
620
+ if (RESERVED_ENTITY_KEYS2.includes(key) || added.includes(key)) {
534
621
  throw new ReservedEntityKey2({ source: "entity", details: { key } });
535
622
  }
536
623
  }
537
624
  for (const key of options.change ?? []) {
538
- if (ENTITY_IDENTITY_KEYS2.includes(key)) {
625
+ if (ENTITY_IDENTITY_KEYS2.includes(key) || added.includes(key)) {
539
626
  throw new ImmutableEntityKey2({ source: "entity", details: { key } });
540
627
  }
541
628
  }
542
- const DeclaredEntity = declareRecord(AbstractEntity2, identity.join(blueprint), { ...options, unique: ["id", ...options.unique ?? []] }, "entity", ENTITY_MEMBERS, context);
629
+ const DeclaredEntity = declareRecord(AbstractEntity2, identity.join(...versioned ? [VersionIdentity] : [], ...expiring ? [
630
+ blueprintFromProperties([], {
631
+ [EXPIRES_AT]: expiresAt
632
+ })
633
+ ] : [], blueprint), {
634
+ ...withoutStorage(options),
635
+ unique: ["id", ...options.unique ?? []]
636
+ }, "entity", ENTITY_MEMBERS, context);
543
637
  Object.defineProperty(DeclaredEntity, IdentifiedRecord, { value: true });
544
638
  Object.defineProperty(DeclaredEntity, EntityClock, { value: clock });
545
639
  Object.defineProperty(DeclaredEntity, EntityClassBrand, { value: true });
546
640
  Object.defineProperty(DeclaredEntity, EntityUniqueKeys2, {
547
641
  value: DeclaredEntity.options.unique
548
642
  });
643
+ Object.defineProperty(DeclaredEntity, EntityLifetime, { value: lifetime });
644
+ Object.defineProperty(DeclaredEntity, EntitySettings2, {
645
+ value: Object.freeze({
646
+ versioned,
647
+ expires: options.expires
648
+ })
649
+ });
549
650
  Object.defineProperty(DeclaredEntity, EntityEventSettings, {
550
651
  value: Object.freeze({
551
652
  onCreate: options.onCreate,
@@ -562,6 +663,59 @@ function isEntityClass2(value) {
562
663
  function isEntity2(value) {
563
664
  return value instanceof AbstractEntity2;
564
665
  }
666
+ function withoutStorage(options) {
667
+ const { versioned: _versioned, expires: _expires, ...rest } = options;
668
+ return rest;
669
+ }
670
+ var DURATION_UNITS = {
671
+ days: 86400000,
672
+ hours: 3600000,
673
+ minutes: 60000,
674
+ seconds: 1000
675
+ };
676
+ function isDateField(field) {
677
+ if (!isProperty2(field))
678
+ return false;
679
+ const schema = field.type.schema.type.Type();
680
+ return schema.type === "string" && (schema.format === "date-time" || schema.format === "date");
681
+ }
682
+ function lifetimeOf(blueprint, options) {
683
+ const { expires } = options;
684
+ if (expires === undefined)
685
+ return;
686
+ const fail = (reason) => {
687
+ throw new InvalidEntityExpiry2({
688
+ source: options.name ?? "entity",
689
+ details: { reason }
690
+ });
691
+ };
692
+ const plain = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
693
+ if (!plain(expires) || Object.keys(expires).length !== 1)
694
+ return fail("expected { after: duration } or { at: key }.");
695
+ if ("at" in expires) {
696
+ const { at } = expires;
697
+ if (typeof at !== "string" || !Object.hasOwn(blueprint.properties, at))
698
+ return fail(`"${String(at)}" is not a key of the blueprint.`);
699
+ if (!isDateField(blueprint.properties[at]))
700
+ return fail(`"${at}" does not hold a date.`);
701
+ return;
702
+ }
703
+ if (!("after" in expires)) {
704
+ return fail("expected { after: duration } or { at: key }.");
705
+ }
706
+ const { after } = expires;
707
+ if (!plain(after) || Object.keys(after).length === 0)
708
+ return fail("after must be a non-empty duration, e.g. { days: 400 }.");
709
+ let total = 0;
710
+ for (const [unit, amount] of Object.entries(after)) {
711
+ if (!Object.hasOwn(DURATION_UNITS, unit))
712
+ return fail(`"${unit}" is not a unit of a duration (days, hours, minutes, seconds).`);
713
+ if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0)
714
+ return fail(`${unit} must be a positive finite number.`);
715
+ total += amount * DURATION_UNITS[unit];
716
+ }
717
+ return total;
718
+ }
565
719
 
566
720
  // src/core/entity/event.ts
567
721
  var EntityClassBrand = Symbol("entityClass");
@@ -569,14 +723,20 @@ var EventBrand = Symbol("event");
569
723
  var EntityEventSettings = Symbol("entityEvents");
570
724
  var definitions = new WeakMap;
571
725
  var queues = new WeakMap;
726
+ var withHandler = new WeakSet;
572
727
  function eventOf2(name, entityClass, handler) {
573
728
  if (!isEntityClass2(entityClass)) {
574
729
  throw new TypeError("An event requires an entity class");
575
730
  }
731
+ if (handler !== undefined && typeof handler !== "function") {
732
+ throw new TypeError(`The handler of event ${name} must be a function`);
733
+ }
576
734
  const event = Object.freeze({
577
735
  name,
578
736
  [EventBrand]: true
579
737
  });
738
+ if (handler)
739
+ withHandler.add(event);
580
740
  definitions.set(event, (entity) => {
581
741
  if (!(entity instanceof entityClass)) {
582
742
  throw new TypeError(`Incompatible entity for event ${name}`);
@@ -585,6 +745,8 @@ function eventOf2(name, entityClass, handler) {
585
745
  name,
586
746
  entity,
587
747
  async handle(context) {
748
+ if (!handler)
749
+ return;
588
750
  await handler(entity, {
589
751
  logger: context?.logger ?? resolveLogger(undefined, undefined) ?? silentLogger
590
752
  });
@@ -593,6 +755,23 @@ function eventOf2(name, entityClass, handler) {
593
755
  });
594
756
  return event;
595
757
  }
758
+ function eventHasHandler(event) {
759
+ return withHandler.has(event);
760
+ }
761
+ function lifecycleEventsOf(entityClass) {
762
+ const settings = entityClass[EntityEventSettings];
763
+ const found = {};
764
+ for (const cycle of ["onCreate", "onUpdate", "onDelete"]) {
765
+ const reference = settings?.[cycle];
766
+ if (reference === undefined)
767
+ continue;
768
+ const events = typeof reference === "function" ? reference() : reference;
769
+ const list = Array.isArray(events) ? events : [events];
770
+ if (list.length > 0)
771
+ found[cycle] = list;
772
+ }
773
+ return found;
774
+ }
596
775
  function enqueueEvent(entity, event) {
597
776
  const bind = definitions.get(event);
598
777
  if (!bind)
@@ -634,6 +813,30 @@ var RESERVED_ENTITY_KEYS2 = Object.freeze([
634
813
  ]);
635
814
  var EntityClock = Symbol("entityClock");
636
815
  var EntityUniqueKeys2 = Symbol("entityUniqueKeys");
816
+ var EntitySettings2 = Symbol("entitySettings");
817
+ var EntityLifetime = Symbol("entityLifetime");
818
+ var EXPIRES_AT = "expiresAt";
819
+ var ENTITY_VERSION_KEY = "version";
820
+ function isVersionedEntityClass(entityClass) {
821
+ if (typeof entityClass !== "function")
822
+ return false;
823
+ return entityClass[EntitySettings2]?.versioned === true;
824
+ }
825
+ function identityKeysOf(entityClass) {
826
+ const keys = [...ENTITY_IDENTITY_KEYS2];
827
+ if (isVersionedEntityClass(entityClass))
828
+ keys.push(ENTITY_VERSION_KEY);
829
+ if (typeof entityClass === "function" && entityClass[EntityLifetime] !== undefined) {
830
+ keys.push(EXPIRES_AT);
831
+ }
832
+ return keys;
833
+ }
834
+ function isIdentityKey(Entity, key) {
835
+ return identityKeysOf(Entity).includes(key);
836
+ }
837
+ function storeEntityVersion(entity, version) {
838
+ storeRecordValue(entity, ENTITY_VERSION_KEY, version);
839
+ }
637
840
  function identityOf(entity) {
638
841
  return entity;
639
842
  }
@@ -648,23 +851,34 @@ function assertAlive(entity) {
648
851
  });
649
852
  }
650
853
  }
651
- function withIdentity(value, clock) {
854
+ function withIdentity(value, clock, lifetime, versioned) {
652
855
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
653
856
  return value;
654
857
  }
655
- const { id, createdAt } = value;
656
- if (id !== undefined && createdAt !== undefined)
858
+ const { id, createdAt, expiresAt, version } = value;
859
+ const expiring = lifetime !== undefined && expiresAt === undefined;
860
+ const unversioned = versioned && version === undefined;
861
+ if (id !== undefined && createdAt !== undefined && !expiring && !unversioned)
657
862
  return value;
658
- return {
863
+ const created = createdAt === undefined ? clock.now() : createdAt;
864
+ const identity = {
659
865
  ...value,
660
866
  id: id === undefined ? clock.generateId() : id,
661
- createdAt: createdAt === undefined ? clock.now() : createdAt
867
+ createdAt: created,
868
+ ...unversioned ? { [ENTITY_VERSION_KEY]: 1 } : {}
662
869
  };
870
+ if (expiring) {
871
+ const start = typeof created === "string" ? Date.parse(created) : NaN;
872
+ if (!Number.isNaN(start)) {
873
+ identity[EXPIRES_AT] = new Date(start + lifetime).toISOString();
874
+ }
875
+ }
876
+ return identity;
663
877
  }
664
878
 
665
879
  class AbstractEntity2 extends AbstractRecord2 {
666
880
  constructor(value) {
667
- super(withIdentity(value, new.target[EntityClock]));
881
+ super(withIdentity(value, new.target[EntityClock], new.target[EntityLifetime], isVersionedEntityClass(new.target)));
668
882
  const input = value;
669
883
  if (input.id === undefined && input.createdAt === undefined) {
670
884
  enqueueLifecycle(this, "onCreate");
@@ -691,7 +905,7 @@ class AbstractEntity2 extends AbstractRecord2 {
691
905
  }
692
906
  set(key, value) {
693
907
  const name = String(key);
694
- if (ENTITY_IDENTITY_KEYS2.includes(name)) {
908
+ if (isIdentityKey(this.constructor, name)) {
695
909
  throw new ImmutableEntityKey2({
696
910
  source: recordNameOf2(this.constructor),
697
911
  details: { key: name }
@@ -745,11 +959,34 @@ var RepositoryUniqueViolation2 = exceptionOf2({
745
959
  name: "RepositoryUniqueViolation",
746
960
  message: ({ key }, source) => `"${source}" already holds an entity with this "${key}".`
747
961
  });
962
+ var RepositoryUsedOutsideApplication2 = exceptionOf2({
963
+ layer: "repository" /* Repository */,
964
+ name: "RepositoryUsedOutsideApplication",
965
+ message: ({
966
+ application,
967
+ repository
968
+ }, source) => `"${source}" is bound to the application "${application}", and its declaration no longer reads or writes: use app.repositories.${repository}, run its use cases with app.run, or give the repository of the application to execute.`
969
+ });
748
970
  var RepositoryEntityNotFound2 = exceptionOf2({
749
971
  layer: "repository" /* Repository */,
750
972
  name: "RepositoryEntityNotFound",
751
973
  message: ({ id }, source) => `"${source}" holds no entity of id "${id}".`
752
974
  });
975
+ var RepositoryConcurrencyConflict2 = exceptionOf2({
976
+ layer: "repository" /* Repository */,
977
+ name: "RepositoryConcurrencyConflict",
978
+ message: ({
979
+ id,
980
+ expected
981
+ }, source) => `"${source}" holds another version of "${id}" than ${expected}: it changed since it was read.`
982
+ });
983
+ var InvalidRepositoryIncrement2 = exceptionOf2({
984
+ layer: "repository" /* Repository */,
985
+ name: "InvalidRepositoryIncrement",
986
+ message: ({
987
+ reason
988
+ }) => reason
989
+ });
753
990
 
754
991
  // src/core/repository/cursor.ts
755
992
  var VERSION = 1;
@@ -892,7 +1129,14 @@ function plain(value) {
892
1129
  function isMembership(value) {
893
1130
  return plain(value) && Object.keys(value).length === 1 && Array.isArray(value.in);
894
1131
  }
895
- var QUERY_OPTIONS = ["where", "orderBy", "limit", "offset", "withDeleted"];
1132
+ var QUERY_OPTIONS = [
1133
+ "where",
1134
+ "orderBy",
1135
+ "limit",
1136
+ "offset",
1137
+ "withDeleted",
1138
+ "withExpired"
1139
+ ];
896
1140
  function prepareQuery(context, input = {}, map = (_key, value) => value, options = QUERY_OPTIONS) {
897
1141
  const fail = (reason) => {
898
1142
  throw new InvalidRepositoryQuery2({
@@ -917,6 +1161,8 @@ function prepareQuery(context, input = {}, map = (_key, value) => value, options
917
1161
  }
918
1162
  if (input.withDeleted !== undefined && typeof input.withDeleted !== "boolean")
919
1163
  fail("Expected withDeleted to be boolean.");
1164
+ if (input.withExpired !== undefined && typeof input.withExpired !== "boolean")
1165
+ fail("Expected withExpired to be boolean.");
920
1166
  let where;
921
1167
  if (input.where !== undefined) {
922
1168
  if (!plain(input.where))
@@ -957,8 +1203,18 @@ function sameValue(a, b) {
957
1203
  const keys = Object.keys(a);
958
1204
  return keys.length === Object.keys(b).length && keys.every((key) => Object.hasOwn(b, key) && sameValue(a[key], b[key]));
959
1205
  }
960
- function matchesQuery(data, query) {
961
- return (query.withDeleted === true || data.deletedAt === undefined) && Object.entries(query.where ?? {}).every(([key, value]) => isMembership(value) ? value.in.some((candidate) => sameValue(data[key], candidate)) : sameValue(data[key], value));
1206
+ function isExpired(value, now) {
1207
+ if (value === undefined || value === null)
1208
+ return false;
1209
+ const at = value instanceof Date ? value.getTime() : typeof value === "string" ? Date.parse(value) : Number.NaN;
1210
+ return !Number.isNaN(at) && at <= now;
1211
+ }
1212
+ function nowOf(query) {
1213
+ const now = query.now === undefined ? Number.NaN : Date.parse(query.now);
1214
+ return Number.isNaN(now) ? Date.now() : now;
1215
+ }
1216
+ function matchesQuery(data, query, expiry, now = expiry === undefined ? 0 : nowOf(query)) {
1217
+ return (query.withDeleted === true || data.deletedAt === undefined) && (expiry === undefined || query.withExpired === true || !isExpired(data[expiry], now)) && Object.entries(query.where ?? {}).every(([key, value]) => isMembership(value) ? value.in.some((candidate) => sameValue(data[key], candidate)) : sameValue(data[key], value));
962
1218
  }
963
1219
  function codePoints(text) {
964
1220
  return Array.from(text, (char) => char.codePointAt(0));
@@ -1008,8 +1264,9 @@ function rowsAfter(rows, orders, after) {
1008
1264
  const position = Object.fromEntries(orders.map(({ key }, index) => [key, after[index]]));
1009
1265
  return rows.filter((row) => compareRows(orders, row, position) > 0);
1010
1266
  }
1011
- function selectRows(rows, query) {
1012
- const ordered = orderRows([...rows].filter((data) => matchesQuery(data, query)), query);
1267
+ function selectRows(rows, query, expiry) {
1268
+ const now = expiry === undefined ? 0 : nowOf(query);
1269
+ const ordered = orderRows([...rows].filter((data) => matchesQuery(data, query, expiry, now)), query);
1013
1270
  const matched = query.after === undefined ? ordered : rowsAfter(ordered, query.orderBy ?? [], query.after);
1014
1271
  const start = query.offset ?? 0;
1015
1272
  return matched.slice(start, query.limit === undefined ? undefined : start + query.limit);
@@ -1062,15 +1319,18 @@ function scopeOf(initial, root) {
1062
1319
  }
1063
1320
  };
1064
1321
  const provider = {
1065
- capabilities: Object.freeze({ transactions: true, keyset: true }),
1322
+ capabilities: Object.freeze({
1323
+ transactions: true,
1324
+ keyset: true,
1325
+ versioning: true,
1326
+ atomic: true,
1327
+ expiry: true
1328
+ }),
1066
1329
  findOne: (context, query = {}) => whenIdle(() => {
1067
- const [first] = selectRows(tableOf(context).values(), {
1068
- ...query,
1069
- limit: Math.min(query.limit ?? 1, 1)
1070
- });
1330
+ const [first] = selectRows(tableOf(context).values(), { ...query, limit: Math.min(query.limit ?? 1, 1) }, context.expiry?.key);
1071
1331
  return first === undefined ? undefined : copy(first);
1072
1332
  }),
1073
- findMany: (context, query = {}) => whenIdle(() => selectRows(tableOf(context).values(), query).map(copy)),
1333
+ findMany: (context, query = {}) => whenIdle(() => selectRows(tableOf(context).values(), query, context.expiry?.key).map(copy)),
1074
1334
  insert: (context, data) => whenIdle(() => {
1075
1335
  const table = tableOf(context);
1076
1336
  if (table.has(idOf(data))) {
@@ -1082,17 +1342,61 @@ function scopeOf(initial, root) {
1082
1342
  assertUnique(context, data);
1083
1343
  table.set(idOf(data), copy(data));
1084
1344
  }),
1085
- update: (context, data) => whenIdle(() => {
1345
+ update: (context, data, options = {}) => whenIdle(() => {
1086
1346
  const table = tableOf(context);
1087
- if (!table.has(idOf(data))) {
1347
+ const stored = table.get(idOf(data));
1348
+ if (!stored) {
1088
1349
  throw new RepositoryEntityNotFound2({
1089
1350
  source: context.name,
1090
1351
  details: { id: idOf(data) }
1091
1352
  });
1092
1353
  }
1354
+ const { expectedVersion: expected } = options;
1355
+ if (expected !== undefined && stored.version !== expected) {
1356
+ throw new RepositoryConcurrencyConflict2({
1357
+ source: context.name,
1358
+ details: {
1359
+ id: idOf(data),
1360
+ expected,
1361
+ ...typeof stored.version === "number" ? { actual: stored.version } : {}
1362
+ }
1363
+ });
1364
+ }
1093
1365
  assertUnique(context, data);
1094
1366
  table.set(idOf(data), copy(data));
1095
1367
  }),
1368
+ increment: (context, id, deltas, options = {}) => whenIdle(() => {
1369
+ const table = tableOf(context);
1370
+ const found = table.get(id);
1371
+ const expiry = context.expiry?.key;
1372
+ const expired = found !== undefined && expiry !== undefined && !matchesQuery(found, { withDeleted: true, now: options.now }, expiry);
1373
+ const stored = expired ? undefined : found;
1374
+ if (!stored || stored.deletedAt != null) {
1375
+ if (!options.create) {
1376
+ throw new RepositoryEntityNotFound2({
1377
+ source: context.name,
1378
+ details: { id }
1379
+ });
1380
+ }
1381
+ if (stored) {
1382
+ throw new RepositoryUniqueViolation2({
1383
+ source: context.name,
1384
+ details: { key: "id" }
1385
+ });
1386
+ }
1387
+ const created = { ...options.create, id };
1388
+ assertUnique(context, created);
1389
+ table.set(id, copy(created));
1390
+ return copy(created);
1391
+ }
1392
+ const next = { ...stored, ...options.set };
1393
+ for (const [key, delta] of Object.entries(deltas)) {
1394
+ const value = stored[key];
1395
+ next[key] = (typeof value === "number" ? value : 0) + delta;
1396
+ }
1397
+ table.set(id, copy(next));
1398
+ return copy(next);
1399
+ }),
1096
1400
  remove: (context, id) => whenIdle(() => {
1097
1401
  if (!tableOf(context).delete(id)) {
1098
1402
  throw new RepositoryEntityNotFound2({
@@ -1102,9 +1406,11 @@ function scopeOf(initial, root) {
1102
1406
  }
1103
1407
  }),
1104
1408
  count: (context, query = {}) => whenIdle(() => {
1409
+ const expiry = context.expiry?.key;
1410
+ const now = expiry === undefined ? 0 : nowOf(query);
1105
1411
  let total = 0;
1106
1412
  for (const data of tableOf(context).values()) {
1107
- if (matchesQuery(data, query))
1413
+ if (matchesQuery(data, query, expiry, now))
1108
1414
  total++;
1109
1415
  }
1110
1416
  return total;
@@ -1145,6 +1451,35 @@ function inMemoryProvider2() {
1145
1451
  });
1146
1452
  }
1147
1453
 
1454
+ // src/core/repository/plan.ts
1455
+ var ID = Object.freeze({ access: "id" });
1456
+ var UNIQUE = Object.freeze({ access: "unique" });
1457
+ var SCAN = Object.freeze({ access: "scan" });
1458
+ function isMembership2(value) {
1459
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 1 && Array.isArray(value.in);
1460
+ }
1461
+ function planQuery(repository, query = {}) {
1462
+ const where = query.where ?? {};
1463
+ const keys = Object.keys(where);
1464
+ if (keys.includes("id"))
1465
+ return ID;
1466
+ if (keys.some((key) => repository.unique.includes(key)))
1467
+ return UNIQUE;
1468
+ const equal = new Set(keys.filter((key) => !isMembership2(where[key])));
1469
+ const first = query.orderBy?.[0]?.key;
1470
+ let best;
1471
+ for (const entry of Object.entries(repository.indexes ?? {})) {
1472
+ const [, index] = entry;
1473
+ if (!index.where.every((key) => equal.has(key)))
1474
+ continue;
1475
+ if (first !== undefined && index.orderBy !== first)
1476
+ continue;
1477
+ if (best === undefined || index.where.length > best[1].where.length)
1478
+ best = entry;
1479
+ }
1480
+ return best === undefined ? SCAN : Object.freeze({ access: "index", index: best[0] });
1481
+ }
1482
+
1148
1483
  // src/core/repository/state.ts
1149
1484
  var states = new WeakMap;
1150
1485
  function findState(repository) {
@@ -1163,11 +1498,22 @@ function stateOf(repository) {
1163
1498
  throw new TypeError("Expected a repository made by repositoryOf");
1164
1499
  return state;
1165
1500
  }
1501
+ function expiryKeyOf(entity) {
1502
+ const expires = entity[EntitySettings2]?.expires;
1503
+ if (expires === undefined)
1504
+ return;
1505
+ return "at" in expires ? expires.at : EXPIRES_AT;
1506
+ }
1166
1507
  function contextOf(state) {
1167
- state.context ??= Object.freeze({
1508
+ if (state.context)
1509
+ return state.context;
1510
+ const key = expiryKeyOf(state.entity);
1511
+ state.context = Object.freeze({
1168
1512
  entity: state.entity,
1169
1513
  name: state.name,
1170
1514
  unique: state.entity[EntityUniqueKeys2],
1515
+ indexes: state.indexes,
1516
+ ...key === undefined ? {} : { expiry: Object.freeze({ key }) },
1171
1517
  env: loadEnvironments(state.environment)
1172
1518
  });
1173
1519
  return state.context;
@@ -1181,6 +1527,8 @@ function inspectRepository(repository) {
1181
1527
  entity: state.entity,
1182
1528
  actions: state.actions,
1183
1529
  deletion: state.deletion,
1530
+ indexes: state.indexes,
1531
+ scans: state.scans,
1184
1532
  explicitProvider: state.explicitProvider,
1185
1533
  provider: state.explicitProvider ? state.provider : undefined,
1186
1534
  environments: environment === undefined ? [] : Array.isArray(environment) ? environment : [environment],
@@ -1192,33 +1540,61 @@ var origins = new WeakMap;
1192
1540
  function declarationOf(state) {
1193
1541
  return origins.get(state) ?? state;
1194
1542
  }
1195
- function bindState(state, provider, instances, bound = new Map) {
1543
+ var declared = new WeakMap;
1544
+ function declareRepository(state, repository) {
1545
+ declared.set(state, repository);
1546
+ }
1547
+ function relatedRepositoryOf(repository, Entity) {
1548
+ const related = findState(repository)?.relations.get(Entity);
1549
+ return related ? declared.get(declarationOf(related)) : undefined;
1550
+ }
1551
+ function bindState(state, provider, instances, logger, bound = new Map) {
1196
1552
  const done = bound.get(state);
1197
1553
  if (done)
1198
1554
  return done;
1199
1555
  const copy = {
1200
1556
  ...state,
1557
+ logger,
1201
1558
  provider: state.explicitProvider ? instances?.get(state.provider) ?? state.provider : provider ?? state.provider,
1202
1559
  context: undefined,
1203
1560
  relations: new Map([...state.relations].map(([target, related]) => [
1204
1561
  target,
1205
- bindState(related, provider, instances, bound)
1562
+ bindState(related, provider, instances, logger, bound)
1206
1563
  ]))
1207
1564
  };
1208
1565
  origins.set(copy, declarationOf(state));
1209
1566
  bound.set(state, copy);
1210
1567
  return copy;
1211
1568
  }
1212
- function bindRepository(repository, provider, instances) {
1569
+ var owners = new WeakMap;
1570
+ function assertUnbound(state) {
1571
+ const owner = owners.get(state);
1572
+ if (owner) {
1573
+ throw new RepositoryUsedOutsideApplication2({
1574
+ source: state.name,
1575
+ details: owner
1576
+ });
1577
+ }
1578
+ }
1579
+ function bindRepository(repository, provider, instances, binding) {
1213
1580
  const state = stateOf(repository);
1214
1581
  const Declared = repository.constructor;
1215
1582
 
1216
1583
  class Bound extends Declared {
1217
1584
  }
1218
1585
  Object.defineProperty(Bound, "name", { value: Declared.name });
1219
- states.set(Bound, bindState(state, provider, instances));
1586
+ states.set(Bound, bindState(state, provider, instances, binding?.logger));
1587
+ if (binding) {
1588
+ owners.set(declarationOf(state), {
1589
+ application: binding.application,
1590
+ repository: binding.key
1591
+ });
1592
+ }
1220
1593
  return Object.freeze(new Bound);
1221
1594
  }
1595
+ function providerOfRepository(repository) {
1596
+ return findState(repository)?.provider;
1597
+ }
1222
1598
 
1223
1599
  // src/core/repository/references.ts
1224
1600
  function isPlain(value) {
@@ -1336,7 +1712,8 @@ function parseEntity(state, data) {
1336
1712
  function loadByIds(state, ids, scope) {
1337
1713
  return scopedProvider(scope, state.provider).findMany(contextOf(state), {
1338
1714
  where: { id: { in: ids } },
1339
- withDeleted: true
1715
+ withDeleted: true,
1716
+ withExpired: true
1340
1717
  });
1341
1718
  }
1342
1719
  async function resolveReferences(state, datas, session, scope) {
@@ -1427,50 +1804,125 @@ class AbstractRepository2 {
1427
1804
  }
1428
1805
  function action(method) {
1429
1806
  return function(...args) {
1430
- return guard2({ layer: "repository" /* Repository */, source: stateOf(this).name }, () => method(this, ...args));
1807
+ const state = stateOf(this);
1808
+ return guard2({ layer: "repository" /* Repository */, source: state.name }, async () => {
1809
+ assertUnbound(state);
1810
+ return method(this, ...args);
1811
+ });
1431
1812
  };
1432
1813
  }
1433
1814
  function queryValueOf(context, key, value) {
1434
1815
  const properties = context.entity.blueprint.properties;
1435
1816
  return recordFieldDataOf(properties[key], value, REFERENCES);
1436
1817
  }
1818
+ function expiryOf(self, withExpired) {
1819
+ const { context, provider } = self;
1820
+ if (context.expiry === undefined)
1821
+ return {};
1822
+ if (provider.capabilities?.expiry !== true) {
1823
+ throw new InvalidRepositoryQuery2({
1824
+ source: context.name,
1825
+ details: {
1826
+ reason: "The provider does not leave expired records out (capabilities.expiry), which an entity with expires needs."
1827
+ }
1828
+ });
1829
+ }
1830
+ const clock = context.entity[EntityClock];
1831
+ return { withExpired: withExpired ?? false, now: clock.now() };
1832
+ }
1437
1833
  function queryOf(self, query) {
1438
1834
  const prepared = prepareQuery(self.context, query, (key, value) => queryValueOf(self.context, key, value));
1439
1835
  return {
1440
1836
  ...prepared,
1441
- withDeleted: prepared.withDeleted ?? stateOf(self).deletion === "hard"
1837
+ withDeleted: prepared.withDeleted ?? stateOf(self).deletion === "hard",
1838
+ ...expiryOf(self, prepared.withExpired)
1442
1839
  };
1443
1840
  }
1444
1841
  var equals = (self, key, value) => queryOf(self, { where: { [key]: value } });
1842
+ function planned(self, method, query, orderBy = query.orderBy) {
1843
+ const state = stateOf(self);
1844
+ const plan = planQuery(self.context, { where: query.where, orderBy });
1845
+ if (plan.access === "scan" && state.scans !== "allow") {
1846
+ const fields = {
1847
+ repository: state.name,
1848
+ method,
1849
+ where: Object.keys(query.where ?? {}),
1850
+ orderBy: (orderBy ?? []).map(({ key }) => key)
1851
+ };
1852
+ if (state.scans === "reject") {
1853
+ throw new InvalidRepositoryQuery2({
1854
+ source: state.name,
1855
+ details: {
1856
+ reason: `"${method}" matches no index of ${state.name} and would read every row (scans: "reject").`,
1857
+ scan: true,
1858
+ ...fields
1859
+ }
1860
+ });
1861
+ }
1862
+ const scope = self[ScopeKey];
1863
+ writeLog(resolveLogger(scope?.logger ?? state.logger, undefined), "warn", "repository-scan", fields);
1864
+ }
1865
+ return plan.index === undefined ? query : { ...query, index: plan.index };
1866
+ }
1445
1867
  var findBy = (key) => action(async (self, value) => {
1446
1868
  const data = await self.provider.findOne(self.context, equals(self, key, value));
1447
1869
  return data === undefined ? undefined : await self.hydrate(data);
1448
1870
  });
1449
- var findManyBy = (key) => action(async (self, value) => self.hydrateMany(await self.provider.findMany(self.context, equals(self, key, value))));
1450
- var findMany = action(async (self, query) => self.hydrateMany(await self.provider.findMany(self.context, queryOf(self, query))));
1451
- var exists = action(async (self, query) => await self.provider.findOne(self.context, queryOf(self, query)) !== undefined);
1871
+ var findManyBy = (key) => action(async (self, value) => self.hydrateMany(await self.provider.findMany(self.context, planned(self, `findManyBy${capitalize(key)}`, equals(self, key, value)))));
1872
+ var findMany = action(async (self, query) => self.hydrateMany(await self.provider.findMany(self.context, planned(self, "findMany", queryOf(self, query)))));
1873
+ var exists = action(async (self, query) => await self.provider.findOne(self.context, planned(self, "exists", queryOf(self, query))) !== undefined);
1874
+ var EXPLAIN_OPTIONS = [
1875
+ "where",
1876
+ "orderBy",
1877
+ "limit",
1878
+ "offset",
1879
+ "withDeleted",
1880
+ "after"
1881
+ ];
1882
+ var explain = action(async (self, query = {}) => {
1883
+ const prepared = prepareQuery(self.context, query, (key, value) => queryValueOf(self.context, key, value), EXPLAIN_OPTIONS);
1884
+ return planQuery(self.context, prepared);
1885
+ });
1452
1886
  var findPage = action(async (self, query) => {
1453
1887
  const {
1454
1888
  orderBy,
1455
1889
  where,
1456
1890
  withDeleted,
1891
+ withExpired,
1892
+ now,
1457
1893
  limit = 20,
1458
- offset = 0
1459
- } = queryOf(self, query);
1894
+ offset = 0,
1895
+ index
1896
+ } = planned(self, "findPage", queryOf(self, query));
1897
+ const expiry = withExpired === undefined ? {} : { withExpired, now };
1460
1898
  const [rows, total] = await Promise.all([
1461
1899
  self.provider.findMany(self.context, {
1462
1900
  where,
1463
1901
  orderBy,
1464
1902
  withDeleted,
1903
+ ...expiry,
1465
1904
  limit,
1466
- offset
1905
+ offset,
1906
+ ...index === undefined ? {} : { index }
1467
1907
  }),
1468
- self.provider.count(self.context, { where, withDeleted })
1908
+ self.provider.count(self.context, {
1909
+ where,
1910
+ withDeleted,
1911
+ ...expiry,
1912
+ ...index === undefined ? {} : { index }
1913
+ })
1469
1914
  ]);
1470
1915
  return { items: await self.hydrateMany(rows), total, limit, offset };
1471
1916
  });
1472
- var CURSOR_OPTIONS = ["where", "orderBy", "limit", "withDeleted", "after"];
1473
- var findCursor = action(async (self, query = {}) => {
1917
+ var CURSOR_OPTIONS = [
1918
+ "where",
1919
+ "orderBy",
1920
+ "limit",
1921
+ "withDeleted",
1922
+ "withExpired",
1923
+ "after"
1924
+ ];
1925
+ function cursorReadOf(self, query) {
1474
1926
  const { context } = self;
1475
1927
  const fail = (reason) => {
1476
1928
  throw new InvalidRepositoryQuery2({
@@ -1479,7 +1931,6 @@ var findCursor = action(async (self, query = {}) => {
1479
1931
  });
1480
1932
  };
1481
1933
  const prepared = prepareQuery(context, query, (key, value) => queryValueOf(context, key, value), CURSOR_OPTIONS);
1482
- const { after } = query;
1483
1934
  const { where, limit = 20 } = prepared;
1484
1935
  if (limit < 1)
1485
1936
  fail("Expected a positive integer for limit.");
@@ -1492,11 +1943,27 @@ var findCursor = action(async (self, query = {}) => {
1492
1943
  }
1493
1944
  if (self.provider.capabilities?.keyset !== true)
1494
1945
  fail("The provider does not read by cursor (capabilities.keyset).");
1946
+ const { index } = planned(self, "findCursor", { where }, declared);
1947
+ expiryOf(self, prepared.withExpired);
1948
+ return {
1949
+ where,
1950
+ orderBy,
1951
+ withDeleted,
1952
+ withExpired: prepared.withExpired,
1953
+ limit,
1954
+ ...index === undefined ? {} : { index }
1955
+ };
1956
+ }
1957
+ async function readCursor(self, read, after) {
1958
+ const { context } = self;
1959
+ const { where, orderBy, withDeleted, limit, index } = read;
1495
1960
  const rows = await self.provider.findMany(context, {
1496
1961
  where,
1497
1962
  orderBy,
1498
1963
  withDeleted,
1964
+ ...expiryOf(self, read.withExpired),
1499
1965
  limit: limit + 1,
1966
+ ...index === undefined ? {} : { index },
1500
1967
  ...after === undefined ? {} : { after: decodeCursor(context.name, orderBy, after) }
1501
1968
  });
1502
1969
  const page = rows.slice(0, limit);
@@ -1506,6 +1973,49 @@ var findCursor = action(async (self, query = {}) => {
1506
1973
  limit,
1507
1974
  next: rows.length > limit && last !== undefined ? encodeCursor(orderBy, last) : undefined
1508
1975
  };
1976
+ }
1977
+ var findCursor = action(async (self, query = {}) => readCursor(self, cursorReadOf(self, query), query.after));
1978
+ function stopperOf(source, until) {
1979
+ if (until === undefined)
1980
+ return () => false;
1981
+ if (typeof until === "function")
1982
+ return () => Boolean(until());
1983
+ const deadline = until instanceof Date ? until.getTime() : typeof until === "number" ? until : Number.NaN;
1984
+ if (Number.isNaN(deadline)) {
1985
+ throw new InvalidRepositoryQuery2({
1986
+ source,
1987
+ details: {
1988
+ reason: "Expected until to be a Date, epoch milliseconds or a function."
1989
+ }
1990
+ });
1991
+ }
1992
+ return () => Date.now() >= deadline;
1993
+ }
1994
+ var forEachPage = action(async (self, query, fn, options) => {
1995
+ const { context } = self;
1996
+ if (typeof fn !== "function") {
1997
+ throw new InvalidRepositoryQuery2({
1998
+ source: context.name,
1999
+ details: { reason: "Expected a function to run on each page." }
2000
+ });
2001
+ }
2002
+ const read = cursorReadOf(self, query ?? {});
2003
+ const stop = stopperOf(context.name, options?.until);
2004
+ let after = query?.after;
2005
+ let pages = 0;
2006
+ let items = 0;
2007
+ while (!stop()) {
2008
+ const page = await readCursor(self, read, after);
2009
+ if (page.items.length > 0) {
2010
+ await fn(page.items, { index: pages, next: page.next });
2011
+ pages += 1;
2012
+ items += page.items.length;
2013
+ }
2014
+ if (page.next === undefined)
2015
+ return { done: true, pages, items };
2016
+ after = page.next;
2017
+ }
2018
+ return { done: false, next: after, pages, items };
1509
2019
  });
1510
2020
  var create = action(async (self, input) => {
1511
2021
  const { entity: Entity } = self.context;
@@ -1513,46 +2023,183 @@ var create = action(async (self, input) => {
1513
2023
  await self.provider.insert(self.context, self.dataOf(entity));
1514
2024
  return entity;
1515
2025
  });
2026
+ function nowOf2(context) {
2027
+ return context.entity[EntityClock].now();
2028
+ }
2029
+ function assertVersioning(self) {
2030
+ if (self.provider.capabilities?.versioning === true)
2031
+ return;
2032
+ invalid3(self.context.name, `${recordNameOf2(self.context.entity)} is versioned, and the provider does not check versions (capabilities.versioning).`);
2033
+ }
2034
+ async function write(self, data) {
2035
+ if (!isVersionedEntityClass(self.context.entity)) {
2036
+ await self.provider.update(self.context, data);
2037
+ return data;
2038
+ }
2039
+ assertVersioning(self);
2040
+ const expected = data.version;
2041
+ const next = { ...data, version: expected + 1 };
2042
+ await self.provider.update(self.context, next, {
2043
+ expectedVersion: expected
2044
+ });
2045
+ return next;
2046
+ }
2047
+ async function writeEntity(self, entity) {
2048
+ const written = await write(self, self.dataOf(entity));
2049
+ if (written.version !== undefined && isEntity2(entity)) {
2050
+ storeEntityVersion(entity, written.version);
2051
+ }
2052
+ }
1516
2053
  var update = action(async (self, entity) => {
1517
- await self.provider.update(self.context, self.dataOf(entity));
2054
+ await writeEntity(self, entity);
1518
2055
  return entity;
1519
2056
  });
1520
- var remove = action(async (self, target) => {
1521
- const { context } = self;
1522
- let entity = target;
1523
- if (typeof target === "string") {
1524
- const data = await self.provider.findOne(context, equals(self, "id", target));
1525
- if (data === undefined) {
1526
- throw new RepositoryEntityNotFound2({
1527
- source: context.name,
1528
- details: { id: target }
2057
+ function isPlainObject2(value) {
2058
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2059
+ }
2060
+ function isNumericField(field) {
2061
+ if (field === undefined)
2062
+ return false;
2063
+ const property = fieldOf(field);
2064
+ if (!isProperty2(property))
2065
+ return false;
2066
+ const { type } = property.type.schema.type.Type();
2067
+ return type === "number" || type === "integer";
2068
+ }
2069
+ function deltasOf(context, deltas) {
2070
+ const { name: source } = context;
2071
+ const fail = (reason, issues) => {
2072
+ throw new InvalidRepositoryIncrement2({
2073
+ source,
2074
+ details: { reason, issues }
2075
+ });
2076
+ };
2077
+ if (!isPlainObject2(deltas) || Object.keys(deltas).length === 0) {
2078
+ const message = "Expected an object of the numbers to add, by key.";
2079
+ fail(message, [{ code: "invalid_type", path: [], message, source }]);
2080
+ }
2081
+ const { change } = context.entity.options;
2082
+ const properties = context.entity.blueprint.properties;
2083
+ const issues = [];
2084
+ for (const [key, value] of Object.entries(deltas)) {
2085
+ if (!change.includes(key) || !isNumericField(properties[key])) {
2086
+ issues.push({
2087
+ code: "undeclared_key",
2088
+ path: [key],
2089
+ message: `"${key}" is not a number in the change list of ${recordNameOf2(context.entity)}.`,
2090
+ source
2091
+ });
2092
+ } else if (typeof value !== "number" || !Number.isFinite(value)) {
2093
+ issues.push({
2094
+ code: "invalid_value",
2095
+ path: [key],
2096
+ message: "Expected a finite number.",
2097
+ source
1529
2098
  });
1530
2099
  }
1531
- entity = await self.hydrate(data);
1532
2100
  }
1533
- const data = self.dataOf(entity);
2101
+ if (issues.length > 0) {
2102
+ fail(`"${source}" cannot increment ${issues.map((issue) => `"${issue.path[0]}"`).join(", ")}.`, issues);
2103
+ }
2104
+ return deltas;
2105
+ }
2106
+ function createdOf(context, id, input, deltas) {
2107
+ const identity = identityKeysOf(context.entity);
2108
+ const value = isPlainObject2(input) ? Object.fromEntries(Object.entries(input).filter(([key]) => !identity.includes(key))) : {};
2109
+ for (const [key, delta] of Object.entries(deltas)) {
2110
+ const base = value[key];
2111
+ value[key] = (typeof base === "number" ? base : 0) + delta;
2112
+ }
2113
+ const Entity = context.entity;
2114
+ const entity = new Entity({ ...value, id });
2115
+ enqueueLifecycle(entity, "onCreate");
2116
+ return entity;
2117
+ }
2118
+ var increment = action(async (self, target, deltas, options) => {
2119
+ const { context } = self;
2120
+ const id = typeof target === "string" ? target : self.dataOf(target).id;
2121
+ const checked = deltasOf(context, deltas);
2122
+ if (options !== undefined && !isPlainObject2(options)) {
2123
+ invalid3(context.name, "Expected the options of increment as an object.");
2124
+ }
2125
+ const versioned = isVersionedEntityClass(context.entity);
2126
+ if (versioned)
2127
+ assertVersioning(self);
2128
+ const { create } = options ?? {};
2129
+ const created = create === undefined ? undefined : createdOf(context, id, create, checked);
2130
+ const stored = await self.provider.increment(context, id, versioned ? { ...checked, version: 1 } : checked, {
2131
+ set: { updatedAt: nowOf2(context) },
2132
+ ...created === undefined ? {} : { create: self.dataOf(created) },
2133
+ ...context.expiry === undefined ? {} : { now: expiryOf(self, false).now }
2134
+ });
2135
+ if (created !== undefined && stored.updatedAt === undefined)
2136
+ return created;
2137
+ const entity = await self.hydrate(stored);
2138
+ enqueueLifecycle(entity, "onUpdate");
2139
+ return entity;
2140
+ });
2141
+ var remove = action(async (self, target) => {
2142
+ const { context } = self;
2143
+ let entity = target;
2144
+ if (typeof target === "string") {
2145
+ const data = await self.provider.findOne(context, equals(self, "id", target));
2146
+ if (data === undefined) {
2147
+ throw new RepositoryEntityNotFound2({
2148
+ source: context.name,
2149
+ details: { id: target }
2150
+ });
2151
+ }
2152
+ entity = await self.hydrate(data);
2153
+ }
2154
+ const data = self.dataOf(entity);
1534
2155
  const destroyable = entity;
1535
2156
  if (!destroyable.isDeleted)
1536
2157
  destroyable.destroy();
1537
2158
  if (stateOf(self).deletion === "soft") {
1538
- await self.provider.update(context, self.dataOf(entity));
2159
+ await writeEntity(self, entity);
1539
2160
  } else {
1540
2161
  await self.provider.remove(context, data.id);
1541
2162
  }
1542
2163
  return entity;
1543
2164
  });
1544
- var count = action(async (self, query) => self.provider.count(self.context, queryOf(self, query)));
2165
+ var restore = action(async (self, target) => {
2166
+ const { context } = self;
2167
+ const id = typeof target === "string" ? target : self.dataOf(target).id;
2168
+ const data = await self.provider.findOne(context, {
2169
+ ...equals(self, "id", id),
2170
+ withDeleted: true
2171
+ });
2172
+ if (data === undefined) {
2173
+ throw new RepositoryEntityNotFound2({
2174
+ source: context.name,
2175
+ details: { id }
2176
+ });
2177
+ }
2178
+ if (data.deletedAt === undefined)
2179
+ return self.hydrate(data);
2180
+ const { deletedAt: _deletedAt, ...alive } = data;
2181
+ const restored = { ...alive, updatedAt: nowOf2(context) };
2182
+ return self.hydrate(await write(self, restored));
2183
+ });
2184
+ var count = action(async (self, query) => {
2185
+ const prepared = queryOf(self, query);
2186
+ return self.provider.count(self.context, planned(self, "count", prepared, []));
2187
+ });
1545
2188
  var NEEDS = {
1546
2189
  findBy: ["findOne"],
1547
2190
  findManyBy: ["findMany"],
1548
2191
  create: ["insert"],
1549
2192
  update: ["update"],
1550
2193
  delete: ["findOne", "remove"],
2194
+ restore: ["findOne", "update"],
1551
2195
  count: ["count"],
1552
2196
  findMany: ["findMany"],
1553
2197
  exists: ["findOne"],
1554
2198
  findPage: ["findMany", "count"],
1555
- findCursor: ["findMany"]
2199
+ findCursor: ["findMany"],
2200
+ explain: [],
2201
+ increment: ["increment"],
2202
+ forEachPage: ["findMany"]
1556
2203
  };
1557
2204
  function capitalize(key) {
1558
2205
  return `${key.charAt(0).toUpperCase()}${key.slice(1)}`;
@@ -1573,6 +2220,57 @@ function unknownAction(entity, action, methods) {
1573
2220
  function invalid3(source, reason) {
1574
2221
  throw new InvalidRepositoryDeclaration2({ source, details: { reason } });
1575
2222
  }
2223
+ var INDEX_NAME = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
2224
+ function indexesOf(name, entity, declared) {
2225
+ if (declared === undefined)
2226
+ return Object.freeze({});
2227
+ if (typeof declared !== "object" || declared === null || Array.isArray(declared)) {
2228
+ invalid3(name, "Expected indexes to be an object of indexes by name.");
2229
+ }
2230
+ const properties = entity.blueprint.properties;
2231
+ const sensitive = entity.options.sensitive;
2232
+ const keyOf = (index, key) => {
2233
+ if (typeof key !== "string" || !Object.hasOwn(properties, key)) {
2234
+ invalid3(name, `The index "${index}" names "${String(key)}", which is not a key of ${recordNameOf2(entity)}.`);
2235
+ }
2236
+ if (sensitive.includes(key)) {
2237
+ invalid3(name, `The index "${index}" names "${key}", which is sensitive and cannot be indexed.`);
2238
+ }
2239
+ return key;
2240
+ };
2241
+ const seen = new Map;
2242
+ const indexes = {};
2243
+ for (const [index, value] of Object.entries(declared)) {
2244
+ if (!INDEX_NAME.test(index) || index === "__proto__") {
2245
+ invalid3(name, `Expected the index "${index}" to be named by an identifier.`);
2246
+ }
2247
+ if (typeof value !== "object" || value === null || Array.isArray(value) || Object.keys(value).some((key) => key !== "where" && key !== "orderBy") || !Array.isArray(value.where) || value.where.length === 0) {
2248
+ invalid3(name, `Expected the index "${index}" to be { where: [keys], orderBy?: key }.`);
2249
+ }
2250
+ const where = value.where.map((key) => keyOf(index, key));
2251
+ if (new Set(where).size !== where.length) {
2252
+ invalid3(name, `The index "${index}" names a key twice in where.`);
2253
+ }
2254
+ const orderBy = value.orderBy === undefined ? undefined : keyOf(index, value.orderBy);
2255
+ if (orderBy !== undefined) {
2256
+ const field = properties[orderBy];
2257
+ if (isBlueprint2(field) || isBlueprintList2(field) || recordFieldOf2(field)) {
2258
+ invalid3(name, `The index "${index}" orders by "${orderBy}", which is not scalar.`);
2259
+ }
2260
+ }
2261
+ const signature = JSON.stringify([[...where].sort(), orderBy ?? null]);
2262
+ const other = seen.get(signature);
2263
+ if (other !== undefined) {
2264
+ invalid3(name, `The indexes "${other}" and "${index}" are the same.`);
2265
+ }
2266
+ seen.set(signature, index);
2267
+ indexes[index] = Object.freeze({
2268
+ where: Object.freeze(where),
2269
+ ...orderBy === undefined ? {} : { orderBy }
2270
+ });
2271
+ }
2272
+ return Object.freeze(indexes);
2273
+ }
1576
2274
  function repositoryOf2(settings) {
1577
2275
  const { entity, actions, environmentDependencies } = settings ?? {};
1578
2276
  if (!isEntityClass2(entity) || !(EntityUniqueKeys2 in entity)) {
@@ -1583,6 +2281,10 @@ function repositoryOf2(settings) {
1583
2281
  if (deletion !== "hard" && deletion !== "soft") {
1584
2282
  invalid3(name, 'Expected deletion to be "hard" or "soft".');
1585
2283
  }
2284
+ const scans = settings.scans ?? "allow";
2285
+ if (scans !== "allow" && scans !== "warn" && scans !== "reject") {
2286
+ invalid3(name, 'Expected scans to be "allow", "warn" or "reject".');
2287
+ }
1586
2288
  const provider = settings.provider ?? inMemoryProvider2();
1587
2289
  if (!Array.isArray(actions) || actions.length === 0) {
1588
2290
  invalid3(name, "Expected a list of actions.");
@@ -1591,6 +2293,7 @@ function repositoryOf2(settings) {
1591
2293
  invalid3(name, "Expected environments made by environmentOf.");
1592
2294
  }
1593
2295
  const relations = relationsOf(name, entity, settings.relations);
2296
+ const indexes = indexesOf(name, entity, settings.indexes);
1594
2297
  const unique = entity[EntityUniqueKeys2];
1595
2298
  const methods = new Map;
1596
2299
  for (const key of Object.keys(entity.blueprint.properties)) {
@@ -1604,11 +2307,15 @@ function repositoryOf2(settings) {
1604
2307
  methods.set("create", ["create", create]);
1605
2308
  methods.set("update", ["update", update]);
1606
2309
  methods.set("delete", ["delete", remove]);
2310
+ methods.set("restore", ["restore", restore]);
1607
2311
  methods.set("count", ["count", count]);
1608
2312
  methods.set("findMany", ["findMany", findMany]);
1609
2313
  methods.set("exists", ["exists", exists]);
1610
2314
  methods.set("findPage", ["findPage", findPage]);
1611
2315
  methods.set("findCursor", ["findCursor", findCursor]);
2316
+ methods.set("explain", ["explain", explain]);
2317
+ methods.set("increment", ["increment", increment]);
2318
+ methods.set("forEachPage", ["forEachPage", forEachPage]);
1612
2319
 
1613
2320
  class DeclaredRepository extends AbstractRepository2 {
1614
2321
  }
@@ -1617,6 +2324,12 @@ function repositoryOf2(settings) {
1617
2324
  if (!method)
1618
2325
  invalid3(name, unknownAction(recordNameOf2(entity), String(name_), methods));
1619
2326
  const [kind, implementation] = method;
2327
+ if (kind === "restore" && deletion !== "soft") {
2328
+ invalid3(name, '"restore" needs deletion to be "soft".');
2329
+ }
2330
+ if (kind === "findCursor" && settings.provider !== undefined && provider[Kind2] !== "provider-slot" && provider.capabilities?.keyset !== true) {
2331
+ invalid3(name, '"findCursor" needs a provider that reads by cursor (capabilities.keyset).');
2332
+ }
1620
2333
  const needs = deletion === "soft" && kind === "delete" ? ["findOne", "update"] : NEEDS[kind] ?? [];
1621
2334
  for (const need of needs) {
1622
2335
  if (typeof provider?.[need] !== "function") {
@@ -1630,7 +2343,7 @@ function repositoryOf2(settings) {
1630
2343
  });
1631
2344
  }
1632
2345
  Object.defineProperty(DeclaredRepository, "name", { value: name });
1633
- states.set(DeclaredRepository, {
2346
+ const state = {
1634
2347
  name,
1635
2348
  entity,
1636
2349
  actions: Object.freeze([...new Set(actions)]),
@@ -1638,9 +2351,14 @@ function repositoryOf2(settings) {
1638
2351
  provider,
1639
2352
  explicitProvider: settings.provider !== undefined,
1640
2353
  environment: environmentDependencies,
1641
- relations
1642
- });
1643
- return Object.freeze(new DeclaredRepository);
2354
+ relations,
2355
+ indexes,
2356
+ scans
2357
+ };
2358
+ states.set(DeclaredRepository, state);
2359
+ const repository = Object.freeze(new DeclaredRepository);
2360
+ declareRepository(state, repository);
2361
+ return repository;
1644
2362
  }
1645
2363
  function RepositoryOf2(repository) {
1646
2364
  if (!isRepository2(repository)) {
@@ -1652,6 +2370,54 @@ function isRepository2(value) {
1652
2370
  return value instanceof AbstractRepository2;
1653
2371
  }
1654
2372
 
2373
+ // src/core/use-case/exceptions.ts
2374
+ var InvalidUseCaseStep2 = exceptionOf2({
2375
+ layer: "use-case" /* UseCase */,
2376
+ name: "InvalidUseCaseStep",
2377
+ message: ({
2378
+ index,
2379
+ reason
2380
+ }) => `Invalid step ${index}: ${reason}`
2381
+ });
2382
+ var InvalidUseCasePlugin2 = exceptionOf2({
2383
+ layer: "use-case" /* UseCase */,
2384
+ name: "InvalidUseCasePlugin",
2385
+ message: ({ reason }, source) => `Invalid use case plugin "${source}": ${reason}.`
2386
+ });
2387
+ var UnknownUseCasePlugin2 = exceptionOf2({
2388
+ layer: "use-case" /* UseCase */,
2389
+ name: "UnknownUseCasePlugin",
2390
+ message: ({ name }) => `No use case plugin "${name}" in the catalog.`
2391
+ });
2392
+ var IncompatibleUseCasePlugin2 = exceptionOf2({
2393
+ layer: "use-case" /* UseCase */,
2394
+ name: "IncompatibleUseCasePlugin",
2395
+ message: ({ reason }, source) => `Incompatible use case plugin "${source}": ${reason}.`
2396
+ });
2397
+ var UseCaseUnavailable2 = exceptionOf2({
2398
+ layer: "use-case" /* UseCase */,
2399
+ name: "UseCaseUnavailable",
2400
+ message: ({
2401
+ repository,
2402
+ reason
2403
+ }, source) => `The use case "${source}" is not available for "${repository}": ${reason}.`
2404
+ });
2405
+ var UseCaseEntityNotFound2 = exceptionOf2({
2406
+ layer: "use-case" /* UseCase */,
2407
+ name: "UseCaseEntityNotFound",
2408
+ message: "The entity was not found."
2409
+ });
2410
+ var UseCaseEntityConflict2 = exceptionOf2({
2411
+ layer: "use-case" /* UseCase */,
2412
+ name: "UseCaseEntityConflict",
2413
+ message: "The entity already exists."
2414
+ });
2415
+ var UseCaseInvalidCredentials2 = exceptionOf2({
2416
+ layer: "use-case" /* UseCase */,
2417
+ name: "UseCaseInvalidCredentials",
2418
+ message: "Invalid credentials."
2419
+ });
2420
+
1655
2421
  // src/core/use-case/steps/references.ts
1656
2422
  var RESERVED_NAMES = [
1657
2423
  "input",
@@ -1759,15 +2525,103 @@ function freezeData(value) {
1759
2525
  return value;
1760
2526
  }
1761
2527
 
1762
- // src/core/use-case/exceptions.ts
1763
- var InvalidUseCaseStep2 = exceptionOf2({
1764
- layer: "use-case" /* UseCase */,
1765
- name: "InvalidUseCaseStep",
1766
- message: ({
1767
- index,
1768
- reason
1769
- }) => `Invalid step ${index}: ${reason}`
1770
- });
2528
+ // src/core/property/input.ts
2529
+ var PropertyInput2 = Symbol("propertyInput");
2530
+ function propertyInputOf2(field) {
2531
+ const property = fieldOf(field);
2532
+ if (!isProperty2(property))
2533
+ return;
2534
+ const declared = property[PropertyInput2];
2535
+ return typeof declared === "object" && declared !== null && isProperty2(declared.accepts) && typeof declared.convert === "function" ? declared : undefined;
2536
+ }
2537
+
2538
+ // src/core/use-case/target.ts
2539
+ function isOptionalField(field) {
2540
+ if (isProperty2(field))
2541
+ return field.type.optional === true;
2542
+ const record = recordFieldOf2(field);
2543
+ if (record)
2544
+ return record.optional;
2545
+ return field.optional === true;
2546
+ }
2547
+ function planOf(entity, key, owners) {
2548
+ const field = fieldOf(entity.blueprint.properties[key]);
2549
+ const optional = isOptionalField(field);
2550
+ const record = recordFieldOf2(field);
2551
+ const item = isBlueprintList2(field) ? recordFieldOf2(field.item) : undefined;
2552
+ const related = [record?.record, item?.record].find(isEntityClass2);
2553
+ const kind = record ? related ? "entity" : "record" : isBlueprintList2(field) ? related ? "entity-list" : "list" : isBlueprint2(field) ? "blueprint" : "property";
2554
+ let input = { key, field };
2555
+ if (kind === "entity" && related) {
2556
+ const id = related.blueprint.properties.id;
2557
+ input = { key: `${key}Id`, field: optional ? optionalOf2(id) : id };
2558
+ }
2559
+ const declared = kind === "property" ? propertyInputOf2(field) : undefined;
2560
+ if (declared) {
2561
+ input = {
2562
+ key,
2563
+ field: optional ? optionalOf2(declared.accepts) : declared.accepts
2564
+ };
2565
+ }
2566
+ return Object.freeze({
2567
+ key,
2568
+ field,
2569
+ identity: identityKeysOf(entity).includes(key),
2570
+ plugin: owners.get(key),
2571
+ sensitive: entity.options.sensitive.includes(key),
2572
+ unique: entity.options.unique.includes(key),
2573
+ optional,
2574
+ change: entity.options.change.includes(key),
2575
+ kind,
2576
+ related,
2577
+ input: Object.freeze(input),
2578
+ convert: declared ? (value) => value === undefined ? undefined : declared.convert(value) : undefined
2579
+ });
2580
+ }
2581
+ function useCaseTargetOf2(repository, source = "useCaseTargetOf") {
2582
+ const state = findState(repository);
2583
+ if (!state)
2584
+ throw new TypeError("Expected a repository made by repositoryOf");
2585
+ const { entity } = state;
2586
+ const unavailable = (reason) => {
2587
+ throw new UseCaseUnavailable2({
2588
+ source,
2589
+ details: { repository: state.name, reason }
2590
+ });
2591
+ };
2592
+ const owners = new Map;
2593
+ for (const plugin of pluginsOf2(entity)) {
2594
+ for (const key of plugin.keys)
2595
+ owners.set(key, plugin.name);
2596
+ }
2597
+ const fields = Object.freeze(Object.keys(entity.blueprint.properties).map((key) => planOf(entity, key, owners)));
2598
+ const field = (key) => fields.find((plan) => plan.key === key) ?? unavailable(`the entity has no key "${key}"`);
2599
+ return Object.freeze({
2600
+ repository,
2601
+ repositoryName: state.name,
2602
+ entity,
2603
+ entityName: recordNameOf2(entity),
2604
+ actions: state.actions,
2605
+ deletion: state.deletion,
2606
+ fields,
2607
+ changeKeys: entity.options.change,
2608
+ uniqueKeys: entity.options.unique,
2609
+ field,
2610
+ relation(key, actions = []) {
2611
+ const { related } = field(key);
2612
+ const found = related ? relatedRepositoryOf(repository, related) : undefined;
2613
+ if (!found) {
2614
+ return unavailable(`the repository has no relation for the entity of "${key}"`);
2615
+ }
2616
+ const missing = actions.filter((action) => !found.actions.includes(action));
2617
+ if (missing.length > 0) {
2618
+ unavailable(`the repository of "${key}" lacks the action ${missing.map((action) => `"${action}"`).join(", ")}`);
2619
+ }
2620
+ return found;
2621
+ },
2622
+ unavailable
2623
+ });
2624
+ }
1771
2625
 
1772
2626
  // src/core/use-case/steps/run.ts
1773
2627
  var FORBIDDEN_METHODS = new Set([
@@ -1801,7 +2655,6 @@ async function executeSteps(dependencies = {}) {
1801
2655
  return followPath(named.get(root), path);
1802
2656
  };
1803
2657
  const value = (raw) => resolveValue(raw, lookup);
1804
- const list = (raw) => raw === undefined ? [] : value(raw);
1805
2658
  for (const [index, step] of steps.entries()) {
1806
2659
  const invalid = (reason) => {
1807
2660
  throw new InvalidUseCaseStep2({
@@ -1809,6 +2662,14 @@ async function executeSteps(dependencies = {}) {
1809
2662
  details: { index, reason, code: "invalid_target" }
1810
2663
  });
1811
2664
  };
2665
+ const list = (raw) => {
2666
+ if (raw === undefined)
2667
+ return [];
2668
+ const resolved = value(raw);
2669
+ if (!Array.isArray(resolved))
2670
+ invalid("args must resolve to a list.");
2671
+ return resolved;
2672
+ };
1812
2673
  const bind = (result) => {
1813
2674
  if (typeof step.as === "string")
1814
2675
  named.set(step.as, result);
@@ -1853,7 +2714,10 @@ async function executeSteps(dependencies = {}) {
1853
2714
  if (typeof namespace !== "object" || namespace === null || typeof namespace[key] !== "function") {
1854
2715
  invalid(`"${key}" is not a change key of the record.`);
1855
2716
  }
1856
- await namespace[key]?.(value(step.value));
2717
+ const changed = value(step.value);
2718
+ if (step.ifPresent !== true || changed !== undefined) {
2719
+ await namespace[key]?.(changed);
2720
+ }
1857
2721
  bind(record);
1858
2722
  } else if (Object.hasOwn(step, "invoke")) {
1859
2723
  const record = target("invoke");
@@ -1867,9 +2731,9 @@ async function executeSteps(dependencies = {}) {
1867
2731
  bind(await child(value(step.input)));
1868
2732
  } else if (Object.hasOwn(step, "run")) {
1869
2733
  const fn = step.fn;
1870
- bind(await fn(...list(step.args), { context: this.context }));
2734
+ bind(await fn(...list(step.args), { context: this.context, dependencies }));
1871
2735
  } else {
1872
- const action = ["create", "update", "delete"].find((kind) => Object.hasOwn(step, kind));
2736
+ const action = ["create", "update", "delete", "restore"].find((kind) => Object.hasOwn(step, kind));
1873
2737
  const methods = dependencies[step[action]];
1874
2738
  bind(await methods[action].call(methods, value(step.value)));
1875
2739
  }
@@ -1889,11 +2753,15 @@ var SHAPES = {
1889
2753
  optional: ["other", "details"]
1890
2754
  },
1891
2755
  build: { required: ["build", "values", "as"], optional: [] },
1892
- change: { required: ["change", "key", "value"], optional: ["as"] },
2756
+ change: {
2757
+ required: ["change", "key", "value"],
2758
+ optional: ["ifPresent", "as"]
2759
+ },
1893
2760
  invoke: { required: ["invoke", "method"], optional: ["args", "as"] },
1894
2761
  create: { required: ["create", "value"], optional: ["as"] },
1895
2762
  update: { required: ["update", "value"], optional: ["as"] },
1896
2763
  delete: { required: ["delete", "value"], optional: ["as"] },
2764
+ restore: { required: ["restore", "value"], optional: ["as"] },
1897
2765
  call: { required: ["call", "input"], optional: ["as"] },
1898
2766
  run: { required: ["run", "fn"], optional: ["args", "as"] },
1899
2767
  return: { required: ["return"], optional: [] }
@@ -1908,7 +2776,13 @@ var ENSURE_KINDS = new Set([
1908
2776
  "false"
1909
2777
  ]);
1910
2778
  var READ_ACTION = /^(?:findBy.+|findManyBy.+|findMany|exists|findPage|findCursor|count)$/;
1911
- var WRITE_ACTIONS = new Set(["create", "update", "delete"]);
2779
+ var WRITE_ACTIONS = new Set([
2780
+ "create",
2781
+ "update",
2782
+ "delete",
2783
+ "restore",
2784
+ "increment"
2785
+ ]);
1912
2786
  function validateSteps(declaration, steps) {
1913
2787
  const { name } = declaration;
1914
2788
  const fail = (index, code, reason) => {
@@ -1964,8 +2838,12 @@ function validateSteps(declaration, steps) {
1964
2838
  const checkArgs = (index, args) => {
1965
2839
  if (args === undefined)
1966
2840
  return;
2841
+ if (typeof args === "string" && args.startsWith("$")) {
2842
+ checkReference(index, args);
2843
+ return;
2844
+ }
1967
2845
  if (!Array.isArray(args)) {
1968
- fail(index, "invalid_step", "args must be a list of values.");
2846
+ fail(index, "invalid_step", "args must be a list of values or a reference to one.");
1969
2847
  }
1970
2848
  checkValue(index, args, "args");
1971
2849
  };
@@ -2026,6 +2904,9 @@ function validateSteps(declaration, steps) {
2026
2904
  if (WRITE_ACTIONS.has(action)) {
2027
2905
  fail(index, "write_in_find", `find cannot call "${action}".`);
2028
2906
  }
2907
+ if (action === "forEachPage") {
2908
+ fail(index, "unknown_action", '"forEachPage" runs a function on each page and cannot be a step; call it from a use case written in TypeScript.');
2909
+ }
2029
2910
  if (!READ_ACTION.test(action)) {
2030
2911
  fail(index, "unknown_action", `"${action}" is not a read action.`);
2031
2912
  }
@@ -2072,6 +2953,9 @@ function validateSteps(declaration, steps) {
2072
2953
  if (typeof record.key !== "string" || record.key === "") {
2073
2954
  fail(index, "invalid_step", "change.key must be a string.");
2074
2955
  }
2956
+ if (record.ifPresent !== undefined && typeof record.ifPresent !== "boolean") {
2957
+ fail(index, "invalid_step", "change.ifPresent must be a boolean.");
2958
+ }
2075
2959
  const reference = parseReference(record.change);
2076
2960
  const known = reference && reference.path.length === 0 ? bound.get(reference.root)?.record : undefined;
2077
2961
  if (known) {
@@ -2094,7 +2978,8 @@ function validateSteps(declaration, steps) {
2094
2978
  }
2095
2979
  case "create":
2096
2980
  case "update":
2097
- case "delete": {
2981
+ case "delete":
2982
+ case "restore": {
2098
2983
  repositoryOf(index, record[kind], kind);
2099
2984
  checkValue(index, record.value, "value");
2100
2985
  break;
@@ -2513,6 +3398,195 @@ function isUseCase2(value) {
2513
3398
  return value instanceof AbstractUseCase2;
2514
3399
  }
2515
3400
 
3401
+ // src/core/use-case/plugin.ts
3402
+ var UseCaseOrigin = Symbol("useCaseOrigin");
3403
+ var NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
3404
+ var identities = new WeakMap;
3405
+ var nextIdentity = 0;
3406
+ function keyOf(value) {
3407
+ if (typeof value === "function" || typeof value === "object" && value) {
3408
+ if (Array.isArray(value))
3409
+ return `[${value.map(keyOf).join(",")}]`;
3410
+ if (isPlain2(value)) {
3411
+ return `{${Object.keys(value).sort().filter((key) => value[key] !== undefined).map((key) => `${JSON.stringify(key)}:${keyOf(value[key])}`).join(",")}}`;
3412
+ }
3413
+ let identity = identities.get(value);
3414
+ if (identity === undefined) {
3415
+ identity = nextIdentity++;
3416
+ identities.set(value, identity);
3417
+ }
3418
+ return `#${identity}`;
3419
+ }
3420
+ return value === undefined ? "undefined" : JSON.stringify(value);
3421
+ }
3422
+ function isUseCasePlugin2(value) {
3423
+ return typeof value === "object" && value !== null && value[Kind2] === "use-case-plugin";
3424
+ }
3425
+ function useCaseOriginOf2(UseCase) {
3426
+ return typeof UseCase === "function" ? UseCase[UseCaseOrigin] : undefined;
3427
+ }
3428
+ function useCasePluginOf2(name, settings) {
3429
+ const source = typeof name === "string" ? name : "useCasePluginOf";
3430
+ const invalid = (reason) => {
3431
+ throw new InvalidUseCasePlugin2({ source, details: { reason } });
3432
+ };
3433
+ if (typeof name !== "string" || !NAME.test(name)) {
3434
+ invalid("its name must be an identifier");
3435
+ }
3436
+ if (!isPlain2(settings) || typeof settings.compile !== "function") {
3437
+ invalid("it needs a compile function");
3438
+ }
3439
+ const { version = "1.0.0", description, compile } = settings;
3440
+ if (!isVersion(version)) {
3441
+ invalid(`"${String(version)}" is not a semantic version`);
3442
+ }
3443
+ if (description !== undefined && typeof description !== "string") {
3444
+ invalid("its description must be a string");
3445
+ }
3446
+ const names = (list, what) => {
3447
+ if (list === undefined)
3448
+ return Object.freeze([]);
3449
+ if (!Array.isArray(list) || list.some((item) => typeof item !== "string" || item === "")) {
3450
+ invalid(`requires.${what} must be a list of names`);
3451
+ }
3452
+ return Object.freeze([...new Set(list)]);
3453
+ };
3454
+ if (settings.requires !== undefined && !isPlain2(settings.requires)) {
3455
+ invalid("requires must be an object of actions and plugins");
3456
+ }
3457
+ const requires = Object.freeze({
3458
+ actions: names(settings.requires?.actions, "actions"),
3459
+ plugins: names(settings.requires?.plugins, "plugins")
3460
+ });
3461
+ const made = new WeakMap;
3462
+ const make = (repository, given) => {
3463
+ if (!isRepository2(repository)) {
3464
+ return invalid("it makes a use case for a repository of repositoryOf");
3465
+ }
3466
+ if (given !== undefined && !isPlain2(given)) {
3467
+ return invalid("its options must be a plain object");
3468
+ }
3469
+ const options = Object.freeze({ ...given });
3470
+ const info = inspectRepository(repository);
3471
+ let classes = made.get(info.state);
3472
+ if (!classes) {
3473
+ classes = new Map;
3474
+ made.set(info.state, classes);
3475
+ }
3476
+ const key = keyOf(options);
3477
+ const known = classes.get(key);
3478
+ if (known)
3479
+ return known;
3480
+ const target = useCaseTargetOf2(repository, name);
3481
+ const missing = {
3482
+ actions: requires.actions.filter((action) => !info.actions.includes(action)),
3483
+ plugins: requires.plugins.filter((plugin) => !hasPlugin2(info.entity, plugin))
3484
+ };
3485
+ if (missing.actions.length > 0 || missing.plugins.length > 0) {
3486
+ const quoted = (list) => list.map((item) => `"${item}"`).join(", ");
3487
+ throw new UseCaseUnavailable2({
3488
+ source: name,
3489
+ details: {
3490
+ repository: target.repositoryName,
3491
+ reason: [
3492
+ missing.actions.length > 0 ? `the repository lacks the action ${quoted(missing.actions)}` : undefined,
3493
+ missing.plugins.length > 0 ? `the entity lacks the plugin ${quoted(missing.plugins)}` : undefined
3494
+ ].filter(Boolean).join(" and "),
3495
+ missing
3496
+ }
3497
+ });
3498
+ }
3499
+ const UseCase = useCaseOf2(compile(target, options));
3500
+ Object.defineProperty(UseCase, UseCaseOrigin, {
3501
+ value: Object.freeze({
3502
+ plugin,
3503
+ repository,
3504
+ options
3505
+ })
3506
+ });
3507
+ classes.set(key, UseCase);
3508
+ return UseCase;
3509
+ };
3510
+ const plugin = Object.freeze({
3511
+ [Kind2]: "use-case-plugin",
3512
+ name,
3513
+ version,
3514
+ ...description === undefined ? {} : { description },
3515
+ requires,
3516
+ make,
3517
+ toJSON: () => ({
3518
+ kind: "use-case-plugin",
3519
+ name,
3520
+ version,
3521
+ ...description === undefined ? {} : { description },
3522
+ requires
3523
+ })
3524
+ });
3525
+ return plugin;
3526
+ }
3527
+
3528
+ // src/core/use-case/plugin-catalog.ts
3529
+ function isUseCaseCatalog2(value) {
3530
+ return typeof value === "object" && value !== null && value[Kind2] === "use-case-catalog";
3531
+ }
3532
+ function useCaseCatalogOf2(...plugins) {
3533
+ const byName = {};
3534
+ const context = {};
3535
+ for (const plugin of plugins) {
3536
+ if (!isUseCasePlugin2(plugin)) {
3537
+ throw new InvalidUseCasePlugin2({
3538
+ source: "catalog",
3539
+ details: {
3540
+ reason: "a catalog takes plugins made by `useCasePluginOf`"
3541
+ }
3542
+ });
3543
+ }
3544
+ if (Object.hasOwn(byName, plugin.name)) {
3545
+ throw new InvalidUseCasePlugin2({
3546
+ source: plugin.name,
3547
+ details: { reason: "the catalog has another plugin with its name" }
3548
+ });
3549
+ }
3550
+ byName[plugin.name] = plugin;
3551
+ context[plugin.name] = plugin.make;
3552
+ }
3553
+ const find = (name) => {
3554
+ const plugin = Object.hasOwn(byName, name) ? byName[name] : undefined;
3555
+ if (!plugin) {
3556
+ throw new UnknownUseCasePlugin2({ source: "catalog", details: { name } });
3557
+ }
3558
+ return plugin;
3559
+ };
3560
+ return Object.freeze({
3561
+ [Kind2]: "use-case-catalog",
3562
+ plugins: Object.freeze(byName),
3563
+ list: Object.freeze([...plugins]),
3564
+ context: Object.freeze(context),
3565
+ resolve(reference) {
3566
+ const at = typeof reference === "string" ? reference.lastIndexOf("@") : -1;
3567
+ if (at <= 0)
3568
+ return find(String(reference));
3569
+ const plugin = find(reference.slice(0, at));
3570
+ const version = reference.slice(at + 1);
3571
+ if (!isVersion(version)) {
3572
+ throw new IncompatibleUseCasePlugin2({
3573
+ source: plugin.name,
3574
+ details: { reason: `"${version}" is not a semantic version` }
3575
+ });
3576
+ }
3577
+ if (!satisfies(plugin.version, version)) {
3578
+ throw new IncompatibleUseCasePlugin2({
3579
+ source: plugin.name,
3580
+ details: {
3581
+ reason: `version ${plugin.version} does not satisfy ${version}`
3582
+ }
3583
+ });
3584
+ }
3585
+ return plugin;
3586
+ }
3587
+ });
3588
+ }
3589
+
2516
3590
  // src/core/command/exceptions.ts
2517
3591
  var InvalidCommandDeclaration2 = exceptionOf2({
2518
3592
  layer: "command" /* Command */,
@@ -2667,9 +3741,12 @@ function callEnvironment(source, environments, given) {
2667
3741
  }
2668
3742
  return { byEnvironment, merged: Object.freeze(merged) };
2669
3743
  }
2670
- function commandOf2(settings) {
2671
- const { name, description, context, useCases, onEventError } = settings;
3744
+ function declareCommand(given, generics) {
3745
+ const { name, description, context, onEventError } = given;
2672
3746
  const source = typeof name === "string" ? name : "commandOf";
3747
+ const picked = given.useCases;
3748
+ const useCases = typeof picked === "function" && !isUseCaseClass2(picked) ? guard2({ layer: "command" /* Command */, source }, () => picked(generics)) : picked;
3749
+ const settings = given;
2673
3750
  const invalid = (reason) => {
2674
3751
  throw new InvalidCommandDeclaration2({ source, details: { reason } });
2675
3752
  };
@@ -2687,7 +3764,7 @@ function commandOf2(settings) {
2687
3764
  invalid("Expected context to be a non-optional blueprint.");
2688
3765
  }
2689
3766
  if (typeof useCases !== "object" || useCases === null || Array.isArray(useCases) || Reflect.ownKeys(useCases).length === 0) {
2690
- invalid("Expected useCases to be a non-empty object of use case classes.");
3767
+ invalid("Expected useCases to be a non-empty object of use case classes, or a function returning one.");
2691
3768
  }
2692
3769
  for (const key of Reflect.ownKeys(useCases)) {
2693
3770
  if (typeof key !== "string" || !IDENTIFIER2.test(key) || !isUseCaseClass2(useCases[key])) {
@@ -2825,6 +3902,19 @@ function commandOf2(settings) {
2825
3902
  verifiers.set(command, providerOfCall);
2826
3903
  return command;
2827
3904
  }
3905
+ function commandBuilderOf2(...catalogs) {
3906
+ if (!catalogs.every(isUseCaseCatalog2)) {
3907
+ throw new InvalidCommandDeclaration2({
3908
+ source: "commandBuilderOf",
3909
+ details: {
3910
+ reason: "Expected catalogs made by useCaseCatalogOf."
3911
+ }
3912
+ });
3913
+ }
3914
+ const generics = Object.freeze(Object.assign({}, ...catalogs.map((catalog) => catalog.context)));
3915
+ return (settings) => declareCommand(settings, generics);
3916
+ }
3917
+ var commandOf = commandBuilderOf2();
2828
3918
  async function applyPolicy(command, policy, onEventError, call) {
2829
3919
  const { operation, raised } = call;
2830
3920
  const source = `${command.commandName}.${operation}`;
@@ -2837,12 +3927,16 @@ async function applyPolicy(command, policy, onEventError, call) {
2837
3927
  get env() {
2838
3928
  return call.env();
2839
3929
  },
2840
- logger: call.logger
3930
+ logger: call.logger,
3931
+ publisher: (name) => resolvePublisher(command, name, source)
2841
3932
  };
2842
3933
  if (typeof policy === "function") {
2843
3934
  try {
2844
3935
  await policy(raised, info);
2845
3936
  } catch (cause) {
3937
+ if (cause instanceof Exception2 && cause.name === "CommandEventFailed") {
3938
+ throw cause;
3939
+ }
2846
3940
  throw new CommandEventFailed2({
2847
3941
  source,
2848
3942
  cause,
@@ -2888,6 +3982,81 @@ function isCommand2(value) {
2888
3982
  return typeof value === "object" && value !== null && value[Kind2] === "command";
2889
3983
  }
2890
3984
 
3985
+ // src/core/event-policy/event-policy.ts
3986
+ var described = new WeakMap;
3987
+ var factories = new WeakMap;
3988
+ function invalid4(source, reason) {
3989
+ throw new InvalidEventPolicy2({ source, details: { reason } });
3990
+ }
3991
+ function optionsOf(name, given, validate) {
3992
+ const options = frozenJson(given ?? {}, "Event policy options", (reason) => invalid4(name, reason));
3993
+ if (options === null || typeof options !== "object" || Array.isArray(options)) {
3994
+ invalid4(name, "Event policy options must be an object.");
3995
+ }
3996
+ const reason = validate?.(options);
3997
+ if (reason !== undefined)
3998
+ invalid4(name, reason);
3999
+ return options;
4000
+ }
4001
+ function eventPolicyOf2(settings) {
4002
+ const name = settings?.name;
4003
+ if (typeof name !== "string" || !/^[A-Za-z_$][\w$]*$/.test(name)) {
4004
+ invalid4("eventPolicyOf", "Expected the policy name to be an identifier.");
4005
+ }
4006
+ if (typeof settings.handle !== "function") {
4007
+ invalid4(name, "Expected handle to be a function.");
4008
+ }
4009
+ if (settings.publishers !== undefined && typeof settings.publishers !== "function") {
4010
+ invalid4(name, "Expected publishers to be a function.");
4011
+ }
4012
+ const { handle, validate, publishers } = settings;
4013
+ const factory = (given) => {
4014
+ const options = optionsOf(name, given, validate);
4015
+ const handler = (events, info) => handle(events, info, options);
4016
+ Object.defineProperty(handler, "name", { value: name });
4017
+ described.set(handler, {
4018
+ description: Object.freeze({
4019
+ kind: "policy",
4020
+ name,
4021
+ options
4022
+ }),
4023
+ publishers: Object.freeze([...publishers?.(options) ?? []])
4024
+ });
4025
+ return handler;
4026
+ };
4027
+ factories.set(factory, name);
4028
+ return factory;
4029
+ }
4030
+ function eventPolicyDescriptionOf2(handler) {
4031
+ return typeof handler === "function" ? described.get(handler)?.description : undefined;
4032
+ }
4033
+ function eventPolicyPublishersOf(handler) {
4034
+ return typeof handler === "function" ? described.get(handler)?.publishers ?? [] : [];
4035
+ }
4036
+ function eventPolicyCatalogOf2(...policies) {
4037
+ const byName = {};
4038
+ for (const factory of policies) {
4039
+ const name = factories.get(factory);
4040
+ if (name === undefined) {
4041
+ invalid4("catalog", "A catalog takes factories made by `eventPolicyOf`.");
4042
+ }
4043
+ if (Object.hasOwn(byName, name)) {
4044
+ invalid4(name, "The catalog has another event policy with its name.");
4045
+ }
4046
+ byName[name] = factory;
4047
+ }
4048
+ return Object.freeze({
4049
+ [Kind2]: "event-policy-catalog",
4050
+ policies: Object.freeze(byName),
4051
+ resolve(name, options) {
4052
+ const factory = Object.hasOwn(byName, name) ? byName[name] : undefined;
4053
+ if (!factory)
4054
+ invalid4("catalog", `Unknown event policy "${name}".`);
4055
+ return factory(options);
4056
+ }
4057
+ });
4058
+ }
4059
+
2891
4060
  // src/core/openapi/exceptions.ts
2892
4061
  var InvalidOpenAPIModel2 = exceptionOf2({
2893
4062
  layer: "output" /* Output */,
@@ -2921,18 +4090,19 @@ function isExportedException(Declared) {
2921
4090
  collected = true;
2922
4091
  for (const module of [
2923
4092
  exports_exceptions4,
2924
- exports_exceptions2,
2925
- exports_exceptions9,
2926
- exports_exceptions6,
4093
+ exports_exceptions3,
4094
+ exports_exceptions10,
4095
+ exports_exceptions7,
2927
4096
  exports_environment,
2928
4097
  exports_exceptions5,
2929
- exports_exceptions10,
4098
+ exports_exceptions6,
2930
4099
  exports_exceptions11,
2931
- exports_exceptions3,
4100
+ exports_exceptions12,
4101
+ exports_exceptions2,
2932
4102
  exports_exceptions,
2933
- exports_exceptions7,
4103
+ exports_exceptions8,
2934
4104
  exports_use_case,
2935
- exports_exceptions8
4105
+ exports_exceptions9
2936
4106
  ]) {
2937
4107
  markExported(...Object.values(module));
2938
4108
  }
@@ -2991,7 +4161,7 @@ function recordSchemaOf(record, scope) {
2991
4161
  const { sensitive = [] } = record.options ?? {};
2992
4162
  return objectOf(record.blueprint.properties, scope, {
2993
4163
  writeOnly: sensitive,
2994
- readOnly: isEntityClass2(record) ? ENTITY_IDENTITY_KEYS2 : []
4164
+ readOnly: isEntityClass2(record) ? identityKeysOf(record) : []
2995
4165
  });
2996
4166
  }
2997
4167
  function schemaOf(field, scope) {
@@ -3052,34 +4222,34 @@ function toOpenAPI2(modelOrModels, { refPrefix = "#/components/schemas/" } = {})
3052
4222
 
3053
4223
  // src/core/output/stage.ts
3054
4224
  var descriptions2 = new WeakMap;
3055
- var factories = new WeakMap;
4225
+ var factories2 = new WeakMap;
3056
4226
  var opaque2 = Object.freeze({ kind: "opaque" });
3057
- function invalid4(source, reason) {
4227
+ function invalid5(source, reason) {
3058
4228
  throw new InvalidOutputStage2({ source, details: { reason } });
3059
4229
  }
3060
- function optionsOf(name, given, validate) {
3061
- const options = frozenJson(given ?? {}, "Stage options", (reason) => invalid4(name, reason));
4230
+ function optionsOf2(name, given, validate) {
4231
+ const options = frozenJson(given ?? {}, "Stage options", (reason) => invalid5(name, reason));
3062
4232
  if (options === null || typeof options !== "object" || Array.isArray(options)) {
3063
- invalid4(name, "Stage options must be an object.");
4233
+ invalid5(name, "Stage options must be an object.");
3064
4234
  }
3065
4235
  const reason = validate?.(options);
3066
4236
  if (reason !== undefined)
3067
- invalid4(name, reason);
4237
+ invalid5(name, reason);
3068
4238
  return options;
3069
4239
  }
3070
4240
  function checkName(source, name) {
3071
4241
  if (typeof name !== "string" || !name.trim()) {
3072
- invalid4(source, "Expected the stage name to be a non-empty string.");
4242
+ invalid5(source, "Expected the stage name to be a non-empty string.");
3073
4243
  }
3074
4244
  }
3075
4245
  function transformOf2(settings) {
3076
4246
  checkName("transformOf", settings?.name);
3077
4247
  if (typeof settings.apply !== "function") {
3078
- invalid4(settings.name, "Expected apply to be a function.");
4248
+ invalid5(settings.name, "Expected apply to be a function.");
3079
4249
  }
3080
4250
  const { name, apply, validate } = settings;
3081
4251
  const factory = (given) => {
3082
- const options = optionsOf(name, given, validate);
4252
+ const options = optionsOf2(name, given, validate);
3083
4253
  const stage = Object.freeze({
3084
4254
  kind: "transform",
3085
4255
  name,
@@ -3093,17 +4263,17 @@ function transformOf2(settings) {
3093
4263
  }));
3094
4264
  return stage;
3095
4265
  };
3096
- factories.set(factory, { kind: "transform", name });
4266
+ factories2.set(factory, { kind: "transform", name });
3097
4267
  return factory;
3098
4268
  }
3099
4269
  function sinkOf2(settings) {
3100
4270
  checkName("sinkOf", settings?.name);
3101
4271
  if (typeof settings.build !== "function") {
3102
- invalid4(settings.name, "Expected build to be a function.");
4272
+ invalid5(settings.name, "Expected build to be a function.");
3103
4273
  }
3104
4274
  const { name, build, validate } = settings;
3105
4275
  const factory = (given) => {
3106
- const options = optionsOf(name, given, validate);
4276
+ const options = optionsOf2(name, given, validate);
3107
4277
  const stage = Object.freeze({
3108
4278
  kind: "sink",
3109
4279
  name,
@@ -3117,14 +4287,14 @@ function sinkOf2(settings) {
3117
4287
  }));
3118
4288
  return stage;
3119
4289
  };
3120
- factories.set(factory, { kind: "sink", name });
4290
+ factories2.set(factory, { kind: "sink", name });
3121
4291
  return factory;
3122
4292
  }
3123
4293
  function stageDescriptionOf2(stage) {
3124
4294
  return descriptions2.get(stage) ?? opaque2;
3125
4295
  }
3126
4296
  function stageKindOf2(factory) {
3127
- return factories.get(factory)?.kind;
4297
+ return factories2.get(factory)?.kind;
3128
4298
  }
3129
4299
  function isStage(value) {
3130
4300
  if (typeof value !== "object" || value === null)
@@ -3135,12 +4305,12 @@ function isStage(value) {
3135
4305
  function stageCatalogOf2(...stages) {
3136
4306
  const byName = {};
3137
4307
  for (const factory of stages) {
3138
- const known = factories.get(factory);
4308
+ const known = factories2.get(factory);
3139
4309
  if (!known) {
3140
- invalid4("catalog", "A catalog takes factories made by `transformOf` or `sinkOf`.");
4310
+ invalid5("catalog", "A catalog takes factories made by `transformOf` or `sinkOf`.");
3141
4311
  }
3142
4312
  if (Object.hasOwn(byName, known.name)) {
3143
- invalid4(known.name, "The catalog has another stage with its name.");
4313
+ invalid5(known.name, "The catalog has another stage with its name.");
3144
4314
  }
3145
4315
  byName[known.name] = factory;
3146
4316
  }
@@ -3151,7 +4321,7 @@ function stageCatalogOf2(...stages) {
3151
4321
  resolve(name, options) {
3152
4322
  const factory = Object.hasOwn(byName, name) ? byName[name] : undefined;
3153
4323
  if (!factory)
3154
- invalid4("catalog", `Unknown stage "${name}".`);
4324
+ invalid5("catalog", `Unknown stage "${name}".`);
3155
4325
  return factory(options);
3156
4326
  }
3157
4327
  });
@@ -3161,38 +4331,38 @@ function stageCatalogOf2(...stages) {
3161
4331
  function isApplicationLike(value) {
3162
4332
  return typeof value === "object" && value !== null && value[Kind2] === "application";
3163
4333
  }
3164
- function invalid5(source, reason) {
4334
+ function invalid6(source, reason) {
3165
4335
  throw new InvalidOutputStage2({ source, details: { reason } });
3166
4336
  }
3167
4337
  var PATCHABLE = ["meta", "tags", "description", "deprecated", "invoke"];
3168
4338
  function operationOf2(operation, patch) {
3169
4339
  const source = operation?.id ?? "operationOf";
3170
4340
  if (operation?.kind !== "operation" || operation.version !== 1) {
3171
- invalid5(source, "Expected an operation descriptor.");
4341
+ invalid6(source, "Expected an operation descriptor.");
3172
4342
  }
3173
4343
  if (typeof patch !== "object" || patch === null || Array.isArray(patch)) {
3174
- invalid5(source, "Expected the patch to be an object.");
4344
+ invalid6(source, "Expected the patch to be an object.");
3175
4345
  }
3176
4346
  for (const key of Reflect.ownKeys(patch)) {
3177
4347
  if (typeof key !== "string" || !PATCHABLE.includes(key)) {
3178
- invalid5(source, `"${String(key)}" cannot be patched; only ${PATCHABLE.join(", ")} can.`);
4348
+ invalid6(source, `"${String(key)}" cannot be patched; only ${PATCHABLE.join(", ")} can.`);
3179
4349
  }
3180
4350
  }
3181
4351
  const { meta, tags, description, deprecated, invoke } = patch;
3182
4352
  if ("meta" in patch && (typeof meta !== "object" || meta === null || Array.isArray(meta))) {
3183
- invalid5(source, "Expected meta to be an object.");
4353
+ invalid6(source, "Expected meta to be an object.");
3184
4354
  }
3185
4355
  if ("tags" in patch && (!Array.isArray(tags) || tags.some((tag) => typeof tag !== "string"))) {
3186
- invalid5(source, "Expected tags to be a list of strings.");
4356
+ invalid6(source, "Expected tags to be a list of strings.");
3187
4357
  }
3188
4358
  if ("description" in patch && description !== undefined && typeof description !== "string") {
3189
- invalid5(source, "Expected description to be a string.");
4359
+ invalid6(source, "Expected description to be a string.");
3190
4360
  }
3191
4361
  if ("deprecated" in patch && typeof deprecated !== "boolean") {
3192
- invalid5(source, "Expected deprecated to be a boolean.");
4362
+ invalid6(source, "Expected deprecated to be a boolean.");
3193
4363
  }
3194
4364
  if ("invoke" in patch && typeof invoke !== "function") {
3195
- invalid5(source, "Expected invoke to be a function.");
4365
+ invalid6(source, "Expected invoke to be a function.");
3196
4366
  }
3197
4367
  return Object.freeze({
3198
4368
  ...operation,
@@ -3233,25 +4403,25 @@ function statusesOf(name, statuses) {
3233
4403
  if (statuses === undefined)
3234
4404
  return Object.freeze({});
3235
4405
  if (typeof statuses !== "object" || statuses === null || Array.isArray(statuses)) {
3236
- invalid5(name, "Expected statuses to be a map of exception names to statuses.");
4406
+ invalid6(name, "Expected statuses to be a map of exception names to statuses.");
3237
4407
  }
3238
4408
  for (const [exception, status] of Object.entries(statuses)) {
3239
4409
  if (!exception || typeof status !== "number" || !Number.isInteger(status) || status < 400 || status > 599) {
3240
- invalid5(name, `Expected the status of "${exception}" to be an integer between 400 and 599.`);
4410
+ invalid6(name, `Expected the status of "${exception}" to be an integer between 400 and 599.`);
3241
4411
  }
3242
4412
  }
3243
4413
  return Object.freeze({ ...statuses });
3244
4414
  }
3245
4415
  function checkPipe(name, pipe) {
3246
4416
  if (!Array.isArray(pipe))
3247
- invalid5(name, "Expected pipe to be a list of stages.");
4417
+ invalid6(name, "Expected pipe to be a list of stages.");
3248
4418
  const stages = pipe;
3249
4419
  stages.forEach((stage, index) => {
3250
4420
  if (!isStage(stage)) {
3251
- invalid5(name, `Expected pipe[${index}] to be a transform or sink stage.`);
4421
+ invalid6(name, `Expected pipe[${index}] to be a transform or sink stage.`);
3252
4422
  }
3253
4423
  if (stage.kind === "sink" && index !== stages.length - 1) {
3254
- invalid5(name, `The sink at pipe[${index}] must be the last stage.`);
4424
+ invalid6(name, `The sink at pipe[${index}] must be the last stage.`);
3255
4425
  }
3256
4426
  });
3257
4427
  return Object.freeze([...stages]);
@@ -3267,7 +4437,7 @@ function transformed(name, operations, transforms, context) {
3267
4437
  if (result === null)
3268
4438
  continue;
3269
4439
  if (typeof result !== "object" || result?.kind !== "operation" || result.id !== operation.id) {
3270
- invalid5(name, `A transform must return the operation "${operation.id}" or null.`);
4440
+ invalid6(name, `A transform must return the operation "${operation.id}" or null.`);
3271
4441
  }
3272
4442
  next.push(result);
3273
4443
  }
@@ -3292,7 +4462,7 @@ function make(name, commands, operations, stages, statuses) {
3292
4462
  return built.value;
3293
4463
  const sink = stages.at(-1);
3294
4464
  if (sink?.kind !== "sink") {
3295
- invalid5(name || "output", "The pipe has no sink to build.");
4465
+ invalid6(name || "output", "The pipe has no sink to build.");
3296
4466
  }
3297
4467
  built = { value: sink.build(operations, context) };
3298
4468
  return built.value;
@@ -3301,7 +4471,7 @@ function make(name, commands, operations, stages, statuses) {
3301
4471
  const next = checkPipe(name || "output", more);
3302
4472
  const last = next.at(-1);
3303
4473
  if (stages.at(-1)?.kind === "sink") {
3304
- invalid5(name || "output", "Cannot add stages after the sink.");
4474
+ invalid6(name || "output", "Cannot add stages after the sink.");
3305
4475
  }
3306
4476
  const ops = transformed(name, operations, next, context);
3307
4477
  if (last?.kind === "sink") {
@@ -3315,11 +4485,11 @@ function make(name, commands, operations, stages, statuses) {
3315
4485
  function outputOf2(settings) {
3316
4486
  const { name, commands } = settings ?? {};
3317
4487
  if (typeof name !== "string" || !name.trim()) {
3318
- invalid5("outputOf", "Expected name to be a non-empty string.");
4488
+ invalid6("outputOf", "Expected name to be a non-empty string.");
3319
4489
  }
3320
4490
  const sources = Array.isArray(commands) ? commands : [commands];
3321
4491
  if (!Array.isArray(commands) && isCommand2(commands) || sources.some((entry) => !isCommand2(entry) && !isApplicationLike(entry))) {
3322
- invalid5(name, "Expected commands to be a list of commands made by commandOf or applications.");
4492
+ invalid6(name, "Expected commands to be a list of commands made by commandOf or applications.");
3323
4493
  }
3324
4494
  const flat = [
3325
4495
  ...new Set(sources.flatMap((entry) => isCommand2(entry) ? [entry] : Object.values(entry.commands)))
@@ -3439,7 +4609,7 @@ function deferredProvider(factory, application) {
3439
4609
  return provider;
3440
4610
  };
3441
4611
  const proxy = {};
3442
- for (const method of METHODS) {
4612
+ for (const method of [...METHODS, "increment"]) {
3443
4613
  proxy[method] = (...args) => real(method)[method]?.(...args);
3444
4614
  }
3445
4615
  proxy.transaction = (run) => real("transaction").transaction(run);
@@ -3500,6 +4670,16 @@ function checkTransactions(source, provider, commands) {
3500
4670
  });
3501
4671
  }
3502
4672
  }
4673
+ function checkCapabilities(source, provider, repositories) {
4674
+ if (repositories.length === 0 || provider.capabilities?.keyset === true)
4675
+ return;
4676
+ throw new InvalidApplicationDeclaration2({
4677
+ source,
4678
+ details: {
4679
+ reason: `The provider made on start does not read by cursor (capabilities.keyset), which the findCursor of ${repositories.join(", ")} needs.`
4680
+ }
4681
+ });
4682
+ }
3503
4683
 
3504
4684
  // src/core/application/providers.ts
3505
4685
  var slots = new WeakMap;
@@ -3514,7 +4694,7 @@ var METHODS2 = [
3514
4694
  ];
3515
4695
  var KEY = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
3516
4696
  var RESERVED = new Set(["inMemory", "__proto__", "constructor", "prototype"]);
3517
- function invalid6(reason) {
4697
+ function invalid7(reason) {
3518
4698
  throw new InvalidApplicationDeclaration2({
3519
4699
  source: "providersOf",
3520
4700
  details: { reason }
@@ -3535,6 +4715,7 @@ function slotOf(key, storage, value) {
3535
4715
  for (const method of METHODS2)
3536
4716
  slot[method] = unbound;
3537
4717
  slot.transaction = unbound;
4718
+ slot.increment = unbound;
3538
4719
  Object.defineProperty(slot, "capabilities", {
3539
4720
  get: () => {
3540
4721
  return;
@@ -3543,7 +4724,7 @@ function slotOf(key, storage, value) {
3543
4724
  });
3544
4725
  } else {
3545
4726
  const target = value;
3546
- for (const method of [...METHODS2, "transaction", "close"]) {
4727
+ for (const method of [...METHODS2, "increment", "transaction", "close"]) {
3547
4728
  if (typeof target[method] === "function") {
3548
4729
  slot[method] = (...args) => target[method]?.(...args);
3549
4730
  }
@@ -3559,7 +4740,7 @@ function slotOf(key, storage, value) {
3559
4740
  }
3560
4741
  function providersOf2(entries) {
3561
4742
  if (typeof entries !== "object" || entries === null || Array.isArray(entries) || Object.keys(entries).length === 0) {
3562
- invalid6("Expected an object of providers by key.");
4743
+ invalid7("Expected an object of providers by key.");
3563
4744
  }
3564
4745
  const storage = Object.create(null);
3565
4746
  storage[Kind2] = "provider-storage";
@@ -3567,17 +4748,17 @@ function providersOf2(entries) {
3567
4748
  const list = [];
3568
4749
  for (const [key, value] of Object.entries(entries)) {
3569
4750
  if (!KEY.test(key) || RESERVED.has(key)) {
3570
- invalid6(key === "inMemory" ? `"inMemory" is the built-in provider; choose another key.` : `Expected "${key}" to be an identifier that is not reserved.`);
4751
+ invalid7(key === "inMemory" ? `"inMemory" is the built-in provider; choose another key.` : `Expected "${key}" to be an identifier that is not reserved.`);
3571
4752
  }
3572
4753
  if (isProviderSlot2(value)) {
3573
- invalid6(`"${key}" holds a provider of another storage; a storage holds providers, not slots.`);
4754
+ invalid7(`"${key}" holds a provider of another storage; a storage holds providers, not slots.`);
3574
4755
  }
3575
4756
  if (!isProviderFactory2(value) && !isProviderObject(value)) {
3576
- invalid6(`Expected "${key}" to be a repository provider or a factory of providerFrom.`);
4757
+ invalid7(`Expected "${key}" to be a repository provider or a factory of providerFrom.`);
3577
4758
  }
3578
4759
  const other = seen.get(value);
3579
4760
  if (other !== undefined) {
3580
- invalid6(`"${other}" and "${key}" hold the same provider.`);
4761
+ invalid7(`"${other}" and "${key}" hold the same provider.`);
3581
4762
  }
3582
4763
  seen.set(value, key);
3583
4764
  const slot = slotOf(key, storage, value);
@@ -3636,9 +4817,34 @@ var KIND_OF = {
3636
4817
  function isApplication2(value) {
3637
4818
  return typeof value === "object" && value !== null && value[Kind2] === "application";
3638
4819
  }
3639
- function invalid7(source, reason) {
4820
+ function invalid8(source, reason) {
3640
4821
  throw new InvalidApplicationDeclaration2({ source, details: { reason } });
3641
4822
  }
4823
+ var publisherOrigins = new WeakMap;
4824
+ function publishersOf(name, declared, extended) {
4825
+ const merged = {};
4826
+ const origins = {};
4827
+ for (const other of extended) {
4828
+ Object.assign(merged, other[ApplicationPublishersKey]);
4829
+ Object.assign(origins, publisherOrigins.get(other));
4830
+ }
4831
+ if (declared !== undefined) {
4832
+ if (typeof declared !== "object" || declared === null || Array.isArray(declared)) {
4833
+ invalid8(name, "Expected `publishers` to be an object of publishers by key.");
4834
+ }
4835
+ for (const [key, publisher] of Object.entries(declared)) {
4836
+ if (!/^[A-Za-z_$][\w$]*$/.test(key)) {
4837
+ invalid8(name, `Expected the publisher key "${key}" to be an identifier.`);
4838
+ }
4839
+ if (!isEventPublisher(publisher)) {
4840
+ invalid8(name, `Expected the publisher "${key}" to be an object with a publish(topic, event) method.`);
4841
+ }
4842
+ merged[key] = publisher;
4843
+ origins[key] = name;
4844
+ }
4845
+ }
4846
+ return { publishers: Object.freeze(merged), origins };
4847
+ }
3642
4848
  function isOfKind(collection, value) {
3643
4849
  switch (collection) {
3644
4850
  case "properties":
@@ -3663,7 +4869,7 @@ function isOfKind(collection, value) {
3663
4869
  return isDeclaredException(value);
3664
4870
  }
3665
4871
  }
3666
- function keyOf(collection, value) {
4872
+ function keyOf2(collection, value) {
3667
4873
  switch (collection) {
3668
4874
  case "properties":
3669
4875
  return fieldOf(value);
@@ -3721,7 +4927,7 @@ function resolve(settings) {
3721
4927
  for (const other of settings.extends ?? []) {
3722
4928
  const resolved = internals.get(other);
3723
4929
  if (!resolved)
3724
- invalid7(source, "Expected `extends` to hold applications.");
4930
+ invalid8(source, "Expected `extends` to hold applications.");
3725
4931
  for (const key of KEYS) {
3726
4932
  for (const entry of resolved[key].values())
3727
4933
  add(entry);
@@ -3732,25 +4938,39 @@ function resolve(settings) {
3732
4938
  if (declared === undefined)
3733
4939
  continue;
3734
4940
  if (typeof declared !== "object" || declared === null || Array.isArray(declared)) {
3735
- invalid7(source, `Expected ${collection} to be an object of declarations.`);
4941
+ invalid8(source, `Expected ${collection} to be an object of declarations.`);
3736
4942
  }
3737
4943
  for (const [name, value] of Object.entries(declared)) {
3738
4944
  if (!isOfKind(collection, value)) {
3739
- invalid7(source, `${collection}.${name} is not a ${KIND_OF[collection]} (${collection} takes ${KIND_OF[collection]} declarations).`);
4945
+ invalid8(source, `${collection}.${name} is not a ${KIND_OF[collection]} (${collection} takes ${KIND_OF[collection]} declarations).`);
3740
4946
  }
3741
4947
  const declaredName = collection === "useCases" ? value.useCaseName : collection === "commands" ? value.commandName : collection === "outputs" ? value.outputName : collection === "exceptions" ? value.name : collection === "records" || collection === "entities" ? declaredRecordNameOf(value) ?? name : name;
3742
4948
  if (declaredName !== name) {
3743
- invalid7(source, `${collection}.${name} must be keyed by its name, "${declaredName}".`);
4949
+ invalid8(source, `${collection}.${name} must be keyed by its name, "${declaredName}".`);
3744
4950
  }
3745
4951
  add({
3746
4952
  collection,
3747
4953
  name,
3748
4954
  value,
3749
- key: keyOf(collection, value),
4955
+ key: keyOf2(collection, value),
3750
4956
  from: source
3751
4957
  });
3752
4958
  }
3753
4959
  }
4960
+ for (const entry of [...registry.commands.values()]) {
4961
+ for (const UseCase of Object.values(entry.value.useCases)) {
4962
+ if (useCaseOriginOf2(UseCase) === undefined || byKey.has(UseCase)) {
4963
+ continue;
4964
+ }
4965
+ add({
4966
+ collection: "useCases",
4967
+ name: UseCase.useCaseName,
4968
+ value: UseCase,
4969
+ key: UseCase,
4970
+ from: entry.from
4971
+ });
4972
+ }
4973
+ }
3754
4974
  return registry;
3755
4975
  }
3756
4976
  function recordsIn(properties) {
@@ -3861,7 +5081,7 @@ function validateOutputs(source, registry) {
3861
5081
  for (const entry of registry.outputs.values()) {
3862
5082
  const output = entry.value;
3863
5083
  if (!output.outputName) {
3864
- invalid7(source, `outputs.${entry.name} has no name (an output made by pipe cannot be registered).`);
5084
+ invalid8(source, `outputs.${entry.name} has no name (an output made by pipe cannot be registered).`);
3865
5085
  }
3866
5086
  for (const command of output.commands) {
3867
5087
  if (commands.has(command))
@@ -4042,8 +5262,9 @@ function recordDescriptionOf(entry, scope) {
4042
5262
  const Record = entry.value;
4043
5263
  const plugins = pluginsOf2(Record).map((plugin) => plugin.toJSON());
4044
5264
  const skip = new Set(plugins.flatMap((plugin) => plugin.keys));
5265
+ const expires = entry.collection === "entities" ? Record[EntitySettings2]?.expires : undefined;
4045
5266
  if (entry.collection === "entities") {
4046
- for (const key of ENTITY_IDENTITY_KEYS2)
5267
+ for (const key of identityKeysOf(Record))
4047
5268
  skip.add(key);
4048
5269
  }
4049
5270
  const own = (keys, flag) => keys.filter((key) => {
@@ -4052,6 +5273,14 @@ function recordDescriptionOf(entry, scope) {
4052
5273
  const field = Record.blueprint.properties[key];
4053
5274
  return !(flag && isProperty2(field) && field.type[flag] === true);
4054
5275
  });
5276
+ const lifecycle = entry.collection === "entities" ? lifecycleEventsOf(Record) : {};
5277
+ const events = Object.fromEntries(Object.entries(lifecycle).map(([cycle, list]) => [
5278
+ cycle,
5279
+ list.map((event) => ({
5280
+ name: event.name,
5281
+ handler: eventHasHandler(event)
5282
+ }))
5283
+ ]));
4055
5284
  return {
4056
5285
  from: entry.from,
4057
5286
  fields: fieldsDescriptionOf(Record.blueprint.properties, scope, skip),
@@ -4060,10 +5289,13 @@ function recordDescriptionOf(entry, scope) {
4060
5289
  unique: own(Record.options.unique, "unique"),
4061
5290
  change: own(Record.options.change)
4062
5291
  },
4063
- plugins
5292
+ plugins,
5293
+ ...Object.keys(events).length > 0 ? { events } : {},
5294
+ ...expires === undefined ? {} : { expires },
5295
+ ...entry.collection === "entities" && isVersionedEntityClass(Record) ? { versioned: true } : {}
4064
5296
  };
4065
5297
  }
4066
- function describeRegistry(name, extended, registry, provider, named, log) {
5298
+ function describeRegistry(name, extended, registry, provider, named, log, publishers) {
4067
5299
  const scope = {
4068
5300
  properties: referencesOf(registry, "properties"),
4069
5301
  blueprints: referencesOf(registry, "blueprints"),
@@ -4087,6 +5319,45 @@ function describeRegistry(name, extended, registry, provider, named, log) {
4087
5319
  })
4088
5320
  };
4089
5321
  };
5322
+ const describeOption = (value) => {
5323
+ if (isLoggerDeclaration(value))
5324
+ return "Logger";
5325
+ if (isUseCaseClass2(value))
5326
+ return useCaseRefs.get(value);
5327
+ if (isEnvironment2(value))
5328
+ return environmentRefs.get(value);
5329
+ if (isRepository2(value)) {
5330
+ return repositories.get(inspectRepository(value).state);
5331
+ }
5332
+ if (typeof value === "function") {
5333
+ if (isDeclaredException(value)) {
5334
+ return exceptionRefs.get(value) ?? { core: value.name };
5335
+ }
5336
+ return scope.entities.get(value) ?? scope.records.get(value) ?? { kind: "opaque" };
5337
+ }
5338
+ if (Array.isArray(value))
5339
+ return value.map(describeOption);
5340
+ if (typeof value === "object" && value !== null) {
5341
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined).map(([key, item]) => [key, describeOption(item)]));
5342
+ }
5343
+ return value;
5344
+ };
5345
+ const describeGeneric = (UseCase) => {
5346
+ const origin = useCaseOriginOf2(UseCase);
5347
+ if (!origin || interpretedStepsOf(UseCase) === undefined)
5348
+ return {};
5349
+ const repository = repositories.get(inspectRepository(origin.repository).state);
5350
+ if (repository === undefined)
5351
+ return {};
5352
+ return {
5353
+ generic: {
5354
+ name: origin.plugin.name,
5355
+ version: origin.plugin.version,
5356
+ repository,
5357
+ options: describeOption(origin.options)
5358
+ }
5359
+ };
5360
+ };
4090
5361
  const refsOf = (blueprints) => {
4091
5362
  const refs = {};
4092
5363
  for (const [key, blueprint] of Object.entries(blueprints)) {
@@ -4125,6 +5396,9 @@ function describeRegistry(name, extended, registry, provider, named, log) {
4125
5396
  })
4126
5397
  }
4127
5398
  },
5399
+ ...Object.keys(publishers).length === 0 ? {} : {
5400
+ publishers: Object.fromEntries(Object.entries(publishers).map(([key, from]) => [key, { from }]))
5401
+ },
4128
5402
  properties: each("properties", (entry) => ({
4129
5403
  from: entry.from,
4130
5404
  ...propertyDescriptionOf(entry.value, scope)
@@ -4146,6 +5420,8 @@ function describeRegistry(name, extended, registry, provider, named, log) {
4146
5420
  entity: scope.entities.get(info.entity),
4147
5421
  actions: info.actions,
4148
5422
  deletion: info.deletion,
5423
+ indexes: info.indexes,
5424
+ scans: info.scans,
4149
5425
  relations: info.relations.map((relation) => repositories.get(relation)),
4150
5426
  environments: info.environments.map((environment) => environmentRefs.get(environment)),
4151
5427
  ...info.provider === undefined ? {} : {
@@ -4188,7 +5464,8 @@ function describeRegistry(name, extended, registry, provider, named, log) {
4188
5464
  ref: repositories.get(inspectRepository(dependency).state)
4189
5465
  }
4190
5466
  ])),
4191
- implementation: describeImplementation(UseCase)
5467
+ implementation: describeImplementation(UseCase),
5468
+ ...describeGeneric(UseCase)
4192
5469
  };
4193
5470
  }),
4194
5471
  commands: each("commands", (entry) => {
@@ -4210,7 +5487,7 @@ function describeRegistry(name, extended, registry, provider, named, log) {
4210
5487
  ...command.environments.length === 0 ? {} : {
4211
5488
  environments: command.environments.map((environment) => environmentRefs.get(environment))
4212
5489
  },
4213
- events: typeof command.events === "function" ? { kind: "opaque" } : command.events,
5490
+ events: typeof command.events === "function" ? eventPolicyDescriptionOf2(command.events) ?? { kind: "opaque" } : command.events,
4214
5491
  ...command.onEventError === undefined ? {} : { onEventError: { kind: "opaque" } }
4215
5492
  };
4216
5493
  }),
@@ -4238,11 +5515,11 @@ function describeRegistry(name, extended, registry, provider, named, log) {
4238
5515
  function applicationOf2(settings) {
4239
5516
  const { name } = settings ?? {};
4240
5517
  if (typeof name !== "string" || name === "") {
4241
- invalid7("applicationOf", "Expected a name.");
5518
+ invalid8("applicationOf", "Expected a name.");
4242
5519
  }
4243
5520
  const extended = settings.extends ?? [];
4244
5521
  if (!Array.isArray(extended) || !extended.every(isApplication2)) {
4245
- invalid7(name, "Expected `extends` to be a list of applications.");
5522
+ invalid8(name, "Expected `extends` to be a list of applications.");
4246
5523
  }
4247
5524
  const registry = resolve(settings);
4248
5525
  validate(name, registry);
@@ -4250,7 +5527,7 @@ function applicationOf2(settings) {
4250
5527
  validateCommands(name, registry);
4251
5528
  validateOutputs(name, registry);
4252
5529
  if (settings.providers !== undefined && !isProviderStorage2(settings.providers)) {
4253
- invalid7(name, "Expected `providers` to be a storage made by providersOf.");
5530
+ invalid8(name, "Expected `providers` to be a storage made by providersOf.");
4254
5531
  }
4255
5532
  const named = new Map;
4256
5533
  const addNamed = (entry) => {
@@ -4307,7 +5584,7 @@ function applicationOf2(settings) {
4307
5584
  const inheritedFrom = settings.provider === undefined ? [...extended].reverse().find((other) => other.provider) : undefined;
4308
5585
  const declaredProvider = settings.provider ?? inheritedFrom?.provider;
4309
5586
  if (declaredProvider !== undefined && typeof declaredProvider !== "object") {
4310
- invalid7(name, "Expected `provider` to be a repository provider.");
5587
+ invalid8(name, "Expected `provider` to be a repository provider.");
4311
5588
  }
4312
5589
  const defaultSlot = isProviderSlot2(declaredProvider) ? declaredProvider : inheritedFrom === undefined ? undefined : defaultSlots.get(inheritedFrom);
4313
5590
  const provider = defaultSlot !== undefined ? instanceOf(defaultSlot, "the application") : isProviderFactory2(declaredProvider) ? deferredProvider(declaredProvider, name) : declaredProvider;
@@ -4340,14 +5617,33 @@ function applicationOf2(settings) {
4340
5617
  log?.set(checkLogSettings(declaredLog));
4341
5618
  }
4342
5619
  const logger = log?.logger;
5620
+ const { publishers, origins } = publishersOf(name, settings.publishers, extended);
5621
+ const hasPublishers = Object.keys(publishers).length > 0;
5622
+ for (const entry of registry.commands.values()) {
5623
+ for (const key of eventPolicyPublishersOf(entry.value.events)) {
5624
+ if (!Object.hasOwn(publishers, key)) {
5625
+ throw new UnregisteredApplicationReference2({
5626
+ source: name,
5627
+ details: {
5628
+ holder: `command "${entry.name}"`,
5629
+ reference: `publisher "${key}"`
5630
+ }
5631
+ });
5632
+ }
5633
+ }
5634
+ }
4343
5635
  const bindsProviders = provider !== undefined || instances.size > 0;
4344
- const rebinds = bindsProviders || logger !== undefined;
5636
+ const rebinds = bindsProviders || logger !== undefined || hasPublishers;
4345
5637
  const collection = (key) => Object.freeze(Object.fromEntries([...registry[key].values()].map((entry) => [entry.name, entry.value])));
4346
5638
  const bound = new Map;
4347
5639
  const boundByState = new Map;
4348
5640
  const repositories = {};
4349
5641
  for (const entry of registry.repositories.values()) {
4350
- const repository = bindsProviders ? bindRepository(entry.value, provider, instances) : entry.value;
5642
+ const repository = bindsProviders ? bindRepository(entry.value, provider, instances, {
5643
+ application: name,
5644
+ key: entry.name,
5645
+ logger
5646
+ }) : entry.value;
4351
5647
  bound.set(entry.value, repository);
4352
5648
  boundByState.set(entry.key, repository);
4353
5649
  repositories[entry.name] = repository;
@@ -4387,7 +5683,7 @@ function applicationOf2(settings) {
4387
5683
  const boundCommandOf = new Map;
4388
5684
  for (const entry of registry.commands.values()) {
4389
5685
  const declared = entry.value;
4390
- const rebuilt = !rebinds ? declared : commandOf2({
5686
+ const rebuilt = !rebinds ? declared : commandOf({
4391
5687
  name: declared.commandName,
4392
5688
  description: declared.description,
4393
5689
  tags: declared.tags,
@@ -4403,6 +5699,8 @@ function applicationOf2(settings) {
4403
5699
  });
4404
5700
  if (bindsProviders)
4405
5701
  verifyCommand(rebuilt);
5702
+ if (hasPublishers)
5703
+ bindPublishers(rebuilt, publishers);
4406
5704
  boundCommandOf.set(entry.value, rebuilt);
4407
5705
  commands[entry.name] = rebuilt;
4408
5706
  }
@@ -4467,6 +5765,7 @@ function applicationOf2(settings) {
4467
5765
  continue;
4468
5766
  const made = await startDeferred(instance, context);
4469
5767
  checkTransactions(name, made, Object.entries(commands).filter(([, command]) => command.transaction && commandTransactionProvider(command) === instance).map(([key]) => key));
5768
+ checkCapabilities(name, made, Object.entries(repositories).filter(([, repository]) => providerOfRepository(repository) === instance && inspectRepository(repository).actions.includes("findCursor")).map(([key]) => key));
4470
5769
  }
4471
5770
  started = true;
4472
5771
  };
@@ -4478,6 +5777,7 @@ function applicationOf2(settings) {
4478
5777
  logger: logger ?? silentLogger,
4479
5778
  context,
4480
5779
  ...logger ? { [ApplicationLoggerKey]: logger } : {},
5780
+ ...hasPublishers ? { [ApplicationPublishersKey]: publishers } : {},
4481
5781
  properties: collection("properties"),
4482
5782
  blueprints: collection("blueprints"),
4483
5783
  records: collection("records"),
@@ -4490,13 +5790,13 @@ function applicationOf2(settings) {
4490
5790
  exceptions: collection("exceptions"),
4491
5791
  useCase(useCase) {
4492
5792
  if (!boundUseCaseOf.has(useCase)) {
4493
- invalid7(name, "Expected a use case registered in the application.");
5793
+ invalid8(name, "Expected a use case registered in the application.");
4494
5794
  }
4495
5795
  return boundUseCaseOf.get(useCase);
4496
5796
  },
4497
5797
  async run(useCase, input = {}, options) {
4498
5798
  if (!boundUseCaseOf.has(useCase)) {
4499
- invalid7(name, "Expected a use case registered in the application.");
5799
+ invalid8(name, "Expected a use case registered in the application.");
4500
5800
  }
4501
5801
  const Bound = boundUseCaseOf.get(useCase);
4502
5802
  const instance = options?.context === undefined ? new Bound(input) : new Bound(input, { context: options.context });
@@ -4504,19 +5804,19 @@ function applicationOf2(settings) {
4504
5804
  },
4505
5805
  command(command) {
4506
5806
  if (!boundCommandOf.has(command)) {
4507
- invalid7(name, "Expected a command registered in the application.");
5807
+ invalid8(name, "Expected a command registered in the application.");
4508
5808
  }
4509
5809
  return boundCommandOf.get(command);
4510
5810
  },
4511
5811
  output(output) {
4512
5812
  if (!boundOutputOf.has(output)) {
4513
- invalid7(name, "Expected an output registered in the application.");
5813
+ invalid8(name, "Expected an output registered in the application.");
4514
5814
  }
4515
5815
  return boundOutputOf.get(output);
4516
5816
  },
4517
5817
  repository(repository) {
4518
5818
  if (!bound.has(repository)) {
4519
- invalid7(name, "Expected a repository registered in the application.");
5819
+ invalid8(name, "Expected a repository registered in the application.");
4520
5820
  }
4521
5821
  return bound.get(repository);
4522
5822
  },
@@ -4528,7 +5828,7 @@ function applicationOf2(settings) {
4528
5828
  ...declaredLog === undefined ? {} : { log: declaredLog }
4529
5829
  });
4530
5830
  },
4531
- describe: () => describeRegistry(name, extended, registry, defaultSlot !== undefined ? referenceOf(defaultSlot) : provider === undefined ? undefined : providerDescriptionOf2(provider), named, isLogFactory2(declaredLog) ? "opaque" : log?.settings),
5831
+ describe: () => describeRegistry(name, extended, registry, defaultSlot !== undefined ? referenceOf(defaultSlot) : provider === undefined ? undefined : providerDescriptionOf2(provider), named, isLogFactory2(declaredLog) ? "opaque" : log?.settings, origins),
4532
5832
  get started() {
4533
5833
  return started;
4534
5834
  },
@@ -4581,7 +5881,7 @@ function applicationOf2(settings) {
4581
5881
  get(declaration) {
4582
5882
  if (isEnvironment2(declaration)) {
4583
5883
  if (!registeredEnvironments.has(declaration)) {
4584
- invalid7(name, "Expected an environment registered in the application.");
5884
+ invalid8(name, "Expected an environment registered in the application.");
4585
5885
  }
4586
5886
  return declaration.load();
4587
5887
  }
@@ -4597,11 +5897,11 @@ function applicationOf2(settings) {
4597
5897
  if (isProviderSlot2(declaration)) {
4598
5898
  const instance = instances.get(declaration);
4599
5899
  if (instance === undefined) {
4600
- invalid7(name, "Expected a provider of a storage registered in the application.");
5900
+ invalid8(name, "Expected a provider of a storage registered in the application.");
4601
5901
  }
4602
5902
  return instance;
4603
5903
  }
4604
- return invalid7(name, "Expected a repository, use case, command, output, environment or provider registered in the application.");
5904
+ return invalid8(name, "Expected a repository, use case, command, output, environment or provider registered in the application.");
4605
5905
  }
4606
5906
  });
4607
5907
  internals.set(application, registry);
@@ -4610,6 +5910,7 @@ function applicationOf2(settings) {
4610
5910
  defaultSlots.set(application, defaultSlot);
4611
5911
  if (declaredLog !== undefined)
4612
5912
  logSettings.set(application, declaredLog);
5913
+ publisherOrigins.set(application, Object.freeze(origins));
4613
5914
  return application;
4614
5915
  }
4615
5916
  // src/core/blueprint/blueprint-plugin.ts
@@ -4620,6 +5921,140 @@ function blueprintPluginOf2(name, properties, factories = {}) {
4620
5921
  factories: Object.freeze({ ...factories })
4621
5922
  });
4622
5923
  }
5924
+ // src/core/event-policy/publish.ts
5925
+ var KEYS2 = ["publisher", "topic", "events", "handle", "onPublishError"];
5926
+ var IDENTIFIER3 = /^[A-Za-z_$][\w$]*$/;
5927
+ var PLACEHOLDER = /\{(event|command|operation|id)\}/g;
5928
+ function validate2(options) {
5929
+ const given = options;
5930
+ for (const key of Object.keys(given)) {
5931
+ if (!KEYS2.includes(key))
5932
+ return `publishEvents has no option "${key}".`;
5933
+ }
5934
+ const { publisher, topic, events, handle, onPublishError } = given;
5935
+ if (typeof publisher !== "string" || !IDENTIFIER3.test(publisher)) {
5936
+ return "Expected publishEvents.publisher to be the key of a publisher of the application.";
5937
+ }
5938
+ if (typeof topic !== "string" || topic === "") {
5939
+ return 'Expected publishEvents.topic to be a non-empty string, such as "posts/{id}".';
5940
+ }
5941
+ const unknown = topic.replaceAll(PLACEHOLDER, "").match(/\{[^}]*\}/);
5942
+ if (unknown) {
5943
+ return `publishEvents.topic has an unknown placeholder ${unknown[0]}: use {event}, {command}, {operation} or {id}.`;
5944
+ }
5945
+ if (events !== undefined && (!Array.isArray(events) || events.some((name) => typeof name !== "string" || name === ""))) {
5946
+ return "Expected publishEvents.events to be a list of event names.";
5947
+ }
5948
+ if (handle !== undefined && typeof handle !== "boolean") {
5949
+ return "Expected publishEvents.handle to be a boolean.";
5950
+ }
5951
+ if (onPublishError !== undefined && onPublishError !== "log" && onPublishError !== "throw") {
5952
+ return 'Expected publishEvents.onPublishError to be "log" or "throw".';
5953
+ }
5954
+ return;
5955
+ }
5956
+ function entityJson(event) {
5957
+ const text = JSON.stringify(event.entity);
5958
+ return text === undefined ? null : JSON.parse(text);
5959
+ }
5960
+ var publishEvents2 = eventPolicyOf2({
5961
+ name: "publishEvents",
5962
+ validate: validate2,
5963
+ publishers: (options) => [options.publisher],
5964
+ handle: async (events, info, options) => {
5965
+ const { command, operation, logger } = info;
5966
+ const commandName = command.commandName;
5967
+ const source = `${commandName}.${operation}`;
5968
+ const only = options.events ? new Set(options.events) : undefined;
5969
+ const failed = (cause, event, index) => new CommandEventFailed2({
5970
+ source,
5971
+ cause,
5972
+ details: {
5973
+ operation,
5974
+ event: event.name,
5975
+ handled: index,
5976
+ remaining: events.length - index - 1
5977
+ }
5978
+ });
5979
+ for (const [index, event] of events.entries()) {
5980
+ if (options.handle !== false) {
5981
+ try {
5982
+ await event.handle({ logger });
5983
+ } catch (cause) {
5984
+ throw failed(cause, event, index);
5985
+ }
5986
+ }
5987
+ if (only && !only.has(event.name))
5988
+ continue;
5989
+ let publisher;
5990
+ try {
5991
+ publisher = info.publisher(options.publisher);
5992
+ } catch (cause) {
5993
+ throw failed(cause, event, index);
5994
+ }
5995
+ const entity = entityJson(event);
5996
+ const id = entity?.id;
5997
+ const topic = options.topic.replaceAll(PLACEHOLDER, (_, key) => key === "event" ? event.name : key === "command" ? commandName : key === "operation" ? operation : id === undefined ? "" : String(id));
5998
+ const published = Object.freeze({
5999
+ name: event.name,
6000
+ command: commandName,
6001
+ operation,
6002
+ entity
6003
+ });
6004
+ try {
6005
+ await publisher.publish(topic, published);
6006
+ } catch (cause) {
6007
+ if (options.onPublishError === "throw")
6008
+ throw failed(cause, event, index);
6009
+ logFailure(logger, "event-publish-failed", {
6010
+ command: commandName,
6011
+ operation,
6012
+ event: event.name,
6013
+ publisher: options.publisher,
6014
+ topic
6015
+ }, cause);
6016
+ }
6017
+ }
6018
+ }
6019
+ });
6020
+ // src/core/output/coerce.ts
6021
+ var scalarTypes = new WeakMap;
6022
+ function scalarTypesOf(blueprint) {
6023
+ let types = scalarTypes.get(blueprint);
6024
+ if (!types) {
6025
+ const schema = toOpenAPI2(blueprint);
6026
+ const found = {};
6027
+ for (const [key, property] of Object.entries(schema.properties ?? {})) {
6028
+ found[key] = [property.type ?? []].flat();
6029
+ }
6030
+ types = Object.freeze(found);
6031
+ scalarTypes.set(blueprint, types);
6032
+ }
6033
+ return types;
6034
+ }
6035
+ function coerce(text, types) {
6036
+ if (!types || types.includes("string"))
6037
+ return text;
6038
+ const trimmed = text.trim();
6039
+ if (types.includes("boolean") && (trimmed === "true" || trimmed === "false")) {
6040
+ return trimmed === "true";
6041
+ }
6042
+ if (types.includes("integer") || types.includes("number")) {
6043
+ const number = trimmed === "" ? Number.NaN : Number(trimmed);
6044
+ if (Number.isFinite(number))
6045
+ return number;
6046
+ }
6047
+ return text;
6048
+ }
6049
+ function coerceTextValues2(blueprint, texts) {
6050
+ const types = scalarTypesOf(blueprint);
6051
+ const out = {};
6052
+ for (const [key, value] of Object.entries(texts)) {
6053
+ if (value !== undefined)
6054
+ out[key] = coerce(value, types[key]);
6055
+ }
6056
+ return out;
6057
+ }
4623
6058
  // src/core/output/status.ts
4624
6059
  var INVALID_INPUT = new Set([
4625
6060
  "InvalidUseCaseInput",
@@ -4629,7 +6064,13 @@ var INVALID_INPUT = new Set([
4629
6064
  var CONFIGURATION = new Set(["InvalidCommandEnvironment"]);
4630
6065
  var NOT_FOUND = new Set([
4631
6066
  "RepositoryEntityNotFound",
4632
- "UnknownCommandOperation"
6067
+ "UnknownCommandOperation",
6068
+ "UseCaseEntityNotFound"
6069
+ ]);
6070
+ var CONFLICT = new Set([
6071
+ "RepositoryUniqueViolation",
6072
+ "RepositoryConcurrencyConflict",
6073
+ "UseCaseEntityConflict"
4633
6074
  ]);
4634
6075
  function raisedOf(error) {
4635
6076
  let raised = error;
@@ -4654,8 +6095,10 @@ function statusOf2(error, operation, statuses) {
4654
6095
  }
4655
6096
  if (NOT_FOUND.has(name))
4656
6097
  return 404;
4657
- if (name === "RepositoryUniqueViolation")
6098
+ if (CONFLICT.has(name))
4658
6099
  return 409;
6100
+ if (name === "UseCaseInvalidCredentials")
6101
+ return 401;
4659
6102
  if (operation?.errors.some((Declared) => error.is(Declared) || Declared.name === name)) {
4660
6103
  return 422;
4661
6104
  }
@@ -4694,6 +6137,8 @@ function outputValueOf2(value) {
4694
6137
 
4695
6138
  // src/core/output/openapi-sink.ts
4696
6139
  var METHODS3 = ["get", "post", "put", "patch", "delete"];
6140
+ var SUCCESS_STATUSES = [200, 201, 202, 204];
6141
+ var REQUEST_ID_HEADER = "x-request-id";
4697
6142
  function isObject(value) {
4698
6143
  return typeof value === "object" && value !== null && !Array.isArray(value);
4699
6144
  }
@@ -4704,36 +6149,199 @@ function onlyKeys(value, allowed, where) {
4704
6149
  }
4705
6150
  return;
4706
6151
  }
4707
- function isPath(value) {
4708
- return typeof value === "string" && value.startsWith("/") && !/[{}?#\s]/.test(value);
6152
+ var PARAMETER = /^\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
6153
+ var HEADER_NAME = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
6154
+ var COMPONENT_NAME = /^[A-Za-z0-9._-]+$/;
6155
+ function isPath(value, parameters = true) {
6156
+ if (typeof value !== "string" || !value.startsWith("/"))
6157
+ return false;
6158
+ return value.split("/").every((segment) => parameters && PARAMETER.test(segment) || !/[{}?#\s:*]/.test(segment));
4709
6159
  }
6160
+ function parametersOfPath(path) {
6161
+ const names = [];
6162
+ for (const segment of path.split("/")) {
6163
+ const name = PARAMETER.exec(segment)?.[1];
6164
+ if (name)
6165
+ names.push(name);
6166
+ }
6167
+ return names;
6168
+ }
6169
+ function isHeaderValue(value) {
6170
+ if (typeof value !== "string")
6171
+ return false;
6172
+ for (let i = 0;i < value.length; i++) {
6173
+ const code = value.charCodeAt(i);
6174
+ if (code < 32 && code !== 9 || code === 127)
6175
+ return false;
6176
+ }
6177
+ return true;
6178
+ }
6179
+ function isScopes(value) {
6180
+ return value === undefined || Array.isArray(value) && value.every((scope) => typeof scope === "string" && scope !== "");
6181
+ }
6182
+ var SCHEME_KEYS = {
6183
+ http: ["scheme", "bearerFormat"],
6184
+ apiKey: ["name", "in"],
6185
+ oauth2: ["flows"],
6186
+ openIdConnect: ["openIdConnectUrl"],
6187
+ mutualTLS: []
6188
+ };
6189
+ function schemeIssue(scheme, where) {
6190
+ if (!isObject(scheme) || typeof scheme.type !== "string") {
6191
+ return `Expected ${where} to be a security scheme with a "type".`;
6192
+ }
6193
+ const keys = Object.hasOwn(SCHEME_KEYS, scheme.type) ? SCHEME_KEYS[scheme.type] : undefined;
6194
+ if (!keys) {
6195
+ return `Expected ${where}.type to be one of ${Object.keys(SCHEME_KEYS).join(", ")}.`;
6196
+ }
6197
+ const bad = onlyKeys(scheme, ["type", "description", ...keys], where);
6198
+ if (bad)
6199
+ return bad;
6200
+ if (scheme.description !== undefined && typeof scheme.description !== "string") {
6201
+ return `Expected ${where}.description to be a string.`;
6202
+ }
6203
+ switch (scheme.type) {
6204
+ case "http":
6205
+ if (typeof scheme.scheme !== "string" || !scheme.scheme) {
6206
+ return `Expected ${where}.scheme to be a scheme such as "bearer".`;
6207
+ }
6208
+ if (scheme.bearerFormat !== undefined && typeof scheme.bearerFormat !== "string") {
6209
+ return `Expected ${where}.bearerFormat to be a string.`;
6210
+ }
6211
+ return;
6212
+ case "apiKey":
6213
+ if (typeof scheme.name !== "string" || !scheme.name) {
6214
+ return `Expected ${where}.name to be a non-empty string.`;
6215
+ }
6216
+ if (!["header", "query", "cookie"].includes(scheme.in)) {
6217
+ return `Expected ${where}.in to be "header", "query" or "cookie".`;
6218
+ }
6219
+ return;
6220
+ case "oauth2":
6221
+ return isObject(scheme.flows) ? undefined : `Expected ${where}.flows to be an object.`;
6222
+ case "openIdConnect":
6223
+ return typeof scheme.openIdConnectUrl === "string" && scheme.openIdConnectUrl ? undefined : `Expected ${where}.openIdConnectUrl to be a URL.`;
6224
+ default:
6225
+ return;
6226
+ }
6227
+ }
6228
+ function requirementIssue(requirement, schemes, where) {
6229
+ if (!isObject(requirement) || onlyKeys(requirement, ["scheme", "scopes"], where) || typeof requirement.scheme !== "string" || !isScopes(requirement.scopes)) {
6230
+ return `Expected ${where} to be { scheme, scopes? }.`;
6231
+ }
6232
+ if (!Object.hasOwn(schemes, requirement.scheme)) {
6233
+ return `${where}.scheme names the unknown security scheme "${requirement.scheme}".`;
6234
+ }
6235
+ return;
6236
+ }
6237
+ function securityIssue(security, where) {
6238
+ if (!isObject(security))
6239
+ return `Expected ${where} to be an object.`;
6240
+ const bad = onlyKeys(security, ["schemes", "operations", "default"], where);
6241
+ if (bad)
6242
+ return bad;
6243
+ const { schemes, operations } = security;
6244
+ if (!isObject(schemes))
6245
+ return `Expected ${where}.schemes to be an object.`;
6246
+ for (const [name, scheme] of Object.entries(schemes)) {
6247
+ if (!COMPONENT_NAME.test(name)) {
6248
+ return `Expected the security scheme "${name}" to be named with letters, digits, ".", "_" or "-".`;
6249
+ }
6250
+ const schemeBad = schemeIssue(scheme, `${where}.schemes["${name}"]`);
6251
+ if (schemeBad)
6252
+ return schemeBad;
6253
+ }
6254
+ if (operations !== undefined) {
6255
+ if (!isObject(operations)) {
6256
+ return `Expected ${where}.operations to be an object.`;
6257
+ }
6258
+ for (const [id, requirement] of Object.entries(operations)) {
6259
+ if (requirement === false)
6260
+ continue;
6261
+ const requirementBad = requirementIssue(requirement, schemes, `${where}.operations["${id}"]`);
6262
+ if (requirementBad)
6263
+ return requirementBad;
6264
+ }
6265
+ }
6266
+ if (security.default !== undefined) {
6267
+ return requirementIssue(security.default, schemes, `${where}.default`);
6268
+ }
6269
+ return;
6270
+ }
6271
+ var ROUTE_OPTION_KEYS2 = [
6272
+ "basePath",
6273
+ "route",
6274
+ "responses",
6275
+ "requestId",
6276
+ "security"
6277
+ ];
4710
6278
  function routeOptionsIssue2(options, where) {
4711
- const { basePath, route } = options;
4712
- if (basePath !== undefined && (typeof basePath !== "string" || basePath !== "" && !isPath(basePath))) {
6279
+ const { basePath, route, responses, requestId, security } = options;
6280
+ if (basePath !== undefined && (typeof basePath !== "string" || basePath !== "" && !isPath(basePath, false))) {
4713
6281
  return `Expected ${where}.basePath to be "" or a path such as "/api".`;
4714
6282
  }
4715
6283
  if (route !== undefined) {
4716
6284
  if (!isObject(route))
4717
6285
  return `Expected ${where}.route to be an object.`;
4718
6286
  for (const [id, entry] of Object.entries(route)) {
4719
- if (!isObject(entry) || onlyKeys(entry, ["method", "path"], "route")) {
4720
- return `Expected ${where}.route["${id}"] to be { method?, path? }.`;
6287
+ if (!isObject(entry) || onlyKeys(entry, ["method", "path", "status"], "route")) {
6288
+ return `Expected ${where}.route["${id}"] to be { method?, path?, status? }.`;
4721
6289
  }
4722
6290
  if (entry.method !== undefined && !METHODS3.includes(entry.method)) {
4723
6291
  return `Expected ${where}.route["${id}"].method to be one of ${METHODS3.join(", ")}.`;
4724
6292
  }
4725
- if (entry.path !== undefined && !isPath(entry.path)) {
4726
- return `Expected ${where}.route["${id}"].path to start with "/" and have no parameters or query.`;
6293
+ if (entry.path !== undefined) {
6294
+ if (!isPath(entry.path)) {
6295
+ return `Expected ${where}.route["${id}"].path to start with "/", with parameters only as whole "{name}" segments and no query.`;
6296
+ }
6297
+ const names = parametersOfPath(entry.path);
6298
+ if (new Set(names).size !== names.length) {
6299
+ return `Expected ${where}.route["${id}"].path to name each parameter once.`;
6300
+ }
6301
+ }
6302
+ if (entry.status !== undefined && !SUCCESS_STATUSES.includes(entry.status)) {
6303
+ return `Expected ${where}.route["${id}"].status to be one of ${SUCCESS_STATUSES.join(", ")}.`;
6304
+ }
6305
+ }
6306
+ }
6307
+ if (responses !== undefined) {
6308
+ if (!isObject(responses)) {
6309
+ return `Expected ${where}.responses to be an object.`;
6310
+ }
6311
+ for (const [id, entry] of Object.entries(responses)) {
6312
+ const at = `${where}.responses["${id}"]`;
6313
+ if (!isObject(entry) || onlyKeys(entry, ["headers"], at) || entry.headers !== undefined && !isObject(entry.headers)) {
6314
+ return `Expected ${at} to be { headers?: { name: value } }.`;
6315
+ }
6316
+ for (const [name, value] of Object.entries(entry.headers ?? {})) {
6317
+ if (!HEADER_NAME.test(name)) {
6318
+ return `Expected ${at}.headers to be named by header names, not "${name}".`;
6319
+ }
6320
+ if (name.toLowerCase() === "content-type") {
6321
+ return `${at}.headers cannot set "content-type": the sink sets it.`;
6322
+ }
6323
+ if (!isHeaderValue(value)) {
6324
+ return `Expected ${at}.headers["${name}"] to be text without line breaks.`;
6325
+ }
4727
6326
  }
4728
6327
  }
4729
6328
  }
6329
+ if (requestId !== undefined && requestId !== true && (typeof requestId !== "string" || !HEADER_NAME.test(requestId))) {
6330
+ return `Expected ${where}.requestId to be true or a header name.`;
6331
+ }
6332
+ if (security !== undefined) {
6333
+ return securityIssue(security, `${where}.security`);
6334
+ }
4730
6335
  return;
4731
6336
  }
4732
- function validate2(options) {
4733
- const bad = onlyKeys(options, ["info", "servers", "basePath", "route"], "openapi");
6337
+ function requestIdHeaderOf2(options) {
6338
+ return options.requestId === true ? REQUEST_ID_HEADER : options.requestId ?? undefined;
6339
+ }
6340
+ function validate3(options) {
6341
+ const bad = onlyKeys(options, ["info", "servers", ...ROUTE_OPTION_KEYS2], "openapi");
4734
6342
  if (bad)
4735
6343
  return bad;
4736
- const { info, servers, basePath, route } = options;
6344
+ const { info, servers } = options;
4737
6345
  if (!isObject(info))
4738
6346
  return "Expected openapi.info to be an object.";
4739
6347
  const infoBad = onlyKeys(info, ["title", "version", "description"], "info");
@@ -4756,21 +6364,38 @@ function validate2(options) {
4756
6364
  }
4757
6365
  }
4758
6366
  }
4759
- return routeOptionsIssue2({ basePath, route }, "openapi");
6367
+ return routeOptionsIssue2(options, "openapi");
4760
6368
  }
4761
- function invalid8(reason, source = "openapi") {
6369
+ function invalid9(reason, source = "openapi") {
4762
6370
  throw new InvalidOutputStage2({ source, details: { reason } });
4763
6371
  }
4764
6372
  function kebab(text) {
4765
6373
  return text.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
4766
6374
  }
6375
+ function isScalarSchema(schema) {
6376
+ const types = [schema.type ?? []].flat();
6377
+ return !types.some((type) => type === "object" || type === "array");
6378
+ }
6379
+ function isScalarKey(operation, name) {
6380
+ if (!Object.hasOwn(operation.input.properties, name))
6381
+ return false;
6382
+ const field = operation.input.properties[name];
6383
+ return isProperty2(field) && isScalarSchema(toOpenAPI2(field));
6384
+ }
4767
6385
  function operationRoutes2(operations, options, source = "openapi") {
4768
6386
  const base = options.basePath ?? "";
4769
6387
  const route = options.route ?? {};
6388
+ const responses = options.responses ?? {};
4770
6389
  const ids = new Set(operations.map(({ id }) => id));
4771
- for (const id of Object.keys(route)) {
4772
- if (!ids.has(id)) {
4773
- invalid8(`${source}.route names the unknown operation "${id}".`, source);
6390
+ for (const [where, entries] of [
6391
+ ["route", route],
6392
+ ["responses", responses],
6393
+ ["security.operations", options.security?.operations ?? {}]
6394
+ ]) {
6395
+ for (const id of Object.keys(entries)) {
6396
+ if (!ids.has(id)) {
6397
+ invalid9(`${source}.${where} names the unknown operation "${id}".`, source);
6398
+ }
4774
6399
  }
4775
6400
  }
4776
6401
  const routes = new Map;
@@ -4779,12 +6404,24 @@ function operationRoutes2(operations, options, source = "openapi") {
4779
6404
  const chosen = route[operation.id] ?? {};
4780
6405
  const method = chosen.method ?? "post";
4781
6406
  const path = base + (chosen.path ?? `/${kebab(operation.command)}/${kebab(operation.name)}`);
4782
- const key = `${method} ${path}`;
6407
+ const parameters = parametersOfPath(path);
6408
+ for (const name of parameters) {
6409
+ if (!isScalarKey(operation, name)) {
6410
+ invalid9(`${source}.route["${operation.id}"].path has the parameter "{${name}}", which is not a scalar key of the input of ${operation.id}.`, source);
6411
+ }
6412
+ }
6413
+ const key = `${method} ${path.replace(/\{[^}]*\}/g, "{}")}`;
4783
6414
  if (taken.has(key)) {
4784
- invalid8(`Two operations are routed to ${method.toUpperCase()} ${path}.`, source);
6415
+ invalid9(`Two operations are routed to ${method.toUpperCase()} ${path}.`, source);
4785
6416
  }
4786
6417
  taken.add(key);
4787
- routes.set(operation.id, { method, path });
6418
+ routes.set(operation.id, {
6419
+ method,
6420
+ path,
6421
+ parameters,
6422
+ status: chosen.status ?? 200,
6423
+ headers: { ...responses[operation.id]?.headers ?? {} }
6424
+ });
4788
6425
  }
4789
6426
  return routes;
4790
6427
  }
@@ -4847,7 +6484,13 @@ var STATUS_TEXT = {
4847
6484
  422: "Declared error of the operation.",
4848
6485
  500: "Internal error."
4849
6486
  };
4850
- function responsesOf(operation, statuses, response) {
6487
+ var SUCCESS_TEXT = {
6488
+ 200: "Success.",
6489
+ 201: "Created.",
6490
+ 202: "Accepted.",
6491
+ 204: "No content."
6492
+ };
6493
+ function responsesOf(operation, statuses, response, route, requestIdHeader) {
4851
6494
  const names = new Map([
4852
6495
  [400, []],
4853
6496
  [500, []]
@@ -4865,10 +6508,22 @@ function responsesOf(operation, statuses, response) {
4865
6508
  list.push(Declared.name);
4866
6509
  names.set(status, list);
4867
6510
  }
6511
+ const idHeaders = requestIdHeader ? {
6512
+ [requestIdHeader]: {
6513
+ description: "The id of the request: the one it sent in this header, else a new one.",
6514
+ schema: { type: "string" }
6515
+ }
6516
+ } : {};
6517
+ const successHeaders = { ...idHeaders };
6518
+ for (const [name, value] of Object.entries(route.headers)) {
6519
+ successHeaders[name] = { schema: { type: "string", const: value } };
6520
+ }
6521
+ const withHeaders = (headers) => Object.keys(headers).length > 0 ? { headers } : {};
4868
6522
  const responses = {
4869
- "200": {
4870
- description: "Success.",
4871
- content: { "application/json": { schema: response } }
6523
+ [String(route.status)]: {
6524
+ description: SUCCESS_TEXT[route.status],
6525
+ ...withHeaders(successHeaders),
6526
+ ...route.status === 204 ? {} : { content: { "application/json": { schema: response } } }
4872
6527
  }
4873
6528
  };
4874
6529
  for (const status of [...names.keys()].sort((a, b) => a - b)) {
@@ -4876,34 +6531,55 @@ function responsesOf(operation, statuses, response) {
4876
6531
  const text = STATUS_TEXT[status] ?? `Error ${status}.`;
4877
6532
  responses[String(status)] = {
4878
6533
  description: declared.length > 0 ? `${text} Declared: ${declared.join(", ")}.` : text,
6534
+ ...withHeaders(idHeaders),
4879
6535
  content: problemContent
4880
6536
  };
4881
6537
  }
4882
6538
  return responses;
4883
6539
  }
4884
- function parametersOf(operation, schema) {
6540
+ function parameterOf(name, property, where, required) {
6541
+ const { description, ...rest } = property;
6542
+ return {
6543
+ name,
6544
+ in: where,
6545
+ required: where === "path" || required,
6546
+ ...typeof description === "string" ? { description } : {},
6547
+ schema: rest
6548
+ };
6549
+ }
6550
+ function queryParametersOf(operation, schema) {
4885
6551
  const required = new Set(schema.required ?? []);
4886
6552
  return Object.entries(schema.properties ?? {}).map(([name, property]) => {
4887
- const field = operation.input.properties[name];
4888
- const type = property.type;
4889
- if (!isProperty2(field) || type === "object" || type === "array" || Array.isArray(type) && type.some((t) => t === "object" || t === "array")) {
4890
- invalid8(`${operation.id} uses a ${"get/delete"} route, whose input becomes query parameters, but "${name}" is not a scalar; route it with method "post".`);
6553
+ if (!isProperty2(operation.input.properties[name]) || !isScalarSchema(property)) {
6554
+ invalid9(`${operation.id} uses a get/delete route, whose input becomes query parameters, but "${name}" is not a scalar; route it with method "post".`);
4891
6555
  }
4892
- const { description, ...rest } = property;
4893
- return {
4894
- name,
4895
- in: "query",
4896
- required: required.has(name),
4897
- ...typeof description === "string" ? { description } : {},
4898
- schema: rest
4899
- };
6556
+ return parameterOf(name, property, "query", required.has(name));
4900
6557
  });
4901
6558
  }
6559
+ function withoutKeys(schema, keys) {
6560
+ if (keys.length === 0)
6561
+ return schema;
6562
+ const properties = { ...schema.properties ?? {} };
6563
+ for (const key of keys)
6564
+ delete properties[key];
6565
+ const required = (schema.required ?? []).filter((key) => !keys.includes(key));
6566
+ const { required: _, ...rest } = schema;
6567
+ return {
6568
+ ...rest,
6569
+ properties,
6570
+ ...required.length > 0 ? { required } : {}
6571
+ };
6572
+ }
6573
+ function requirementOf(requirement) {
6574
+ return { [requirement.scheme]: [...requirement.scopes ?? []] };
6575
+ }
4902
6576
  var openapi2 = sinkOf2({
4903
6577
  name: "openapi",
4904
- validate: validate2,
6578
+ validate: validate3,
4905
6579
  build: (operations, options, { output }) => {
4906
6580
  const routes = operationRoutes2(operations, options);
6581
+ const requestIdHeader = requestIdHeaderOf2(options);
6582
+ const security = options.security;
4907
6583
  const records = new Set;
4908
6584
  for (const operation of operations) {
4909
6585
  collectRecords(operation.input.properties, records);
@@ -4913,11 +6589,11 @@ var openapi2 = sinkOf2({
4913
6589
  const classes = new Map;
4914
6590
  for (const record of records) {
4915
6591
  const name = recordNameOf2(record);
4916
- if (!/^[A-Za-z0-9._-]+$/.test(name) || name === "Problem") {
4917
- invalid8(`The record class "${name}" cannot be a component name; give the class a name that is not "Problem".`);
6592
+ if (!COMPONENT_NAME.test(name) || name === "Problem") {
6593
+ invalid9(`The record class "${name}" cannot be a component name; give the class a name that is not "Problem".`);
4918
6594
  }
4919
6595
  if (classes.has(name)) {
4920
- invalid8(`Two record classes are named "${name}"; rename one.`);
6596
+ invalid9(`Two record classes are named "${name}"; rename one.`);
4921
6597
  }
4922
6598
  classes.set(name, record);
4923
6599
  models[name] = record;
@@ -4934,40 +6610,52 @@ var openapi2 = sinkOf2({
4934
6610
  components[name] = schemas[name];
4935
6611
  components.Problem = problemSchema();
4936
6612
  for (const operation of operations) {
4937
- const { method, path } = routes.get(operation.id);
6613
+ const route = routes.get(operation.id);
6614
+ const { method, path } = route;
4938
6615
  const operationId = operation.id.replaceAll(".", "_");
4939
6616
  if (operationIds.has(operationId)) {
4940
- invalid8(`The operationId "${operationId}" is produced twice.`);
6617
+ invalid9(`The operationId "${operationId}" is produced twice.`);
4941
6618
  }
4942
6619
  operationIds.add(operationId);
4943
6620
  const slot = paths[path] ?? {};
4944
6621
  paths[path] = slot;
4945
- const input = schemas[`${operation.id}#input`];
6622
+ const full = schemas[`${operation.id}#input`];
6623
+ const input = withoutKeys(full, route.parameters);
4946
6624
  const response = schemas[`${operation.id}#response`];
4947
6625
  const hasBody = method !== "get" && method !== "delete";
4948
6626
  const hasInput = Object.keys(input.properties ?? {}).length > 0;
6627
+ const parameters = [
6628
+ ...route.parameters.map((name) => parameterOf(name, full.properties?.[name], "path", true)),
6629
+ ...hasBody ? [] : queryParametersOf(operation, input)
6630
+ ];
6631
+ const requirement = security?.operations?.[operation.id];
4949
6632
  slot[method] = {
4950
6633
  operationId,
4951
6634
  summary: operation.id,
4952
6635
  ...operation.description ? { description: operation.description } : {},
4953
6636
  ...operation.tags.length > 0 ? { tags: [...operation.tags] } : {},
4954
6637
  ...operation.deprecated ? { deprecated: true } : {},
4955
- ...!hasBody ? { parameters: parametersOf(operation, input) } : {},
6638
+ ...!hasBody || parameters.length > 0 ? { parameters } : {},
4956
6639
  ...hasBody && hasInput ? {
4957
6640
  requestBody: {
4958
6641
  required: true,
4959
6642
  content: { "application/json": { schema: input } }
4960
6643
  }
4961
6644
  } : {},
4962
- responses: responsesOf(operation, output.statuses, response)
6645
+ responses: responsesOf(operation, output.statuses, response, route, requestIdHeader),
6646
+ ...requirement === false ? { security: [] } : requirement ? { security: [requirementOf(requirement)] } : {}
4963
6647
  };
4964
6648
  }
4965
6649
  return {
4966
6650
  openapi: "3.1.0",
4967
6651
  info: options.info,
4968
6652
  ...options.servers ? { servers: options.servers } : {},
6653
+ ...security?.default ? { security: [requirementOf(security.default)] } : {},
4969
6654
  paths,
4970
- components: { schemas: components }
6655
+ components: {
6656
+ schemas: components,
6657
+ ...security ? { securitySchemes: security.schemes } : {}
6658
+ }
4971
6659
  };
4972
6660
  }
4973
6661
  });
@@ -4999,18 +6687,42 @@ var select2 = transformOf2({
4999
6687
  return operation;
5000
6688
  }
5001
6689
  });
5002
- // src/core/record/plugin-catalog.ts
5003
- function satisfies(available, requested) {
5004
- const [major, minor, patch] = versionParts(available);
5005
- const [wantedMajor, wantedMinor, wantedPatch] = versionParts(requested);
5006
- if (major !== wantedMajor)
5007
- return false;
5008
- if (major === 0 && minor !== wantedMinor)
5009
- return false;
5010
- if (minor !== wantedMinor)
5011
- return minor > wantedMinor;
5012
- return patch >= wantedPatch;
6690
+ // src/core/parse/strip.ts
6691
+ function collectionOf(properties, value) {
6692
+ if (!isPlainObject(value))
6693
+ return value;
6694
+ const kept = {};
6695
+ for (const [name, field] of fieldsOf(properties)) {
6696
+ if (!Object.hasOwn(value, name))
6697
+ continue;
6698
+ const item = fieldOf2(field, value[name]);
6699
+ if (item === undefined)
6700
+ continue;
6701
+ Object.defineProperty(kept, name, {
6702
+ value: item,
6703
+ enumerable: true,
6704
+ writable: true,
6705
+ configurable: true
6706
+ });
6707
+ }
6708
+ return kept;
5013
6709
  }
6710
+ function fieldOf2(field, value) {
6711
+ if (isBlueprintList2(field)) {
6712
+ return Array.isArray(value) ? value.map((item) => fieldOf2(field.item, item)) : value;
6713
+ }
6714
+ if (isBlueprint2(field))
6715
+ return collectionOf(field.properties, value);
6716
+ const record = recordFieldOf2(field);
6717
+ if (record) {
6718
+ return collectionOf(record.record.blueprint.properties, value);
6719
+ }
6720
+ return value;
6721
+ }
6722
+ function stripUndeclared2(shape, value) {
6723
+ return fieldOf2(shape, value);
6724
+ }
6725
+ // src/core/record/plugin-catalog.ts
5014
6726
  var LISTS2 = ["keys", "sensitive", "unique", "change", "members"];
5015
6727
  function isDescriptor(value) {
5016
6728
  if (typeof value !== "object" || value === null)
@@ -5120,37 +6832,37 @@ function recordPluginCatalogOf2(...plugins) {
5120
6832
  });
5121
6833
  }
5122
6834
  // src/std/properties/contact.ts
5123
- import { Type } from "typebox";
6835
+ import { Type as Type2 } from "typebox";
5124
6836
  var Email2 = propertyOf2({
5125
6837
  name: "email",
5126
- schema: Type.String({ format: "email", maxLength: 254 }),
6838
+ schema: Type2.String({ format: "email", maxLength: 254 }),
5127
6839
  default: "demo@example.com",
5128
6840
  preHandle: (value) => value.trim().toLowerCase()
5129
6841
  });
5130
6842
  var Phone2 = propertyOf2({
5131
6843
  name: "phone",
5132
- schema: Type.String({ pattern: "^\\+[1-9]\\d{1,14}$" }),
6844
+ schema: Type2.String({ pattern: "^\\+[1-9]\\d{1,14}$" }),
5133
6845
  default: "+5511999999999",
5134
6846
  preHandle: (value) => value.replace(/[\s().-]/g, "")
5135
6847
  });
5136
6848
  var Url2 = propertyOf2({
5137
6849
  name: "url",
5138
- schema: Type.String({ format: "url", maxLength: 2048 }),
6850
+ schema: Type2.String({ format: "url", maxLength: 2048 }),
5139
6851
  default: "https://example.com",
5140
6852
  preHandle: (value) => value.trim()
5141
6853
  });
5142
6854
  var Ip2 = propertyOf2({
5143
6855
  name: "ip",
5144
- schema: Type.Union([
5145
- Type.String({ format: "ipv4" }),
5146
- Type.String({ format: "ipv6" })
6856
+ schema: Type2.Union([
6857
+ Type2.String({ format: "ipv4" }),
6858
+ Type2.String({ format: "ipv6" })
5147
6859
  ]),
5148
6860
  default: "127.0.0.1",
5149
6861
  preHandle: (value) => value.trim()
5150
6862
  });
5151
6863
 
5152
6864
  // src/std/properties/enum.ts
5153
- import { Type as Type2 } from "typebox";
6865
+ import { Type as Type3 } from "typebox";
5154
6866
  function enumOf2(values, {
5155
6867
  name = "enum",
5156
6868
  optional,
@@ -5158,7 +6870,7 @@ function enumOf2(values, {
5158
6870
  } = {}) {
5159
6871
  const Enum = propertyOf2({
5160
6872
  name,
5161
- schema: Type2.Enum(values),
6873
+ schema: Type3.Enum(values),
5162
6874
  optional,
5163
6875
  default: _default
5164
6876
  });
@@ -5176,9 +6888,9 @@ function enumOf2(values, {
5176
6888
  }
5177
6889
 
5178
6890
  // src/std/properties/headless.ts
5179
- import { Type as Type3 } from "typebox";
6891
+ import { Type as Type4 } from "typebox";
5180
6892
  import { Meta } from "typebox/schema";
5181
- var JSON_SCHEMA = Type3.Unsafe({
6893
+ var JSON_SCHEMA = Type4.Unsafe({
5182
6894
  type: "object",
5183
6895
  allOf: [Meta["https://json-schema.org/draft/2020-12/schema"]]
5184
6896
  });
@@ -5186,7 +6898,7 @@ var JSON_SCHEMA = Type3.Unsafe({
5186
6898
  class SchemaProperty2 extends propertyOf2({
5187
6899
  name: "schema",
5188
6900
  schema: JSON_SCHEMA,
5189
- default: Type3.String()
6901
+ default: Type4.String()
5190
6902
  }) {
5191
6903
  compiled;
5192
6904
  static deserialize(json) {
@@ -5221,7 +6933,7 @@ class SchemaProperty2 extends propertyOf2({
5221
6933
 
5222
6934
  // src/std/properties/identity.ts
5223
6935
  import slugify from "slugify";
5224
- import { Type as Type4 } from "typebox";
6936
+ import { Type as Type5 } from "typebox";
5225
6937
 
5226
6938
  // src/std/runtime.ts
5227
6939
  var PasswordHasherRequired2 = exceptionOf2({
@@ -5417,7 +7129,7 @@ var UUID_V7 = "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{
5417
7129
 
5418
7130
  class Id2 extends propertyOf2({
5419
7131
  name: "id",
5420
- schema: Type4.String({ pattern: UUID_V7 }),
7132
+ schema: Type5.String({ pattern: UUID_V7 }),
5421
7133
  default: "01a173d2-051c-77bb-8166-a371e999573e",
5422
7134
  preHandle: (value) => value.toLowerCase()
5423
7135
  }) {
@@ -5428,7 +7140,7 @@ class Id2 extends propertyOf2({
5428
7140
 
5429
7141
  class Slug2 extends propertyOf2({
5430
7142
  name: "slug",
5431
- schema: Type4.String({
7143
+ schema: Type5.String({
5432
7144
  pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$",
5433
7145
  maxLength: 255
5434
7146
  }),
@@ -5438,8 +7150,183 @@ class Slug2 extends propertyOf2({
5438
7150
  }) {
5439
7151
  }
5440
7152
 
7153
+ // src/std/properties/json.ts
7154
+ import { Type as Type6 } from "typebox";
7155
+ import { Meta as Meta3 } from "typebox/schema";
7156
+ import Value from "typebox/value";
7157
+
7158
+ // src/std/properties/exceptions.ts
7159
+ var InvalidJsonData2 = exceptionOf2({
7160
+ layer: "property" /* Property */,
7161
+ name: "InvalidJsonData",
7162
+ message: ({ issues }, source) => `Invalid JSON data for "${source}": ${issues.map((issue) => `${issue.path.length === 0 ? "/" : `/${issue.path.join("/")}`}: ${issue.message}`).join("; ")}`
7163
+ });
7164
+ var InvalidJsonSchema2 = exceptionOf2({
7165
+ layer: "schema" /* Schema */,
7166
+ name: "InvalidJsonSchema",
7167
+ message: (_, source) => `The schema of "${source}" is not a JSON Schema.`
7168
+ });
7169
+ markExported(InvalidJsonData2, InvalidJsonSchema2);
7170
+
7171
+ // src/std/properties/json.ts
7172
+ function notJson(value, path = [], ancestors = []) {
7173
+ if (value === null)
7174
+ return;
7175
+ switch (typeof value) {
7176
+ case "string":
7177
+ case "boolean":
7178
+ return;
7179
+ case "number":
7180
+ return Number.isFinite(value) ? undefined : { path, message: `is not JSON (${value})` };
7181
+ case "object":
7182
+ break;
7183
+ default:
7184
+ return { path, message: `is not JSON (${typeof value})` };
7185
+ }
7186
+ if (ancestors.includes(value))
7187
+ return { path, message: "is circular" };
7188
+ const inner = [...ancestors, value];
7189
+ if (Array.isArray(value)) {
7190
+ for (let index = 0;index < value.length; index++) {
7191
+ const found = notJson(value[index], [...path, index], inner);
7192
+ if (found)
7193
+ return found;
7194
+ }
7195
+ return;
7196
+ }
7197
+ const prototype = Object.getPrototypeOf(value);
7198
+ if (prototype !== Object.prototype && prototype !== null) {
7199
+ return {
7200
+ path,
7201
+ message: `is not JSON (an instance of ${prototype?.constructor?.name ?? "a class"})`
7202
+ };
7203
+ }
7204
+ for (const [key, item] of Object.entries(value)) {
7205
+ const found = notJson(item, [...path, key], inner);
7206
+ if (found)
7207
+ return found;
7208
+ }
7209
+ return;
7210
+ }
7211
+ var JSON_SCHEMA2 = Type6.Unsafe({
7212
+ type: "object",
7213
+ allOf: [Meta3["https://json-schema.org/draft/2020-12/schema"]]
7214
+ });
7215
+ var checked = new WeakSet;
7216
+ function assertJsonSchema(schema, source) {
7217
+ if (typeof schema === "object" && schema !== null && checked.has(schema)) {
7218
+ return schema;
7219
+ }
7220
+ if (!Value.Check(JSON_SCHEMA2, schema)) {
7221
+ throw new InvalidJsonSchema2({ source });
7222
+ }
7223
+ checked.add(schema);
7224
+ return schema;
7225
+ }
7226
+ function pathOf(pointer) {
7227
+ if (pointer === "")
7228
+ return [];
7229
+ return pointer.slice(1).split("/").map((segment) => segment.replaceAll("~1", "/").replaceAll("~0", "~")).map((segment) => /^(?:0|[1-9][0-9]*)$/.test(segment) ? +segment : segment);
7230
+ }
7231
+ function issuesOfErrors(errors, source) {
7232
+ const issues = [];
7233
+ const add = (path, code, message) => {
7234
+ issues.push({ path, code, message, source });
7235
+ };
7236
+ for (const error of errors) {
7237
+ const path = pathOf(error.instancePath);
7238
+ const keys = (name) => Array.isArray(error.params[name]) ? error.params[name] : [];
7239
+ if (error.keyword === "required") {
7240
+ for (const key of keys("requiredProperties")) {
7241
+ add([...path, key], "required", "is required");
7242
+ }
7243
+ } else if (error.keyword === "additionalProperties") {
7244
+ for (const key of keys("additionalProperties")) {
7245
+ add([...path, key], "undeclared_key", "is not declared");
7246
+ }
7247
+ } else if (!(error.keyword === "boolean" && error.schemaPath.endsWith("/additionalProperties"))) {
7248
+ add(path, error.keyword === "type" ? "invalid_type" : "invalid_value", error.message);
7249
+ }
7250
+ }
7251
+ return issues;
7252
+ }
7253
+ function jsonSchemaIssues2(schema, value, { source = "json" } = {}) {
7254
+ const checkedSchema = assertJsonSchema(schema, source);
7255
+ const found = notJson(value);
7256
+ if (found) {
7257
+ return [
7258
+ {
7259
+ path: found.path,
7260
+ code: "invalid_value",
7261
+ message: found.message,
7262
+ source
7263
+ }
7264
+ ];
7265
+ }
7266
+ if (Value.Check(checkedSchema, value))
7267
+ return [];
7268
+ return issuesOfErrors(Value.Errors(checkedSchema, value), source);
7269
+ }
7270
+ function checkJsonSchema2(schema, value, options = {}) {
7271
+ const issues = jsonSchemaIssues2(schema, value, options);
7272
+ if (issues.length > 0) {
7273
+ throw new InvalidJsonData2({
7274
+ source: options.source ?? "json",
7275
+ details: { issues }
7276
+ });
7277
+ }
7278
+ return value;
7279
+ }
7280
+ function demoOf(schema) {
7281
+ try {
7282
+ return Value.Create(schema);
7283
+ } catch {
7284
+ return;
7285
+ }
7286
+ }
7287
+ function assertJson(value) {
7288
+ const found = notJson(value);
7289
+ if (found) {
7290
+ throw new Error(`${found.path.length === 0 ? "" : `/${found.path.join("/")}: `}${found.message}`);
7291
+ }
7292
+ return value;
7293
+ }
7294
+ var JsonProperty2 = propertyOf2({
7295
+ name: "json",
7296
+ schema: Type6.Unsafe({}),
7297
+ default: {},
7298
+ preHandle: assertJson
7299
+ });
7300
+ function jsonOf2({
7301
+ schema,
7302
+ name = "json",
7303
+ optional,
7304
+ default: _default
7305
+ }) {
7306
+ assertJsonSchema(schema, name);
7307
+ const made = _default ?? demoOf(schema);
7308
+ const Json = propertyOf2({
7309
+ name,
7310
+ schema,
7311
+ optional,
7312
+ ...made !== undefined && Value.Check(schema, made) ? { default: made } : {},
7313
+ preHandle: assertJson
7314
+ });
7315
+ recordPropertyLineage(Json, {
7316
+ kind: "factory",
7317
+ factory: "jsonOf",
7318
+ args: Object.freeze({
7319
+ schema,
7320
+ ...name === "json" ? {} : { name },
7321
+ ...optional ? { optional: true } : {},
7322
+ ..._default === undefined ? {} : { default: _default }
7323
+ })
7324
+ });
7325
+ return Json;
7326
+ }
7327
+
5441
7328
  // src/std/properties/password.ts
5442
- import { Type as Type5 } from "typebox";
7329
+ import { Type as Type7 } from "typebox";
5443
7330
  var PASSWORD_MIN_LENGTH2 = 7;
5444
7331
  var PASSWORD_MAX_LENGTH2 = 128;
5445
7332
  var PASSWORD_SPECIAL_CHARACTERS2 = "!@#$%^&*()-_=+[]{}|\\;:'\",.<>/?`~";
@@ -5467,13 +7354,30 @@ function passwordIssues2(value) {
5467
7354
  }
5468
7355
  return issues;
5469
7356
  }
7357
+ var escaped = PASSWORD_SPECIAL_CHARACTERS2.replace(/[\\\]^-]/g, "\\$&");
7358
+
7359
+ class PlainPassword2 extends propertyOf2({
7360
+ name: "plainPassword",
7361
+ schema: Type7.String({
7362
+ minLength: PASSWORD_MIN_LENGTH2,
7363
+ maxLength: PASSWORD_MAX_LENGTH2,
7364
+ pattern: `^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[${escaped}])`
7365
+ }),
7366
+ default: "Demo@123",
7367
+ sensitive: true
7368
+ }) {
7369
+ }
5470
7370
 
5471
7371
  class Password2 extends propertyOf2({
5472
7372
  name: "password",
5473
- schema: Type5.String({ pattern: ARGON2_HASH }),
7373
+ schema: Type7.String({ pattern: ARGON2_HASH }),
5474
7374
  default: DEMO_HASH,
5475
7375
  sensitive: true
5476
7376
  }) {
7377
+ static [PropertyInput2] = {
7378
+ accepts: PlainPassword2,
7379
+ convert: async (plain) => (await Password2.hash(plain)).value
7380
+ };
5477
7381
  static async hash(plain) {
5478
7382
  const issues = passwordIssues2(plain);
5479
7383
  if (issues.length > 0) {
@@ -5499,36 +7403,36 @@ class Password2 extends propertyOf2({
5499
7403
  }
5500
7404
 
5501
7405
  // src/std/properties/primitives.ts
5502
- import { Type as Type6 } from "typebox";
7406
+ import { Type as Type8 } from "typebox";
5503
7407
  var StringProperty2 = propertyOf2({
5504
7408
  name: "string",
5505
- schema: Type6.String(),
7409
+ schema: Type8.String(),
5506
7410
  default: "demo"
5507
7411
  });
5508
7412
  var Text2 = propertyOf2({
5509
7413
  name: "text",
5510
- schema: Type6.String({ minLength: 1 }),
7414
+ schema: Type8.String({ minLength: 1 }),
5511
7415
  default: "Demo",
5512
7416
  preHandle: (value) => value.trim()
5513
7417
  });
5514
7418
  var NumberProperty2 = propertyOf2({
5515
7419
  name: "number",
5516
- schema: Type6.Number(),
7420
+ schema: Type8.Number(),
5517
7421
  default: 0
5518
7422
  });
5519
7423
  var Integer2 = propertyOf2({
5520
7424
  name: "integer",
5521
- schema: Type6.Integer(),
7425
+ schema: Type8.Integer(),
5522
7426
  default: 0
5523
7427
  });
5524
7428
  var BooleanProperty2 = propertyOf2({
5525
7429
  name: "boolean",
5526
- schema: Type6.Boolean(),
7430
+ schema: Type8.Boolean(),
5527
7431
  default: false
5528
7432
  });
5529
7433
 
5530
7434
  // src/std/properties/time.ts
5531
- import { Type as Type7 } from "typebox";
7435
+ import { Type as Type9 } from "typebox";
5532
7436
  var DEMO_DATE_TIME = "2026-01-01T00:00:00.000Z";
5533
7437
  function dateTimeOf({
5534
7438
  name,
@@ -5537,7 +7441,7 @@ function dateTimeOf({
5537
7441
  }) {
5538
7442
  return class extends propertyOf2({
5539
7443
  name,
5540
- schema: Type7.String({ format: "date-time" }),
7444
+ schema: Type9.String({ format: "date-time" }),
5541
7445
  optional,
5542
7446
  default: _default
5543
7447
  }) {
@@ -5554,7 +7458,7 @@ class DateTime2 extends dateTimeOf({
5554
7458
  }
5555
7459
  var DateProperty2 = propertyOf2({
5556
7460
  name: "date",
5557
- schema: Type7.String({ format: "date" }),
7461
+ schema: Type9.String({ format: "date" }),
5558
7462
  default: "2026-01-01"
5559
7463
  });
5560
7464
 
@@ -5577,6 +7481,12 @@ class DeletedAt2 extends dateTimeOf({
5577
7481
  }) {
5578
7482
  }
5579
7483
 
7484
+ class ExpiresAt2 extends dateTimeOf({
7485
+ name: "expiresAt",
7486
+ default: DEMO_DATE_TIME
7487
+ }) {
7488
+ }
7489
+
5580
7490
  // src/std/blueprint-plugin.ts
5581
7491
  var Std2 = blueprintPluginOf2("std", {
5582
7492
  id: Id2,
@@ -5596,11 +7506,601 @@ var Std2 = blueprintPluginOf2("std", {
5596
7506
  createdAt: CreatedAt2,
5597
7507
  updatedAt: UpdatedAt2,
5598
7508
  deletedAt: DeletedAt2,
5599
- schema: SchemaProperty2
5600
- }, { enumOf: enumOf2 });
7509
+ schema: SchemaProperty2,
7510
+ json: JsonProperty2
7511
+ }, { enumOf: enumOf2, jsonOf: jsonOf2 });
5601
7512
 
5602
7513
  // src/std/blueprint.ts
5603
7514
  var blueprintOf2 = blueprintBuilderOf2(Std2);
7515
+ // src/std/use-cases/shared.ts
7516
+ var fields = blueprintBuilderOf2();
7517
+ var IDENTIFIER4 = /^[A-Za-z_][A-Za-z0-9_]*$/;
7518
+ var capitalize2 = (key) => `${key.charAt(0).toUpperCase()}${key.slice(1)}`;
7519
+ function literal(value) {
7520
+ if (typeof value === "string") {
7521
+ return value.startsWith("$") ? { literal: value } : value;
7522
+ }
7523
+ if (Array.isArray(value))
7524
+ return value.map(literal);
7525
+ if (isPlain2(value)) {
7526
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, literal(item)]));
7527
+ }
7528
+ return value;
7529
+ }
7530
+ function checkOptions(target, options, allowed) {
7531
+ const known = [...COMMON, ...allowed];
7532
+ for (const key of Object.keys(options)) {
7533
+ if (!known.includes(key)) {
7534
+ target.unavailable(`it has no option "${key}" (options: ${known.join(", ")})`);
7535
+ }
7536
+ }
7537
+ }
7538
+ var COMMON = ["name", "description", "tags", "deprecated", "errors"];
7539
+ var HOOKS = ["before", "after", "dependencies", "throws"];
7540
+ function metaOf(target, options, verb) {
7541
+ const { name, description, tags, deprecated } = options;
7542
+ if (name !== undefined && (typeof name !== "string" || name === "")) {
7543
+ target.unavailable("the option name must be a non-empty string");
7544
+ }
7545
+ return {
7546
+ name: name ?? `${verb}${target.entityName}`,
7547
+ ...description === undefined ? {} : { description },
7548
+ ...tags === undefined ? {} : { tags },
7549
+ ...deprecated === undefined ? {} : { deprecated }
7550
+ };
7551
+ }
7552
+ function asOf(target, options) {
7553
+ const { as = "item" } = options;
7554
+ if (typeof as !== "string" || !IDENTIFIER4.test(as)) {
7555
+ target.unavailable("the option as must be an identifier");
7556
+ }
7557
+ return as;
7558
+ }
7559
+ function errorOf(target, options, kind, fallback) {
7560
+ const { errors } = options;
7561
+ if (errors === undefined)
7562
+ return fallback;
7563
+ if (!isPlain2(errors)) {
7564
+ return target.unavailable("the option errors must be an object of exception classes");
7565
+ }
7566
+ const given = errors[kind];
7567
+ if (given === undefined)
7568
+ return fallback;
7569
+ if (!isDeclaredException(given)) {
7570
+ return target.unavailable(`errors.${kind} must be a class made by exceptionOf`);
7571
+ }
7572
+ return given;
7573
+ }
7574
+ function keysOf(target, options, option) {
7575
+ const given = options[option];
7576
+ if (given === undefined)
7577
+ return [];
7578
+ if (!Array.isArray(given) || given.some((key) => typeof key !== "string")) {
7579
+ return target.unavailable(`the option ${option} must be a list of keys`);
7580
+ }
7581
+ return given.map((key) => target.field(key));
7582
+ }
7583
+ function valuesOf(target, options, option) {
7584
+ const given = options[option];
7585
+ if (given === undefined)
7586
+ return {};
7587
+ if (!isPlain2(given)) {
7588
+ return target.unavailable(`the option ${option} must be an object`);
7589
+ }
7590
+ for (const key of Object.keys(given)) {
7591
+ if (target.field(key).identity) {
7592
+ target.unavailable(`${option}.${key} is an identity key`);
7593
+ }
7594
+ }
7595
+ return given;
7596
+ }
7597
+ function ownerOf(target, options) {
7598
+ const { owner } = options;
7599
+ if (owner === undefined)
7600
+ return;
7601
+ if (!isPlain2(owner) || typeof owner.context !== "string" || !IDENTIFIER4.test(owner.context) || owner.key !== undefined && typeof owner.key !== "string") {
7602
+ return target.unavailable("the option owner must be { key?, context }, context naming a context key");
7603
+ }
7604
+ const plan = target.field(owner.key ?? "ownerId");
7605
+ if (plan.identity || plan.optional) {
7606
+ target.unavailable(`owner.key "${plan.key}" must be a required key`);
7607
+ }
7608
+ if (plan.kind === "entity" && plan.related) {
7609
+ return {
7610
+ plan,
7611
+ context: owner.context,
7612
+ field: plan.related.blueprint.properties.id,
7613
+ reference: `$item.${plan.key}.id`
7614
+ };
7615
+ }
7616
+ if (plan.kind !== "property") {
7617
+ target.unavailable(`owner.key "${plan.key}" must hold an id or an entity`);
7618
+ }
7619
+ return {
7620
+ plan,
7621
+ context: owner.context,
7622
+ field: plan.field,
7623
+ reference: `$item.${plan.key}`
7624
+ };
7625
+ }
7626
+ function contextOf2(owner) {
7627
+ return owner ? { context: fields(() => ({ [owner.context]: owner.field })) } : {};
7628
+ }
7629
+ function loadSteps(owner, notFound) {
7630
+ return [
7631
+ { find: "repository.findById", args: ["$input.id"], as: "item" },
7632
+ { ensure: "exists", value: "$item", error: notFound },
7633
+ ...owner ? [
7634
+ {
7635
+ ensure: "equals",
7636
+ value: owner.reference,
7637
+ other: `$context.${owner.context}`,
7638
+ error: notFound
7639
+ }
7640
+ ] : []
7641
+ ];
7642
+ }
7643
+ function hooksOf(target, options) {
7644
+ const { before, after, dependencies = {}, throws = [] } = options;
7645
+ for (const [key, hook] of [
7646
+ ["before", before],
7647
+ ["after", after]
7648
+ ]) {
7649
+ if (hook !== undefined && typeof hook !== "function") {
7650
+ target.unavailable(`the option ${key} must be a function`);
7651
+ }
7652
+ }
7653
+ if (!isPlain2(dependencies) || Object.keys(dependencies).some((key) => !IDENTIFIER4.test(key))) {
7654
+ target.unavailable("the option dependencies must be an object of repositories, use cases or environments by name");
7655
+ }
7656
+ if (!Array.isArray(throws) || throws.some((error) => !isDeclaredException(error))) {
7657
+ target.unavailable("the option throws must be a list of classes made by exceptionOf");
7658
+ }
7659
+ const input = before === undefined ? "$input" : "$prepared";
7660
+ return {
7661
+ input,
7662
+ dependencies,
7663
+ throws,
7664
+ before: (entity) => before === undefined ? [] : [
7665
+ {
7666
+ run: "before",
7667
+ fn: async (given, ...rest) => {
7668
+ const { context, dependencies } = rest.at(-1);
7669
+ const replaced = await before({
7670
+ input: given,
7671
+ ...entity === undefined ? {} : { entity: rest[0] },
7672
+ dependencies,
7673
+ context
7674
+ });
7675
+ return replaced === undefined ? given : replaced;
7676
+ },
7677
+ args: entity === undefined ? ["$input"] : ["$input", entity],
7678
+ as: "prepared"
7679
+ }
7680
+ ],
7681
+ after: (entity) => after === undefined ? [] : [
7682
+ {
7683
+ run: "after",
7684
+ fn: async (written, given, { context, dependencies }) => {
7685
+ await after({
7686
+ entity: written,
7687
+ input: given,
7688
+ dependencies,
7689
+ context
7690
+ });
7691
+ },
7692
+ args: [entity, input]
7693
+ }
7694
+ ]
7695
+ };
7696
+ }
7697
+ function joinDependencies(target, made, hooks) {
7698
+ for (const key of Object.keys(hooks.dependencies)) {
7699
+ if (Object.hasOwn(made, key)) {
7700
+ target.unavailable(`dependencies.${key} is a name the use case gives its own dependency`);
7701
+ }
7702
+ }
7703
+ return { ...made, ...hooks.dependencies };
7704
+ }
7705
+ var idOf2 = (target) => target.field("id").field;
7706
+ var distinct = (...errors) => [...new Set(errors)];
7707
+ function entityUseCase(target, options, verb, allowed, steps, hooks = false) {
7708
+ checkOptions(target, options, [
7709
+ "as",
7710
+ "owner",
7711
+ ...allowed,
7712
+ ...hooks ? HOOKS : []
7713
+ ]);
7714
+ const owner = ownerOf(target, options);
7715
+ const notFound = errorOf(target, options, "notFound", UseCaseEntityNotFound2);
7716
+ const hooked = hooksOf(target, options);
7717
+ const body = steps({ owner, input: hooked.input });
7718
+ return {
7719
+ ...metaOf(target, options, verb),
7720
+ blueprint: fields(() => ({ id: idOf2(target) })),
7721
+ response: fields(() => ({ [asOf(target, options)]: target.entity })),
7722
+ ...contextOf2(owner),
7723
+ dependencies: joinDependencies(target, { repository: target.repository }, hooked),
7724
+ errors: distinct(notFound, ...hooked.throws),
7725
+ steps: [
7726
+ ...loadSteps(owner, notFound),
7727
+ ...hooked.before("$item"),
7728
+ ...body.steps,
7729
+ ...hooked.after(body.result),
7730
+ { return: { [asOf(target, options)]: body.result } }
7731
+ ]
7732
+ };
7733
+ }
7734
+
7735
+ // src/std/use-cases/crud.ts
7736
+ function createCompilation(target, options, verb, checks = () => [], errors = []) {
7737
+ checkOptions(target, options, ["as", "owner", "omit", "values", ...HOOKS]);
7738
+ const owner = ownerOf(target, options);
7739
+ const hooks = hooksOf(target, options);
7740
+ const omitted = keysOf(target, options, "omit");
7741
+ const constants = valuesOf(target, options, "values");
7742
+ const notFound = errorOf(target, options, "notFound", UseCaseEntityNotFound2);
7743
+ const ownerError = errorOf(target, options, "owner", notFound);
7744
+ const input = {};
7745
+ const values = {};
7746
+ const dependencies = {
7747
+ repository: target.repository
7748
+ };
7749
+ const steps = [];
7750
+ const used = new Set([...errors, ...hooks.throws]);
7751
+ const take = (key, field) => {
7752
+ if (Object.hasOwn(input, key)) {
7753
+ target.unavailable(`two keys of the entity take the input "${key}"`);
7754
+ }
7755
+ input[key] = field;
7756
+ };
7757
+ const load = (key, reference, error) => {
7758
+ const dependency = `${key}Repository`;
7759
+ dependencies[dependency] = target.relation(key, ["findById"]);
7760
+ used.add(error);
7761
+ steps.push({
7762
+ find: `${dependency}.findById`,
7763
+ args: [reference],
7764
+ as: `${key}Found`
7765
+ }, { ensure: "exists", value: `$${key}Found`, error });
7766
+ return `$${key}Found`;
7767
+ };
7768
+ for (const plan of target.fields) {
7769
+ const { key } = plan;
7770
+ if (plan.identity)
7771
+ continue;
7772
+ if (Object.hasOwn(constants, key)) {
7773
+ values[key] = literal(constants[key]);
7774
+ continue;
7775
+ }
7776
+ if (owner?.plan.key === key) {
7777
+ values[key] = plan.kind === "entity" ? load(key, `$context.${owner.context}`, ownerError) : `$context.${owner.context}`;
7778
+ continue;
7779
+ }
7780
+ const unloadable = plan.kind === "entity-list" || plan.kind === "entity" && plan.optional;
7781
+ if (omitted.includes(plan) || unloadable) {
7782
+ if (!plan.optional) {
7783
+ target.unavailable(unloadable ? `"${key}" holds a list of entities, which the input cannot take; give it in values` : `"${key}" is required and cannot be omitted`);
7784
+ }
7785
+ continue;
7786
+ }
7787
+ take(plan.input.key, plan.input.field);
7788
+ const given = `${hooks.input}.${plan.input.key}`;
7789
+ if (plan.kind === "entity") {
7790
+ values[key] = load(key, given, notFound);
7791
+ } else if (plan.convert) {
7792
+ steps.push({
7793
+ run: `convert${capitalize2(key)}`,
7794
+ fn: plan.convert,
7795
+ args: [given],
7796
+ as: `${key}Converted`
7797
+ });
7798
+ values[key] = `$${key}Converted`;
7799
+ } else {
7800
+ values[key] = given;
7801
+ }
7802
+ }
7803
+ return {
7804
+ ...metaOf(target, options, verb),
7805
+ blueprint: fields(() => input),
7806
+ response: fields(() => ({ [asOf(target, options)]: target.entity })),
7807
+ ...contextOf2(owner),
7808
+ dependencies: joinDependencies(target, dependencies, hooks),
7809
+ errors: distinct(...used),
7810
+ steps: [
7811
+ ...hooks.before(),
7812
+ ...checks(hooks.input),
7813
+ ...steps,
7814
+ { build: target.entity, values, as: "built" },
7815
+ { create: "repository", value: "$built", as: "item" },
7816
+ ...hooks.after("$item"),
7817
+ { return: { [asOf(target, options)]: "$item" } }
7818
+ ]
7819
+ };
7820
+ }
7821
+ var create2 = useCasePluginOf2("create", {
7822
+ description: "Builds an entity from the input (its keys, less the identity; an entity it holds by id) and stores it.",
7823
+ requires: { actions: ["create"] },
7824
+ compile: (target, options) => createCompilation(target, options, "Create")
7825
+ });
7826
+ var get = useCasePluginOf2("get", {
7827
+ description: "Gives the entity of an id.",
7828
+ requires: { actions: ["findById"] },
7829
+ compile: (target, options) => entityUseCase(target, options, "Get", [], () => ({
7830
+ steps: [],
7831
+ result: "$item"
7832
+ }))
7833
+ });
7834
+ var list = useCasePluginOf2("list", {
7835
+ description: "Gives a page of entities: { items, total, limit, offset }.",
7836
+ requires: { actions: ["findPage"] },
7837
+ compile: (target, options) => {
7838
+ checkOptions(target, options, ["owner", "orderBy"]);
7839
+ const owner = ownerOf(target, options);
7840
+ const { orderBy = [{ key: "createdAt", direction: "desc" }] } = options;
7841
+ if (!Array.isArray(orderBy)) {
7842
+ target.unavailable("the option orderBy must be a list of orders");
7843
+ }
7844
+ for (const order of orderBy) {
7845
+ target.field(String(order?.key));
7846
+ }
7847
+ return {
7848
+ ...metaOf(target, options, "List"),
7849
+ blueprint: fields(() => ({
7850
+ limit: optionalOf2(Integer2),
7851
+ offset: optionalOf2(Integer2)
7852
+ })),
7853
+ response: fields(() => ({
7854
+ items: listOf2(target.entity),
7855
+ total: Integer2,
7856
+ limit: Integer2,
7857
+ offset: Integer2
7858
+ })),
7859
+ ...contextOf2(owner),
7860
+ dependencies: { repository: target.repository },
7861
+ steps: [
7862
+ {
7863
+ find: "repository.findPage",
7864
+ args: [
7865
+ {
7866
+ ...owner ? {
7867
+ where: {
7868
+ [owner.plan.key]: `$context.${owner.context}`
7869
+ }
7870
+ } : {},
7871
+ orderBy: literal(orderBy),
7872
+ limit: "$input.limit",
7873
+ offset: "$input.offset"
7874
+ }
7875
+ ],
7876
+ as: "page"
7877
+ },
7878
+ {
7879
+ return: {
7880
+ items: "$page.items",
7881
+ total: "$page.total",
7882
+ limit: "$page.limit",
7883
+ offset: "$page.offset"
7884
+ }
7885
+ }
7886
+ ]
7887
+ };
7888
+ }
7889
+ });
7890
+ var update2 = useCasePluginOf2("update", {
7891
+ description: "Changes the change keys the input gives, leaving the others as they are, and stores the entity.",
7892
+ requires: { actions: ["findById", "update"] },
7893
+ compile: (target, options) => {
7894
+ checkOptions(target, options, ["as", "owner", ...HOOKS]);
7895
+ const owner = ownerOf(target, options);
7896
+ const hooks = hooksOf(target, options);
7897
+ const notFound = errorOf(target, options, "notFound", UseCaseEntityNotFound2);
7898
+ const plans = target.changeKeys.map((key) => target.field(key)).filter((plan) => plan.kind !== "entity" && plan.kind !== "entity-list");
7899
+ if (plans.length === 0) {
7900
+ target.unavailable("the entity has no change key an input can take");
7901
+ }
7902
+ if (plans.some((plan) => plan.key === "id")) {
7903
+ target.unavailable('"id" cannot be a change key');
7904
+ }
7905
+ const steps = [];
7906
+ for (const plan of plans) {
7907
+ const given = `${hooks.input}.${plan.input.key}`;
7908
+ let value = given;
7909
+ if (plan.convert) {
7910
+ steps.push({
7911
+ run: `convert${capitalize2(plan.key)}`,
7912
+ fn: plan.convert,
7913
+ args: [given],
7914
+ as: `${plan.key}Converted`
7915
+ });
7916
+ value = `$${plan.key}Converted`;
7917
+ }
7918
+ steps.push({ change: "$item", key: plan.key, value, ifPresent: true });
7919
+ }
7920
+ return {
7921
+ ...metaOf(target, options, "Update"),
7922
+ blueprint: fields(() => ({
7923
+ id: idOf2(target),
7924
+ ...Object.fromEntries(plans.map((plan) => [plan.input.key, optionalOf2(plan.input.field)]))
7925
+ })),
7926
+ response: fields(() => ({ [asOf(target, options)]: target.entity })),
7927
+ ...contextOf2(owner),
7928
+ dependencies: joinDependencies(target, { repository: target.repository }, hooks),
7929
+ errors: distinct(notFound, ...hooks.throws),
7930
+ steps: [
7931
+ ...loadSteps(owner, notFound),
7932
+ ...hooks.before("$item"),
7933
+ ...steps,
7934
+ { update: "repository", value: "$item", as: "saved" },
7935
+ ...hooks.after("$saved"),
7936
+ { return: { [asOf(target, options)]: "$saved" } }
7937
+ ]
7938
+ };
7939
+ }
7940
+ });
7941
+ var remove2 = useCasePluginOf2("delete", {
7942
+ description: "Deletes the entity of an id, hard or soft as the repository declares, and gives it.",
7943
+ requires: { actions: ["findById", "delete"] },
7944
+ compile: (target, options) => entityUseCase(target, options, "Delete", [], () => ({
7945
+ steps: [{ delete: "repository", value: "$item", as: "deleted" }],
7946
+ result: "$deleted"
7947
+ }), true)
7948
+ });
7949
+ var restore2 = useCasePluginOf2("restore", {
7950
+ description: "Brings back the soft-deleted entity of an id; for a repository with soft deletion and the restore action.",
7951
+ requires: { actions: ["restore"] },
7952
+ compile: (target, options) => {
7953
+ checkOptions(target, options, ["as", ...HOOKS]);
7954
+ const hooks = hooksOf(target, options);
7955
+ return {
7956
+ ...metaOf(target, options, "Restore"),
7957
+ blueprint: fields(() => ({ id: idOf2(target) })),
7958
+ response: fields(() => ({ [asOf(target, options)]: target.entity })),
7959
+ dependencies: joinDependencies(target, { repository: target.repository }, hooks),
7960
+ errors: distinct(...hooks.throws),
7961
+ steps: [
7962
+ ...hooks.before(),
7963
+ { restore: "repository", value: `${hooks.input}.id`, as: "item" },
7964
+ ...hooks.after("$item"),
7965
+ { return: { [asOf(target, options)]: "$item" } }
7966
+ ]
7967
+ };
7968
+ }
7969
+ });
7970
+
7971
+ // src/std/use-cases/auth.ts
7972
+ var authenticatable = (actions) => ({
7973
+ requires: { actions, plugins: ["authenticatable"] }
7974
+ });
7975
+ var register = useCasePluginOf2("register", {
7976
+ description: "Stores an authenticatable entity built from the input, its password given plain and stored as a hash, unless the email is taken.",
7977
+ ...authenticatable(["findByEmail", "create"]),
7978
+ compile: (target, options) => {
7979
+ const conflict = errorOf(target, options, "conflict", UseCaseEntityConflict2);
7980
+ return createCompilation(target, options, "Register", (input) => [
7981
+ {
7982
+ find: "repository.findByEmail",
7983
+ args: [`${input}.email`],
7984
+ as: "existing"
7985
+ },
7986
+ { ensure: "absent", value: "$existing", error: conflict }
7987
+ ], [conflict]);
7988
+ }
7989
+ });
7990
+ var login = useCasePluginOf2("login", {
7991
+ description: "Gives the authenticatable entity of an email when the password matches; issues no token.",
7992
+ ...authenticatable(["findByEmail"]),
7993
+ compile: (target, options) => {
7994
+ checkOptions(target, options, ["as"]);
7995
+ const invalid = errorOf(target, options, "invalid", UseCaseInvalidCredentials2);
7996
+ return {
7997
+ ...metaOf(target, options, "Login"),
7998
+ blueprint: fields(() => ({
7999
+ email: target.field("email").field,
8000
+ password: StringProperty2
8001
+ })),
8002
+ response: fields(() => ({ [asOf(target, options)]: target.entity })),
8003
+ dependencies: { repository: target.repository },
8004
+ errors: [invalid],
8005
+ steps: [
8006
+ { find: "repository.findByEmail", args: ["$input.email"], as: "item" },
8007
+ { ensure: "exists", value: "$item", error: invalid },
8008
+ {
8009
+ invoke: "$item",
8010
+ method: "verifyPassword",
8011
+ args: ["$input.password"],
8012
+ as: "matches"
8013
+ },
8014
+ { ensure: "true", value: "$matches", error: invalid },
8015
+ { return: { [asOf(target, options)]: "$item" } }
8016
+ ]
8017
+ };
8018
+ }
8019
+ });
8020
+
8021
+ // src/std/use-cases/lifecycle.ts
8022
+ function invoking(target, options, verb, method) {
8023
+ const set = valuesOf(target, options, "set");
8024
+ const changes = Object.entries(set).map(([key, value]) => {
8025
+ if (!target.changeKeys.includes(key)) {
8026
+ target.unavailable(`set.${key} is not a change key of the entity`);
8027
+ }
8028
+ return { change: "$item", key, value: literal(value) };
8029
+ });
8030
+ return entityUseCase(target, options, verb, ["set"], () => ({
8031
+ steps: [
8032
+ ...changes,
8033
+ { invoke: "$item", method },
8034
+ { update: "repository", value: "$item", as: "saved" }
8035
+ ],
8036
+ result: "$saved"
8037
+ }), true);
8038
+ }
8039
+ var lifecycle = (plugin) => ({
8040
+ requires: { actions: ["findById", "update"], plugins: [plugin] }
8041
+ });
8042
+ var publish = useCasePluginOf2("publish", {
8043
+ description: "Publishes the publishable entity of an id and stores it.",
8044
+ ...lifecycle("publishable"),
8045
+ compile: (target, options) => invoking(target, options, "Publish", "publish")
8046
+ });
8047
+ var unpublish = useCasePluginOf2("unpublish", {
8048
+ description: "Makes the publishable entity of an id a draft again and stores it.",
8049
+ ...lifecycle("publishable"),
8050
+ compile: (target, options) => invoking(target, options, "Unpublish", "unpublish")
8051
+ });
8052
+ var archive = useCasePluginOf2("archive", {
8053
+ description: "Archives the archivable entity of an id and stores it.",
8054
+ ...lifecycle("archivable"),
8055
+ compile: (target, options) => invoking(target, options, "Archive", "archive")
8056
+ });
8057
+ var unarchive = useCasePluginOf2("unarchive", {
8058
+ description: "Makes the archived entity of an id active again and stores it.",
8059
+ ...lifecycle("archivable"),
8060
+ compile: (target, options) => invoking(target, options, "Unarchive", "restore")
8061
+ });
8062
+ function tagging(target, options, verb, method) {
8063
+ checkOptions(target, options, ["as", "owner", ...HOOKS]);
8064
+ const owner = ownerOf(target, options);
8065
+ const hooks = hooksOf(target, options);
8066
+ const notFound = errorOf(target, options, "notFound", UseCaseEntityNotFound2);
8067
+ const { item } = target.field("tags").field;
8068
+ return {
8069
+ ...metaOf(target, options, verb),
8070
+ blueprint: fields(() => ({
8071
+ id: idOf2(target),
8072
+ tags: listOf2(item, { minItems: 1 })
8073
+ })),
8074
+ response: fields(() => ({ [asOf(target, options)]: target.entity })),
8075
+ ...contextOf2(owner),
8076
+ dependencies: joinDependencies(target, { repository: target.repository }, hooks),
8077
+ errors: distinct(notFound, ...hooks.throws),
8078
+ steps: [
8079
+ ...loadSteps(owner, notFound),
8080
+ ...hooks.before("$item"),
8081
+ { invoke: "$item", method, args: `${hooks.input}.tags` },
8082
+ { update: "repository", value: "$item", as: "saved" },
8083
+ ...hooks.after("$saved"),
8084
+ { return: { [asOf(target, options)]: "$saved" } }
8085
+ ]
8086
+ };
8087
+ }
8088
+ var tag = useCasePluginOf2("tag", {
8089
+ description: "Adds tags to the taggable entity of an id and stores it.",
8090
+ ...lifecycle("taggable"),
8091
+ compile: (target, options) => tagging(target, options, "Tag", "tag")
8092
+ });
8093
+ var untag = useCasePluginOf2("untag", {
8094
+ description: "Removes tags from the taggable entity of an id and stores it.",
8095
+ ...lifecycle("taggable"),
8096
+ compile: (target, options) => tagging(target, options, "Untag", "untag")
8097
+ });
8098
+
8099
+ // src/std/use-cases/index.ts
8100
+ var StdUseCases2 = useCaseCatalogOf2(create2, get, list, update2, remove2, restore2, publish, unpublish, archive, unarchive, tag, untag, register, login);
8101
+
8102
+ // src/std/command.ts
8103
+ var commandOf2 = commandBuilderOf2(StdUseCases2);
5604
8104
  // src/std/record-plugins.ts
5605
8105
  var now = () => new Date().toISOString();
5606
8106
 
@@ -5682,10 +8182,13 @@ var entityOf2 = entityBuilderOf2({
5682
8182
  identity: Identity,
5683
8183
  generateId,
5684
8184
  now: () => new Date().toISOString(),
8185
+ expiresAt: ExpiresAt2,
5685
8186
  plugins: [StdRecordPlugins2]
5686
8187
  });
8188
+ // src/std/event-policies.ts
8189
+ var StdEventPolicies2 = eventPolicyCatalogOf2(publishEvents2);
5687
8190
  // src/std/record.ts
5688
8191
  var recordOf2 = recordBuilderOf2(StdRecordPlugins2);
5689
8192
  // src/std/stages.ts
5690
8193
  var StdStages2 = stageCatalogOf2(select2, openapi2);
5691
- export { InvalidEnvironment2, environmentOf2, isEnvironment2, guard2, InvalidApplicationDeclaration2, DuplicateApplicationDeclaration2, ApplicationConflict2, UnregisteredApplicationReference2, UnregisteredRepositoryDependency2, UnregisteredUseCaseDependency2, ApplicationNotStarted2, ProviderNotBound2, ApplicationAlreadyStarted2, InvalidApplicationEnvironment2, useApplication2, resetApplication2, InvalidLogDeclaration2, LogLevels2, logProviderOf2, consoleLogProvider2, jsonLogProvider2, memoryLogProvider2, Logger2, ReservedEntityKey2, ImmutableEntityKey2, DeletedEntity2, entityBuilderOf2, isEntityClass2, isEntity2, eventOf2, ENTITY_IDENTITY_KEYS2, RESERVED_ENTITY_KEYS2, EntityUniqueKeys2, AbstractEntity2, InvalidRepositoryQuery2, InvalidRepositoryCursor2, InvalidRepositoryDeclaration2, InvalidRepositoryEntity2, InvalidRepositoryData2, RepositoryUniqueViolation2, RepositoryEntityNotFound2, providerOf2, providerDescriptionOf2, inMemoryProvider2, AbstractRepository2, repositoryOf2, RepositoryOf2, isRepository2, InvalidUseCaseStep2, InvalidUseCaseDeclaration2, UseCaseAlreadyExecuted2, InvalidUseCaseDependencies2, AbstractUseCase2, useCaseOf2, isUseCaseClass2, isUseCase2, InvalidCommandDeclaration2, UnknownCommandOperation2, InvalidCommandContext2, InvalidCommandEnvironment2, CommandEventFailed2, commandOf2, isCommand2, InvalidOpenAPIModel2, InvalidOutputStage2, DuplicateOperation2, toOpenAPI2, transformOf2, sinkOf2, stageDescriptionOf2, stageKindOf2, stageCatalogOf2, operationOf2, outputOf2, isOutput2, providerFrom2, logFrom2, isLogFactory2, isProviderFactory2, providersOf2, isProviderSlot2, isProviderStorage2, isApplication2, applicationOf2, blueprintPluginOf2, statusOf2, PROBLEM_MEDIA_TYPE2, problemOf2, outputValueOf2, routeOptionsIssue2, operationRoutes2, openapi2, select2, recordPluginCatalogOf2, Email2, Phone2, Url2, Ip2, enumOf2, SchemaProperty2, PasswordHasherRequired2, SyncVerifyUnsupported2, configure2, resetRuntime2, Id2, Slug2, PASSWORD_MIN_LENGTH2, PASSWORD_MAX_LENGTH2, PASSWORD_SPECIAL_CHARACTERS2, isPasswordHash2, passwordIssues2, Password2, StringProperty2, Text2, NumberProperty2, Integer2, BooleanProperty2, DateTime2, DateProperty2, CreatedAt2, UpdatedAt2, DeletedAt2, Std2, blueprintOf2, Publishable2, Archivable2, Taggable2, Ownable2, Authenticatable2, StdRecordPlugins2, entityOf2, recordOf2, StdStages2 };
8194
+ export { InvalidEnvironment2, environmentOf2, isEnvironment2, InvalidApplicationDeclaration2, DuplicateApplicationDeclaration2, ApplicationConflict2, UnregisteredApplicationReference2, UnregisteredRepositoryDependency2, UnregisteredUseCaseDependency2, ApplicationNotStarted2, ProviderNotBound2, ApplicationAlreadyStarted2, InvalidApplicationEnvironment2, useApplication2, resetApplication2, InvalidEventPolicy2, PublisherNotBound2, guard2, InvalidLogDeclaration2, LogLevels2, logProviderOf2, consoleLogProvider2, jsonLogProvider2, memoryLogProvider2, Logger2, ReservedEntityKey2, ImmutableEntityKey2, DeletedEntity2, InvalidEntityExpiry2, EntityVersion2, entityBuilderOf2, isEntityClass2, isEntity2, eventOf2, ENTITY_IDENTITY_KEYS2, RESERVED_ENTITY_KEYS2, EntityUniqueKeys2, EntitySettings2, AbstractEntity2, InvalidRepositoryQuery2, InvalidRepositoryCursor2, InvalidRepositoryDeclaration2, InvalidRepositoryEntity2, InvalidRepositoryData2, RepositoryUniqueViolation2, RepositoryUsedOutsideApplication2, RepositoryEntityNotFound2, RepositoryConcurrencyConflict2, InvalidRepositoryIncrement2, providerOf2, providerDescriptionOf2, inMemoryProvider2, AbstractRepository2, repositoryOf2, RepositoryOf2, isRepository2, InvalidUseCaseStep2, InvalidUseCasePlugin2, UnknownUseCasePlugin2, IncompatibleUseCasePlugin2, UseCaseUnavailable2, UseCaseEntityNotFound2, UseCaseEntityConflict2, UseCaseInvalidCredentials2, PropertyInput2, propertyInputOf2, useCaseTargetOf2, InvalidUseCaseDeclaration2, UseCaseAlreadyExecuted2, InvalidUseCaseDependencies2, AbstractUseCase2, useCaseOf2, isUseCaseClass2, isUseCase2, isUseCasePlugin2, useCaseOriginOf2, useCasePluginOf2, isUseCaseCatalog2, useCaseCatalogOf2, InvalidCommandDeclaration2, UnknownCommandOperation2, InvalidCommandContext2, InvalidCommandEnvironment2, CommandEventFailed2, commandBuilderOf2, isCommand2, eventPolicyOf2, eventPolicyDescriptionOf2, eventPolicyCatalogOf2, InvalidOpenAPIModel2, InvalidOutputStage2, DuplicateOperation2, toOpenAPI2, transformOf2, sinkOf2, stageDescriptionOf2, stageKindOf2, stageCatalogOf2, operationOf2, outputOf2, isOutput2, providerFrom2, logFrom2, isLogFactory2, isProviderFactory2, providersOf2, isProviderSlot2, isProviderStorage2, isApplication2, applicationOf2, blueprintPluginOf2, publishEvents2, coerceTextValues2, statusOf2, PROBLEM_MEDIA_TYPE2, problemOf2, outputValueOf2, ROUTE_OPTION_KEYS2, routeOptionsIssue2, requestIdHeaderOf2, operationRoutes2, openapi2, select2, stripUndeclared2, recordPluginCatalogOf2, Email2, Phone2, Url2, Ip2, enumOf2, SchemaProperty2, PasswordHasherRequired2, SyncVerifyUnsupported2, configure2, resetRuntime2, Id2, Slug2, InvalidJsonData2, InvalidJsonSchema2, jsonSchemaIssues2, checkJsonSchema2, JsonProperty2, jsonOf2, PASSWORD_MIN_LENGTH2, PASSWORD_MAX_LENGTH2, PASSWORD_SPECIAL_CHARACTERS2, isPasswordHash2, passwordIssues2, PlainPassword2, Password2, StringProperty2, Text2, NumberProperty2, Integer2, BooleanProperty2, DateTime2, DateProperty2, CreatedAt2, UpdatedAt2, DeletedAt2, ExpiresAt2, Std2, blueprintOf2, StdUseCases2, commandOf2, Publishable2, Archivable2, Taggable2, Ownable2, Authenticatable2, StdRecordPlugins2, entityOf2, StdEventPolicies2, recordOf2, StdStages2 };