@afora/core 0.0.9 → 0.0.11

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 (186) hide show
  1. package/README.md +52 -30
  2. package/dist/adapters.js +31 -55
  3. package/dist/core/application/application.d.ts +30 -0
  4. package/dist/core/application/application.js +1291 -0
  5. package/dist/core/application/exceptions.js +85 -0
  6. package/dist/core/application/migrate.js +67 -0
  7. package/dist/core/application/providers.js +108 -0
  8. package/dist/core/application/registry.js +23 -0
  9. package/dist/core/application/schema.js +398 -0
  10. package/dist/core/application/start.js +162 -0
  11. package/dist/core/application/types/application.interface.d.ts +23 -3
  12. package/dist/core/auth/jwt.js +200 -0
  13. package/dist/core/auth/types/jwt.interface.d.ts +10 -2
  14. package/dist/core/blueprint/blueprint-plugin.js +7 -0
  15. package/dist/core/blueprint/blueprint.js +146 -0
  16. package/dist/core/blueprint/callable.js +13 -0
  17. package/dist/core/blueprint/exceptions.js +20 -0
  18. package/dist/core/blueprint/is-blueprint.js +4 -0
  19. package/dist/core/blueprint/list.js +19 -0
  20. package/dist/core/blueprint/optional.js +69 -0
  21. package/dist/core/blueprint/record-field.js +14 -0
  22. package/dist/core/blueprint/slice.js +127 -0
  23. package/dist/core/blueprint/types/blueprint.interface.d.ts +16 -0
  24. package/dist/core/blueprint/variant.js +72 -0
  25. package/dist/core/command/command.js +428 -0
  26. package/dist/core/command/exceptions.js +30 -0
  27. package/dist/core/command/types/command.interface.d.ts +10 -1
  28. package/dist/core/entity/abstract-entity.d.ts +19 -0
  29. package/dist/core/entity/abstract-entity.js +181 -0
  30. package/dist/core/entity/entity.js +209 -0
  31. package/dist/core/entity/event.d.ts +39 -0
  32. package/dist/core/entity/event.js +80 -0
  33. package/dist/core/entity/exceptions.js +27 -0
  34. package/dist/core/entity/trust.js +7 -0
  35. package/dist/core/entity/version.js +13 -0
  36. package/dist/core/environment/environment.d.ts +26 -3
  37. package/dist/core/environment/environment.js +71 -0
  38. package/dist/core/event-policy/event-policy.js +76 -0
  39. package/dist/core/event-policy/exceptions.js +12 -0
  40. package/dist/core/event-policy/publish.js +98 -0
  41. package/dist/core/event-policy/publishers.js +18 -0
  42. package/dist/core/event-policy/types/event-policy.interface.d.ts +8 -2
  43. package/dist/core/exception/enums/exception-layers.js +15 -0
  44. package/dist/core/exception/exception.d.ts +15 -0
  45. package/dist/core/exception/exception.js +128 -0
  46. package/dist/core/exception/exported.js +49 -0
  47. package/dist/core/exception/guard.d.ts +20 -1
  48. package/dist/core/exception/guard.js +16 -0
  49. package/dist/core/exception/types/exception.interface.d.ts +13 -0
  50. package/dist/core/json.js +19 -0
  51. package/dist/core/logger/exceptions.js +7 -0
  52. package/dist/core/logger/logger.d.ts +14 -1
  53. package/dist/core/logger/logger.js +178 -0
  54. package/dist/core/openapi/exceptions.js +7 -0
  55. package/dist/core/openapi/openapi.js +132 -0
  56. package/dist/core/openapi/types/openapi.interface.d.ts +7 -1
  57. package/dist/core/output/access.js +66 -0
  58. package/dist/core/output/coerce.js +38 -0
  59. package/dist/core/output/exceptions.js +22 -0
  60. package/dist/{index-y280ew0k.js → core/output/http.js} +35 -80
  61. package/dist/core/output/openapi-sink.d.ts +10 -2
  62. package/dist/{index-hg1z2s2v.js → core/output/openapi-sink.js} +60 -544
  63. package/dist/core/output/output.js +192 -0
  64. package/dist/core/output/problem.js +30 -0
  65. package/dist/core/output/select.js +28 -0
  66. package/dist/core/output/stage.js +111 -0
  67. package/dist/core/output/status.js +52 -0
  68. package/dist/core/output/types/http.interface.d.ts +5 -1
  69. package/dist/core/output/types/output.interface.d.ts +24 -0
  70. package/dist/core/parse/issue.js +31 -0
  71. package/dist/core/parse/parse.d.ts +8 -1
  72. package/dist/core/parse/parse.js +323 -0
  73. package/dist/core/parse/strip.js +48 -0
  74. package/dist/core/property/abstract-property.js +37 -0
  75. package/dist/core/property/define-property.js +209 -0
  76. package/dist/core/property/enums/property-states.js +8 -0
  77. package/dist/core/property/exceptions.js +12 -0
  78. package/dist/core/property/input.js +10 -0
  79. package/dist/core/property/is-property.js +3 -0
  80. package/dist/core/property/lineage.js +8 -0
  81. package/dist/core/property/property.d.ts +27 -0
  82. package/dist/core/property/property.js +46 -0
  83. package/dist/core/property/refine.js +103 -0
  84. package/dist/core/record/abstract-record.d.ts +2 -1
  85. package/dist/core/record/abstract-record.js +307 -0
  86. package/dist/core/record/exceptions.js +49 -0
  87. package/dist/core/record/marks.js +7 -0
  88. package/dist/core/record/mask.js +60 -0
  89. package/dist/core/record/name.js +23 -0
  90. package/dist/core/record/plugin-catalog.js +115 -0
  91. package/dist/core/record/plugin.js +198 -0
  92. package/dist/core/record/record.js +117 -0
  93. package/dist/core/record/types/record.interface.d.ts +5 -0
  94. package/dist/core/repository/cursor.js +83 -0
  95. package/dist/core/repository/exceptions.js +72 -0
  96. package/dist/core/repository/in-memory.js +196 -0
  97. package/dist/core/repository/migrate.js +254 -0
  98. package/dist/core/repository/plan.js +27 -0
  99. package/dist/core/repository/provider.js +29 -0
  100. package/dist/core/repository/query.js +153 -0
  101. package/dist/core/repository/references.js +196 -0
  102. package/dist/core/repository/repository.d.ts +22 -2
  103. package/dist/core/repository/repository.js +720 -0
  104. package/dist/core/repository/state.js +138 -0
  105. package/dist/core/repository/types/query.interface.d.ts +13 -0
  106. package/dist/core/repository/types/repository.interface.d.ts +20 -8
  107. package/dist/core/repository/types/schema.interface.d.ts +8 -2
  108. package/dist/core/repository/upcast.js +179 -0
  109. package/dist/core/schema/schema.d.ts +9 -0
  110. package/dist/core/schema/schema.js +542 -0
  111. package/dist/core/standard/standard-schema.js +1 -0
  112. package/dist/core/standard/standard.js +23 -0
  113. package/dist/core/symbols/demo.d.ts +4 -0
  114. package/dist/core/symbols/demo.js +1 -0
  115. package/dist/core/symbols/kind.js +1 -0
  116. package/dist/core/symbols/meta.js +1 -0
  117. package/dist/core/symbols/state.d.ts +5 -0
  118. package/dist/core/symbols/state.js +1 -0
  119. package/dist/core/tracing/exceptions.d.ts +4 -0
  120. package/dist/core/tracing/exceptions.js +7 -0
  121. package/dist/core/tracing/tracing.d.ts +72 -0
  122. package/dist/core/tracing/tracing.js +101 -0
  123. package/dist/core/use-case/access.js +56 -0
  124. package/dist/core/use-case/decorators.js +65 -0
  125. package/dist/core/use-case/exceptions.d.ts +5 -1
  126. package/dist/core/use-case/exceptions.js +48 -0
  127. package/dist/core/use-case/plugin-catalog.js +68 -0
  128. package/dist/core/use-case/plugin.js +133 -0
  129. package/dist/core/use-case/route.js +200 -0
  130. package/dist/core/use-case/scope.d.ts +5 -0
  131. package/dist/core/use-case/scope.js +4 -0
  132. package/dist/core/use-case/steps/describe.d.ts +5 -1
  133. package/dist/core/use-case/steps/describe.js +24 -0
  134. package/dist/core/use-case/steps/references.js +105 -0
  135. package/dist/core/use-case/steps/run.js +139 -0
  136. package/dist/core/use-case/steps/types/check.interface.d.ts +27 -8
  137. package/dist/core/use-case/steps/types/steps.interface.d.ts +8 -1
  138. package/dist/core/use-case/steps/validate.js +368 -0
  139. package/dist/core/use-case/target.js +100 -0
  140. package/dist/core/use-case/types/route.interface.d.ts +25 -5
  141. package/dist/core/use-case/use-case.d.ts +77 -2
  142. package/dist/core/use-case/use-case.js +493 -0
  143. package/dist/experimental.d.ts +5 -0
  144. package/dist/experimental.js +18 -161
  145. package/dist/index.d.ts +7 -5
  146. package/dist/index.js +213 -367
  147. package/dist/std/blueprint-plugin.js +42 -0
  148. package/dist/std/blueprint.d.ts +21 -0
  149. package/dist/std/blueprint.js +3 -0
  150. package/dist/std/command.js +3 -0
  151. package/dist/std/entity.d.ts +20 -1
  152. package/dist/std/entity.js +30 -0
  153. package/dist/std/event-policies.js +3 -0
  154. package/dist/std/properties/contact.d.ts +3 -0
  155. package/dist/std/properties/contact.js +29 -0
  156. package/dist/std/properties/enum.js +26 -0
  157. package/dist/std/properties/exceptions.js +14 -0
  158. package/dist/std/properties/headless.js +45 -0
  159. package/dist/std/properties/identity.js +28 -0
  160. package/dist/std/properties/json.js +161 -0
  161. package/dist/std/properties/password.js +79 -0
  162. package/dist/std/properties/primitives.d.ts +4 -0
  163. package/dist/std/properties/primitives.js +28 -0
  164. package/dist/std/properties/time.js +55 -0
  165. package/dist/std/record-plugins.js +73 -0
  166. package/dist/std/record.d.ts +20 -1
  167. package/dist/std/record.js +3 -0
  168. package/dist/std/resource.js +39 -0
  169. package/dist/std/runtime.js +193 -0
  170. package/dist/std/stages.js +4 -0
  171. package/dist/std/use-cases/auth.js +193 -0
  172. package/dist/std/use-cases/crud.js +261 -0
  173. package/dist/std/use-cases/index.js +12 -0
  174. package/dist/std/use-cases/lifecycle.js +97 -0
  175. package/dist/std/use-cases/shared.js +232 -0
  176. package/dist/std/use-cases/types.d.ts +5 -1
  177. package/dist/std/use-cases/types.js +0 -0
  178. package/dist/testing/http-provider-contract.js +344 -0
  179. package/dist/testing/index.js +3 -1435
  180. package/dist/testing/memory-publisher.js +12 -0
  181. package/dist/testing/provider-contract.d.ts +12 -3
  182. package/dist/testing/provider-contract.js +1071 -0
  183. package/package.json +10 -3
  184. package/dist/index-3aae5jfq.js +0 -2997
  185. package/dist/index-ny3as79r.js +0 -4567
  186. package/dist/index-xegv6a1p.js +0 -4291
