@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.
- package/dist/boot/Emissions.d.ts +2 -2
- package/dist/boot/Emissions.d.ts.map +1 -1
- package/dist/boot/Emissions.js +1 -1
- package/dist/boot/HandlerFacade.d.ts +1 -1
- package/dist/boot/HandlerFacade.d.ts.map +1 -1
- package/dist/boot/HandlerFacade.js +1 -1
- package/dist/boot/HandlerFacade.js.map +1 -1
- package/dist/boot/auth.d.ts +6 -6
- package/dist/boot/auth.d.ts.map +1 -1
- package/dist/boot/boot.d.ts +2 -2
- package/dist/boot/boot.d.ts.map +1 -1
- package/dist/boot/boot.js +1 -1
- package/dist/boot/boot.js.map +1 -1
- package/dist/boot/bootstrap.d.ts.map +1 -1
- package/dist/boot/bootstrap.js +47 -38
- package/dist/boot/bootstrap.js.map +1 -1
- package/dist/boot/card.d.ts +21 -0
- package/dist/boot/card.d.ts.map +1 -0
- package/dist/boot/card.js +100 -0
- package/dist/boot/card.js.map +1 -0
- package/dist/boot/frame.d.ts +3 -3
- package/dist/boot/frame.d.ts.map +1 -1
- package/dist/boot/frame.js +7 -7
- package/dist/boot/frame.js.map +1 -1
- package/dist/boot/hosted.d.ts +8 -2
- package/dist/boot/hosted.d.ts.map +1 -1
- package/dist/boot/hosted.js +10 -10
- package/dist/boot/hosted.js.map +1 -1
- package/dist/boot/ownership.d.ts +7 -5
- package/dist/boot/ownership.d.ts.map +1 -1
- package/dist/boot/ownership.js +9 -14
- package/dist/boot/ownership.js.map +1 -1
- package/dist/boot/ports.d.ts +2 -2
- package/dist/boot/ports.d.ts.map +1 -1
- package/dist/boot/ports.js +1 -1
- package/dist/boot/runner.d.ts +28 -0
- package/dist/boot/runner.d.ts.map +1 -0
- package/dist/boot/runner.js +26 -0
- package/dist/boot/runner.js.map +1 -0
- package/dist/boot/seed.d.ts +2 -2
- package/dist/boot/seed.d.ts.map +1 -1
- package/dist/boot/seed.js +5 -5
- package/dist/boot/seed.js.map +1 -1
- package/dist/boot/statement-drift.d.ts +2 -1
- package/dist/boot/statement-drift.d.ts.map +1 -1
- package/dist/boot/statement-drift.js.map +1 -1
- package/dist/boot/together.d.ts +4 -4
- package/dist/boot/together.d.ts.map +1 -1
- package/dist/boot/together.js +17 -19
- package/dist/boot/together.js.map +1 -1
- package/dist/boot/types.d.ts +12 -11
- package/dist/boot/types.d.ts.map +1 -1
- package/dist/contract/CallLog.d.ts +2 -2
- package/dist/contract/CallLog.d.ts.map +1 -1
- package/dist/contract/RouteAddress.d.ts +2 -0
- package/dist/contract/RouteAddress.d.ts.map +1 -1
- package/dist/contract/RouteAddress.js.map +1 -1
- package/dist/{scan/declare.d.ts → declare.d.ts} +3 -3
- package/dist/declare.d.ts.map +1 -0
- package/dist/{scan/declare.js → declare.js} +3 -3
- package/dist/declare.js.map +1 -0
- package/dist/{scan → descriptor}/Fronds.d.ts +6 -7
- package/dist/descriptor/Fronds.d.ts.map +1 -0
- package/dist/{scan → descriptor}/Fronds.js +6 -7
- package/dist/descriptor/Fronds.js.map +1 -0
- package/dist/{scan → descriptor}/frond.d.ts +7 -47
- package/dist/descriptor/frond.d.ts.map +1 -0
- package/dist/{scan → descriptor}/frond.js.map +1 -1
- package/dist/dispatch/ArgumentResolver.d.ts +5 -2
- package/dist/dispatch/ArgumentResolver.d.ts.map +1 -1
- package/dist/dispatch/ArgumentResolver.js +48 -3
- package/dist/dispatch/ArgumentResolver.js.map +1 -1
- package/dist/dispatch/DispatchEvent.d.ts +1 -1
- package/dist/dispatch/DispatchEvent.d.ts.map +1 -1
- package/dist/dispatch/PresenterArgumentResolver.d.ts +1 -1
- package/dist/dispatch/PresenterArgumentResolver.d.ts.map +1 -1
- package/dist/dispatch/PresenterArgumentResolver.js +1 -1
- package/dist/dispatch/Route.d.ts +1 -2
- package/dist/dispatch/Route.d.ts.map +1 -1
- package/dist/dispatch/StorageGuard.d.ts +2 -2
- package/dist/dispatch/StorageGuard.d.ts.map +1 -1
- package/dist/dispatch/StorageGuard.js +7 -7
- package/dist/dispatch/StorageGuard.js.map +1 -1
- package/dist/effective-operation.d.ts +29 -3
- package/dist/effective-operation.d.ts.map +1 -1
- package/dist/effective-operation.js +52 -1
- package/dist/effective-operation.js.map +1 -1
- package/dist/emit.d.ts +1 -1
- package/dist/frond-config.d.ts +2 -2
- package/dist/graph.d.ts +1 -1
- package/dist/graph.d.ts.map +1 -1
- package/dist/imports.d.ts +1 -1
- package/dist/imports.d.ts.map +1 -1
- package/dist/index.d.ts +15 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -6
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +2 -2
- package/dist/node.js.map +1 -1
- package/dist/prefab/collector.d.ts +2 -2
- package/dist/prefab/collector.js +2 -2
- package/dist/prefab/crud.d.ts +5 -5
- package/dist/prefab/crud.d.ts.map +1 -1
- package/dist/prefab/crud.js +12 -12
- package/dist/prefab/crud.js.map +1 -1
- package/dist/prefab/mirror.d.ts +5 -5
- package/dist/prefab/mirror.d.ts.map +1 -1
- package/dist/prefab/mirror.js +5 -5
- package/dist/prefab/mirror.js.map +1 -1
- package/dist/prefab/presenter.d.ts +2 -2
- package/dist/prefab/presenter.js +2 -2
- package/dist/prefab/repository.d.ts +14 -14
- package/dist/prefab/repository.d.ts.map +1 -1
- package/dist/prefab/repository.js +19 -19
- package/dist/prefab/repository.js.map +1 -1
- package/dist/rows.d.ts +40 -0
- package/dist/rows.d.ts.map +1 -0
- package/dist/rows.js +146 -0
- package/dist/rows.js.map +1 -0
- package/dist/scan/conventions.d.ts +8 -12
- package/dist/scan/conventions.d.ts.map +1 -1
- package/dist/scan/conventions.js +0 -13
- package/dist/scan/conventions.js.map +1 -1
- package/dist/scan/emit.d.ts +1 -1
- package/dist/scan/emit.d.ts.map +1 -1
- package/dist/scan/emit.js +1 -1
- package/dist/scan/emit.js.map +1 -1
- package/dist/scan/handler-parser.d.ts +11 -56
- package/dist/scan/handler-parser.d.ts.map +1 -1
- package/dist/scan/handler-parser.js +2 -2
- package/dist/scan/handler-parser.js.map +1 -1
- package/dist/scan/result.d.ts +41 -0
- package/dist/scan/result.d.ts.map +1 -0
- package/dist/scan/result.js +2 -0
- package/dist/scan/result.js.map +1 -0
- package/dist/scan/scanner.d.ts +18 -1
- package/dist/scan/scanner.d.ts.map +1 -1
- package/dist/scan/scanner.js +34 -17
- package/dist/scan/scanner.js.map +1 -1
- package/dist/scan/statement.d.ts +1 -1
- package/dist/scan/statement.d.ts.map +1 -1
- package/dist/scan/statement.js.map +1 -1
- package/dist/{orm.d.ts → storage.d.ts} +98 -16
- package/dist/storage.d.ts.map +1 -0
- package/dist/{orm.js → storage.js} +53 -5
- package/dist/storage.js.map +1 -0
- package/dist/verify.d.ts +1 -1
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +2 -2
- package/dist/verify.js.map +1 -1
- package/dist/{boot → wire}/binding.d.ts +2 -10
- package/dist/wire/binding.d.ts.map +1 -0
- package/dist/{boot → wire}/binding.js +0 -52
- package/dist/wire/binding.js.map +1 -0
- package/dist/wire/call.d.ts +2 -40
- package/dist/wire/call.d.ts.map +1 -1
- package/dist/wire/call.js +0 -125
- package/dist/wire/call.js.map +1 -1
- package/dist/wire/operation.d.ts +5 -31
- package/dist/wire/operation.d.ts.map +1 -1
- package/dist/wire/operation.js +0 -51
- package/dist/wire/operation.js.map +1 -1
- package/dist/wire/signature.d.ts +58 -0
- package/dist/wire/signature.d.ts.map +1 -0
- package/dist/wire/signature.js +2 -0
- package/dist/wire/signature.js.map +1 -0
- package/package.json +3 -3
- package/src/boot/Emissions.ts +2 -2
- package/src/boot/HandlerFacade.ts +3 -3
- package/src/boot/auth.ts +6 -6
- package/src/boot/boot.ts +3 -3
- package/src/boot/bootstrap.ts +50 -39
- package/src/boot/card.ts +113 -0
- package/src/boot/frame.ts +7 -7
- package/src/boot/hosted.ts +11 -14
- package/src/boot/ownership.ts +14 -15
- package/src/boot/ports.ts +2 -2
- package/src/boot/runner.ts +34 -0
- package/src/boot/seed.ts +6 -6
- package/src/boot/statement-drift.ts +5 -4
- package/src/boot/together.ts +24 -25
- package/src/boot/types.ts +12 -11
- package/src/contract/CallLog.ts +2 -2
- package/src/contract/RouteAddress.ts +3 -0
- package/src/{scan/declare.ts → declare.ts} +5 -5
- package/src/{scan → descriptor}/Fronds.ts +6 -7
- package/src/{scan → descriptor}/frond.ts +7 -48
- package/src/dispatch/ArgumentResolver.ts +53 -7
- package/src/dispatch/DispatchEvent.ts +1 -1
- package/src/dispatch/PresenterArgumentResolver.ts +2 -2
- package/src/dispatch/Route.ts +1 -3
- package/src/dispatch/StorageGuard.ts +7 -7
- package/src/effective-operation.ts +63 -11
- package/src/emit.ts +1 -1
- package/src/frond-config.ts +2 -2
- package/src/graph.ts +1 -1
- package/src/imports.ts +1 -1
- package/src/index.ts +15 -13
- package/src/node.ts +2 -2
- package/src/prefab/collector.ts +2 -2
- package/src/prefab/crud.ts +18 -18
- package/src/prefab/mirror.ts +8 -8
- package/src/prefab/presenter.ts +2 -2
- package/src/prefab/repository.ts +32 -32
- package/src/rows.ts +155 -0
- package/src/scan/conventions.ts +0 -18
- package/src/scan/emit.ts +3 -5
- package/src/scan/handler-parser.ts +14 -67
- package/src/scan/result.ts +42 -0
- package/src/scan/scanner.ts +44 -21
- package/src/scan/statement.ts +2 -1
- package/src/{orm.ts → storage.ts} +126 -17
- package/src/verify.ts +3 -3
- package/src/{boot → wire}/binding.ts +2 -66
- package/src/wire/call.ts +1 -129
- package/src/wire/operation.ts +5 -62
- package/src/wire/signature.ts +59 -0
- package/dist/boot/binding.d.ts.map +0 -1
- package/dist/boot/binding.js.map +0 -1
- package/dist/orm.d.ts.map +0 -1
- package/dist/orm.js.map +0 -1
- package/dist/scan/Fronds.d.ts.map +0 -1
- package/dist/scan/Fronds.js.map +0 -1
- package/dist/scan/declare.d.ts.map +0 -1
- package/dist/scan/declare.js.map +0 -1
- package/dist/scan/frond.d.ts.map +0 -1
- /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 './
|
|
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
|
-
|
|
53
|
-
|
|
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 {
|
|
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 './
|
|
75
|
-
export type { FrondDeclaration, Declared, DeclaredSubject } from './
|
|
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 './
|
|
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 {
|
|
95
|
-
export { togetherKeyOf, membersOfTogetherKey } from './
|
|
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 './
|
|
112
|
+
} from './descriptor/frond.js';
|
|
111
113
|
export type { AuthConfig, AuthContext, AuthRuntime } from './boot/auth.js';
|
|
112
|
-
export { Fronds } from './
|
|
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,
|
|
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';
|
package/src/prefab/collector.ts
CHANGED
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
* Usage:
|
|
13
13
|
* ```ts
|
|
14
14
|
* export default class UserCollector extends Collector(User) {
|
|
15
|
-
* constructor(private
|
|
15
|
+
* constructor(private userStorage: UserStorage) { super(); }
|
|
16
16
|
*
|
|
17
17
|
* async collect(ctx: InvocationContext) {
|
|
18
|
-
* return this.
|
|
18
|
+
* return this.userStorage.findById(ctx.state.userId as string);
|
|
19
19
|
* }
|
|
20
20
|
* }
|
|
21
21
|
* ```
|
package/src/prefab/crud.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
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
|
|
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
|
-
|
|
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
|
-
// `
|
|
135
|
-
// spell its own constructor, and `super(
|
|
136
|
-
// typed on the entity, as the `
|
|
137
|
-
new (
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
178
|
-
constructor(
|
|
179
|
-
this.
|
|
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.
|
|
183
|
-
async findById(id: string): Promise<T | undefined> { return this.
|
|
184
|
-
async create(input: Partial<T>): Promise<T> { return this.
|
|
185
|
-
async update(id: string, input: Partial<T>): Promise<T> { return this.
|
|
186
|
-
async delete(id: string): Promise<boolean> { return this.
|
|
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
|
}
|
package/src/prefab/mirror.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FieldSet, Lifecycle, type EntityConstructor, type Fields, type ValidationResult } from '@fougere/schema';
|
|
2
|
-
import type {
|
|
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(
|
|
13
|
-
* super(
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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.
|
|
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.
|
|
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
|
}
|
package/src/prefab/presenter.ts
CHANGED
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
* Usage:
|
|
10
10
|
* ```ts
|
|
11
11
|
* export default class PostPresenter extends Presenter(Post) {
|
|
12
|
-
* constructor(private
|
|
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.
|
|
19
|
+
* return this.commentStorage.countFor(post.id);
|
|
20
20
|
* }
|
|
21
21
|
* }
|
|
22
22
|
* ```
|
package/src/prefab/repository.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { upperFirst, type EntityConstructor } from '@fougere/schema';
|
|
2
|
-
import type {
|
|
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 `
|
|
9
|
-
* `
|
|
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.
|
|
33
|
+
* const [commandes, stock] = this.storages;
|
|
34
34
|
* …
|
|
35
35
|
* }
|
|
36
36
|
* }
|
|
37
37
|
* ```
|
|
38
38
|
*
|
|
39
|
-
* `this.
|
|
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:
|
|
51
|
+
* constructor(commandes: CommandeStorage, stock: StockStorage, private frame: Together<[Commande, Stock]>) {
|
|
52
52
|
* super(commandes, stock);
|
|
53
53
|
* }
|
|
54
54
|
* ```
|
|
55
55
|
*
|
|
56
|
-
* The
|
|
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 `
|
|
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> =
|
|
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 (
|
|
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
|
|
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]:
|
|
89
|
+
{ [K in keyof E]: Storage<InstanceType<E[K]>> };
|
|
90
90
|
|
|
91
91
|
export type AggregateConstructor<E extends readonly EntityConstructor[]> =
|
|
92
|
-
(new (...
|
|
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
|
|
114
|
+
class RepositoryBase implements Storage<T> {
|
|
115
115
|
static readonly __entity = entity;
|
|
116
116
|
|
|
117
|
-
constructor(protected
|
|
118
|
-
|
|
119
|
-
list(options?: ListOptions & SelectOption): Promise<ListResult<T>> { return this.
|
|
120
|
-
findById(id: string, options?: SelectOption) { return this.
|
|
121
|
-
findBy(criteria: Partial<T> | Record<string, unknown>, options?: SelectOption) { return this.
|
|
122
|
-
findAllBy(criteria: Partial<T> | Record<string, unknown>, options?: SelectOption) { return this.
|
|
123
|
-
findByKeys(ids: readonly string[], options?: SelectOption) { return this.
|
|
124
|
-
findAllByKeys(field: string, keys: readonly string[], options?: SelectOption) { return this.
|
|
125
|
-
create(input: Partial<T>, options?: SelectOption) { return this.
|
|
126
|
-
upsert(input: Partial<T>, options?: SelectOption) { return this.
|
|
127
|
-
upsertAll(inputs: readonly Partial<T>[], options?: SelectOption) { return this.
|
|
128
|
-
update(id: string, input: Partial<T>, options?: SelectOption) { return this.
|
|
129
|
-
delete(id: string) { return this.
|
|
130
|
-
output(schema: SchemaView) { return this.
|
|
131
|
-
get client(): unknown { return this.
|
|
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
|
|
147
|
+
protected storages: AggregateOf<E>;
|
|
148
148
|
|
|
149
|
-
constructor(...
|
|
150
|
-
this.
|
|
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
|
+
}
|
package/src/scan/conventions.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
223
|
+
fronds: Fronds.hosting([
|
|
226
224
|
${fronds}
|
|
227
225
|
]),
|
|
228
226
|
diagnostics: [${diagnostics.join(', ')}],
|