create-qpq-app 0.1.14 → 0.1.15
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/package.json +2 -2
- package/template/docusaurus/docs/actions/core/dynamic-functions/_category_.json +1 -0
- package/template/docusaurus/docs/actions/core/dynamic-functions/ask-dynamic-function-execute.md +89 -0
- package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-create.md +3 -3
- package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-event-append.md +1 -1
- package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-event-list.md +4 -1
- package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-get-by-id.md +1 -1
- package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-get-draft.md +5 -5
- package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-provide-store.md +6 -8
- package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-references-from-state.md +50 -0
- package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-references.md +11 -7
- package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-render-for-collection.md +54 -0
- package/template/docusaurus/docs/actions/features/event-doc-transfer/ask-event-doc-manifest.md +1 -1
- package/template/docusaurus/docs/config/core/dynamic-functions.md +65 -0
- package/template/docusaurus/docs/config/features/admin-session-event-doc.md +1 -1
- package/template/docusaurus/docs/config/features/event-doc-routes.md +16 -15
- package/template/docusaurus/docs/config/features/event-doc-summary.md +26 -6
- package/template/docusaurus/docs/config/features/event-doc-transfer.md +16 -19
- package/template/docusaurus/docs/config/features/event-doc.md +39 -24
- package/template/docusaurus/docs/config/features/tenanted-event-doc-transfer.md +3 -4
- package/template/docusaurus/docs/config/features/tenanted-event-doc.md +9 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-qpq-app",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Scaffold a new quidproquo app: npx create-qpq-app my-app",
|
|
5
5
|
"main": "./lib/commonjs/index.js",
|
|
6
6
|
"module": "./lib/esm/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/node": "^22.13.13",
|
|
55
|
-
"quidproquo-tsconfig": "0.1.
|
|
55
|
+
"quidproquo-tsconfig": "0.1.15"
|
|
56
56
|
},
|
|
57
57
|
"bin": {
|
|
58
58
|
"create-qpq-app": "./lib/commonjs/bin/createQpqApp.js"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "label": "Dynamic Functions", "link": { "type": "generated-index", "description": "Invoke a member of a registered functions object by name from within a story." } }
|
package/template/docusaurus/docs/actions/core/dynamic-functions/ask-dynamic-function-execute.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: askDynamicFunctionExecute
|
|
3
|
+
description: Invoke a member of a registered dynamic-functions object by name and return its result, running generator members in-process as a nested story.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# askDynamicFunctionExecute
|
|
7
|
+
|
|
8
|
+
Invokes one member of a [dynamic functions](../../../config/core/dynamic-functions.md) object by name, passing it positional args, and returns whatever the member resolves to. A **plain or async member** is called and (if needed) awaited directly. A **generator member** — the shape of every qpq story — is instead driven through the runtime as a nested story (one depth deeper), sharing the caller's action processors and session context, and its story result is returned.
|
|
9
|
+
|
|
10
|
+
- **Action type:** `DynamicFunctionsActionType.Execute`
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { askDynamicFunctionExecute } from 'quidproquo-core';
|
|
14
|
+
|
|
15
|
+
import { templateEventDoc } from '../eventDocs/templateEventDoc';
|
|
16
|
+
|
|
17
|
+
export function* foldTemplate(events: unknown[]) {
|
|
18
|
+
const views = yield* askDynamicFunctionExecute<typeof templateEventDoc>(
|
|
19
|
+
'templateEventDoc',
|
|
20
|
+
'foldSnapshotViews',
|
|
21
|
+
events,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return views;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Signature
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
function* askDynamicFunctionExecute<TFunctions extends DynamicFunctions, TName extends keyof TFunctions & string>(
|
|
32
|
+
dynamicFunctionsName: string,
|
|
33
|
+
functionName: TName,
|
|
34
|
+
...args: Parameters<TFunctions[TName]>
|
|
35
|
+
): AskResponse<DynamicFunctionResult<TFunctions[TName]>>;
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The `TFunctions` generic is the type of the registered functions object — pass it explicitly (e.g. `askDynamicFunctionExecute<typeof templateEventDoc>(...)`) so `functionName` and `args` are checked against its members and the return type is inferred.
|
|
39
|
+
|
|
40
|
+
## Parameters
|
|
41
|
+
|
|
42
|
+
| Parameter | Type | Description |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| `dynamicFunctionsName` | `string` | Name of the dynamic functions object to invoke — must match the `dynamicFunctionsName` of a surface registered with [defineDynamicFunctions](../../../config/core/dynamic-functions.md). |
|
|
45
|
+
| `functionName` | `TName` | Name of the member to call on the registered object. |
|
|
46
|
+
| `args` | `Parameters<TFunctions[TName]>` | Positional arguments passed to the member, typed from `TFunctions`. |
|
|
47
|
+
|
|
48
|
+
## Returns
|
|
49
|
+
|
|
50
|
+
`DynamicFunctionResult<TFunctions[TName]>` — the value the member resolves to:
|
|
51
|
+
|
|
52
|
+
- A **generator** member (a story) resolves to its story return value.
|
|
53
|
+
- A **promise-returning** member resolves to its awaited value.
|
|
54
|
+
- Any other member resolves to its plain return value.
|
|
55
|
+
|
|
56
|
+
## Errors
|
|
57
|
+
|
|
58
|
+
`askDynamicFunctionExecute` fails with `DynamicFunctionsExecuteErrorTypeEnum`:
|
|
59
|
+
|
|
60
|
+
| Error | Cause |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| `DynamicFunctionsNotFound` | No `defineDynamicFunctions` setting is registered under `dynamicFunctionsName`. |
|
|
63
|
+
| `ModuleLoadFailed` | The registered module could not be loaded at runtime. |
|
|
64
|
+
| `FunctionNotFound` | `functionName` is not an own enumerable function on the loaded object (inherited/prototype members don't count). |
|
|
65
|
+
| `FunctionThrew` | A plain or async member threw or rejected. |
|
|
66
|
+
|
|
67
|
+
If the invoked member is a generator and its own story throws, that error propagates to the caller **with its original error type** (not wrapped in one of the above), with the function name added to the error stack. Catch failures with `askCatch`:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
const outcome = yield* askCatch(
|
|
71
|
+
askDynamicFunctionExecute<typeof templateEventDoc>('templateEventDoc', 'foldSnapshotViews', events),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (outcome.success) {
|
|
75
|
+
const views = outcome.result;
|
|
76
|
+
} else {
|
|
77
|
+
// outcome.error.errorType / outcome.error.errorText
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Notes
|
|
82
|
+
|
|
83
|
+
- The invoked member runs **in the same process** as the caller — a generator member runs nested one level deeper, not as a separate deployed service — dynamic functions add no infrastructure of their own.
|
|
84
|
+
- `createDynamicFunctionCaller` (also exported from `quidproquo-core`) wraps this action in a typed proxy so call sites read like a normal method call instead of a string-keyed action: `const caller = createDynamicFunctionCaller<typeof templateEventDoc>('templateEventDoc'); yield* caller.foldSnapshotViews(events);` yields the exact same action as calling `askDynamicFunctionExecute` directly.
|
|
85
|
+
|
|
86
|
+
## Related
|
|
87
|
+
|
|
88
|
+
- [defineDynamicFunctions](../../../config/core/dynamic-functions.md) — registers the functions object this action invokes members on.
|
|
89
|
+
- [askInlineFunctionExecute](../../core/inline-function/ask-inline-function-execute.md) — the single-function predecessor to this action.
|
|
@@ -57,7 +57,7 @@ A point-in-time snapshot of who produced an event, captured server-side at appen
|
|
|
57
57
|
|
|
58
58
|
## Notes
|
|
59
59
|
|
|
60
|
-
- The record is validated against `
|
|
60
|
+
- The record is validated against `eventDocSummaryViewSchema` before it is written; a schema violation throws (`ErrorTypeEnum` from quidproquo-core).
|
|
61
61
|
- Not concurrency-safe against duplicate codes on its own. If two callers may create the same `code` at once, prefer [askEventDocGetByCodeOrCreate](./ask-event-doc-get-by-code.md#askeventdocgetbycodeorcreate) and serialise, or add a conditional create.
|
|
62
62
|
|
|
63
63
|
---
|
|
@@ -91,12 +91,12 @@ Use `askEventDocCreate` unless you are building a custom create flow that needs
|
|
|
91
91
|
The bare storage write for a summary record — a thin wrapper over the key-value store upsert (with retry). Business rules and validation live in the logic layer (e.g. `askEventDocCreate`, [askEventDocSoftDelete](./ask-event-doc-soft-delete.md)); this just persists whatever record you hand it.
|
|
92
92
|
|
|
93
93
|
```typescript
|
|
94
|
-
function* askEventDocUpsert(
|
|
94
|
+
function* askEventDocUpsert(view: EventDocSummaryView): AskResponse<void>;
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
| Parameter | Type | Description |
|
|
98
98
|
| --- | --- | --- |
|
|
99
|
-
| `
|
|
99
|
+
| `view` | `EventDocSummaryView` | The summary view to write (create or overwrite) — `type` is resolved from the store context and stamped on internally. |
|
|
100
100
|
|
|
101
101
|
**Returns** `void`.
|
|
102
102
|
|
|
@@ -84,7 +84,7 @@ What the client POSTs to append an event. `modelId` and the server-stamped prove
|
|
|
84
84
|
|
|
85
85
|
## Notes
|
|
86
86
|
|
|
87
|
-
- **No dedup, no version check, and no lifecycle/domain validation at append time.** All three are decided when the log is folded (`foldEventDocLog`), against the accepted events before the one in question: a repeated `clientMessageId` is ignored, an event whose version is older than the log's highest accepted version is ignored, and the collection's `
|
|
87
|
+
- **No dedup, no version check, and no lifecycle/domain validation at append time.** All three are decided when the log is folded (`foldEventDocLog`), against the accepted events before the one in question: a repeated `clientMessageId` is ignored, an event whose version is older than the log's highest accepted version is ignored, and the collection's `validators` registry (or `defaultEventDocEventValidator` when none is configured) is run there too. A rejected event is skipped silently — the document reads as though it was never written — rather than causing the append to throw.
|
|
88
88
|
- **No read, no retry, no coordination.** The append does not read the tail or the log; it mints a sortable id and writes. Two appends landing in the same millisecond get an arbitrary but stable relative order, which is fine because ordering only has to be stable, not wall-clock-precise.
|
|
89
89
|
- **Write uniqueness** is still enforced by [askEventDocEventWrite](./ask-event-doc-event-write.md)'s conditional (`ifNotExists`) write, but since ids are unique by construction this should never fire in practice — a collision surfaces as `KeyValueStoreUpsertErrorTypeEnum.Conflict` and indicates a bug (two writers minting the same id), not ordinary contention, so there is no retry around it.
|
|
90
90
|
- After writing, it calls `askEventDocSummaryRederive`, which re-folds the whole log and re-derives the document's summary record so the queryable view (identity, version history, timestamps) stays in sync — this is the one piece of read-model maintenance still on the write path, until a stream projector replaces it.
|
|
@@ -46,6 +46,8 @@ function* askEventDocEventList(
|
|
|
46
46
|
| `limit` | `number` | (store default) | Max number of events to return in the page. |
|
|
47
47
|
| `nextPageKey` | `string` | — | Continuation token from a previous page's `nextPageKey`. |
|
|
48
48
|
| `afterEventId` | `string` | — | Return only events whose event id sorts after this one (exclusive). A sort-key range condition on the events store's primary key — no GSI involved. |
|
|
49
|
+
| `upToEventId` | `string` | — | Return only events whose event id sorts at or before this one (inclusive) — the log prefix up to a known event, for folding a document as of that event (a snapshot). Not combinable with `afterEventId`: a key condition holds one sort-key range. |
|
|
50
|
+
| `sortDescending` | `boolean` | `false` | Newest first, for a display read that walks backwards in time (e.g. a history panel's latest-page-then-load-older). Folding reads never set this — a fold consumes the log in order. |
|
|
49
51
|
| `consistentRead` | `boolean` | `false` | Strongly consistent read. Needed by a caller that just appended and is now folding to decide something — the default eventually-consistent read can otherwise miss that caller's own most recent event. Costs roughly double the read capacity, so leave it off for ordinary reads. |
|
|
50
52
|
|
|
51
53
|
### Returns
|
|
@@ -69,7 +71,7 @@ export function* fullHistory(docId: string) {
|
|
|
69
71
|
```typescript
|
|
70
72
|
function* askEventDocEventListAll(
|
|
71
73
|
modelId: string,
|
|
72
|
-
options?: { consistentRead?: boolean },
|
|
74
|
+
options?: { consistentRead?: boolean; upToEventId?: string },
|
|
73
75
|
): AskResponse<EventDocEvent[]>;
|
|
74
76
|
```
|
|
75
77
|
|
|
@@ -79,6 +81,7 @@ function* askEventDocEventListAll(
|
|
|
79
81
|
| --- | --- | --- |
|
|
80
82
|
| `modelId` | `string` | The document id to read the full log for. |
|
|
81
83
|
| `options.consistentRead` | `boolean` | Optional, default `false`. Strongly consistent read — for a caller that just appended and is now folding to decide something, so it does not miss its own most recent event. Leave off for ordinary reads; it doubles the read cost. |
|
|
84
|
+
| `options.upToEventId` | `string` | Optional. Return only the log prefix up to and including this event id — for folding a document as of a known event (a snapshot). |
|
|
82
85
|
|
|
83
86
|
### Returns
|
|
84
87
|
|
|
@@ -29,7 +29,7 @@ An event document is never stored as a mutable blob. Its authoritative state is
|
|
|
29
29
|
- **Draft vs published** — the tail (highest) version with no `publishedAt` is the **draft**; a `PUBLISH` event freezes it and starts the next draft. Each version pointer records the `eventId` (a sortable id) of its last event (its head), so folding events whose `eventId` sorts at or before that head reconstructs the version's content as it was. `publishedAt` is when a version was published; `effectiveFrom` is when that publish takes effect (used for as-of time-travel).
|
|
30
30
|
- **Code** — the caller-chosen, stable business key set at create (via `INIT_STATE`) and editable with `SET_CODE`. It stays constant across versions and is expected unique within the collection (and any owner scope), so you can address a document by `code` instead of its generated `id`.
|
|
31
31
|
|
|
32
|
-
The version-pointer reads ([askEventDocGetDraft, askEventDocGetLatestPublished, askEventDocGetPublishedAsOf,
|
|
32
|
+
The version-pointer reads ([askEventDocGetDraft, askEventDocGetLatestPublished, askEventDocGetPublishedAsOf, askEventDocPublishedVersionAsOf](./ask-event-doc-get-draft.md)) resolve entries in this model.
|
|
33
33
|
|
|
34
34
|
### The summary record
|
|
35
35
|
|
|
@@ -83,15 +83,15 @@ function* askEventDocGetPublishedAsOf(
|
|
|
83
83
|
|
|
84
84
|
**Returns** `EventDocVersion | null` — the highest version with `publishedAt <= clock`, or `null`.
|
|
85
85
|
|
|
86
|
-
##
|
|
86
|
+
## askEventDocPublishedVersionAsOf
|
|
87
87
|
|
|
88
|
-
Returns the
|
|
88
|
+
Returns the version published and *effective* at `clock`, together with the document **state** at that version's head — folded snapshot-seeded (via `askEventDocDocumentStateAsOf`), so cost tracks the gap since the nearest snapshot rather than the whole log. It resolves the version from the summary via `effectiveFrom` (when the publish takes effect, so a publish scheduled for the future stays invisible until then), then folds the state as of `version.eventId`. Use `state` directly to render the published, as-of-`clock` content, and `version.publishedAt` to pin the doc's own linked assets to the moment it was published — the generic backbone of a "render published" flow. (This mirrors `askEventDocGetPublishedAsOf`, which returns only the version pointer, and keys on `publishedAt` rather than `effectiveFrom`.)
|
|
89
89
|
|
|
90
90
|
```typescript
|
|
91
|
-
function*
|
|
91
|
+
function* askEventDocPublishedVersionAsOf(
|
|
92
92
|
id: string,
|
|
93
93
|
clock: QpqIsoDateTime,
|
|
94
|
-
): AskResponse<Nullable<
|
|
94
|
+
): AskResponse<Nullable<EventDocVersionState>>;
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
| Parameter | Type | Description |
|
|
@@ -99,7 +99,7 @@ function* askEventDocPublishedEventsAsOf(
|
|
|
99
99
|
| `id` | `string` | Id of the document. |
|
|
100
100
|
| `clock` | `QpqIsoDateTime` | ISO-8601 timestamp to resolve the effective version as-of. |
|
|
101
101
|
|
|
102
|
-
**Returns** `
|
|
102
|
+
**Returns** `EventDocVersionState | null` — `{ version, state }` for the effective version, or `null` when the document is missing/deleted, nothing is effective yet, or the version's events are gone (a rewritten log).
|
|
103
103
|
|
|
104
104
|
## Related
|
|
105
105
|
|
|
@@ -17,10 +17,11 @@ This is the same pattern as [askContextProvideValue](../../core/context/ask-cont
|
|
|
17
17
|
| `eventsStoreName` | `string` | The events-log store name — by convention `` `${storeName}Events` ``. |
|
|
18
18
|
| `type` | `string` | Pins the document type within a store that can hold several. |
|
|
19
19
|
| `storageDriveName` | `string` | The collection's blob bucket (assets + runtime artifacts), keyed per-doc. |
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
20
|
+
| `onPublish` | `string` (optional) | The collection's on-publish inline-function name, if configured. Invoked after a Publish event is durably appended. |
|
|
21
|
+
| `onAppend` | `string` (optional) | The collection's on-append inline-function name, if configured. Invoked after every successful append. |
|
|
22
22
|
| `scopeResolver` | `string` (optional) | The collection's ambient storage scope resolver inline-function name, if configured (e.g. per-tenant). |
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
Render, references, and snapshot behaviour are no longer carried on the store binding — they come from the collection's registered `EventDocFunctions` object, addressed by `storeName`/`type` via `eventDocFunctionsName(storeName, type)` (see [defineEventDoc](../../../config/features/event-doc.md)).
|
|
24
25
|
|
|
25
26
|
There are two ways to establish the context — one for custom routes, one for the built-in routes — plus the raw provide/read primitives and a resolver that throws when the binding is missing.
|
|
26
27
|
|
|
@@ -64,12 +65,9 @@ function* askEventDocProvideStore<T>(
|
|
|
64
65
|
| --- | --- | --- |
|
|
65
66
|
| `storeName` | `string` | The collection's record store name; the events-table and blob-drive names are derived from it. |
|
|
66
67
|
| `type` | `string` | The document type pinned within the store. |
|
|
67
|
-
| `eventValidator` | `string` (optional) | Append-time validator inline-function name. |
|
|
68
|
-
| `eventRenderer` | `string` (optional) | Render inline-function name. |
|
|
69
68
|
| `onPublish` | `string` (optional) | Inline-function name invoked after a Publish append. |
|
|
70
69
|
| `onAppend` | `string` (optional) | Inline-function name invoked after every append. |
|
|
71
70
|
| `scopeResolver` | `string` (optional) | Ambient storage scope resolver inline-function name. |
|
|
72
|
-
| `referenceResolver` | `string` (optional) | Reference-collector inline-function name (powers `GET .../references`). |
|
|
73
71
|
|
|
74
72
|
### Returns
|
|
75
73
|
|
|
@@ -77,7 +75,7 @@ function* askEventDocProvideStore<T>(
|
|
|
77
75
|
|
|
78
76
|
## askEventDocProvideStoreFromGlobals
|
|
79
77
|
|
|
80
|
-
The built-in-routes counterpart: bridges the per-route **globals** that `defineEventDocRoutes` sets (store name, events-store name, type, storage drive, and the optional
|
|
78
|
+
The built-in-routes counterpart: bridges the per-route **globals** that `defineEventDocRoutes` sets (store name, events-store name, type, storage drive, and the optional onPublish/onAppend/scopeResolver hooks) into the store context, then runs the controller sub-story. Reads each global with [askConfigGetGlobal](../../core/config/ask-config-get-global.md), which throws if a route forgot to set them.
|
|
81
79
|
|
|
82
80
|
```typescript
|
|
83
81
|
import { askEventDocProvideStoreFromGlobals } from 'quidproquo-features';
|
|
@@ -123,7 +121,7 @@ function* askEventDocStoreProvide<T>(
|
|
|
123
121
|
|
|
124
122
|
## askEventDocStoreRead
|
|
125
123
|
|
|
126
|
-
The raw context **reader**: returns the currently bound [`EventDocStore`](#eventdocstore--the-binding). Built with `createContextReader`. Outside a provider it returns the **empty default** (blank `storeName` / `type`) rather than throwing — so most callers should prefer [askEventDocResolveStore](#askeventdocresolvestore). Internal data stories that only need one field (e.g. `
|
|
124
|
+
The raw context **reader**: returns the currently bound [`EventDocStore`](#eventdocstore--the-binding). Built with `createContextReader`. Outside a provider it returns the **empty default** (blank `storeName` / `type`) rather than throwing — so most callers should prefer [askEventDocResolveStore](#askeventdocresolvestore). Internal data stories that only need one field (e.g. `scopeResolver`) read it directly.
|
|
127
125
|
|
|
128
126
|
### Signature
|
|
129
127
|
|
package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-references-from-state.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: askEventDocReferencesFromState
|
|
3
|
+
description: The other docs the CURRENT document depends on, one hop out, folded snapshot-seeded rather than walking the log.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# askEventDocReferencesFromState
|
|
7
|
+
|
|
8
|
+
Reads the `EventDocLink`s the **current** document state depends on, one hop out. Folds the document snapshot-seeded (via [askEventDocDocumentStateLatest](./ask-event-doc-get-by-id.md)) and hands the folded state to the `collectReferencesFromState` member of the collection's registered `EventDocFunctions` object (looked up as `eventDocFunctionsName(storeName, type)`, see [defineEventDoc](../../../config/features/event-doc.md)). No log walk — this is the references ROUTE's read: what the document references now, at the cost of the gap since the nearest snapshot rather than the whole log.
|
|
9
|
+
|
|
10
|
+
A collection with no registered functions object is a leaf: this returns `[]`. So does a document with no events.
|
|
11
|
+
|
|
12
|
+
- **Built from:** [askEventDocResolveStore](./ask-event-doc-provide-store.md#askeventdocresolvestore) (to read the collection's `storeName`/`type`, which address its functions registration) and `askEventDocDocumentStateLatest` (the folded state `collectReferencesFromState` walks). Requires the store context — call it inside `askEventDocProvideStore({ storeName, type }, ...)`, or from a built-in route where the context is already provided.
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { askEventDocReferencesFromState } from 'quidproquo-features';
|
|
16
|
+
|
|
17
|
+
export function* readTemplateDependencies(templateId: string) {
|
|
18
|
+
const links = yield* askEventDocReferencesFromState(templateId);
|
|
19
|
+
|
|
20
|
+
return links; // EventDocLink[] — e.g. the template's layout, styles, and content
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Signature
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
function* askEventDocReferencesFromState(docId: string): AskResponse<EventDocLink[]>;
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Parameters
|
|
31
|
+
|
|
32
|
+
| Parameter | Type | Description |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| `docId` | `string` | The document to read outbound references for. |
|
|
35
|
+
|
|
36
|
+
## Returns
|
|
37
|
+
|
|
38
|
+
`EventDocLink[]` — the doc's outbound links, one hop out, as of the current state. Empty when the collection has no registered `EventDocFunctions` object, that object's `collectReferencesFromState` returns none, or the document has no events.
|
|
39
|
+
|
|
40
|
+
## Notes
|
|
41
|
+
|
|
42
|
+
- This is a **one-hop, current-state** read — the full-history equivalent (every link ANY historical state ever held) is [askEventDocReferences](./ask-event-doc-references.md), used by the transfer export instead.
|
|
43
|
+
- `GET {basePath}/{id}/references`, mounted by [defineEventDocRoutes](../../../config/features/event-doc-routes.md), calls this for one document; the route is always mounted, resolving to `[]` when no functions object is registered.
|
|
44
|
+
|
|
45
|
+
## Related
|
|
46
|
+
|
|
47
|
+
- [defineEventDoc](../../../config/features/event-doc.md) — registers the `EventDocFunctions` object (`collectReferencesFromState`) this reads.
|
|
48
|
+
- [askEventDocReferences](./ask-event-doc-references.md) — the full-history sibling read, used by the transfer export.
|
|
49
|
+
- [askEventDocRenderForCollection](./ask-event-doc-render-for-collection.md) — the sibling read (`render`) on the same registered object.
|
|
50
|
+
- [askEventDocGetByIdOrThrow](./ask-event-doc-get-by-id.md) — read the document's own summary alongside its references.
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: askEventDocReferences
|
|
3
|
-
description:
|
|
3
|
+
description: Every doc one document has ever depended on, across its whole history, via its collection's registered EventDocFunctions object.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# askEventDocReferences
|
|
7
7
|
|
|
8
|
-
Reads the `EventDocLink`s a document
|
|
8
|
+
Reads the `EventDocLink`s a document has **ever** depended on, one hop out, across its whole history. Hands the document's whole event log to the `collectReferences` member of the collection's registered `EventDocFunctions` object (looked up as `eventDocFunctionsName(storeName, type)`, see [defineEventDoc](../../../config/features/event-doc.md)) and lets it fold + walk it, so a link that existed only in an older version is still found. A collection with no registered functions object is a leaf: this returns `[]`.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
This is the **transfer export's** read — it exports the whole history and must chase links from all of it. `GET {basePath}/{id}/references` uses [askEventDocReferencesFromState](./ask-event-doc-references-from-state.md) instead (the CURRENT document only, folded snapshot-seeded rather than walking the log).
|
|
11
|
+
|
|
12
|
+
- **Built from:** [askEventDocResolveStore](./ask-event-doc-provide-store.md#askeventdocresolvestore) (to read the collection's `storeName`/`type`, which address its functions registration) and `askEventDocEventListAll` (the full log `collectReferences` folds). Requires the store context — call it inside `askEventDocProvideStore({ storeName, type }, ...)`, or from a built-in route where the context is already provided.
|
|
11
13
|
|
|
12
14
|
```typescript
|
|
13
15
|
import { askEventDocReferences } from 'quidproquo-features';
|
|
@@ -33,15 +35,17 @@ function* askEventDocReferences(docId: string): AskResponse<EventDocLink[]>;
|
|
|
33
35
|
|
|
34
36
|
## Returns
|
|
35
37
|
|
|
36
|
-
`EventDocLink[]` — the doc's outbound links, one hop out. Empty when the collection has no `
|
|
38
|
+
`EventDocLink[]` — the doc's outbound links, one hop out. Empty when the collection has no registered `EventDocFunctions` object, or that object's `collectReferences` returns none.
|
|
37
39
|
|
|
38
40
|
## Notes
|
|
39
41
|
|
|
40
|
-
- This is a **one-hop** read
|
|
41
|
-
- `GET {basePath}/{id}/references`, mounted by [defineEventDocRoutes](../../../config/features/event-doc-routes.md), calls
|
|
42
|
+
- This is a **one-hop** read, over the **whole log**. The recursive walk over these edges (following a template into its content, then that content's own references, and so on) is the transfer feature's job — see [askEventDocManifest](../event-doc-transfer/ask-event-doc-manifest.md).
|
|
43
|
+
- `GET {basePath}/{id}/references`, mounted by [defineEventDocRoutes](../../../config/features/event-doc-routes.md), calls [askEventDocReferencesFromState](./ask-event-doc-references-from-state.md) instead of this — the route only ever needs the CURRENT document's references.
|
|
42
44
|
|
|
43
45
|
## Related
|
|
44
46
|
|
|
45
|
-
- [
|
|
47
|
+
- [defineEventDoc](../../../config/features/event-doc.md) — registers the `EventDocFunctions` object (`collectReferences`) this reads.
|
|
48
|
+
- [askEventDocReferencesFromState](./ask-event-doc-references-from-state.md) — the sibling, current-state-only read backing the references route.
|
|
49
|
+
- [askEventDocRenderForCollection](./ask-event-doc-render-for-collection.md) — the sibling read (`render`) on the same registered object.
|
|
46
50
|
- [askEventDocManifest](../event-doc-transfer/ask-event-doc-manifest.md) — the recursive walk built on top of this, one collection at a time.
|
|
47
51
|
- [askEventDocGetByIdOrThrow](./ask-event-doc-get-by-id.md) — read the document's own summary alongside its references.
|
package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-render-for-collection.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: askEventDocRenderForCollection
|
|
3
|
+
description: Render a document from another collection in-process, addressed by identity alone.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# askEventDocRenderForCollection
|
|
7
|
+
|
|
8
|
+
Renders a document of **another** collection in-process — the in-lambda twin of the `GET {basePath}/{id}/render` route, for cross-collection composition (a template resolving its layout or content links as HTML). Provides the target collection's store context so the renderer's own reads (blob-drive assets, linked docs) resolve against the right stores, then invokes the `render` member of that collection's registered `EventDocFunctions` object with the caller-supplied `input`.
|
|
9
|
+
|
|
10
|
+
Unlike the render route, nothing here is soft: the caller names a specific collection expecting a renderer, so a missing registration or a functions object with no `render` member propagates as its dynamic-functions error rather than a 404.
|
|
11
|
+
|
|
12
|
+
- **Built from:** `askEventDocProvideStore` (to bind the target collection's store context) and `createDynamicFunctionCaller<EventDocInvokableFunctions>` (to invoke `render` on `eventDocFunctionsName(storeName, type)`, see [defineEventDoc](../../../config/features/event-doc.md)). The caller resolves WHICH state to render first — this only renders the input it's handed.
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { askEventDocRenderForCollection } from 'quidproquo-features';
|
|
16
|
+
|
|
17
|
+
export function* renderTemplateLayout(state: unknown, docId: string) {
|
|
18
|
+
const result = yield* askEventDocRenderForCollection('layouts', 'layout', { state, docId });
|
|
19
|
+
|
|
20
|
+
return result.html;
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Signature
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
function* askEventDocRenderForCollection(
|
|
28
|
+
storeName: string,
|
|
29
|
+
type: string,
|
|
30
|
+
input: EventDocRenderInput,
|
|
31
|
+
): AskResponse<EventDocRenderResult>;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Parameters
|
|
35
|
+
|
|
36
|
+
| Parameter | Type | Description |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `storeName` | `string` | The target collection's store name (not necessarily the caller's own). |
|
|
39
|
+
| `type` | `string` | The target collection's document type. |
|
|
40
|
+
| `input` | `EventDocRenderInput` | `{ state, docId, version?, renderMode?, effectiveAt? }` — the already-folded document state to render, exactly as the render route builds it. |
|
|
41
|
+
|
|
42
|
+
## Returns
|
|
43
|
+
|
|
44
|
+
`AskResponse<EventDocRenderResult>` — the rendered result (e.g. `{ kind: 'html', html }`).
|
|
45
|
+
|
|
46
|
+
## Notes
|
|
47
|
+
|
|
48
|
+
- Requires the target collection to have a `render` member on its registered `EventDocFunctions` object; a collection with none throws the dynamic-functions "not found" error rather than resolving to an empty result.
|
|
49
|
+
- The caller is responsible for resolving `renderMode`/`effectiveAt` into a concrete, already-folded `state` before calling this — it renders exactly what it's handed, the same contract the render route's controller follows.
|
|
50
|
+
|
|
51
|
+
## Related
|
|
52
|
+
|
|
53
|
+
- [defineEventDoc](../../../config/features/event-doc.md) — registers the `EventDocFunctions` object (`render`) this invokes.
|
|
54
|
+
- [askEventDocReferences](./ask-event-doc-references.md) — the sibling one-hop read (`collectReferences`) on the same registered object.
|
package/template/docusaurus/docs/actions/features/event-doc-transfer/ask-event-doc-manifest.md
CHANGED
|
@@ -5,7 +5,7 @@ description: Walk one or more docs' references outward to find everything that h
|
|
|
5
5
|
|
|
6
6
|
# askEventDocManifest
|
|
7
7
|
|
|
8
|
-
Finds every doc that has to travel with a list of starting docs, by following each doc's `
|
|
8
|
+
Finds every doc that has to travel with a list of starting docs, by following each doc's `collectReferences` links outward — breadth-first, across collections, with a visited set so a stylesheet three templates share is walked once and lands in the result once. Also the source of a cycle's termination: a link cycle (template → content → template) stops on the visited check instead of recursing forever.
|
|
9
9
|
|
|
10
10
|
Takes a **list** of roots so selecting several documents produces one merged manifest, rather than one per selection. A soft-deleted doc is reported (`deleted: true`) but not walked into — it will never be bundled, so its own dependencies are moot.
|
|
11
11
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: defineDynamicFunctions
|
|
3
|
+
description: Register a module's exported object of functions as a named dynamic-functions surface that other stories can invoke by member name with askDynamicFunctionExecute.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# defineDynamicFunctions
|
|
7
|
+
|
|
8
|
+
Registers a module export — an **object whose properties are functions** — under a name, so any story can invoke one of its members by name with [askDynamicFunctionExecute](../../actions/core/dynamic-functions/ask-dynamic-function-execute.md) without a per-function registration. It is the successor to [defineInlineFunction](./inline-function.md): one setting addresses a whole surface (name + member) instead of one function per entry.
|
|
9
|
+
|
|
10
|
+
- **On AWS:** deploys **no dedicated infrastructure** of its own. The referenced module is loaded and its member is invoked inside whatever Lambda is already running the calling story, sharing its action processors and session context. Registration simply makes the surface resolvable by name.
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { defineDynamicFunctions } from 'quidproquo-core';
|
|
14
|
+
|
|
15
|
+
export default [
|
|
16
|
+
defineDynamicFunctions('templateEventDoc', '/entry/eventDocs::templateEventDoc'),
|
|
17
|
+
];
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Signature
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
function defineDynamicFunctions(
|
|
24
|
+
dynamicFunctionsName: string,
|
|
25
|
+
runtime: QpqFunctionRuntime,
|
|
26
|
+
options?: QPQConfigAdvancedDynamicFunctionsSettings,
|
|
27
|
+
): DynamicFunctionsQPQConfigSetting;
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Parameters
|
|
31
|
+
|
|
32
|
+
### `dynamicFunctionsName` — `string` (required)
|
|
33
|
+
|
|
34
|
+
The name callers pass to [askDynamicFunctionExecute](../../actions/core/dynamic-functions/ask-dynamic-function-execute.md). This is the config's `uniqueKey`.
|
|
35
|
+
|
|
36
|
+
### `runtime` — `QpqFunctionRuntime` (required)
|
|
37
|
+
|
|
38
|
+
A reference to the module to register, usually a relative path string of the form `'/path/to/file::exportedObjectName'`. The exported value must be an object whose own enumerable properties are functions — that object's members are the callable surface.
|
|
39
|
+
|
|
40
|
+
### `options` — `QPQConfigAdvancedDynamicFunctionsSettings` (optional)
|
|
41
|
+
|
|
42
|
+
| Property | Type | Default | Description |
|
|
43
|
+
| --- | --- | --- | --- |
|
|
44
|
+
| `owner` | `CrossModuleOwner<'dynamicFunctionsName'>` | – | Declares that the functions object is owned by **another** module/service, so it can be resolved/invoked across modules. `{ module, application, feature, environment, dynamicFunctionsName }` — all optional; unset parts default to the current service. |
|
|
45
|
+
| `deprecated` | `boolean` | `false` | Marks the setting as deprecated in the config. |
|
|
46
|
+
|
|
47
|
+
## Examples
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { defineDynamicFunctions } from 'quidproquo-core';
|
|
51
|
+
|
|
52
|
+
export default [
|
|
53
|
+
defineDynamicFunctions('templateEventDoc', '/entry/eventDocs::templateEventDoc'),
|
|
54
|
+
|
|
55
|
+
// Owned by another module
|
|
56
|
+
defineDynamicFunctions('billingHelpers', '/entry/functions/billing::billingHelpers', {
|
|
57
|
+
owner: { module: 'billing' },
|
|
58
|
+
}),
|
|
59
|
+
];
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Related
|
|
63
|
+
|
|
64
|
+
- [askDynamicFunctionExecute](../../actions/core/dynamic-functions/ask-dynamic-function-execute.md) — invokes a member of a registered dynamic-functions object by name and returns its result.
|
|
65
|
+
- [defineInlineFunction](./inline-function.md) — registers a single story as a callable function; use this instead when the surface is one exported object with several members.
|
|
@@ -7,7 +7,7 @@ description: Declare the event document that records one audited session per adm
|
|
|
7
7
|
|
|
8
8
|
Declares the **admin session event document** — the audit record behind the admin dashboard. It creates one event doc per admin login, appending every user-intent event for that session so operator activity in the dashboard is fully auditable.
|
|
9
9
|
|
|
10
|
-
It is built on the quidproquo **eventDoc** feature
|
|
10
|
+
It is built on the quidproquo **eventDoc** feature — an event document is an append-only, per-instance record backed by storage, routes, and a WebSocket feed — composed directly from [defineEventDocSummary](./event-doc-summary.md) and [defineEventDocRoutes](./event-doc-routes.md) rather than [defineEventDoc](./event-doc.md), since the session collection registers no functions object (no render, no references, no snapshots). This define fixes the store name, type, base path, and auth directory to the admin conventions, so you don't configure them yourself.
|
|
11
11
|
|
|
12
12
|
`defineAdminSessionEventDoc` returns a `QPQConfig` array. It is spread in automatically by [defineAdminSettings](./admin-settings.md), so you rarely call it directly — but it is exported for services that assemble the admin feature piece by piece.
|
|
13
13
|
|
|
@@ -5,7 +5,7 @@ description: Mount the built-in HTTP routes for an event-sourced document collec
|
|
|
5
5
|
|
|
6
6
|
# defineEventDocRoutes
|
|
7
7
|
|
|
8
|
-
Mounts the built-in REST **routes** for one document `type` in an event-document collection. It returns a `QPQConfig` (an array of route settings). The route controllers ship inside `quidproquo-features` and resolve the store, type, user directory
|
|
8
|
+
Mounts the built-in REST **routes** for one document `type` in an event-document collection. It returns a `QPQConfig` (an array of route settings). The route controllers ship inside `quidproquo-features` and resolve the store, type, and user directory from per-route globals, invoking the collection's registered `EventDocFunctions` object (looked up by convention as `eventDocFunctionsName(storeName, type)`, see [defineEventDoc](./event-doc.md)) for render/references/snapshot behaviour — so a service needs no controller wiring of its own: declare the store, register the functions object, add these routes, and the collection is fully served.
|
|
9
9
|
|
|
10
10
|
`defineEventDocRoutes` defines **only** the routes; it assumes the store already exists (declared with [defineEventDocSummary](./event-doc-summary.md)). Use it directly when **several document types share one store** — call `defineEventDocSummary` once, then `defineEventDocRoutes` per type. For the single-type case, [defineEventDoc](./event-doc.md) does both in one call.
|
|
11
11
|
|
|
@@ -33,9 +33,9 @@ All paths are prefixed with the version segment `/v{version}` (default `/v1`):
|
|
|
33
33
|
| --- | --- | --- |
|
|
34
34
|
| `GET` | `{basePath}` | List one page of the collection's documents (newest first). Accepts `?limit=` and `?nextPageKey=` and returns `QpqPagedData`. |
|
|
35
35
|
| `GET` | `{basePath}/{id}` | Get one document's summary record. |
|
|
36
|
-
| `GET` | `{basePath}/{id}/events` | List a document's event log. |
|
|
37
|
-
| `GET` | `{basePath}/{id}/render` | Render the document
|
|
38
|
-
| `GET` | `{basePath}/{id}/references` | List the `EventDocLink`s
|
|
36
|
+
| `GET` | `{basePath}/{id}/events` | List a document's event log. Accepts `?limit=`, `?nextPageKey=`, `?afterEventId=` (exclusive — the tail since a known event), and `?newestFirst=true` (walk the log backwards — the history panel's latest-page-then-load-older read). With `?includeBase=true` the response becomes the bootstrap shape: `{ base, items, nextPageKey }`, where `base` is the newest usable document-view snapshot (`{ eventId, state }`, era-pinned) and `items` starts after it — or `base: null` with the log from event zero when no usable snapshot exists, so the fallback needs no second request shape. The base rides the first page only; page the rest with `?afterEventId=base.eventId`. |
|
|
37
|
+
| `GET` | `{basePath}/{id}/render` | Render the document. Resolves the target state snapshot-seeded (draft = the log's head, `?renderMode=published` = the version effective at `?effectiveAt=`/now) and hands the registered `render` member `{ state, docId, version?, ... }` — a render never replays a whole log. 404s as "no renderer configured" when the collection's registered functions object has no `render` member. |
|
|
38
|
+
| `GET` | `{basePath}/{id}/references` | List the `EventDocLink`s the CURRENT document state depends on (walks the folded state, not the log; the transfer export keeps the full-history walk). Resolves to `[]` when the collection has no registered functions object. |
|
|
39
39
|
| `POST` | `{basePath}` | Create a document. |
|
|
40
40
|
| `POST` | `{basePath}/{id}/events` | Append an event to a document. |
|
|
41
41
|
| `POST` | `{basePath}/{id}/assets` | Request an asset upload URL. |
|
|
@@ -60,14 +60,13 @@ The single `options` argument is an `EventDocRoutesOptions`:
|
|
|
60
60
|
| `basePath` | `` `/${string}` `` | – (required) | URL prefix the routes mount under. Must start with `/`, e.g. `/articles`. |
|
|
61
61
|
| `routeAuthSettings` | `RouteAuthSettings` | – | Auth settings applied to every mounted route (from quidproquo-webserver — see [route](../webserver/route.md)). When it carries a `userDirectoryName`, that directory is exposed to the controllers so mutations can attribute the acting user. **Omit to leave the routes open** — mutations then have no user to attribute. |
|
|
62
62
|
| `version` | `number` | `1` | Version number for the `/v{version}` path prefix on every route. |
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `onPublish` | `string` | – | Name of a registered inline function. When set, every successful append of a Publish event invokes it with `{ docId, event, summary }`, after the event is durably written and the summary re-derived. This is the seam for syncing a folded document into a materialized read model. Errors propagate to the caller: the event has landed but the side effect did not, so the caller learns the read model may be stale. |
|
|
66
|
-
| `onAppend` | `string` | – | Name of a registered inline function. When set, EVERY successful append (domain events and lifecycle events alike) invokes it with `{ docId, event, summary, events }`, after the event is durably written and the summary re-derived. This is the seam for reacting to any mutation (e.g. broadcasting the doc's fresh fold). Runs after `onPublish` when both fire on the same Publish event. Errors propagate to the caller: the event has landed but the side effect did not. |
|
|
63
|
+
| `onPublish` | `string` | – | Name of a registered inline function. When set, every successful append of a Publish event invokes it with `{ docId, event, summary, state, previousState }` — the FOLDED document as of the publish event and as of the event before it (latest-shaped, snapshot-seeded, so hook cost tracks the burst since the last snapshot, never the log) — after the event is durably written. This is the seam for syncing a folded document into a materialized read model: use `state`, never re-fold. Errors propagate to the caller: the event has landed but the side effect did not, so the caller learns the read model may be stale. |
|
|
64
|
+
| `onAppend` | `string` | – | Name of a registered inline function. When set, EVERY successful append (domain events and lifecycle events alike) invokes it with `{ docId, event, summary, state, previousState }` — the FOLDED document as of the event and as of its predecessor (latest-shaped, snapshot-seeded) — after the event is durably written. This is the seam for reacting to any mutation (e.g. broadcasting the doc's fresh fold, or diffing `previousState` vs `state` to detect a transition). Runs after `onPublish` when both fire on the same Publish event. Errors propagate to the caller: the event has landed but the side effect did not. |
|
|
67
65
|
| `scopeResolver` | `string` | – | Name of a registered inline function. When set, every route invokes it with `{ event }` before running; a non-null result becomes the ambient storage scope for the whole request, transparently partitioning the collection's stores and assets (e.g. per-tenant). Null means unscoped. Omit for collections that never partition. |
|
|
68
|
-
| `referenceResolver` | `string` | – | Name of a registered inline function. When set, `GET {basePath}/{id}/references` invokes it with the document's `{ events, docId }` log, which folds and returns the `EventDocLink`s that view depends on (e.g. a template's layout/style/content links). The transfer feature's manifest walk follows those links recursively. Omit for a leaf collection (a stylesheet, a layout) — the route is still mounted but always resolves to `[]`. |
|
|
69
66
|
| `excludeRoutes` | `EventDocRouteName[]` | `[]` | Route names to leave unmounted (`'list' \| 'get' \| 'listEvents' \| 'render' \| 'references' \| 'create' \| 'appendEvent' \| 'createAsset' \| 'getAsset' \| 'listAssets' \| 'remove'`). For a collection that must own one of these itself instead of using the stock behavior — e.g. a `create` that must also perform some side effect the stock controller doesn't know about. |
|
|
70
67
|
|
|
68
|
+
Append-time validation, rendering, references, and snapshotting are no longer per-route options: they come from the collection's registered `EventDocFunctions` object (`collectReferences`, `render`, `foldSnapshotViews`, and the doc type's own fold-gate `validators`), addressed by `eventDocFunctionsName(storeName, type)`. Register it with [defineDynamicFunctions](../core/dynamic-functions.md) directly (a multi-type store calling `defineEventDocRoutes` per type) or let [defineEventDoc](./event-doc.md) do it for the single-type case.
|
|
69
|
+
|
|
71
70
|
### `RouteAuthSettings`
|
|
72
71
|
|
|
73
72
|
`routeAuthSettings` is the standard quidproquo-webserver route auth object (the same one [defineRoute](../webserver/route.md) accepts). Its `userDirectoryName` names the [user directory](../core/key-value-store.md) callers authenticate against; the controllers read it to resolve the acting user for event attribution.
|
|
@@ -75,19 +74,20 @@ The single `options` argument is an `EventDocRoutesOptions`:
|
|
|
75
74
|
## Examples
|
|
76
75
|
|
|
77
76
|
```typescript
|
|
78
|
-
import {
|
|
77
|
+
import { defineDynamicFunctions } from 'quidproquo-core';
|
|
78
|
+
import { defineEventDocSummary, defineEventDocRoutes, eventDocFunctionsName } from 'quidproquo-features';
|
|
79
79
|
|
|
80
80
|
export default [
|
|
81
81
|
// One store, two types, each on its own path and route version.
|
|
82
|
-
...defineEventDocSummary('content'),
|
|
82
|
+
...defineEventDocSummary('content', { snapshotFunctions: { article: eventDocFunctionsName('content', 'article') } }),
|
|
83
|
+
|
|
84
|
+
defineDynamicFunctions(eventDocFunctionsName('content', 'article'), '/entry/eventDocs::articleDefinition'),
|
|
83
85
|
|
|
84
86
|
...defineEventDocRoutes({
|
|
85
87
|
storeName: 'content',
|
|
86
88
|
type: 'article',
|
|
87
89
|
basePath: '/articles',
|
|
88
90
|
routeAuthSettings: { userDirectoryName: 'editors' },
|
|
89
|
-
eventValidator: 'validateArticleEvent',
|
|
90
|
-
eventRenderer: 'renderArticle',
|
|
91
91
|
}),
|
|
92
92
|
|
|
93
93
|
...defineEventDocRoutes({
|
|
@@ -102,7 +102,8 @@ export default [
|
|
|
102
102
|
## Related
|
|
103
103
|
|
|
104
104
|
- [defineEventDocSummary](./event-doc-summary.md) — declares the store these routes serve (required before mounting routes).
|
|
105
|
-
- [defineEventDoc](./event-doc.md) — declares the store *and* these routes in one call for the single-type case.
|
|
106
|
-
- [
|
|
105
|
+
- [defineEventDoc](./event-doc.md) — declares the store, registers the functions object, *and* mounts these routes in one call for the single-type case.
|
|
106
|
+
- [defineDynamicFunctions](../core/dynamic-functions.md) — registers the `EventDocFunctions` object these routes invoke for render/references/snapshot behaviour.
|
|
107
|
+
- [defineEventDocTransfer](./event-doc-transfer.md) — mounts the export/import routes that read every registered collection's functions object (`collectReferences`) to build a transfer manifest.
|
|
107
108
|
- [defineRoute](../webserver/route.md) — the underlying webserver route config (source of `RouteAuthSettings`).
|
|
108
109
|
- **Custom routes over the same store:** wrap your controllers in `askEventDocProvideStore({ storeName, type }, ...)` (from quidproquo-features) so the generic [reads](../../actions/features/event-doc/ask-event-doc-get-by-id.md) resolve the collection, then compose actions like [askEventDocCreate](../../actions/features/event-doc/ask-event-doc-create.md).
|
|
@@ -5,16 +5,17 @@ description: Declare the stores for an event-sourced document collection — its
|
|
|
5
5
|
|
|
6
6
|
# defineEventDocSummary
|
|
7
7
|
|
|
8
|
-
Declares the **stores** that back an event-document collection, without any routes. It returns a `QPQConfig` (an array of config settings) that expands to
|
|
8
|
+
Declares the **stores** that back an event-document collection, without any routes. It returns a `QPQConfig` (an array of config settings) that expands to five underlying core stores:
|
|
9
9
|
|
|
10
10
|
1. A **summary key-value store** (partition key `type`, sort key `id`) — the queryable record for each document, derived by folding the identity/lifecycle events of its log. Rows are the [`EventDocSummary`](../../actions/features/event-doc/ask-event-doc-get-by-id.md) shape and carry the version history. A secondary index on `(type, updatedAt)` supports the recently-updated ordering [askEventDocList](../../actions/features/event-doc/ask-event-doc-list.md) returns. It is a pure projection: nothing on the append path writes it directly. The events store below declares an [`onStream`](../core/key-value-store.md#change-data-capture-onstream) handler that rebuilds a document's summary row from its log whenever an event is appended, so the summary is eventually (not immediately) consistent with the log.
|
|
11
11
|
2. An **append-only events store** (`<storeName>EventLog`, partition key `pk`, string sort key `sk`) — the live ordered log every document is folded from, keyed on a sortable event id (UUIDv7). It has **no** secondary index on purpose: the local dev-server query processor can't target one, so all event reads go through the main table.
|
|
12
|
-
3. A **
|
|
13
|
-
4. A **
|
|
12
|
+
3. A **snapshots store** (`<storeName>SS`, partition key `pk`, sort key `sk`) — per-view folded states at points along the log (`pk = docId#view`, `sk` the same sortable event id the log is ordered by). Populated only for the document types you enable via `options.snapshotFunctions`; a type with no entry there simply is not snapshotted. For a type with a registered functions object, the `onStream` handler runs ONE incremental fold per delivery — resuming from the newest usable snapshot and folding only the gap since it — and persists both the snapshot set and the summary row from that same fold (the fold's `summary` view IS the queryable record). The whole-log summary re-derivation remains only as the fallback: types with no registered functions, Remove stream records (a transfer rewrote the log, so snapshots can't seed), and folds that decline.
|
|
13
|
+
4. A **legacy events store** (`<storeName>Events`, partition key `pk`, numeric sort key `sk`) — the pre-sortable-id log, kept declared but unread/unwritten at runtime so its data stays reachable until it is migrated into the events store above.
|
|
14
|
+
5. A **storage drive** (`<storeName>edocs`, lower-cased) — the collection's blob bucket, holding each document's immutable uploaded assets (and later its derived runtime artifacts) under per-document prefixes.
|
|
14
15
|
|
|
15
|
-
Point-in-time recovery is enabled on all
|
|
16
|
+
Point-in-time recovery is enabled on all tables.
|
|
16
17
|
|
|
17
|
-
- **On AWS:** deploys
|
|
18
|
+
- **On AWS:** deploys four DynamoDB tables (via [defineKeyValueStore](../core/key-value-store.md)) and one S3 bucket (via [defineStorageDrive](../core/storage-drive.md)). All physical names are derived from `keyValueStoreName`, so a collection needs only that one name.
|
|
18
19
|
|
|
19
20
|
```typescript
|
|
20
21
|
import { defineEventDocSummary } from 'quidproquo-features';
|
|
@@ -29,7 +30,7 @@ Use `defineEventDocSummary` when you want to define the store separately from th
|
|
|
29
30
|
## Signature
|
|
30
31
|
|
|
31
32
|
```typescript
|
|
32
|
-
function defineEventDocSummary(keyValueStoreName: string): QPQConfig;
|
|
33
|
+
function defineEventDocSummary(keyValueStoreName: string, options?: EventDocSummaryOptions): QPQConfig;
|
|
33
34
|
```
|
|
34
35
|
|
|
35
36
|
## Parameters
|
|
@@ -38,6 +39,12 @@ function defineEventDocSummary(keyValueStoreName: string): QPQConfig;
|
|
|
38
39
|
|
|
39
40
|
The collection's base store name. It is used directly as the summary store name and as the `storeName` that route definitions and store-context calls reference. The events table name (`` `${name}Events` ``) and asset bucket name (`` `${name}edocs`.toLowerCase() ``) are both derived from it, so the whole collection is addressed by this single name. It must match the `storeName` passed to any [defineEventDocRoutes](./event-doc-routes.md) (or [askEventDocProvideStore](#related)) for the same collection.
|
|
40
41
|
|
|
42
|
+
### `options` — `EventDocSummaryOptions` (optional)
|
|
43
|
+
|
|
44
|
+
| Property | Type | Default | Description |
|
|
45
|
+
| --- | --- | --- | --- |
|
|
46
|
+
| `snapshotFunctions` | `Record<string, string>` | `{}` | Registered dynamic-functions names (see [defineDynamicFunctions](../core/dynamic-functions.md)) of each collection's `EventDocFunctions` object, keyed by document `type` — keyed because one events table can host several collections and its one stream serves them all. When a row's type has an entry, the stream projector invokes that object's `foldSnapshotViews` with the doc's log prefix (or, once a usable snapshot exists, just the gap since it, plus that snapshot's per-view state as `seedViews`) and writes the folded views to the snapshots store; a type with no entry is not snapshotted. [defineEventDoc](./event-doc.md) threads the single-type case through automatically — call this directly only for a multi-type store. |
|
|
47
|
+
|
|
41
48
|
## Examples
|
|
42
49
|
|
|
43
50
|
```typescript
|
|
@@ -52,6 +59,19 @@ export default [
|
|
|
52
59
|
];
|
|
53
60
|
```
|
|
54
61
|
|
|
62
|
+
```typescript
|
|
63
|
+
import { defineDynamicFunctions } from 'quidproquo-core';
|
|
64
|
+
import { defineEventDocRoutes, defineEventDocSummary, eventDocFunctionsName } from 'quidproquo-features';
|
|
65
|
+
|
|
66
|
+
// A shared store where "article" documents are snapshotted and "page" documents are not.
|
|
67
|
+
export default [
|
|
68
|
+
...defineEventDocSummary('content', { snapshotFunctions: { article: eventDocFunctionsName('content', 'article') } }),
|
|
69
|
+
defineDynamicFunctions(eventDocFunctionsName('content', 'article'), '/entry/eventDocs::articleDefinition'),
|
|
70
|
+
...defineEventDocRoutes({ storeName: 'content', type: 'article', basePath: '/articles' }),
|
|
71
|
+
...defineEventDocRoutes({ storeName: 'content', type: 'page', basePath: '/pages' }),
|
|
72
|
+
];
|
|
73
|
+
```
|
|
74
|
+
|
|
55
75
|
## Related
|
|
56
76
|
|
|
57
77
|
- [defineEventDocRoutes](./event-doc-routes.md) — mounts the HTTP routes against a store declared here (one per type).
|
|
@@ -7,7 +7,7 @@ description: Mount export/import routes that move event-doc collections between
|
|
|
7
7
|
|
|
8
8
|
Mounts the export/import **routes** for a service's event-doc collections: a staging storage drive plus five routes that build, stage, plan, and apply portable bundles of one or more documents (and everything they reference). It returns a `QPQConfig` (an array of config settings) that you spread into a service's infrastructure default export, alongside its `defineEventDoc` calls.
|
|
9
9
|
|
|
10
|
-
An export walks a doc's `
|
|
10
|
+
An export walks a doc's registered functions object `collectReferences` (see [defineEventDoc](./event-doc.md)) to pull in every doc it depends on — a template's layout, styles, and content — so a bundle is self-contained. An import replays that bundle's event log against the target's own collections, either as a fresh doc or appended onto one that already exists there, and reports what it would do (or did) per document.
|
|
11
11
|
|
|
12
12
|
The route controllers ship inside `quidproquo-features` and read the collection registry from per-route globals, so a service needs no controller wiring of its own.
|
|
13
13
|
|
|
@@ -15,15 +15,14 @@ The route controllers ship inside `quidproquo-features` and read the collection
|
|
|
15
15
|
|
|
16
16
|
```typescript
|
|
17
17
|
import { defineEventDoc, defineEventDocTransfer } from 'quidproquo-features';
|
|
18
|
+
import { articleDefinition } from './articleDefinition';
|
|
19
|
+
import { templateDefinition } from './templateDefinition';
|
|
18
20
|
|
|
19
|
-
const collections = [
|
|
20
|
-
{ storeName: 'templates', type: 'template', referenceResolver: 'collectTemplateReferences' },
|
|
21
|
-
{ storeName: 'content', type: 'article' },
|
|
22
|
-
];
|
|
21
|
+
const collections = [templateDefinition, articleDefinition];
|
|
23
22
|
|
|
24
23
|
export default [
|
|
25
|
-
...defineEventDoc(
|
|
26
|
-
...defineEventDoc(
|
|
24
|
+
...defineEventDoc(templateDefinition, '/entry/eventDocs::templateDefinition', { basePath: '/templates' }),
|
|
25
|
+
...defineEventDoc(articleDefinition, '/entry/eventDocs::articleDefinition', { basePath: '/articles' }),
|
|
27
26
|
|
|
28
27
|
...defineEventDocTransfer({
|
|
29
28
|
service: 'cms',
|
|
@@ -39,7 +38,7 @@ All paths are prefixed with the version segment `/v{version}` (default `/v1`):
|
|
|
39
38
|
|
|
40
39
|
| Method | Path | Purpose |
|
|
41
40
|
| --- | --- | --- |
|
|
42
|
-
| `POST` | `/transfer/manifest` | Every doc that would travel with a given `{ docs: [{ service, type, id }, ...] }` selection, merged and deduped across all of them (follows `
|
|
41
|
+
| `POST` | `/transfer/manifest` | Every doc that would travel with a given `{ docs: [{ service, type, id }, ...] }` selection, merged and deduped across all of them (follows each collection's `collectReferences` links), without building anything. Feeds an export UI's "these will be included" preview. |
|
|
43
42
|
| `POST` | `/transfer/export` | Stage one bundle for `{ docs: [...] }` and return its download url. |
|
|
44
43
|
| `POST` | `/transfer/upload` | A presigned PUT url for uploading a bundle file, plus the id used to plan/import it. |
|
|
45
44
|
| `POST` | `/transfer/plan` | What importing an uploaded bundle (`{ transferId }`) would do to each of its docs (`new`, `fastForward`, `same`, or a blocking `diverged`/`codeConflict`) without writing anything. |
|
|
@@ -58,24 +57,22 @@ The single `options` argument is an `EventDocTransferOptions`:
|
|
|
58
57
|
| Property | Type | Default | Description |
|
|
59
58
|
| --- | --- | --- | --- |
|
|
60
59
|
| `service` | `string` | – (required) | The service name `EventDocLink`s use to address this service's collections (`link.eventDocService`). Transfers never leave it: the stores live here, so a reference into another service throws rather than being silently dropped from a manifest. |
|
|
61
|
-
| `collections` | `
|
|
60
|
+
| `collections` | `EventDocTransferCollectionSource[]` | – (required) | The collections a transfer may read and write. Feed this the same array the service maps into its `defineEventDoc` calls (each entry's functions object carries the identity), so the two cannot drift — see `EventDocTransferCollectionSource` below. |
|
|
62
61
|
| `scopeResolver` | `string` | – | Name of a registered inline function. Establishes the ambient storage scope for the whole request, exactly like a collection's own `scopeResolver`. Needed separately because a transfer spans collections, so there is no single store to read the resolver name from. Omit only if none of the collections partition. |
|
|
63
62
|
| `routeAuthSettings` | `RouteAuthSettings` | – | Auth settings applied to every mounted route (from quidproquo-webserver — see [route](../webserver/route.md)). Import writes unvalidated history and export reads across every registered collection, so gate these harder than the collections' own routes. |
|
|
64
63
|
| `version` | `number` | `1` | Version number for the `/v{version}` path prefix on every route. |
|
|
65
64
|
|
|
66
|
-
### `
|
|
65
|
+
### `EventDocTransferCollectionSource`
|
|
67
66
|
|
|
68
|
-
|
|
67
|
+
Each `collections` entry can be any of:
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
| `onAppend` | `string` | Carried so an imported append fires the same reaction as it does on the collection's own routes. |
|
|
76
|
-
| `referenceResolver` | `string` | Carried so the manifest walk can follow this collection's links. `eventValidator` is deliberately **not** carried — replayed history was already validated at its origin, so imports skip it. |
|
|
69
|
+
- An `EventDocFunctions` object — the same value passed as `defineEventDoc`'s `functions` argument. Its `collectReferences` drives the manifest walk; `storeName`/`type` are read off it.
|
|
70
|
+
- A collection-list entry carrying its functions object under `functions` (`{ functions, ... }`) — so the exact array a service maps over for its `defineEventDoc` calls passes straight through, unmapped.
|
|
71
|
+
- A bare `EventDocTransferCollection` registry entry (`{ storeName, type, onPublish?, onAppend? }`), for a collection that needs the import hooks carried explicitly without passing a live functions object.
|
|
72
|
+
|
|
73
|
+
`onPublish`/`onAppend` are carried so an imported publish/append behaves exactly as it does on the collection's own routes. Fold-gate validation is deliberately **not** applied to imported events — replayed history was already validated at its origin.
|
|
77
74
|
|
|
78
75
|
## Related
|
|
79
76
|
|
|
80
|
-
- [defineEventDoc](./event-doc.md) / [defineEventDocRoutes](./event-doc-routes.md) — declare the collections a transfer reads and writes, including the `
|
|
77
|
+
- [defineEventDoc](./event-doc.md) / [defineEventDocRoutes](./event-doc-routes.md) — declare the collections a transfer reads and writes, including the functions object (`collectReferences`) the manifest walk follows.
|
|
81
78
|
- [defineTenantedEventDocTransfer](./tenanted-event-doc-transfer.md) — the same routes with the tenant scope resolver pre-wired, so export/import never cross tenant partitions.
|
|
@@ -1,71 +1,85 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: defineEventDoc
|
|
3
|
-
description:
|
|
3
|
+
description: Register an event-sourced document collection's functions object, declare its stores, and mount its HTTP routes, in one call.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# defineEventDoc
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Registers a document collection's **functions object** (its identity plus its behaviour — fold, references, and optionally render — as a [dynamic-functions](../core/dynamic-functions.md) surface), declares the stores that back it, and mounts the HTTP routes that serve it, all in one call. This is the all-in-one helper for the common **one-store-one-type** case. It returns a `QPQConfig` (an array of config settings) that you spread into a service's infrastructure default export.
|
|
9
9
|
|
|
10
|
-
An event document is not stored as a mutable row. It is derived by folding an ordered, append-only log of events. `defineEventDoc` provisions that log (plus a queryable summary table and a blob bucket) and mounts the REST routes that create documents, append events, read them back, and resolve their draft/published versions.
|
|
10
|
+
An event document is not stored as a mutable row. It is derived by folding an ordered, append-only log of events. `defineEventDoc` provisions that log (plus a queryable summary table and a blob bucket), registers the collection's functions object so the backend can invoke it by name, and mounts the REST routes that create documents, append events, read them back, and resolve their draft/published versions.
|
|
11
11
|
|
|
12
|
-
- **On AWS:** deploys everything [defineEventDocSummary](./event-doc-summary.md) deploys (
|
|
12
|
+
- **On AWS:** deploys everything [defineEventDocSummary](./event-doc-summary.md) deploys (DynamoDB tables for the summary, events, and snapshots, plus an S3 bucket for assets), everything [defineEventDocRoutes](./event-doc-routes.md) deploys (the API Gateway routes and their Lambda handlers), and a [defineDynamicFunctions](../core/dynamic-functions.md) registration for `functions` — no infrastructure of its own. It reads `functions`' `storeName`/`type` off the object itself, so `options` carries no identity fields.
|
|
13
13
|
|
|
14
14
|
```typescript
|
|
15
15
|
import { defineEventDoc } from 'quidproquo-features';
|
|
16
|
+
import { articleDefinition } from './articleDefinition';
|
|
16
17
|
|
|
17
18
|
export default [
|
|
18
|
-
...defineEventDoc({
|
|
19
|
-
storeName: 'content',
|
|
20
|
-
type: 'article',
|
|
19
|
+
...defineEventDoc(articleDefinition, '/entry/eventDocs::articleDefinition', {
|
|
21
20
|
basePath: '/articles',
|
|
22
21
|
routeAuthSettings: { userDirectoryName: 'editors' },
|
|
23
22
|
}),
|
|
24
23
|
];
|
|
25
24
|
```
|
|
26
25
|
|
|
26
|
+
## Registering a collection's functions
|
|
27
|
+
|
|
28
|
+
`functions` is an `EventDocFunctions` object: `{ storeName, type, foldSnapshotViews, collectReferences, render? }`. The object `createEventDocDefinition` returns — given `storeName`/`type` in its config — satisfies this shape directly, so a collection with no service-only render can register its definition verbatim. A collection that needs a render step only service code can perform (resolving linked docs, reading blob-drive assets) layers it on with `extendEventDocFunctions(definition, { render })`, which returns a new object and never mutates the definition itself.
|
|
29
|
+
|
|
30
|
+
`runtime` is a [`QpqFunctionRuntime`](../core/dynamic-functions.md#runtime--qpqfunctionruntime-required) path to that SAME export — the dynamic-functions pattern: identity is read off the object here at config time, behaviour is loaded from the path by the processors at request time. Both must point at the exact object being registered, or the registration and the runtime will disagree about what the collection can do.
|
|
31
|
+
|
|
27
32
|
## When to use `defineEventDoc` vs `defineEventDocSummary` + `defineEventDocRoutes`
|
|
28
33
|
|
|
29
|
-
`defineEventDoc` defines the store **and** the routes together, so it assumes exactly one document `type` per store. If you want several document types to share one physical store (one summary table, one events table, one bucket), you must not define the store more than once. In that case, call [defineEventDocSummary](./event-doc-summary.md) **once** for the shared store, then [defineEventDocRoutes](./event-doc-routes.md) **per type** — each with the same `storeName` but a different `type` and `
|
|
34
|
+
`defineEventDoc` defines the store, the functions registration, **and** the routes together, so it assumes exactly one document `type` per store. If you want several document types to share one physical store (one summary table, one events table, one bucket), you must not define the store more than once. In that case, call [defineEventDocSummary](./event-doc-summary.md) **once** for the shared store, then [defineDynamicFunctions](../core/dynamic-functions.md) and [defineEventDocRoutes](./event-doc-routes.md) **per type** — each with the same `storeName` but a different `type`, `basePath`, and functions object, passing `defineEventDocSummary` the whole `snapshotFunctions` map. Use `defineEventDoc` whenever the store backs a single type.
|
|
35
|
+
|
|
36
|
+
A collection with no definition at all (no render, no references, no snapshots) composes the low-level pair directly instead of calling `defineEventDoc`: `[...defineEventDocSummary(storeName), ...defineEventDocRoutes({ storeName, type, ...options })]`.
|
|
30
37
|
|
|
31
38
|
## Signature
|
|
32
39
|
|
|
33
40
|
```typescript
|
|
34
|
-
function defineEventDoc(
|
|
41
|
+
function defineEventDoc(
|
|
42
|
+
functions: EventDocFunctions,
|
|
43
|
+
runtime: QpqFunctionRuntime,
|
|
44
|
+
options: EventDocCollectionOptions,
|
|
45
|
+
): QPQConfig;
|
|
35
46
|
```
|
|
36
47
|
|
|
37
48
|
## Parameters
|
|
38
49
|
|
|
39
|
-
|
|
50
|
+
### `functions` — `EventDocFunctions` (required)
|
|
51
|
+
|
|
52
|
+
The collection's callable surface, read for its identity (`storeName`, `type`) at config time and registered under `runtime` for invocation at request time.
|
|
40
53
|
|
|
41
54
|
| Property | Type | Required | Description |
|
|
42
55
|
| --- | --- | --- | --- |
|
|
43
56
|
| `storeName` | `string` | yes | Name of the summary store to create and serve. Also derives the events table and asset bucket names. |
|
|
44
57
|
| `type` | `string` | yes | The document type this collection holds — the store's partition value, so one store can (via the split helpers) hold several types. |
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
| `foldSnapshotViews` | `(events, seedViews?) => Nullable<EventDocSnapshotViews>` | yes | Every view of a log prefix, era-pinned — what a snapshot stores. Invoked by the event store's stream projector (which also writes the summary row from the fold's `summary` view). |
|
|
59
|
+
| `foldDocumentState` | `(events, seedState?) => unknown` | yes | The document view at one point, LATEST-shaped, resumable from a stored snapshot's era-pinned document state. The read side's fold: render, references, as-of reads, and the append hooks' state derivation all go through it. |
|
|
60
|
+
| `collectReferences` | `(events) => EventDocLink[]` | yes | The `EventDocLink`s this doc's whole HISTORY depends on; `[]` for a leaf doc type. Invoked by the transfer manifest walk (it exports the whole history). |
|
|
61
|
+
| `collectReferencesFromState` | `(state) => EventDocLink[]` | yes | The `EventDocLink`s the CURRENT state depends on; `[]` for a leaf doc type. Invoked by the references route against a snapshot-seeded folded state. |
|
|
62
|
+
| `render` | `(input: EventDocRenderInput) => EventDocRenderResult \| AskResponse<EventDocRenderResult>` | no | Render the resolved, already-folded document state (`input.state`, resolved snapshot-seeded by the route). Omit and `GET {basePath}/{id}/render` 404s as "no renderer configured". Plain function or story — the dynamic-functions processor runs either. |
|
|
63
|
+
|
|
64
|
+
### `runtime` — `QpqFunctionRuntime` (required)
|
|
65
|
+
|
|
66
|
+
A reference to the module exporting `functions`, usually a relative path string of the form `'/path/to/file::exportedObjectName'` — see [defineDynamicFunctions](../core/dynamic-functions.md#runtime--qpqfunctionruntime-required). Must resolve to the SAME object passed as `functions`.
|
|
67
|
+
|
|
68
|
+
### `options` — `EventDocCollectionOptions` (required)
|
|
69
|
+
|
|
70
|
+
The same object [defineEventDocRoutes](./event-doc-routes.md#parameters) takes, minus `storeName`/`type` (read off `functions` instead). See the [defineEventDocRoutes parameter reference](./event-doc-routes.md#parameters) for every field: `basePath`, `routeAuthSettings`, `version`, `onPublish`, `onAppend`, `scopeResolver`, `excludeRoutes`.
|
|
54
71
|
|
|
55
72
|
## Examples
|
|
56
73
|
|
|
57
74
|
```typescript
|
|
58
75
|
import { defineEventDoc } from 'quidproquo-features';
|
|
76
|
+
import { articleDefinition } from './articleDefinition';
|
|
59
77
|
|
|
60
78
|
export default [
|
|
61
79
|
// A single "article" collection with authenticated mutations.
|
|
62
|
-
...defineEventDoc({
|
|
63
|
-
storeName: 'content',
|
|
64
|
-
type: 'article',
|
|
80
|
+
...defineEventDoc(articleDefinition, '/entry/eventDocs::articleDefinition', {
|
|
65
81
|
basePath: '/articles',
|
|
66
82
|
routeAuthSettings: { userDirectoryName: 'editors' },
|
|
67
|
-
eventValidator: 'validateArticleEvent',
|
|
68
|
-
eventRenderer: 'renderArticle',
|
|
69
83
|
}),
|
|
70
84
|
];
|
|
71
85
|
```
|
|
@@ -74,6 +88,7 @@ export default [
|
|
|
74
88
|
|
|
75
89
|
- [defineEventDocSummary](./event-doc-summary.md) — the store half of this helper; call it directly when several types share one store.
|
|
76
90
|
- [defineEventDocRoutes](./event-doc-routes.md) — the routes half; call it per type in the split setup.
|
|
91
|
+
- [defineDynamicFunctions](../core/dynamic-functions.md) — the registration this threads `functions`/`runtime` through.
|
|
77
92
|
- [defineKeyValueStore](../core/key-value-store.md) / [defineStorageDrive](../core/storage-drive.md) — the underlying core config the summary helper composes.
|
|
78
93
|
- **Reading a document in your own stories:** [askEventDocGetByIdOrThrow](../../actions/features/event-doc/ask-event-doc-get-by-id.md), [askEventDocList](../../actions/features/event-doc/ask-event-doc-list.md), and the [version reads](../../actions/features/event-doc/ask-event-doc-get-draft.md).
|
|
79
94
|
- **Creating a document in your own stories:** [askEventDocCreate](../../actions/features/event-doc/ask-event-doc-create.md).
|
|
@@ -11,8 +11,9 @@ The pairing matters: transferring collections declared with [defineTenantedEvent
|
|
|
11
11
|
|
|
12
12
|
```typescript
|
|
13
13
|
import { defineTenant, defineTenantedEventDoc, defineTenantedEventDocTransfer } from 'quidproquo-features';
|
|
14
|
+
import { articleDefinition } from './articleDefinition';
|
|
14
15
|
|
|
15
|
-
const collections = [
|
|
16
|
+
const collections = [articleDefinition];
|
|
16
17
|
|
|
17
18
|
export default [
|
|
18
19
|
...defineTenant({
|
|
@@ -21,9 +22,7 @@ export default [
|
|
|
21
22
|
routeAuthSettings: { userDirectoryName: 'users' },
|
|
22
23
|
}),
|
|
23
24
|
|
|
24
|
-
...defineTenantedEventDoc({
|
|
25
|
-
storeName: 'content',
|
|
26
|
-
type: 'article',
|
|
25
|
+
...defineTenantedEventDoc(articleDefinition, '/entry/eventDocs::articleDefinition', {
|
|
27
26
|
basePath: '/articles',
|
|
28
27
|
routeAuthSettings: { userDirectoryName: 'users' },
|
|
29
28
|
}),
|
|
@@ -11,6 +11,7 @@ The deploying service must still register the resolver implementation by calling
|
|
|
11
11
|
|
|
12
12
|
```typescript
|
|
13
13
|
import { defineTenantedEventDoc, defineTenant } from 'quidproquo-features';
|
|
14
|
+
import { articleDefinition } from './articleDefinition';
|
|
14
15
|
|
|
15
16
|
export default [
|
|
16
17
|
...defineTenant({
|
|
@@ -19,9 +20,7 @@ export default [
|
|
|
19
20
|
routeAuthSettings: { userDirectoryName: 'users' },
|
|
20
21
|
}),
|
|
21
22
|
|
|
22
|
-
...defineTenantedEventDoc({
|
|
23
|
-
storeName: 'content',
|
|
24
|
-
type: 'article',
|
|
23
|
+
...defineTenantedEventDoc(articleDefinition, '/entry/eventDocs::articleDefinition', {
|
|
25
24
|
basePath: '/articles',
|
|
26
25
|
routeAuthSettings: { userDirectoryName: 'users' },
|
|
27
26
|
}),
|
|
@@ -31,14 +30,18 @@ export default [
|
|
|
31
30
|
## Signature
|
|
32
31
|
|
|
33
32
|
```typescript
|
|
34
|
-
function defineTenantedEventDoc(
|
|
33
|
+
function defineTenantedEventDoc(
|
|
34
|
+
functions: EventDocFunctions,
|
|
35
|
+
runtime: QpqFunctionRuntime,
|
|
36
|
+
options: TenantedEventDocCollectionOptions,
|
|
37
|
+
): QPQConfig;
|
|
35
38
|
```
|
|
36
39
|
|
|
37
|
-
`
|
|
40
|
+
`TenantedEventDocCollectionOptions` is `EventDocCollectionOptions` with `scopeResolver` omitted — see [defineEventDoc](./event-doc.md#parameters) for the remaining options.
|
|
38
41
|
|
|
39
42
|
## Parameters
|
|
40
43
|
|
|
41
|
-
Same as [defineEventDoc](./event-doc.md#parameters): `
|
|
44
|
+
Same as [defineEventDoc](./event-doc.md#parameters): `functions`, `runtime`, and `options` (`basePath`, `routeAuthSettings`, `version`, `onPublish`, `onAppend`, without `scopeResolver`, which this always sets to `TENANT_SCOPE_RESOLVER_FN`).
|
|
42
45
|
|
|
43
46
|
## Returns
|
|
44
47
|
|