@fougere/core 0.5.0-alpha.0 → 0.6.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/dist/boot/Emissions.d.ts +2 -2
  2. package/dist/boot/Emissions.d.ts.map +1 -1
  3. package/dist/boot/Emissions.js +1 -1
  4. package/dist/boot/HandlerFacade.d.ts +1 -1
  5. package/dist/boot/HandlerFacade.d.ts.map +1 -1
  6. package/dist/boot/HandlerFacade.js +1 -1
  7. package/dist/boot/HandlerFacade.js.map +1 -1
  8. package/dist/boot/auth.d.ts +6 -6
  9. package/dist/boot/auth.d.ts.map +1 -1
  10. package/dist/boot/boot.d.ts +2 -2
  11. package/dist/boot/boot.d.ts.map +1 -1
  12. package/dist/boot/boot.js +1 -1
  13. package/dist/boot/boot.js.map +1 -1
  14. package/dist/boot/bootstrap.d.ts.map +1 -1
  15. package/dist/boot/bootstrap.js +47 -38
  16. package/dist/boot/bootstrap.js.map +1 -1
  17. package/dist/boot/card.d.ts +21 -0
  18. package/dist/boot/card.d.ts.map +1 -0
  19. package/dist/boot/card.js +100 -0
  20. package/dist/boot/card.js.map +1 -0
  21. package/dist/boot/frame.d.ts +3 -3
  22. package/dist/boot/frame.d.ts.map +1 -1
  23. package/dist/boot/frame.js +7 -7
  24. package/dist/boot/frame.js.map +1 -1
  25. package/dist/boot/hosted.d.ts +8 -2
  26. package/dist/boot/hosted.d.ts.map +1 -1
  27. package/dist/boot/hosted.js +10 -10
  28. package/dist/boot/hosted.js.map +1 -1
  29. package/dist/boot/ownership.d.ts +7 -5
  30. package/dist/boot/ownership.d.ts.map +1 -1
  31. package/dist/boot/ownership.js +9 -14
  32. package/dist/boot/ownership.js.map +1 -1
  33. package/dist/boot/ports.d.ts +2 -2
  34. package/dist/boot/ports.d.ts.map +1 -1
  35. package/dist/boot/ports.js +1 -1
  36. package/dist/boot/runner.d.ts +28 -0
  37. package/dist/boot/runner.d.ts.map +1 -0
  38. package/dist/boot/runner.js +26 -0
  39. package/dist/boot/runner.js.map +1 -0
  40. package/dist/boot/seed.d.ts +2 -2
  41. package/dist/boot/seed.d.ts.map +1 -1
  42. package/dist/boot/seed.js +5 -5
  43. package/dist/boot/seed.js.map +1 -1
  44. package/dist/boot/statement-drift.d.ts +2 -1
  45. package/dist/boot/statement-drift.d.ts.map +1 -1
  46. package/dist/boot/statement-drift.js.map +1 -1
  47. package/dist/boot/together.d.ts +4 -4
  48. package/dist/boot/together.d.ts.map +1 -1
  49. package/dist/boot/together.js +17 -19
  50. package/dist/boot/together.js.map +1 -1
  51. package/dist/boot/types.d.ts +12 -11
  52. package/dist/boot/types.d.ts.map +1 -1
  53. package/dist/contract/CallLog.d.ts +2 -2
  54. package/dist/contract/CallLog.d.ts.map +1 -1
  55. package/dist/contract/RouteAddress.d.ts +2 -0
  56. package/dist/contract/RouteAddress.d.ts.map +1 -1
  57. package/dist/contract/RouteAddress.js.map +1 -1
  58. package/dist/{scan/declare.d.ts → declare.d.ts} +3 -3
  59. package/dist/declare.d.ts.map +1 -0
  60. package/dist/{scan/declare.js → declare.js} +3 -3
  61. package/dist/declare.js.map +1 -0
  62. package/dist/{scan → descriptor}/Fronds.d.ts +6 -7
  63. package/dist/descriptor/Fronds.d.ts.map +1 -0
  64. package/dist/{scan → descriptor}/Fronds.js +6 -7
  65. package/dist/descriptor/Fronds.js.map +1 -0
  66. package/dist/{scan → descriptor}/frond.d.ts +7 -47
  67. package/dist/descriptor/frond.d.ts.map +1 -0
  68. package/dist/{scan → descriptor}/frond.js.map +1 -1
  69. package/dist/dispatch/ArgumentResolver.d.ts +5 -2
  70. package/dist/dispatch/ArgumentResolver.d.ts.map +1 -1
  71. package/dist/dispatch/ArgumentResolver.js +48 -3
  72. package/dist/dispatch/ArgumentResolver.js.map +1 -1
  73. package/dist/dispatch/DispatchEvent.d.ts +1 -1
  74. package/dist/dispatch/DispatchEvent.d.ts.map +1 -1
  75. package/dist/dispatch/PresenterArgumentResolver.d.ts +1 -1
  76. package/dist/dispatch/PresenterArgumentResolver.d.ts.map +1 -1
  77. package/dist/dispatch/PresenterArgumentResolver.js +1 -1
  78. package/dist/dispatch/Route.d.ts +1 -2
  79. package/dist/dispatch/Route.d.ts.map +1 -1
  80. package/dist/dispatch/StorageGuard.d.ts +2 -2
  81. package/dist/dispatch/StorageGuard.d.ts.map +1 -1
  82. package/dist/dispatch/StorageGuard.js +7 -7
  83. package/dist/dispatch/StorageGuard.js.map +1 -1
  84. package/dist/effective-operation.d.ts +29 -3
  85. package/dist/effective-operation.d.ts.map +1 -1
  86. package/dist/effective-operation.js +52 -1
  87. package/dist/effective-operation.js.map +1 -1
  88. package/dist/emit.d.ts +1 -1
  89. package/dist/frond-config.d.ts +2 -2
  90. package/dist/graph.d.ts +1 -1
  91. package/dist/graph.d.ts.map +1 -1
  92. package/dist/imports.d.ts +1 -1
  93. package/dist/imports.d.ts.map +1 -1
  94. package/dist/index.d.ts +15 -10
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +9 -6
  97. package/dist/index.js.map +1 -1
  98. package/dist/node.d.ts +2 -2
  99. package/dist/node.d.ts.map +1 -1
  100. package/dist/node.js +2 -2
  101. package/dist/node.js.map +1 -1
  102. package/dist/prefab/collector.d.ts +2 -2
  103. package/dist/prefab/collector.js +2 -2
  104. package/dist/prefab/crud.d.ts +5 -5
  105. package/dist/prefab/crud.d.ts.map +1 -1
  106. package/dist/prefab/crud.js +12 -12
  107. package/dist/prefab/crud.js.map +1 -1
  108. package/dist/prefab/mirror.d.ts +5 -5
  109. package/dist/prefab/mirror.d.ts.map +1 -1
  110. package/dist/prefab/mirror.js +5 -5
  111. package/dist/prefab/mirror.js.map +1 -1
  112. package/dist/prefab/presenter.d.ts +2 -2
  113. package/dist/prefab/presenter.js +2 -2
  114. package/dist/prefab/repository.d.ts +14 -14
  115. package/dist/prefab/repository.d.ts.map +1 -1
  116. package/dist/prefab/repository.js +19 -19
  117. package/dist/prefab/repository.js.map +1 -1
  118. package/dist/rows.d.ts +40 -0
  119. package/dist/rows.d.ts.map +1 -0
  120. package/dist/rows.js +146 -0
  121. package/dist/rows.js.map +1 -0
  122. package/dist/scan/conventions.d.ts +8 -12
  123. package/dist/scan/conventions.d.ts.map +1 -1
  124. package/dist/scan/conventions.js +0 -13
  125. package/dist/scan/conventions.js.map +1 -1
  126. package/dist/scan/emit.d.ts +1 -1
  127. package/dist/scan/emit.d.ts.map +1 -1
  128. package/dist/scan/emit.js +1 -1
  129. package/dist/scan/emit.js.map +1 -1
  130. package/dist/scan/handler-parser.d.ts +11 -56
  131. package/dist/scan/handler-parser.d.ts.map +1 -1
  132. package/dist/scan/handler-parser.js +2 -2
  133. package/dist/scan/handler-parser.js.map +1 -1
  134. package/dist/scan/result.d.ts +41 -0
  135. package/dist/scan/result.d.ts.map +1 -0
  136. package/dist/scan/result.js +2 -0
  137. package/dist/scan/result.js.map +1 -0
  138. package/dist/scan/scanner.d.ts +18 -1
  139. package/dist/scan/scanner.d.ts.map +1 -1
  140. package/dist/scan/scanner.js +34 -17
  141. package/dist/scan/scanner.js.map +1 -1
  142. package/dist/scan/statement.d.ts +1 -1
  143. package/dist/scan/statement.d.ts.map +1 -1
  144. package/dist/scan/statement.js.map +1 -1
  145. package/dist/{orm.d.ts → storage.d.ts} +98 -16
  146. package/dist/storage.d.ts.map +1 -0
  147. package/dist/{orm.js → storage.js} +53 -5
  148. package/dist/storage.js.map +1 -0
  149. package/dist/verify.d.ts +1 -1
  150. package/dist/verify.d.ts.map +1 -1
  151. package/dist/verify.js +2 -2
  152. package/dist/verify.js.map +1 -1
  153. package/dist/{boot → wire}/binding.d.ts +2 -10
  154. package/dist/wire/binding.d.ts.map +1 -0
  155. package/dist/{boot → wire}/binding.js +0 -52
  156. package/dist/wire/binding.js.map +1 -0
  157. package/dist/wire/call.d.ts +2 -40
  158. package/dist/wire/call.d.ts.map +1 -1
  159. package/dist/wire/call.js +0 -125
  160. package/dist/wire/call.js.map +1 -1
  161. package/dist/wire/operation.d.ts +5 -31
  162. package/dist/wire/operation.d.ts.map +1 -1
  163. package/dist/wire/operation.js +0 -51
  164. package/dist/wire/operation.js.map +1 -1
  165. package/dist/wire/signature.d.ts +58 -0
  166. package/dist/wire/signature.d.ts.map +1 -0
  167. package/dist/wire/signature.js +2 -0
  168. package/dist/wire/signature.js.map +1 -0
  169. package/package.json +3 -3
  170. package/src/boot/Emissions.ts +2 -2
  171. package/src/boot/HandlerFacade.ts +3 -3
  172. package/src/boot/auth.ts +6 -6
  173. package/src/boot/boot.ts +3 -3
  174. package/src/boot/bootstrap.ts +50 -39
  175. package/src/boot/card.ts +113 -0
  176. package/src/boot/frame.ts +7 -7
  177. package/src/boot/hosted.ts +11 -14
  178. package/src/boot/ownership.ts +14 -15
  179. package/src/boot/ports.ts +2 -2
  180. package/src/boot/runner.ts +34 -0
  181. package/src/boot/seed.ts +6 -6
  182. package/src/boot/statement-drift.ts +5 -4
  183. package/src/boot/together.ts +24 -25
  184. package/src/boot/types.ts +12 -11
  185. package/src/contract/CallLog.ts +2 -2
  186. package/src/contract/RouteAddress.ts +3 -0
  187. package/src/{scan/declare.ts → declare.ts} +5 -5
  188. package/src/{scan → descriptor}/Fronds.ts +6 -7
  189. package/src/{scan → descriptor}/frond.ts +7 -48
  190. package/src/dispatch/ArgumentResolver.ts +53 -7
  191. package/src/dispatch/DispatchEvent.ts +1 -1
  192. package/src/dispatch/PresenterArgumentResolver.ts +2 -2
  193. package/src/dispatch/Route.ts +1 -3
  194. package/src/dispatch/StorageGuard.ts +7 -7
  195. package/src/effective-operation.ts +63 -11
  196. package/src/emit.ts +1 -1
  197. package/src/frond-config.ts +2 -2
  198. package/src/graph.ts +1 -1
  199. package/src/imports.ts +1 -1
  200. package/src/index.ts +15 -13
  201. package/src/node.ts +2 -2
  202. package/src/prefab/collector.ts +2 -2
  203. package/src/prefab/crud.ts +18 -18
  204. package/src/prefab/mirror.ts +8 -8
  205. package/src/prefab/presenter.ts +2 -2
  206. package/src/prefab/repository.ts +32 -32
  207. package/src/rows.ts +155 -0
  208. package/src/scan/conventions.ts +0 -18
  209. package/src/scan/emit.ts +3 -5
  210. package/src/scan/handler-parser.ts +14 -67
  211. package/src/scan/result.ts +42 -0
  212. package/src/scan/scanner.ts +44 -21
  213. package/src/scan/statement.ts +2 -1
  214. package/src/{orm.ts → storage.ts} +126 -17
  215. package/src/verify.ts +3 -3
  216. package/src/{boot → wire}/binding.ts +2 -66
  217. package/src/wire/call.ts +1 -129
  218. package/src/wire/operation.ts +5 -62
  219. package/src/wire/signature.ts +59 -0
  220. package/dist/boot/binding.d.ts.map +0 -1
  221. package/dist/boot/binding.js.map +0 -1
  222. package/dist/orm.d.ts.map +0 -1
  223. package/dist/orm.js.map +0 -1
  224. package/dist/scan/Fronds.d.ts.map +0 -1
  225. package/dist/scan/Fronds.js.map +0 -1
  226. package/dist/scan/declare.d.ts.map +0 -1
  227. package/dist/scan/declare.js.map +0 -1
  228. package/dist/scan/frond.d.ts.map +0 -1
  229. /package/dist/{scan → descriptor}/frond.js +0 -0