@@ -0,0 +1,138 @@
1
+ import {
2
+ EntitySettings,
3
+ EntityUniqueKeys,
4
+ EXPIRES_AT,
5
+ SCHEMA_VERSION_KEY
6
+ } from "../entity/abstract-entity.js";
7
+ import {
8
+ loadEnvironments
9
+ } from "../environment/environment.js";
10
+ import { RepositoryUsedOutsideApplication } from "./exceptions.js";
11
+ export const states = new WeakMap;
12
+ export function findState(repository) {
13
+ if (typeof repository !== "object" || repository === null)
14
+ return;
15
+ for (let Class = repository.constructor;typeof Class === "function"; Class = Object.getPrototypeOf(Class)) {
16
+ const state = states.get(Class);
17
+ if (state)
18
+ return state;
19
+ }
20
+ return;
21
+ }
22
+ export function stateOf(repository) {
23
+ const state = findState(repository);
24
+ if (!state)
25
+ throw new TypeError("Expected a repository made by repositoryOf");
26
+ return state;
27
+ }
28
+ function expiryKeyOf(entity) {
29
+ const expires = entity[EntitySettings]?.expires;
30
+ if (expires === undefined)
31
+ return;
32
+ return "at" in expires ? expires.at : EXPIRES_AT;
33
+ }
34
+
35
+ export { SCHEMA_VERSION_KEY };
36
+ function schemaOf(entity) {
37
+ const settings = entity[EntitySettings];
38
+ if (!settings?.schemaVersioned)
39
+ return;
40
+ return Object.freeze({
41
+ version: settings.schemaVersion,
42
+ key: SCHEMA_VERSION_KEY,
43
+ previousNames: settings.previousNames
44
+ });
45
+ }
46
+ export function contextOf(state) {
47
+ if (state.context)
48
+ return state.context;
49
+ const key = expiryKeyOf(state.entity);
50
+ const schema = schemaOf(state.entity);
51
+ state.context = Object.freeze({
52
+ entity: state.entity,
53
+ name: state.name,
54
+ unique: state.entity[EntityUniqueKeys],
55
+ indexes: state.indexes,
56
+ ...key === undefined ? {} : { expiry: Object.freeze({ key }) },
57
+ ...schema === undefined ? {} : { schema },
58
+ env: loadEnvironments(state.environment)
59
+ });
60
+ return state.context;
61
+ }
62
+ export function inspectRepository(repository) {
63
+ const state = findState(repository);
64
+ if (!state)
65
+ return;
66
+ const { environment } = state;
67
+ return {
68
+ entity: state.entity,
69
+ actions: state.actions,
70
+ deletion: state.deletion,
71
+ indexes: state.indexes,
72
+ scans: state.scans,
73
+ explicitProvider: state.explicitProvider,
74
+ provider: state.explicitProvider ? state.provider : undefined,
75
+ environments: environment === undefined ? [] : Array.isArray(environment) ? environment : [environment],
76
+ relations: [...state.relations.values()].map(declarationOf),
77
+ state: declarationOf(state)
78
+ };
79
+ }
80
+ const origins = new WeakMap;
81
+ function declarationOf(state) {
82
+ return origins.get(state) ?? state;
83
+ }
84
+ const declared = new WeakMap;
85
+ export function declareRepository(state, repository) {
86
+ declared.set(state, repository);
87
+ }
88
+ export function relatedRepositoryOf(repository, Entity) {
89
+ const related = findState(repository)?.relations.get(Entity);
90
+ return related ? declared.get(declarationOf(related)) : undefined;
91
+ }
92
+ function bindState(state, provider, instances, logger, bound = new Map) {
93
+ const done = bound.get(state);
94
+ if (done)
95
+ return done;
96
+ const copy = {
97
+ ...state,
98
+ logger,
99
+ provider: state.explicitProvider ? instances?.get(state.provider) ?? state.provider : provider ?? state.provider,
100
+ context: undefined,
101
+ relations: new Map([...state.relations].map(([target, related]) => [
102
+ target,
103
+ bindState(related, provider, instances, logger, bound)
104
+ ]))
105
+ };
106
+ origins.set(copy, declarationOf(state));
107
+ bound.set(state, copy);
108
+ return copy;
109
+ }
110
+ const owners = new WeakMap;
111
+ export function assertUnbound(state) {
112
+ const owner = owners.get(state);
113
+ if (owner) {
114
+ throw new RepositoryUsedOutsideApplication({
115
+ source: state.name,
116
+ details: owner
117
+ });
118
+ }
119
+ }
120
+ export function bindRepository(repository, provider, instances, binding) {
121
+ const state = stateOf(repository);
122
+ const Declared = repository.constructor;
123
+
124
+ class Bound extends Declared {
125
+ }
126
+ Object.defineProperty(Bound, "name", { value: Declared.name });
127
+ states.set(Bound, bindState(state, provider, instances, binding?.logger));
128
+ if (binding) {
129
+ owners.set(declarationOf(state), {
130
+ application: binding.application,
131
+ repository: binding.key
132
+ });
133
+ }
134
+ return Object.freeze(new Bound);
135
+ }
136
+ export function providerOfRepository(repository) {
137
+ return findState(repository)?.provider;
138
+ }
@@ -98,6 +98,7 @@ export interface RepositoryPlan {
98
98
  * throw `InvalidRepositoryQuery` before the provider is called (`"reject"`).
99
99
  */
100
100
  export type RepositoryScans = "allow" | "warn" | "reject";
101
+ /** The `explain` method of a repository, for the actions that list it. */
101
102
  export interface RepositoryExplain<Entity> {
102
103
  /**
103
104
  * How the query would be read (`{ access, index? }`), without reading
@@ -105,6 +106,7 @@ export interface RepositoryExplain<Entity> {
105
106
  */
106
107
  explain(query?: RepositoryQuery<Entity> | RepositoryCursorQuery<Entity>): Promise<RepositoryPlan>;
107
108
  }
109
+ /** The `findCursor` method of a repository, for the actions that list it. */
108
110
  export interface RepositoryFindCursor<Entity> {
109
111
  /**
110
112
  * A page of `limit` items (20 by default) after the cursor `after`. Unlike
@@ -145,6 +147,7 @@ export interface RepositoryWalkResult {
145
147
  /** The items of those pages. */
146
148
  readonly items: number;
147
149
  }
150
+ /** The `forEachPage` method of a repository, for the actions that list it. */
148
151
  export interface RepositoryForEachPage<Entity> {
149
152
  /**
150
153
  * Reads the pages of `query` by cursor, as `findCursor` does, and runs
@@ -155,12 +158,21 @@ export interface RepositoryForEachPage<Entity> {
155
158
  */
156
159
  forEachPage(query: RepositoryCursorQuery<Entity>, fn: (items: EntityInstanceOf<Entity>[], page: RepositoryWalkPage) => unknown, options?: RepositoryWalkOptions): Promise<RepositoryWalkResult>;
157
160
  }
161
+ /**
162
+ * The `findMany` method of a repository: the entities that match a query, all
163
+ * of them without one.
164
+ */
158
165
  export interface RepositoryFindMany<Entity> {
159
166
  findMany(query?: RepositoryQuery<Entity>): Promise<EntityInstanceOf<Entity>[]>;
160
167
  }
168
+ /** The `exists` method of a repository: whether any entity matches a query. */
161
169
  export interface RepositoryExists<Entity> {
162
170
  exists(query?: RepositoryQuery<Entity>): Promise<boolean>;
163
171
  }
172
+ /**
173
+ * The `findPage` method of a repository: one page of the entities that match a
174
+ * query, with its total.
175
+ */
164
176
  export interface RepositoryFindPage<Entity> {
165
177
  /** Defaults to limit 20 and offset 0. */
166
178
  findPage(query?: RepositoryQuery<Entity>): Promise<RepositoryPage<Entity>>;
@@ -169,6 +181,7 @@ export interface RepositoryFindPage<Entity> {
169
181
  export type ProviderOption = null | boolean | number | string | readonly ProviderOption[] | {
170
182
  readonly [key: string]: ProviderOption;
171
183
  };
184
+ /** The JSON options of a named provider, as its description keeps them. */
172
185
  export type ProviderOptions = Readonly<Record<string, ProviderOption>>;
173
186
  /** A named provider can be reconstructed; opaque providers require an import mapping. */
174
187
  export type ProviderDescription = {
@@ -31,11 +31,20 @@ type ValueAt<Entity, Key extends string> = Key extends keyof EntityValueOf<Entit
31
31
  export type QueryValueOf<Value> = Value extends readonly (infer Item)[] ? Item extends AbstractEntity<any> ? readonly (Item | string)[] : Value : Value extends AbstractEntity<any> ? Value | string : Value;
32
32
  /** What `create` takes: an entity, or the input that builds one. */
33
33
  export type RepositoryCreateInput<Entity> = EntityInstanceOf<Entity> | (Entity extends new (value: infer Input) => unknown ? Input : never);
34
- /** `create`: stores the entity, or a new one built from the input, and gives it. */
34
+ /**
35
+ * `create`: stores the entity, or a new one built from the input, and gives it.
36
+ *
37
+ * @throws {@link RepositoryUniqueViolation} when another entity holds the same value of a unique key (`id` included).
38
+ */
35
39
  export interface RepositoryCreate<Entity> {
36
40
  create(entity: RepositoryCreateInput<Entity>): Promise<EntityInstanceOf<Entity>>;
37
41
  }
38
- /** `update`: stores the current value of an entity it holds, and gives it. */
42
+ /**
43
+ * `update`: stores the current value of an entity it holds, and gives it.
44
+ *
45
+ * @throws {@link RepositoryUniqueViolation} when another entity holds the same value of a unique key.
46
+ * @throws {@link RepositoryEntityNotFound} when the repository holds no entity with its id.
47
+ */
39
48
  export interface RepositoryUpdate<Entity> {
40
49
  update(entity: EntityInstanceOf<Entity>): Promise<EntityInstanceOf<Entity>>;
41
50
  }
@@ -197,12 +206,6 @@ export interface RepositoryContext<Environment = unknown> {
197
206
  /** The variables of the `environmentDependencies` of the repository. */
198
207
  readonly env: Environment;
199
208
  }
200
- /**
201
- * How a repository reads and writes: plain values of its entity, by its
202
- * context. `insert` and `update` enforce `context.unique`, throwing
203
- * `RepositoryUniqueViolation`; `update` and `remove` throw
204
- * `RepositoryEntityNotFound` for an unknown id.
205
- */
206
209
  /**
207
210
  * What `IRepositoryProvider.migrate` gives when it did not apply the plan to
208
211
  * the store (it wrote it to a file to be run later): `app.migrate` skips the
@@ -211,6 +214,15 @@ export interface RepositoryContext<Environment = unknown> {
211
214
  export type ProviderMigrationResult = {
212
215
  readonly deferred: true;
213
216
  };
217
+ /**
218
+ * How a repository reads and writes: plain values of its entity, by its
219
+ * context. Implement it to store entities somewhere else; `inMemoryProvider`
220
+ * is the default, and `providerContract` (from `@afora/core/testing`) checks
221
+ * an implementation.
222
+ *
223
+ * @throws {@link RepositoryUniqueViolation} from `insert` and `update`, when they would break `context.unique`.
224
+ * @throws {@link RepositoryEntityNotFound} from `update` and `remove`, for an unknown id.
225
+ */
214
226
  export interface IRepositoryProvider<Environment = unknown> {
215
227
  findOne(context: RepositoryContext<Environment>, query?: ProviderQuery): Promise<RepositoryData | undefined>;
216
228
  /** `query.after` is only given to a provider with `capabilities.keyset`. */
@@ -73,10 +73,16 @@ export interface SchemaMigrationOptions {
73
73
  readonly mode: "check" | "apply";
74
74
  /**
75
75
  * Remove the keys the blueprints no longer declare from the stored rows,
76
- * and the rows under `previousNames` once moved. Default `false`.
76
+ * and the rows under `previousNames` once moved.
77
+ *
78
+ * @default false
77
79
  */
78
80
  readonly destructive?: boolean;
79
- /** Rows read per page. Default 100. */
81
+ /**
82
+ * Rows read per page.
83
+ *
84
+ * @default 100
85
+ */
80
86
  readonly pageSize?: number;
81
87
  /**
82
88
  * The committed lock (`afora.lock.json`): in `apply`, the changes of each
@@ -0,0 +1,179 @@
1
+ import { fieldOf } from "../blueprint/callable.js";
2
+ import { isBlueprint } from "../blueprint/is-blueprint.js";
3
+ import { isBlueprintList } from "../blueprint/list.js";
4
+ import { recordFieldOf } from "../blueprint/record-field.js";
5
+ import { EntitySettings, SCHEMA_VERSION_KEY } from "../entity/abstract-entity.js";
6
+ import { isEntityClass } from "../entity/entity.js";
7
+ import { isProperty } from "../property/is-property.js";
8
+ import {
9
+ InvalidRepositoryData,
10
+ InvalidStoredSchemaVersion
11
+ } from "./exceptions.js";
12
+ const isPlain = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
13
+ function stepOf(field) {
14
+ if (field === undefined)
15
+ return { kind: "opaque" };
16
+ if (isBlueprintList(field))
17
+ return { kind: "list", item: stepOf(field.item) };
18
+ if (isBlueprint(field)) {
19
+ return { kind: "collection", fields: stepsOf(field.properties) };
20
+ }
21
+ const record = recordFieldOf(field);
22
+ if (record) {
23
+ if (isEntityClass(record.record))
24
+ return { kind: "opaque" };
25
+ return {
26
+ kind: "collection",
27
+ fields: stepsOf(record.record.blueprint.properties)
28
+ };
29
+ }
30
+ const property = fieldOf(field);
31
+ if (!isProperty(property))
32
+ return { kind: "opaque" };
33
+ const { fill, renamedFrom } = property.type;
34
+ return {
35
+ kind: "property",
36
+ ...fill === undefined ? {} : { fill },
37
+ ...renamedFrom === undefined ? {} : { renamedFrom }
38
+ };
39
+ }
40
+ function stepsOf(properties) {
41
+ return {
42
+ fields: new Map(Object.entries(properties).map(([key, field]) => [key, stepOf(field)]))
43
+ };
44
+ }
45
+ function declaresSteps(steps) {
46
+ const visit = (step) => {
47
+ switch (step.kind) {
48
+ case "property":
49
+ return step.fill !== undefined || step.renamedFrom !== undefined;
50
+ case "collection":
51
+ return declaresSteps(step.fields);
52
+ case "list":
53
+ return visit(step.item);
54
+ case "opaque":
55
+ return false;
56
+ }
57
+ };
58
+ for (const step of steps.fields.values())
59
+ if (visit(step))
60
+ return true;
61
+ return false;
62
+ }
63
+ const plans = new WeakMap;
64
+ function upcastPlanOf(entity) {
65
+ const cached = plans.get(entity);
66
+ if (cached !== undefined)
67
+ return cached ?? undefined;
68
+ const plan = fullUpcastPlanOf(entity);
69
+ const needed = plan.versioned || plan.renamed || declaresSteps(plan.fields) ? plan : null;
70
+ plans.set(entity, needed);
71
+ return needed ?? undefined;
72
+ }
73
+ export function fullUpcastPlanOf(entity) {
74
+ const settings = entity[EntitySettings];
75
+ return {
76
+ versioned: settings?.schemaVersioned === true,
77
+ version: settings?.schemaVersion ?? 1,
78
+ renamed: (settings?.previousNames.length ?? 0) > 0,
79
+ upgrade: settings?.upgrade ?? {},
80
+ fields: stepsOf(entity.blueprint.properties)
81
+ };
82
+ }
83
+ const join = (path, key) => path ? `${path}.${key}` : key;
84
+ const fillOf = (value) => typeof value === "object" && value !== null ? structuredClone(value) : value;
85
+ function applySteps(steps, value, drop, path, report) {
86
+ const result = { ...value };
87
+ for (const [key, step] of steps.fields) {
88
+ if (step.kind !== "property" || step.renamedFrom === undefined)
89
+ continue;
90
+ const from = step.renamedFrom;
91
+ if (result[key] === undefined && Object.hasOwn(result, from)) {
92
+ result[key] = result[from];
93
+ delete result[from];
94
+ report?.renamed.push(join(path, key));
95
+ }
96
+ }
97
+ for (const [key, step] of steps.fields) {
98
+ const current = result[key];
99
+ if (current === undefined || current === null) {
100
+ if (step.kind === "property" && step.fill !== undefined && current === undefined) {
101
+ result[key] = fillOf(step.fill);
102
+ report?.filled.push(join(path, key));
103
+ }
104
+ continue;
105
+ }
106
+ result[key] = applyField(step, current, drop, join(path, key), report);
107
+ }
108
+ for (const key of Object.keys(result)) {
109
+ if (steps.fields.has(key))
110
+ continue;
111
+ if (result[key] !== undefined)
112
+ report?.removed.push(join(path, key));
113
+ if (drop)
114
+ delete result[key];
115
+ }
116
+ return result;
117
+ }
118
+ function applyField(step, value, drop, path, report) {
119
+ switch (step.kind) {
120
+ case "collection":
121
+ return isPlain(value) ? applySteps(step.fields, value, drop, path, report) : value;
122
+ case "list":
123
+ return Array.isArray(value) ? value.map((item) => item === undefined || item === null ? item : applyField(step.item, item, drop, `${path}[]`, report)) : value;
124
+ default:
125
+ return value;
126
+ }
127
+ }
128
+ function storedVersionOf(data) {
129
+ return Object.hasOwn(data, SCHEMA_VERSION_KEY) ? data[SCHEMA_VERSION_KEY] : 1;
130
+ }
131
+ export function upcast(source, plan, data, drop = true, report) {
132
+ const stored = storedVersionOf(data);
133
+ if (typeof stored !== "number" || !Number.isSafeInteger(stored) || stored < 1 || stored > plan.version) {
134
+ throw new InvalidStoredSchemaVersion({
135
+ source,
136
+ details: { id: String(data.id), stored, current: plan.version }
137
+ });
138
+ }
139
+ if (report)
140
+ report.from = stored;
141
+ let value = { ...data };
142
+ delete value[SCHEMA_VERSION_KEY];
143
+ for (let version = stored + 1;version <= plan.version; version++) {
144
+ const step = plan.upgrade[version];
145
+ if (step === undefined)
146
+ continue;
147
+ const next = step(Object.freeze(value));
148
+ if (!isPlain(next)) {
149
+ throw new InvalidStoredSchemaVersion({
150
+ source,
151
+ details: {
152
+ id: String(data.id),
153
+ stored,
154
+ current: plan.version,
155
+ reason: `The upgrade step ${version} did not give an object.`
156
+ }
157
+ });
158
+ }
159
+ value = { ...next };
160
+ delete value[SCHEMA_VERSION_KEY];
161
+ }
162
+ return applySteps(plan.fields, value, drop, "", report);
163
+ }
164
+ export function upcastRows(state, rows) {
165
+ const plan = upcastPlanOf(state.entity);
166
+ if (plan === undefined)
167
+ return rows;
168
+ return rows.map((row) => {
169
+ try {
170
+ return upcast(state.name, plan, row);
171
+ } catch (error) {
172
+ throw new InvalidRepositoryData({ source: state.name, cause: error });
173
+ }
174
+ });
175
+ }
176
+ export function withSchemaVersion(entity, data) {
177
+ const plan = upcastPlanOf(entity);
178
+ return plan?.versioned ? { ...data, [SCHEMA_VERSION_KEY]: plan.version } : data;
179
+ }
@@ -33,6 +33,15 @@ export interface SchemaChecks {
33
33
  * @internal
34
34
  */
35
35
  export declare function checksOfSchema(schema: ISchema<TSchema>): SchemaChecks;
36
+ /**
37
+ * The TypeBox schema of `schema`, as it was given, without compiling it:
38
+ * `schema.type.Type()` gives the same schema but compiles it first, which is
39
+ * most of the time spent at import when only the schema is read (the
40
+ * meta-schema of `ctx.schema` alone takes milliseconds). A schema not made
41
+ * by `schemaOf` is asked through `type`.
42
+ * @internal
43
+ */
44
+ export declare function rawTypeOf<SchemaType extends TSchema>(schema: ISchema<SchemaType>): SchemaType;
36
45
  /** A schema class declared with `schemaOf`. */
37
46
  export interface SchemaClass<SchemaType extends TSchema> {
38
47
  new (type?: SchemaType): ISchema<SchemaType>;