create-qpq-app 0.1.13 → 0.1.14
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/key-value-store/ask-key-value-store-query.md +1 -0
- package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-event-list.md +6 -1
- package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-generate-asset-upload-url.md +42 -3
- package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-list.md +57 -11
- package/template/docusaurus/docs/config/core/queue.md +1 -1
- package/template/docusaurus/docs/config/features/event-doc-routes.md +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-qpq-app",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
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.14"
|
|
56
56
|
},
|
|
57
57
|
"bin": {
|
|
58
58
|
"create-qpq-app": "./lib/commonjs/bin/createQpqApp.js"
|
|
@@ -55,6 +55,7 @@ function* askKeyValueStoreQuery<KvsItem>(
|
|
|
55
55
|
| `sortAscending` | `boolean` | `true` | Order results by the sort key. `false` returns the newest/highest first. |
|
|
56
56
|
| `limit` | `number` | – | Maximum number of records to return in this page. |
|
|
57
57
|
| `nextPageKey` | `string` | – | Opaque cursor from a previous page's `nextPageKey`; pass it to fetch the following page. |
|
|
58
|
+
| `consistentRead` | `boolean` | `false` | Strongly consistent (read-your-own-writes) read. Costs roughly double the read capacity on DynamoDB and cannot be served from a global secondary index. Use it when a caller just wrote and is now querying to decide something (e.g. folding a log right after appending to it); leave it off for ordinary reads. |
|
|
58
59
|
| `ttlInSeconds` | `number` | – | Accepted but not implemented: no processor currently applies a TTL to query results, so setting it has no effect. |
|
|
59
60
|
| `scope` | `string` | – | Optional storage scope. The processor composes it into the partition-key conditions, so the query only matches records written under the same scope (used by tenant/scoped features). Requires a string-typed partition key, and the key condition must constrain the partition key. |
|
|
60
61
|
|
|
@@ -46,6 +46,7 @@ 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
|
+
| `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. |
|
|
49
50
|
|
|
50
51
|
### Returns
|
|
51
52
|
|
|
@@ -66,7 +67,10 @@ export function* fullHistory(docId: string) {
|
|
|
66
67
|
### Signature
|
|
67
68
|
|
|
68
69
|
```typescript
|
|
69
|
-
function* askEventDocEventListAll(
|
|
70
|
+
function* askEventDocEventListAll(
|
|
71
|
+
modelId: string,
|
|
72
|
+
options?: { consistentRead?: boolean },
|
|
73
|
+
): AskResponse<EventDocEvent[]>;
|
|
70
74
|
```
|
|
71
75
|
|
|
72
76
|
### Parameters
|
|
@@ -74,6 +78,7 @@ function* askEventDocEventListAll(modelId: string): AskResponse<EventDocEvent[]>
|
|
|
74
78
|
| Parameter | Type | Description |
|
|
75
79
|
| --- | --- | --- |
|
|
76
80
|
| `modelId` | `string` | The document id to read the full log for. |
|
|
81
|
+
| `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. |
|
|
77
82
|
|
|
78
83
|
### Returns
|
|
79
84
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: askEventDocGenerateAssetUploadUrl
|
|
3
|
-
description: Manage a document's immutable assets — mint presigned upload/download URLs,
|
|
3
|
+
description: Manage a document's immutable assets and size-aware values — mint presigned upload/download URLs, write server-generated bytes directly, or record a value inline vs as an asset depending on its size.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Document assets
|
|
7
7
|
|
|
8
8
|
A document can point at binary **assets** — images, fonts, rendered artifacts — without embedding the bytes. Each asset is stored immutably on the collection's storage drive under `<docId>/assets/<guid>`, and the document records only a reference (the guid) inside a domain event. Because every upload gets a fresh guid, a re-upload is a new blob and prior references stay addressable for history and rollback.
|
|
9
9
|
|
|
10
|
-
These
|
|
10
|
+
These helpers cover three flows: presigned URLs for a browser to upload/download bytes directly, a server-side write for when the backend already holds the bytes, and a size-aware write that decides between inline and asset storage for values of unpredictable size.
|
|
11
11
|
|
|
12
12
|
- **Requires the store context** — provide it via [askEventDocProvideStore](./ask-event-doc-provide-store.md) / [askEventDocProvideStoreFromGlobals](./ask-event-doc-provide-store.md#askeventdocprovidestorefromglobals). The drive name is resolved from that context.
|
|
13
13
|
- **Built from:** the core file secure-URL / binary-write actions on the collection's storage drive, plus [askEventDocResolveStore](./ask-event-doc-provide-store.md#askeventdocresolvestore). Not single actions.
|
|
@@ -115,10 +115,49 @@ function* askEventDocWriteAsset(
|
|
|
115
115
|
|
|
116
116
|
`AskResponse<EventDocAssetRef>` — `{ guid, filename, mimetype }`, a first-class reference to record in a domain event so the document points at the bytes without embedding them.
|
|
117
117
|
|
|
118
|
+
## askEventDocWriteValue
|
|
119
|
+
|
|
120
|
+
Records a **value** (data of unpredictable size — a node output, a variable, a trigger input) for an event: inline when it is small enough, or as an asset when it is not. This is the size-aware counterpart to `askEventDocWriteAsset` above, which stays the right choice for genuine files where a reference is always correct.
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { askEventDocWriteValue } from 'quidproquo-features';
|
|
124
|
+
|
|
125
|
+
export function* recordNodeOutput(docId: string, nodeId: string, output: unknown) {
|
|
126
|
+
const ref = yield* askEventDocWriteValue(docId, output, `${nodeId}.json`);
|
|
127
|
+
// ref is either { kind: 'inline', value } or { kind: 'asset', guid, filename, mimetype }
|
|
128
|
+
return ref;
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Signature
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
function* askEventDocWriteValue(
|
|
136
|
+
docId: string,
|
|
137
|
+
value: unknown,
|
|
138
|
+
filename: string,
|
|
139
|
+
maxInlineBytes?: number,
|
|
140
|
+
): AskResponse<EventDocValueRef>;
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Parameters
|
|
144
|
+
|
|
145
|
+
| Parameter | Type | Description |
|
|
146
|
+
| --- | --- | --- |
|
|
147
|
+
| `docId` | `string` | The document the value belongs to — used for the asset blob key when the value doesn't fit inline. |
|
|
148
|
+
| `value` | `unknown` | The value to record. Serialised with `JSON.stringify`; `undefined` is normalised to `null`. |
|
|
149
|
+
| `filename` | `string` | Filename to use if the value is written as an asset. |
|
|
150
|
+
| `maxInlineBytes` | `number` | Optional, default 4KB. The per-value inline ceiling. A caller recording several values against one event's combined size budget can pass the *remaining* budget so a value that no longer fits falls back to an asset; passing `0` always forces an asset. |
|
|
151
|
+
|
|
152
|
+
### Returns
|
|
153
|
+
|
|
154
|
+
`AskResponse<EventDocValueRef>` — either `{ kind: 'inline', value }` (the value travelled in the event, nothing to fetch) or `{ kind: 'asset', guid, filename, mimetype }` (fetch it like any other asset reference).
|
|
155
|
+
|
|
118
156
|
## Notes
|
|
119
157
|
|
|
120
|
-
- All
|
|
158
|
+
- All four resolve the drive from the store context, so a missing context throws (see [askEventDocResolveStore](./ask-event-doc-provide-store.md#askeventdocresolvestore)).
|
|
121
159
|
- Assets are immutable: guid-named so a re-upload is always a fresh blob; the old one stays addressable for history/rollback. Assets live under `<docId>/assets/<guid>`; derived, disposable artifacts use a sibling `<docId>/runtime/<...>` prefix written elsewhere.
|
|
160
|
+
- On the read side, `isInlineEventDocValueRef`, `readInlineEventDocValueRefs`, and `resolveEventDocValueRef` (all in `quidproquo-features`) are the shared helpers for branching on a ref's `kind` and resolving it against fetched asset snapshots — use them rather than checking `ref.kind` by hand, so every reader agrees on what "available" means.
|
|
122
161
|
|
|
123
162
|
## Related
|
|
124
163
|
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: askEventDocList
|
|
3
|
-
description: List
|
|
3
|
+
description: List event documents in the current collection, newest-updated first, hiding soft-deleted rows by default — as a full array or one page at a time.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Listing a collection
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Two ways to list the summary records of a collection (all rows sharing the collection's `type`), sorted by `updatedAt` **descending** (most recently updated first). Soft-deleted documents are hidden unless you opt in. Both require the store context — call inside `askEventDocProvideStore({ storeName, type }, ...)`, or from a built-in route where the context is already provided.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## askEventDocList
|
|
11
|
+
|
|
12
|
+
Reads the **whole** collection into memory, filters and sorts it, and returns it as a flat array. Fine for a handful of documents; costs read capacity proportional to the whole collection, so it does not scale to a large collection — use [askEventDocListPage](#askeventdoclistpage) for that.
|
|
13
|
+
|
|
14
|
+
- **Built from:** a key-value store query over the collection's `type`, filtered and sorted in memory.
|
|
11
15
|
|
|
12
16
|
```typescript
|
|
13
17
|
import { askEventDocList } from 'quidproquo-features';
|
|
@@ -18,7 +22,7 @@ export function* listArticles() {
|
|
|
18
22
|
}
|
|
19
23
|
```
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
### Signature
|
|
22
26
|
|
|
23
27
|
```typescript
|
|
24
28
|
function* askEventDocList<T extends EventDocSummary = EventDocSummary>(
|
|
@@ -26,9 +30,9 @@ function* askEventDocList<T extends EventDocSummary = EventDocSummary>(
|
|
|
26
30
|
): AskResponse<T[]>;
|
|
27
31
|
```
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
### Parameters
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
#### `options` — `EventDocListOptions` (optional)
|
|
32
36
|
|
|
33
37
|
| Property | Type | Default | Description |
|
|
34
38
|
| --- | --- | --- | --- |
|
|
@@ -36,18 +40,60 @@ function* askEventDocList<T extends EventDocSummary = EventDocSummary>(
|
|
|
36
40
|
|
|
37
41
|
The generic `T` lets callers narrow to a collection-specific extension of [`EventDocSummary`](./ask-event-doc-get-by-id.md#the-summary-record); it defaults to `EventDocSummary`.
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
### Returns
|
|
40
44
|
|
|
41
45
|
`EventDocSummary[]` — the collection's records, sorted by `updatedAt` descending. Empty array when the collection has no (visible) documents.
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
### Notes
|
|
44
48
|
|
|
45
49
|
- Ordering is applied in memory, but the summary store carries a `(type, updatedAt)` index so this stays efficient.
|
|
46
50
|
- Because there is no secondary index on `code`, the code-based reads ([askEventDocGetByCode](./ask-event-doc-get-by-code.md) and friends) list the collection via this action and filter in memory.
|
|
47
51
|
|
|
52
|
+
## askEventDocListPage
|
|
53
|
+
|
|
54
|
+
Reads **one page** of the collection straight off the store, newest first, without reading or holding the rest of the collection in memory. This is what the built-in `GET {basePath}` list route uses.
|
|
55
|
+
|
|
56
|
+
- **Built from:** [askKeyValueStoreQuery](../../core/key-value-store/ask-key-value-store-query.md) against the summary store, ordered by its `(type, updatedAt)` index (descending), with soft-deleted rows excluded by a query filter.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { askEventDocListPage } from 'quidproquo-features';
|
|
60
|
+
|
|
61
|
+
export function* listArticlesPage(nextPageKey?: string) {
|
|
62
|
+
return yield* askEventDocListPage({ nextPageKey }); // QpqPagedData<EventDocSummary>
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Signature
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
function* askEventDocListPage<T extends EventDocSummary = EventDocSummary>(
|
|
70
|
+
options?: EventDocListPageOptions,
|
|
71
|
+
): AskResponse<QpqPagedData<T>>;
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Parameters
|
|
75
|
+
|
|
76
|
+
#### `options` — `EventDocListPageOptions` (optional)
|
|
77
|
+
|
|
78
|
+
| Property | Type | Default | Description |
|
|
79
|
+
| --- | --- | --- | --- |
|
|
80
|
+
| `includeDeleted` | `boolean` | `false` | When `true`, soft-deleted documents are included. When `false` (default), they are excluded by a query filter. |
|
|
81
|
+
| `limit` | `number` | `10` | Maximum number of records to return in this page. |
|
|
82
|
+
| `nextPageKey` | `string` | — | Opaque cursor from a previous page's `nextPageKey`; pass it to fetch the following page. |
|
|
83
|
+
|
|
84
|
+
### Returns
|
|
85
|
+
|
|
86
|
+
`AskResponse<QpqPagedData<T>>` — `{ items: T[]; nextPageKey?: string }`, one page ordered by `updatedAt` descending.
|
|
87
|
+
|
|
88
|
+
### Notes
|
|
89
|
+
|
|
90
|
+
- Because the exclusion of soft-deleted rows is a query filter (applied **after** rows are read), a page can come back shorter than `limit` while more pages remain. Page on the presence of `nextPageKey`, never on item count, or documents past a short page are silently hidden.
|
|
91
|
+
- There is no jump-to-page-N or total count without reading the whole collection — the store hands back an opaque "continue from here" cursor, not an offset, so pages are walked rather than addressed.
|
|
92
|
+
|
|
48
93
|
## Related
|
|
49
94
|
|
|
50
95
|
- [askEventDocGetById / askEventDocGetByIdOrThrow](./ask-event-doc-get-by-id.md) — read a single document.
|
|
51
96
|
- [askEventDocGetByCode](./ask-event-doc-get-by-code.md) — find one document by its business `code`.
|
|
52
|
-
- [askEventDocSoftDelete](./ask-event-doc-soft-delete.md) — sets the `deletedAt`
|
|
53
|
-
- [
|
|
97
|
+
- [askEventDocSoftDelete](./ask-event-doc-soft-delete.md) — sets the `deletedAt` these actions filter on.
|
|
98
|
+
- [askKeyValueStoreQuery](../../core/key-value-store/ask-key-value-store-query.md) — the underlying paged query action.
|
|
99
|
+
- [defineEventDocSummary](../../../config/features/event-doc-summary.md) — declares the store (and its ordering index) these read from.
|
|
@@ -52,7 +52,7 @@ Each key is matched against a delivered message's `type` field. The key may be a
|
|
|
52
52
|
| Property | Type | Default | Description |
|
|
53
53
|
| --- | --- | --- | --- |
|
|
54
54
|
| `batchSize` | `number` | `0` | Max number of messages delivered to the consumer Lambda per invocation. `0` leaves the SQS default. When set (> 0), the event source uses this batch size. |
|
|
55
|
-
| `batchWindowInSeconds` | `number` | `
|
|
55
|
+
| `batchWindowInSeconds` | `number` | `0` | Max time SQS waits to fill a batch before invoking the consumer — invoke as soon as a message arrives. Only applied when `batchSize > 0`, and **not** applied to FIFO queues (which don't support a batching window). A queue that wants to accumulate a batch before invoking should set this explicitly. |
|
|
56
56
|
| `concurrency` | `number` | `1` | Consumer concurrency hint. |
|
|
57
57
|
| `maxTries` | `number` | `1` | How many times a message is delivered before it is sent to the dead-letter queue (SQS `maxReceiveCount`). |
|
|
58
58
|
| `ttRetryInSeconds` | `number` | `900` | Retry/visibility timeout in seconds — how long a message stays invisible while being processed before it can be redelivered. Also used as the consumer Lambda timeout. Capped at 900 (15 minutes). |
|
|
@@ -31,7 +31,7 @@ All paths are prefixed with the version segment `/v{version}` (default `/v1`):
|
|
|
31
31
|
|
|
32
32
|
| Method | Path | Purpose |
|
|
33
33
|
| --- | --- | --- |
|
|
34
|
-
| `GET` | `{basePath}` | List the collection's documents. |
|
|
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
36
|
| `GET` | `{basePath}/{id}/events` | List a document's event log. |
|
|
37
37
|
| `GET` | `{basePath}/{id}/render` | Render the document to HTML. **Only mounted when `eventRenderer` is set.** |
|