package/src/imports.ts CHANGED
@@ -25,7 +25,7 @@
25
25
  import type ts from '@typescript/typescript6';
26
26
  import { readdir, readFile } from 'node:fs/promises';
27
27
  import { join, resolve, dirname, relative, isAbsolute } from 'node:path';
28
- import type { FrondDescriptor } from './scan/frond.js';
28
+ import type { FrondDescriptor } from './descriptor/frond.js';
29
29
 
30
30
  let _ts: typeof ts | undefined;
31
31
  async function loadTs(): Promise<typeof ts> {
package/src/index.ts CHANGED
@@ -40,17 +40,17 @@ export type { DispatchObserver } from './dispatch/DispatchEvent.js';
40
40
  export type { DispatchPort } from './dispatch/DispatchPort.js';
41
41
  export {
42
42
  inferOperationKind,
43
- resolveContracts,
44
43
  resolveIsReadOp,
45
44
  resolveOperationKind,
46
45
  } from './wire/operation.js';
46
+ export { resolveContracts } from './effective-operation.js';
47
47
  export type {
48
48
  OperationContract,
49
49
  OperationKind,
50
50
  OperationKindInference,
51
51
  OperationsMap,
52
- ParsedParam,
53
- ParsedType,
52
+ Param,
53
+ TypeRef,
54
54
  } from './wire/operation.js';
55
55
  export {
56
56
  EFFECTIVE_OPERATION_SEMANTICS,
@@ -67,16 +67,17 @@ export type {
67
67
  export type { ModuleLoader } from './loader.js';
68
68
  export { FougereError, ErrorCode, validationErrorsOf, type FougereErrorOptions } from './wire/errors.js';
69
69
  export type { OperationContext, AppNext, AppMiddleware } from './wire/middleware.js';
70
- export { createLocalRunner, createAppRunner, assertIdentityCard, RPC_ENTITY, MAX_BODY_BYTES } from './wire/call.js';
70
+ export { assertIdentityCard, RPC_ENTITY, MAX_BODY_BYTES } from './wire/call.js';
71
+ export { createLocalRunner, createAppRunner } from './boot/runner.js';
71
72
 
72
73
  // A frond an app STATES, for a boot that will not scan. No disk, no `typescript` — which
73
74
  // is why it sits here and not in `/node`, beside the scanner that reads a project.
74
- export { frond } from './scan/declare.js';
75
- export type { FrondDeclaration, Declared, DeclaredSubject } from './scan/declare.js';
75
+ export { frond } from './declare.js';
76
+ export type { FrondDeclaration, Declared, DeclaredSubject } from './declare.js';
76
77
  // What this app would answer to `rpc.discover`. Served by the boot, and reached until now
77
78
  // only by tests through a deep path — a consumer comparing its own card to a producer's
78
79
  // had to dispatch a call to ask a question about itself.
79
- export { identityCardOf } from './wire/call.js';
80
+ export { identityCardOf } from './boot/card.js';
80
81
  export type {
81
82
  FrondCall, Transport, IdentityCard, CardOp, Facade, RpcAnswer,
82
83
  TopologyReport, FrondPlacement, Edge,
@@ -91,12 +92,13 @@ export type { LogLevel, LogRecord, LogSink } from './builtins/logger.js';
91
92
  // What a re-read config changes in a running process — and what it cannot.
92
93
  export { applyConfig, type ConfigApplication } from './boot/apply.js';
93
94
  export { Config } from './builtins/config.js';
94
- export type { EntityOrm, OrmFactory, ListOptions, ListResult, Together } from './orm.js';
95
- export { togetherKeyOf, membersOfTogetherKey } from './orm.js';
95
+ export type { Storage, StorageFactory, Source, SourceConfig, SourceView, ListOptions, ListResult, Together } from './storage.js';
96
+ export { Sources, togetherKeyOf, membersOfTogetherKey } from './storage.js';
97
+ export { storageOver } from './rows.js';
98
+ export type { Rows, Row } from './rows.js';
96
99
  export type { App, CreateAppOptions } from './boot/types.js';
100
+ export type { ScanResult, ScanDiagnostic } from './scan/result.js';
97
101
  export type {
98
- ScanResult,
99
- ScanDiagnostic,
100
102
  FrondDescriptor,
101
103
  FrondSource,
102
104
  ProviderEntry,
@@ -107,9 +109,9 @@ export type {
107
109
  PresenterEntry,
108
110
  PresenterFieldMeta,
109
111
  CollectorEntry,
110
- } from './scan/frond.js';
112
+ } from './descriptor/frond.js';
111
113
  export type { AuthConfig, AuthContext, AuthRuntime } from './boot/auth.js';
112
- export { Fronds } from './scan/Fronds.js';
114
+ export { Fronds } from './descriptor/Fronds.js';
113
115
  export { verify, assertSplittable, type Violation } from './verify.js';
114
116
  // Same question as verify(), answered from the source text instead of the model.
115
117
  export type { CrossFrondImport } from './imports.js';
package/src/node.ts CHANGED
@@ -11,10 +11,10 @@
11
11
  */
12
12
  export { boot } from './boot/boot.js';
13
13
 
14
- export { scanProject, frondAliases } from './scan/scanner.js';
14
+ export { scanProject, frondAliases, watchPathsOf } from './scan/scanner.js';
15
15
  export {
16
16
  type Conventions, type ConventionsInput, DEFAULT_CONVENTIONS,
17
- resolveConventions, frondPackage, frondDirsOf, providerDirsOf, watchPathsOf,
17
+ resolveConventions, frondPackage, frondDirsOf, providerDirsOf,
18
18
  } from './scan/conventions.js';
19
19
  export { RUNTIME_PACKAGES } from './scan/bundling.js';
20
20
  export { emitScan, type EmitOptions } from './scan/emit.js';
@@ -12,10 +12,10 @@
12
12
  * Usage:
13
13
  * ```ts
14
14
  * export default class UserCollector extends Collector(User) {
15
- * constructor(private userOrm: UserOrm) { super(); }
15
+ * constructor(private userStorage: UserStorage) { super(); }
16
16
  *
17
17
  * async collect(ctx: InvocationContext) {
18
- * return this.userOrm.findById(ctx.state.userId as string);
18
+ * return this.userStorage.findById(ctx.state.userId as string);
19
19
  * }
20
20
  * }
21
21
  * ```
@@ -1,4 +1,4 @@
1
- import type { EntityOrm, ListOptions, ListResult } from '../orm.js';
1
+ import type { Storage, ListOptions, ListResult } from '../storage.js';
2
2
  import type { OperationContract } from '../wire/operation.js';
3
3
  import type { EntityConstructor, SchemaView } from '@fougere/schema';
4
4
 
@@ -79,7 +79,7 @@ function crudOps(entity: SchemaView & { partial?: () => SchemaView }): Record<st
79
79
  /**
80
80
  * The mixin's single "trust me" point — the twin of `asSchemaConstructor` in @fougere/schema.
81
81
  *
82
- * The implementation returns whatever the ORM hands back; the declaration names the view
82
+ * The implementation returns whatever the storage hands back; the declaration names the view
83
83
  * each op emits at the port. TypeScript cannot connect the two (the view is a runtime
84
84
  * argument, the type is a generic), so one assertion states that the mixin honours what it
85
85
  * declared — and the façade makes it true, projecting each op's result onto its view
@@ -121,7 +121,7 @@ type OutOf<V, K extends CrudOpName, T> =
121
121
  * a judged redefinition assignable.
122
122
  */
123
123
  export interface CrudOps<T, V = {}> {
124
- orm: EntityOrm<T>;
124
+ storage: Storage<T>;
125
125
  list(options?: ListOptions, ...collected: never[]): Promise<ListResult<OutOf<V, 'list', T>>>;
126
126
  findById(id: string, ...collected: never[]): Promise<OutOf<V, 'findById', T> | undefined>;
127
127
  create(input: Partial<T>, ...collected: never[]): Promise<OutOf<V, 'create', T>>;
@@ -131,10 +131,10 @@ export interface CrudOps<T, V = {}> {
131
131
 
132
132
  /** The prefab handler class — its ops, plus the statics the bootstrap and adapters read. */
133
133
  export interface CrudConstructor<T, V = {}> {
134
- // `EntityOrm<T>`, not the bare `EntityOrm`: a handler that injects a second ORM has to
135
- // spell its own constructor, and `super(orm)` with the ORM the container hands it —
136
- // typed on the entity, as the `orm` property below already says — was refused.
137
- new (orm: EntityOrm<T>): CrudOps<T, V>;
134
+ // `Storage<T>`, not the bare `Storage`: a handler that injects a second storage has to
135
+ // spell its own constructor, and `super(storage)` with the storage the container hands it —
136
+ // typed on the entity, as the `storage` property below already says — was refused.
137
+ new (storage: Storage<T>): CrudOps<T, V>;
138
138
  readonly __entity: unknown;
139
139
  readonly __output: unknown;
140
140
  readonly __opOutputs?: CrudViews;
@@ -149,10 +149,10 @@ export interface CrudConstructor<T, V = {}> {
149
149
  *
150
150
  * Crud(Post) → every op emits Post
151
151
  * Crud(Post, { list: PostCard }) → list emits cards, the rest emit Post. Declaration
152
- * only: the handler keeps its full-row ORM, so a
152
+ * only: the handler keeps its full-row storage, so a
153
153
  * judge can still read `body`.
154
154
  * Crud(Post, PostPublic) → every op emits PostPublic, and the bootstrap
155
- * scopes the injected ORM via .output(PostPublic) —
155
+ * scopes the injected storage via .output(PostPublic) —
156
156
  * the whole handler speaks the restricted view.
157
157
  */
158
158
  export function Crud<E extends EntityConstructor, V extends CrudViews | EntityConstructor = {}>(
@@ -168,21 +168,21 @@ export function Crud<E extends EntityConstructor, V extends CrudViews | EntityCo
168
168
 
169
169
  return asCrudConstructor<T, V>(class CrudHandler {
170
170
  static __entity = entity;
171
- /** Handler-wide view only — a per-op map must NOT scope the ORM the judges read. */
171
+ /** Handler-wide view only — a per-op map must NOT scope the storage the judges read. */
172
172
  static __output = wholeHandler ?? entity;
173
173
  static __opOutputs = perOp;
174
174
  /** What this prefab handler declares — read by the façade, merged under the author's own methods. */
175
175
  static __ops: Record<string, OperationContract> = crudOps(entity as unknown as SchemaView & { partial?: () => SchemaView });
176
176
 
177
- orm: EntityOrm<T>;
178
- constructor(orm: EntityOrm) {
179
- this.orm = orm as EntityOrm<T>;
177
+ storage: Storage<T>;
178
+ constructor(storage: Storage) {
179
+ this.storage = storage as Storage<T>;
180
180
  }
181
181
 
182
- async list(options?: ListOptions): Promise<ListResult<T>> { return this.orm.list(options) as Promise<ListResult<T>>; }
183
- async findById(id: string): Promise<T | undefined> { return this.orm.findById(id); }
184
- async create(input: Partial<T>): Promise<T> { return this.orm.create(input); }
185
- async update(id: string, input: Partial<T>): Promise<T> { return this.orm.update(id, input); }
186
- async delete(id: string): Promise<boolean> { return this.orm.delete(id); }
182
+ async list(options?: ListOptions): Promise<ListResult<T>> { return this.storage.list(options) as Promise<ListResult<T>>; }
183
+ async findById(id: string): Promise<T | undefined> { return this.storage.findById(id); }
184
+ async create(input: Partial<T>): Promise<T> { return this.storage.create(input); }
185
+ async update(id: string, input: Partial<T>): Promise<T> { return this.storage.update(id, input); }
186
+ async delete(id: string): Promise<boolean> { return this.storage.delete(id); }
187
187
  });
188
188
  }
@@ -1,5 +1,5 @@
1
1
  import { FieldSet, Lifecycle, type EntityConstructor, type Fields, type ValidationResult } from '@fougere/schema';
2
- import type { EntityOrm } from '../orm.js';
2
+ import type { Storage } from '../storage.js';
3
3
 
4
4
  /**
5
5
  * A paginated local copy of a source that cannot be queried directly.
@@ -9,8 +9,8 @@ import type { EntityOrm } from '../orm.js';
9
9
  *
10
10
  * ```ts
11
11
  * export default class PartnerCatalog extends Mirror(BookCard) {
12
- * constructor(orm: EntityOrm<BookCard>, private catalog: Facade<CatalogHandler>) {
13
- * super(orm);
12
+ * constructor(storage: Storage<BookCard>, private catalog: Facade<CatalogHandler>) {
13
+ * super(storage);
14
14
  * }
15
15
  *
16
16
  * async *pull(since?: Date) {
@@ -36,7 +36,7 @@ export interface Refreshed {
36
36
 
37
37
  export interface MirrorOf<T> {
38
38
  /** The copy's own storage — where a page lands, and where its age is read. */
39
- orm: EntityOrm<T>;
39
+ storage: Storage<T>;
40
40
  /** Pages of rows to write. The one thing a mirror's author supplies. */
41
41
  pull(since?: Date): AsyncIterable<Partial<T>[]>;
42
42
  /** Run one pass: read the high-water mark, pull from there, write each page. */
@@ -46,7 +46,7 @@ export interface MirrorOf<T> {
46
46
  }
47
47
 
48
48
  export interface MirrorConstructor<T> {
49
- new (orm: EntityOrm<T>): MirrorOf<T>;
49
+ new (storage: Storage<T>): MirrorOf<T>;
50
50
  readonly __entity: unknown;
51
51
  }
52
52
 
@@ -66,13 +66,13 @@ export function Mirror<E extends EntityConstructor>(shape: E): MirrorConstructor
66
66
  abstract class MirrorBase implements MirrorOf<T> {
67
67
  static readonly __entity = shape;
68
68
 
69
- constructor(public orm: EntityOrm<T>) {}
69
+ constructor(public storage: Storage<T>) {}
70
70
 
71
71
  abstract pull(since?: Date): AsyncIterable<Partial<T>[]>;
72
72
 
73
73
  /** The freshest persisted stamp; no process-local freshness state is kept. */
74
74
  async freshness(): Promise<Date | undefined> {
75
- const [newest] = await this.orm.list({ orderBy: age, order: 'desc', limit: 1 });
75
+ const [newest] = await this.storage.list({ orderBy: age, order: 'desc', limit: 1 });
76
76
  const value = (newest as Record<string, unknown> | undefined)?.[age];
77
77
  return value instanceof Date ? value : undefined;
78
78
  }
@@ -84,7 +84,7 @@ export function Mirror<E extends EntityConstructor>(shape: E): MirrorConstructor
84
84
  // Preserve the source's page boundaries: one page becomes one upsert.
85
85
  for await (const page of this.pull(since)) {
86
86
  if (page.length === 0) continue;
87
- written += await this.orm.upsertAll(judgePage(shape, page) as Partial<T>[]);
87
+ written += await this.storage.upsertAll(judgePage(shape, page) as Partial<T>[]);
88
88
  }
89
89
  return { written, since, ms: Date.now() - started };
90
90
  }
@@ -9,14 +9,14 @@
9
9
  * Usage:
10
10
  * ```ts
11
11
  * export default class PostPresenter extends Presenter(Post) {
12
- * constructor(private commentOrm: CommentOrm) { super(); }
12
+ * constructor(private commentStorage: CommentStorage) { super(); }
13
13
  *
14
14
  * excerpt(post: Post) {
15
15
  * return post.body.slice(0, 200);
16
16
  * }
17
17
  *
18
18
  * async commentCount(post: Post) {
19
- * return this.commentOrm.countFor(post.id);
19
+ * return this.commentStorage.countFor(post.id);
20
20
  * }
21
21
  * }
22
22
  * ```
@@ -1,12 +1,12 @@
1
1
  import { upperFirst, type EntityConstructor } from '@fougere/schema';
2
- import type { EntityOrm, ListOptions, ListResult, SelectOption } from '../orm.js';
2
+ import type { Storage, ListOptions, ListResult, SelectOption } from '../storage.js';
3
3
  import type { SchemaView } from '@fougere/schema';
4
4
 
5
5
  /**
6
6
  * Repository(…entities) — who owns an entity's storage, and where its questions are named.
7
7
  *
8
- * **The arity decides**, which is the second reading of what `orm.ts` already states for
9
- * `EntityOrm` against `Together`: the arity of the unit is the whole distinction.
8
+ * **The arity decides**, which is the second reading of what `storage.ts` already states for
9
+ * `Storage` against `Together`: the arity of the unit is the whole distinction.
10
10
  *
11
11
  * ONE entity — the repository IS that entity's storage, with your methods added. The port's
12
12
  * thirteen gestures are forwarded, so a handler reads `this.posts.list()` whether or not
@@ -30,13 +30,13 @@ import type { SchemaView } from '@fougere/schema';
30
30
  * // repositories/CommandeRepository.ts
31
31
  * export default class CommandeRepository extends Repository(Commande, Stock) {
32
32
  * avecLignes(id: string) {
33
- * const [commandes, stock] = this.orms;
33
+ * const [commandes, stock] = this.storages;
34
34
  * …
35
35
  * }
36
36
  * }
37
37
  * ```
38
38
  *
39
- * `this.orms` is protected, because the whole point is that the storage stops at this file.
39
+ * `this.storages` is protected, because the whole point is that the storage stops at this file.
40
40
  * `protected` is erased at runtime, like `abstract` on a port: a compile-time guarantee, not
41
41
  * a barrier. The same bargain, already accepted.
42
42
  *
@@ -48,12 +48,12 @@ import type { SchemaView } from '@fougere/schema';
48
48
  * frame is ASKED FOR here as anywhere else, which is what makes it declared:
49
49
  *
50
50
  * ```ts
51
- * constructor(commandes: CommandeOrm, stock: StockOrm, private frame: Together<[Commande, Stock]>) {
51
+ * constructor(commandes: CommandeStorage, stock: StockStorage, private frame: Together<[Commande, Stock]>) {
52
52
  * super(commandes, stock);
53
53
  * }
54
54
  * ```
55
55
  *
56
- * The ORM words come back in that line, and only there: this class IS the holder, which is
56
+ * The storage words come back in that line, and only there: this class IS the holder, which is
57
57
  * the one case `boot/ownership.ts` lets name the storage it was built on.
58
58
  *
59
59
  * A repository is NOT a door: it has no façade, so nothing here is reachable from the wire.
@@ -62,14 +62,14 @@ import type { SchemaView } from '@fougere/schema';
62
62
  /**
63
63
  * The shape a repository of ONE entity has — the port itself, plus whatever the subclass
64
64
  * names on top. Written by an author who has not written the file: `RepositoryOf<Post>` is
65
- * to `PostRepository` what `EntityOrm<Post>` is to `PostOrm`, one spelling of one key, and
65
+ * to `PostRepository` what `Storage<Post>` is to `PostStorage`, one spelling of one key, and
66
66
  * `depKeyOf` reads both.
67
67
  */
68
- export type RepositoryOf<T> = EntityOrm<T>;
68
+ export type RepositoryOf<T> = Storage<T>;
69
69
 
70
70
  /** The single-entity form: the port, plus whatever the subclass names. */
71
71
  export interface RepositoryConstructor<T> {
72
- new (orm: EntityOrm<T>): EntityOrm<T>;
72
+ new (storage: Storage<T>): Storage<T>;
73
73
  readonly __entity: unknown;
74
74
  }
75
75
 
@@ -81,15 +81,15 @@ export interface RepositoryConstructor<T> {
81
81
  * value it describes is fabricated by `many()` for an arity only known at the call.
82
82
  */
83
83
  declare abstract class AggregateShape<E extends readonly EntityConstructor[]> {
84
- protected orms: { [K in keyof E]: EntityOrm<InstanceType<E[K]>> };
84
+ protected storages: { [K in keyof E]: Storage<InstanceType<E[K]>> };
85
85
  }
86
86
 
87
87
  /** The tuple an aggregate holds — the readable form of {@link AggregateShape}'s member. */
88
88
  export type AggregateOf<E extends readonly EntityConstructor[]> =
89
- { [K in keyof E]: EntityOrm<InstanceType<E[K]>> };
89
+ { [K in keyof E]: Storage<InstanceType<E[K]>> };
90
90
 
91
91
  export type AggregateConstructor<E extends readonly EntityConstructor[]> =
92
- (new (...orms: unknown[]) => AggregateShape<E>) & {
92
+ (new (...storages: unknown[]) => AggregateShape<E>) & {
93
93
  readonly __entity: unknown;
94
94
  /** The entities this class owns. Present from two on — an owner of one owns nothing. */
95
95
  readonly __owns: E;
@@ -111,24 +111,24 @@ export function Repository(...entities: EntityConstructor[]): unknown {
111
111
  function one<E extends EntityConstructor>(entity: E): RepositoryConstructor<InstanceType<E>> {
112
112
  type T = InstanceType<E>;
113
113
 
114
- class RepositoryBase implements EntityOrm<T> {
114
+ class RepositoryBase implements Storage<T> {
115
115
  static readonly __entity = entity;
116
116
 
117
- constructor(protected orm: EntityOrm<T>) {}
118
-
119
- list(options?: ListOptions & SelectOption): Promise<ListResult<T>> { return this.orm.list(options); }
120
- findById(id: string, options?: SelectOption) { return this.orm.findById(id, options); }
121
- findBy(criteria: Partial<T> | Record<string, unknown>, options?: SelectOption) { return this.orm.findBy(criteria, options); }
122
- findAllBy(criteria: Partial<T> | Record<string, unknown>, options?: SelectOption) { return this.orm.findAllBy(criteria, options); }
123
- findByKeys(ids: readonly string[], options?: SelectOption) { return this.orm.findByKeys(ids, options); }
124
- findAllByKeys(field: string, keys: readonly string[], options?: SelectOption) { return this.orm.findAllByKeys(field, keys, options); }
125
- create(input: Partial<T>, options?: SelectOption) { return this.orm.create(input, options); }
126
- upsert(input: Partial<T>, options?: SelectOption) { return this.orm.upsert(input, options); }
127
- upsertAll(inputs: readonly Partial<T>[], options?: SelectOption) { return this.orm.upsertAll(inputs, options); }
128
- update(id: string, input: Partial<T>, options?: SelectOption) { return this.orm.update(id, input, options); }
129
- delete(id: string) { return this.orm.delete(id); }
130
- output(schema: SchemaView) { return this.orm.output(schema); }
131
- get client(): unknown { return this.orm.client; }
117
+ constructor(protected storage: Storage<T>) {}
118
+
119
+ list(options?: ListOptions & SelectOption): Promise<ListResult<T>> { return this.storage.list(options); }
120
+ findById(id: string, options?: SelectOption) { return this.storage.findById(id, options); }
121
+ findBy(criteria: Partial<T> | Record<string, unknown>, options?: SelectOption) { return this.storage.findBy(criteria, options); }
122
+ findAllBy(criteria: Partial<T> | Record<string, unknown>, options?: SelectOption) { return this.storage.findAllBy(criteria, options); }
123
+ findByKeys(ids: readonly string[], options?: SelectOption) { return this.storage.findByKeys(ids, options); }
124
+ findAllByKeys(field: string, keys: readonly string[], options?: SelectOption) { return this.storage.findAllByKeys(field, keys, options); }
125
+ create(input: Partial<T>, options?: SelectOption) { return this.storage.create(input, options); }
126
+ upsert(input: Partial<T>, options?: SelectOption) { return this.storage.upsert(input, options); }
127
+ upsertAll(inputs: readonly Partial<T>[], options?: SelectOption) { return this.storage.upsertAll(inputs, options); }
128
+ update(id: string, input: Partial<T>, options?: SelectOption) { return this.storage.update(id, input, options); }
129
+ delete(id: string) { return this.storage.delete(id); }
130
+ output(schema: SchemaView) { return this.storage.output(schema); }
131
+ get client(): unknown { return this.storage.client; }
132
132
  }
133
133
 
134
134
  return RepositoryBase as unknown as RepositoryConstructor<T>;
@@ -144,10 +144,10 @@ function many<E extends readonly EntityConstructor[]>(entities: E): AggregateCon
144
144
  static readonly __entity = entities[0];
145
145
  static readonly __owns = entities;
146
146
 
147
- protected orms: AggregateOf<E>;
147
+ protected storages: AggregateOf<E>;
148
148
 
149
- constructor(...orms: unknown[]) {
150
- this.orms = orms.slice(0, entities.length) as AggregateOf<E>;
149
+ constructor(...storages: unknown[]) {
150
+ this.storages = storages.slice(0, entities.length) as AggregateOf<E>;
151
151
  }
152
152
  }
153
153
 
package/src/rows.ts ADDED
@@ -0,0 +1,155 @@
1
+ /**
2
+ * The thirteen gestures of {@link Storage}, derived from four.
3
+ *
4
+ * A place rows live varies in ONE thing — how a row is fetched, written, dropped and
5
+ * enumerated. Everything above that is the same everywhere: what a page is, how a criterion
6
+ * matches, which stamps survive an upsert, and the two refusals a `create` owes its caller.
7
+ * Measured on the Map realization: 14 lines of 140 touched the store.
8
+ *
9
+ * So an adapter supplies {@link Rows} and receives a whole `StorageFactory`. It is the frame
10
+ * a third party writing a source needs — without it, `Sources.register` hands over a
11
+ * four-gesture contract and leaves the thirteen to be re-derived, which is how three
12
+ * divergent copies of the same store ended up in this repo's own demos.
13
+ *
14
+ * What is NOT here: `transacted`. A unit of work belongs to the engine that has one, and
15
+ * this frame has none — which is why a `Source` built on it leaves the key absent and a
16
+ * frame compensates.
17
+ */
18
+ import { applyCreate, applyUpdate, Lifecycle, Role, type SchemaView } from '@fougere/schema';
19
+ import type { Storage, StorageFactory } from './storage.js';
20
+
21
+ /** One row, as every realization hands it over. */
22
+ export type Row = Record<string, unknown>;
23
+
24
+ /**
25
+ * A keyed collection of rows — what an adapter supplies, and all of it.
26
+ *
27
+ * Async on purpose, even where a realization is not: a Map answers instantly and a directory
28
+ * does not, and the frame above cannot be written twice. `client` is what the port's own
29
+ * escape hatch exposes, `unknown` for the reason it is there.
30
+ */
31
+ export interface Rows {
32
+ get(key: string): Promise<Row | undefined>;
33
+ has(key: string): Promise<boolean>;
34
+ set(key: string, row: Row): Promise<void>;
35
+ delete(key: string): Promise<boolean>;
36
+ /** Every row it holds. A realization that cannot answer this cheaply says so in its doc. */
37
+ all(): Promise<Row[]>;
38
+ /** Handed on as `Storage.client` — the Map, the directory, whatever it is. */
39
+ readonly client: unknown;
40
+ }
41
+
42
+ export function storageOver(open: (entity: SchemaView, name: string) => Rows): StorageFactory {
43
+ return (entity: SchemaView, name: string): Storage => {
44
+ const fields = entity.getFields();
45
+ const rows = open(entity, name);
46
+ const pk = Object.entries(fields).find(([, field]) => Role.of(field).isPrimary)?.[0] ?? 'id';
47
+ // `Storage.findById(id: string)` — but a key can hold a number, and a Map keyed on
48
+ // `1` does not answer `'1'`. SQL never had the question; here the divergence was
49
+ // silent and only on this storage.
50
+ const keyOf = (value: unknown) => String(value);
51
+ // Same contract as SQL: a criterion may name a SET, and an empty set matches nothing.
52
+ const matches = (row: Record<string, unknown>, criteria: Record<string, unknown>) =>
53
+ Object.entries(criteria).every(([key, value]) => Array.isArray(value)
54
+ ? value.some((v) => Object.is(row[key], v))
55
+ : Object.is(row[key], value));
56
+ return {
57
+ client: rows.client,
58
+ async list(options?: any) {
59
+ let items = await rows.all();
60
+ if (options?.where) items = items.filter((row) => matches(row, options.where));
61
+ // Held before the page is cut, and after the filter: `total` answers "how many
62
+ // match", which is what a paginator divides. Reading `store.size` at the end
63
+ // answered a different question — every row the store holds, including the ones
64
+ // the filter exists to keep out of this caller's sight.
65
+ const matching = items.length;
66
+ const limit = options?.limit;
67
+ const offset = options?.page && limit ? (options.page - 1) * limit : options?.offset ?? 0;
68
+ if (offset > 0) items = items.slice(offset);
69
+ const hasMore = limit ? items.length > limit : false;
70
+ if (limit) items = items.slice(0, limit);
71
+ const result = items as any;
72
+ result.hasMore = hasMore;
73
+ result.endCursor = items.length > 0 ? String((items[items.length - 1] as any)[pk] ?? '') : undefined;
74
+ if (options?.count) result.total = matching;
75
+ return result;
76
+ },
77
+ async findById(id: string) { return await rows.get(keyOf(id)); },
78
+ async findBy(criteria: Record<string, unknown>) {
79
+ return (await rows.all()).find((row) => matches(row, criteria));
80
+ },
81
+ async findAllBy(criteria: Record<string, unknown>) {
82
+ return (await rows.all()).filter((row) => matches(row, criteria));
83
+ },
84
+ // Same contract as SQL: a map keyed by the primary key, a miss being an absent key.
85
+ async findByKeys(ids: readonly string[]) {
86
+ const found = new Map<string, Record<string, unknown>>();
87
+ for (const id of ids) {
88
+ const row = await rows.get(keyOf(id));
89
+ if (row) found.set(String(id), row);
90
+ }
91
+ return found;
92
+ },
93
+ // The dual, same contract as SQL: grouped by the value read off the ROW.
94
+ async findAllByKeys(field: string, keys: readonly string[]) {
95
+ const grouped = new Map<string, Record<string, unknown>[]>();
96
+ if (keys.length === 0) return grouped;
97
+ const wanted = new Set(keys.map(String));
98
+ for (const row of await rows.all()) {
99
+ const key = String(row[field]);
100
+ if (!wanted.has(key)) continue;
101
+ const held = grouped.get(key);
102
+ if (held) held.push(row); else grouped.set(key, [row]);
103
+ }
104
+ return grouped;
105
+ },
106
+ // Same contract as SQL: the key and the creation stamps survive an overwrite.
107
+ async upsert(input: Partial<Record<string, unknown>>) {
108
+ const record = applyCreate(fields, applyUpdate(fields, input));
109
+ const id = record[pk] as string | undefined;
110
+ if (id === undefined) throw new Error(`${name}.upsert(): no \`${pk}\` — an upsert needs the key it writes at.`);
111
+ const held = await rows.get(keyOf(id));
112
+ if (held) {
113
+ for (const [key, field] of Object.entries(fields)) {
114
+ if (key === pk || Lifecycle.of(field).stampedOnce) record[key] = held[key];
115
+ }
116
+ }
117
+ await rows.set(keyOf(id), record);
118
+ return record;
119
+ },
120
+ async upsertAll(inputs: readonly Partial<Record<string, unknown>>[]) {
121
+ for (const input of inputs) await this.upsert(input);
122
+ return inputs.length;
123
+ },
124
+ async create(input: Partial<Record<string, unknown>>) {
125
+ const record = applyCreate(fields, input);
126
+ const id = record[pk] as string | undefined;
127
+ // `primary(text())` declares no generator, so nothing fills the hole and the
128
+ // caller has to. Keying on `undefined` would let the second create overwrite the
129
+ // first, in silence — the old version hid this by inventing an `id` field the
130
+ // entity never declared.
131
+ if (id === undefined) {
132
+ throw new Error(`${name}.create: '${pk}' is the primary key and nothing supplied it — this entity declares no generator for it.`);
133
+ }
134
+ // A create is not an upsert. `Map.set` overwrites, so a second create under the
135
+ // same key answered "created" while destroying the previous row — SQL answers a
136
+ // constraint violation, and a store that loses data silently is worse than one
137
+ // that fails.
138
+ if (await rows.has(keyOf(id))) {
139
+ throw new Error(`${name}.create: '${pk}' ${JSON.stringify(id)} already exists.`);
140
+ }
141
+ await rows.set(keyOf(id), record);
142
+ return record;
143
+ },
144
+ async update(id: string, input: Partial<Record<string, unknown>>) {
145
+ const existing = await rows.get(keyOf(id));
146
+ if (!existing) throw new Error(`Not found: ${id}`);
147
+ const updated = { ...existing, ...applyUpdate(fields, input), [pk]: existing[pk] };
148
+ await rows.set(keyOf(id), updated);
149
+ return updated;
150
+ },
151
+ async delete(id: string) { return await rows.delete(keyOf(id)); },
152
+ output(_schema: SchemaView) { return this; },
153
+ };
154
+ };
155
+ }
@@ -6,7 +6,6 @@
6
6
  * literals in five packages, so a project could not move any of them and `frondsDir` was
7
7
  * a declared key with no reader.
8
8
  */
9
- import { join } from 'node:path';
10
9
 
11
10
  export interface Conventions {
12
11
  /**
@@ -103,20 +102,3 @@ export function frondDirsOf(conventions: Conventions): string[] {
103
102
  entities, handlers, presenters, collectors, seeds, versions, ...providerDirsOf(conventions),
104
103
  ])];
105
104
  }
106
-
107
- /**
108
- * What changes when a frond's domain changes — the paths a dev loop watches.
109
- *
110
- * The root frond IS the scan root, so watching its path would match every write in the
111
- * project: `.nuxt/`, `node_modules/`, the build output. Its convention directories are
112
- * the frond, and they are what a scan re-reads.
113
- */
114
- export function watchPathsOf(
115
- frond: { source: { path: string } },
116
- scanRoot: string,
117
- conventions: Conventions,
118
- ): string[] {
119
- return frond.source.path === scanRoot
120
- ? frondDirsOf(conventions).map((dir) => join(scanRoot, dir))
121
- : [frond.source.path];
122
- }
package/src/scan/emit.ts CHANGED
@@ -16,10 +16,8 @@
16
16
  */
17
17
  import { dirname, relative } from 'node:path';
18
18
  import { ANONYMOUS_SCHEMA_NAME, Card, type SchemaView } from '@fougere/schema';
19
- import type {
20
- ScanResult, FrondDescriptor, EntityEntry, HandlerEntry,
21
- PresenterEntry, CollectorEntry, ProviderEntry, SeedEntry,
22
- } from './frond.js';
19
+ import type { FrondDescriptor, EntityEntry, HandlerEntry, PresenterEntry, CollectorEntry, ProviderEntry, SeedEntry } from '../descriptor/frond.js';
20
+ import type { ScanResult } from './result.js';
23
21
  import type { OperationContract } from '../wire/operation.js';
24
22
 
25
23
  export interface EmitOptions {
@@ -222,7 +220,7 @@ import { Fronds } from '${core}';
222
220
  ${typed ? `import type { ScanResult } from '${core}';\n` : ''}${imports.render()}
223
221
 
224
222
  export const scan${typed ? ': ScanResult' : ''} = {
225
- fronds: Fronds.scanned([
223
+ fronds: Fronds.hosting([
226
224
  ${fronds}
227
225
  ]),
228
226
  diagnostics: [${diagnostics.join(', ')}],