create-qpq-app 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +4 -4
  2. package/lib/commonjs/steps/013_printNextSteps.js +2 -2
  3. package/lib/commonjs/steps/013_printNextSteps.js.map +1 -1
  4. package/lib/esm/steps/013_printNextSteps.js +2 -2
  5. package/lib/esm/steps/013_printNextSteps.js.map +1 -1
  6. package/package.json +2 -2
  7. package/template/apps/qpqjs/bootstrap.qpq.ts +6 -0
  8. package/template/docusaurus/docs/actions/core/ai/ask-ai-prompt-stream.md +21 -1
  9. package/template/docusaurus/docs/actions/core/ai/ask-ai-prompt.md +9 -3
  10. package/template/docusaurus/docs/actions/core/array/ask-flat-map-parallel-batch.md +1 -1
  11. package/template/docusaurus/docs/actions/core/array/ask-map-parallel-batch.md +2 -2
  12. package/template/docusaurus/docs/actions/core/date/date-time-math.md +2 -2
  13. package/template/docusaurus/docs/actions/core/event/ask-process-event.md +1 -1
  14. package/template/docusaurus/docs/actions/core/file/ask-file-delete.md +3 -0
  15. package/template/docusaurus/docs/actions/core/file/ask-file-exists.md +3 -0
  16. package/template/docusaurus/docs/actions/core/file/ask-file-generate-temporary-secure-url.md +3 -0
  17. package/template/docusaurus/docs/actions/core/file/ask-file-generate-temporary-upload-secure-url.md +3 -0
  18. package/template/docusaurus/docs/actions/core/file/ask-file-is-cold-storage.md +3 -0
  19. package/template/docusaurus/docs/actions/core/file/ask-file-list-directory.md +4 -1
  20. package/template/docusaurus/docs/actions/core/file/ask-file-read-binary-contents.md +3 -0
  21. package/template/docusaurus/docs/actions/core/file/ask-file-read-object-json.md +5 -0
  22. package/template/docusaurus/docs/actions/core/file/ask-file-read-text-contents.md +4 -0
  23. package/template/docusaurus/docs/actions/core/file/ask-file-stream-open.md +3 -0
  24. package/template/docusaurus/docs/actions/core/file/ask-file-write-binary-contents.md +3 -0
  25. package/template/docusaurus/docs/actions/core/file/ask-file-write-object-json.md +3 -0
  26. package/template/docusaurus/docs/actions/core/file/ask-file-write-text-contents.md +3 -0
  27. package/template/docusaurus/docs/actions/core/graph-database/ask-graph-database-internal-field-names.md +1 -1
  28. package/template/docusaurus/docs/actions/core/json/ask-decode-json.md +1 -0
  29. package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-delete.md +9 -1
  30. package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-get-all.md +9 -1
  31. package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-get.md +9 -1
  32. package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-query-single.md +3 -0
  33. package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-query.md +4 -1
  34. package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-scan-all.md +2 -0
  35. package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-scan.md +12 -0
  36. package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-update-partial-properties.md +5 -1
  37. package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-update.md +9 -1
  38. package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-upsert-with-retry.md +1 -1
  39. package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-upsert.md +3 -0
  40. package/template/docusaurus/docs/actions/core/network/ask-network-request.md +1 -1
  41. package/template/docusaurus/docs/actions/core/state/ask-reduce-state.md +1 -1
  42. package/template/docusaurus/docs/actions/core/state/ask-state-dispatch.md +1 -1
  43. package/template/docusaurus/docs/actions/core/stream/ask-stream-map.md +1 -1
  44. package/template/docusaurus/docs/actions/core/stream/ask-stream-process.md +2 -1
  45. package/template/docusaurus/docs/actions/core/system/ask-trace-story.md +66 -0
  46. package/template/docusaurus/docs/actions/core/user-directory/ask-user-directory-authenticate-user.md +3 -3
  47. package/template/docusaurus/docs/actions/core/user-directory/ask-user-directory-set-access-token.md +1 -0
  48. package/template/docusaurus/docs/actions/features/event-doc/ask-apply-event-doc-event.md +47 -0
  49. package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-event-append.md +1 -0
  50. package/template/docusaurus/docs/actions/features/event-doc-ai/ask-event-doc-ai-process-send.md +11 -8
  51. package/template/docusaurus/docs/actions/features/web-socket-queue/_category_.json +1 -0
  52. package/template/docusaurus/docs/actions/{webserver/service → features/web-socket-queue}/ask-service-request.md +4 -3
  53. package/template/docusaurus/docs/actions/webserver/generic-data-resource/ask-put-generic-data-resource.md +4 -0
  54. package/template/docusaurus/docs/actions/webserver/generic-data-resource/ask-scan-generic-data-resource.md +4 -0
  55. package/template/docusaurus/docs/actions/webserver/websocket/ask-websocket-send-message.md +2 -2
  56. package/template/docusaurus/docs/architecture-overview.md +2 -0
  57. package/template/docusaurus/docs/config/config-aws/bootstrap-waf.md +36 -0
  58. package/template/docusaurus/docs/config/core/action-processors.md +24 -2
  59. package/template/docusaurus/docs/config/core/ai.md +5 -5
  60. package/template/docusaurus/docs/config/core/bundle-options.md +102 -0
  61. package/template/docusaurus/docs/config/core/global.md +1 -1
  62. package/template/docusaurus/docs/config/core/queue.md +1 -1
  63. package/template/docusaurus/docs/config/features/admin-settings.md +4 -7
  64. package/template/docusaurus/docs/config/features/event-doc-routes.md +2 -0
  65. package/template/docusaurus/docs/config/features/event-doc.md +2 -0
  66. package/template/docusaurus/docs/config/{webserver → features}/state-dispatch-over-websockets.md +2 -2
  67. package/template/docusaurus/docs/config/features/tenant-stores.md +80 -0
  68. package/template/docusaurus/docs/config/features/tenant.md +81 -0
  69. package/template/docusaurus/docs/config/features/tenanted-event-doc.md +51 -0
  70. package/template/docusaurus/docs/config/features/tenanted-web-socket-queue.md +52 -0
  71. package/template/docusaurus/docs/config/{webserver → features}/web-socket-queue.md +11 -8
  72. package/template/docusaurus/docs/config/webserver/websocket.md +3 -3
  73. package/template/package.json +4 -13
  74. package/template/docusaurus/docs/actions/webserver/service/_category_.json +0 -1
@@ -37,7 +37,13 @@ function* askKeyValueStoreGetAll<Value>(
37
37
  | Parameter | Type | Description |
38
38
  | --- | --- | --- |
39
39
  | `keyValueStoreName` | `string` | Name of the store to read from — must match a store declared with [defineKeyValueStore](../../../config/core/key-value-store.md) (or one shared via its `owner` option). |
40
- | `options` | `KeyValueStoreGetAllOptions` | Reserved for future read options; currently empty. |
40
+ | `options` | `KeyValueStoreGetAllOptions` | Optional read options (see below). |
41
+
42
+ ### `KeyValueStoreGetAllOptions`
43
+
44
+ | Property | Type | Default | Description |
45
+ | --- | --- | --- | --- |
46
+ | `scope` | `string` | – | Optional storage scope. The processor enforces it as a begins-with prefix filter on the partition key, so only records written under the same scope are returned (used by tenant/scoped features). Requires the store's partition key to be string-typed. When no scope is given, scope-composed records are excluded, so one tenant's data never appears in an unscoped listing. |
41
47
 
42
48
  ## Returns
43
49
 
@@ -49,6 +55,8 @@ function* askKeyValueStoreGetAll<Value>(
49
55
  | --- | --- |
50
56
  | `KeyValueStoreGetAllErrorTypeEnum.ServiceUnavailable` | DynamoDB internal error or throttling. |
51
57
  | `KeyValueStoreGetAllErrorTypeEnum.ResourceNotFound` | The underlying table does not exist. |
58
+ | `KeyValueStoreGetAllErrorTypeEnum.InvalidScope` | The `scope` option is malformed (empty, `.`, over 128 characters, or containing path separators, `..`, `@`, or null bytes), the store's partition key is not string-typed, or a scoped call's partition-key value contains the reserved `@@QPQSCOPE@@` delimiter. |
59
+ | `KeyValueStoreGetAllErrorTypeEnum.StoreNotFound` | The key value store is not declared in the qpq config (misconfiguration, e.g. a wrong name or a missing `defineKeyValueStore`). |
52
60
 
53
61
  Catch errors with `askCatch` — it returns `{ success: true, result }` or `{ success: false, error }`.
54
62
 
@@ -39,7 +39,13 @@ function* askKeyValueStoreGet<Value>(
39
39
  | --- | --- | --- |
40
40
  | `keyValueStoreName` | `string` | Name of the store to read from — must match a store declared with [defineKeyValueStore](../../../config/core/key-value-store.md) (or one shared via its `owner` option). |
41
41
  | `key` | `string` | The partition key value of the record to fetch. |
42
- | `options` | `KeyValueStoreGetOptions` | Reserved for future read options; currently empty. |
42
+ | `options` | `KeyValueStoreGetOptions` | Optional read options (see below). |
43
+
44
+ ### `KeyValueStoreGetOptions`
45
+
46
+ | Property | Type | Default | Description |
47
+ | --- | --- | --- | --- |
48
+ | `scope` | `string` | – | Optional storage scope. The processor composes it into the partition key value, so scoped and unscoped records live under separate keys in the same store (used by tenant/scoped features). Requires the store's partition key to be string-typed. |
43
49
 
44
50
  ## Returns
45
51
 
@@ -51,6 +57,8 @@ function* askKeyValueStoreGet<Value>(
51
57
  | --- | --- |
52
58
  | `KeyValueStoreGetErrorTypeEnum.ServiceUnavailable` | DynamoDB internal error or throttling. |
53
59
  | `KeyValueStoreGetErrorTypeEnum.ResourceNotFound` | The underlying table does not exist. |
60
+ | `KeyValueStoreGetErrorTypeEnum.InvalidScope` | The `scope` option is malformed (empty, `.`, over 128 characters, or containing path separators, `..`, `@`, or null bytes), the store's partition key is not string-typed, or the partition-key value contains the reserved `@@QPQSCOPE@@` delimiter (reserved on string-pk stores, so unscoped calls reject it too). |
61
+ | `KeyValueStoreGetErrorTypeEnum.StoreNotFound` | The key value store is not declared in the qpq config (misconfiguration, e.g. a wrong name or a missing `defineKeyValueStore`). |
54
62
 
55
63
  Errors thrown by actions can be caught with `askCatch` from quidproquo-core. It returns an object — `{ success: true, result }` on success, or `{ success: false, error }` on failure:
56
64
 
@@ -32,6 +32,7 @@ function* askKeyValueStoreQuerySingle<T>(
32
32
  filter?: KvsQueryOperation,
33
33
  sortAscending?: boolean,
34
34
  limit?: number,
35
+ scope?: string,
35
36
  ): AskResponse<T | null>;
36
37
  ```
37
38
 
@@ -44,6 +45,7 @@ function* askKeyValueStoreQuerySingle<T>(
44
45
  | `filter` | `KvsQueryOperation` | – | Optional filter on non-key attributes, applied after the key match. |
45
46
  | `sortAscending` | `boolean` | – | Order by the sort key; `false` returns the highest/newest first. |
46
47
  | `limit` | `number` | `1` | How many records to fetch per underlying page while searching. The story still returns only the first matching record. |
48
+ | `scope` | `string` | – | Optional storage scope, passed through as the underlying query's `scope` option. See [`KeyValueStoreQueryOptions`](./ask-key-value-store-query.md#keyvaluestorequeryoptions). An invalid scope surfaces as `KeyValueStoreQueryErrorTypeEnum.InvalidScope`. |
47
49
 
48
50
  ## Returns
49
51
 
@@ -52,6 +54,7 @@ function* askKeyValueStoreQuerySingle<T>(
52
54
  ## Notes
53
55
 
54
56
  - Because `filter` is applied after the key match, when a filter is supplied the helper pages through results until it has gathered up to `limit` records before returning the first — so a matching record isn't missed just because it fell outside the first page.
57
+ - The record returned is the first one **collected across all fetched pages**, so a match found on an early page still wins even when a later page comes back empty.
55
58
  - Errors surface the same as the underlying query (`KeyValueStoreQueryErrorTypeEnum`); catch them with `askCatch`.
56
59
 
57
60
  ## Related
@@ -55,7 +55,8 @@ 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
- | `ttlInSeconds` | `number` | – | Time-to-live in seconds for a cached result of this query. |
58
+ | `ttlInSeconds` | `number` | – | Accepted but not implemented: no processor currently applies a TTL to query results, so setting it has no effect. |
59
+ | `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. |
59
60
 
60
61
  ## Query conditions (`KvsQueryOperation`)
61
62
 
@@ -116,6 +117,8 @@ When `nextPageKey` is set, pass it back as `options.nextPageKey` to fetch the ne
116
117
  | --- | --- |
117
118
  | `KeyValueStoreQueryErrorTypeEnum.ServiceUnavailable` | DynamoDB internal error or throttling. |
118
119
  | `KeyValueStoreQueryErrorTypeEnum.ResourceNotFound` | The underlying table does not exist. |
120
+ | `KeyValueStoreQueryErrorTypeEnum.InvalidScope` | The `scope` option is malformed (empty, `.`, over 128 characters, or containing path separators, `..`, `@`, or null bytes), the store's partition key is not string-typed, a scoped query's key condition does not constrain the partition key (or constrains it with an operator that cannot be scoped), or a partition-key condition value contains the reserved `@@QPQSCOPE@@` delimiter (reserved on string-pk stores, so unscoped queries reject it too). |
121
+ | `KeyValueStoreQueryErrorTypeEnum.StoreNotFound` | The key value store is not declared in the qpq config (misconfiguration, e.g. a wrong name or a missing `defineKeyValueStore`). |
119
122
 
120
123
  Catch errors with `askCatch` — it returns `{ success: true, result }` or `{ success: false, error }`.
121
124
 
@@ -29,6 +29,7 @@ export function* askAllSuspendedUsers() {
29
29
  function* askKeyValueStoreScanAll<T>(
30
30
  storeName: string,
31
31
  filterCondition?: KvsQueryOperation,
32
+ options?: KeyValueStoreScanOptions,
32
33
  ): AskResponse<T[]>;
33
34
  ```
34
35
 
@@ -38,6 +39,7 @@ function* askKeyValueStoreScanAll<T>(
38
39
  | --- | --- | --- |
39
40
  | `storeName` | `string` | Name of the store to scan — must match a store declared with [defineKeyValueStore](../../../config/core/key-value-store.md). |
40
41
  | `filterCondition` | `KvsQueryOperation` | Optional filter applied to every scanned record — see [Query conditions](./ask-key-value-store-query.md#query-conditions-kvsqueryoperation). Omit it to return every record. |
42
+ | `options` | `KeyValueStoreScanOptions` | Optional scan options, forwarded to the underlying scan on **every** page (see [`KeyValueStoreScanOptions`](./ask-key-value-store-scan.md#keyvaluestorescanoptions)). In particular `scope` restricts every page to records written under that storage scope. |
41
43
 
42
44
  ## Returns
43
45
 
@@ -30,6 +30,7 @@ function* askKeyValueStoreScan<KvsItem>(
30
30
  keyValueStoreName: string,
31
31
  filterCondition?: KvsQueryOperation,
32
32
  nextPageKey?: string,
33
+ options?: KeyValueStoreScanOptions,
33
34
  ): AskResponse<QpqPagedData<KvsItem>>;
34
35
  ```
35
36
 
@@ -40,6 +41,15 @@ function* askKeyValueStoreScan<KvsItem>(
40
41
  | `keyValueStoreName` | `string` | Name of the store to scan — must match a store declared with [defineKeyValueStore](../../../config/core/key-value-store.md) (or one shared via its `owner` option). |
41
42
  | `filterCondition` | `KvsQueryOperation` | Optional filter applied to every scanned record. Built with the `kvs*` condition helpers — see [Query conditions](./ask-key-value-store-query.md#query-conditions-kvsqueryoperation). Omit it to return everything. |
42
43
  | `nextPageKey` | `string` | Opaque cursor from a previous page's `nextPageKey`; pass it to fetch the following page. |
44
+ | `options` | `KeyValueStoreScanOptions` | Optional scan options (see below). |
45
+
46
+ ### `KeyValueStoreScanOptions`
47
+
48
+ | Property | Type | Default | Description |
49
+ | --- | --- | --- | --- |
50
+ | `limit` | `number` | – | Accepted but not implemented: no processor currently caps the page size of a scan, so setting it has no effect. |
51
+ | `ttlInSeconds` | `number` | – | Accepted but not implemented: no processor currently applies a TTL to scans, so setting it has no effect. |
52
+ | `scope` | `string` | – | Optional storage scope. The processor enforces it as a begins-with prefix filter on the partition key, so only records written under the same scope are returned (used by tenant/scoped features). It is still a full-table scan on the storage side; only the results are isolated. Requires the store's partition key to be string-typed. When no scope is given, scope-composed records are excluded, so one tenant's data never appears in an unscoped listing. |
43
53
 
44
54
  ## Returns
45
55
 
@@ -60,6 +70,8 @@ When `nextPageKey` is set, pass it back as the third argument to fetch the next
60
70
  | --- | --- |
61
71
  | `KeyValueStoreScanErrorTypeEnum.ServiceUnavailable` | DynamoDB internal error or throttling. |
62
72
  | `KeyValueStoreScanErrorTypeEnum.ResourceNotFound` | The underlying table does not exist. |
73
+ | `KeyValueStoreScanErrorTypeEnum.InvalidScope` | The `scope` option is malformed (empty, `.`, over 128 characters, or containing path separators, `..`, `@`, or null bytes), the store's partition key is not string-typed, or a scoped call's partition-key value contains the reserved `@@QPQSCOPE@@` delimiter. |
74
+ | `KeyValueStoreScanErrorTypeEnum.StoreNotFound` | The key value store is not declared in the qpq config (misconfiguration, e.g. a wrong name or a missing `defineKeyValueStore`). |
63
75
 
64
76
  Catch errors with `askCatch` — it returns `{ success: true, result }` or `{ success: false, error }`.
65
77
 
@@ -38,6 +38,8 @@ function* askKeyValueStoreUpdatePartialProperties<TModel, PartitionKey extends k
38
38
  keyValueStoreName: string,
39
39
  partitionKeyName: PartitionKey,
40
40
  partialProperties: Partial<TModel> & { [k in PartitionKey]: TModel[PartitionKey] },
41
+ sortKeyName?: undefined,
42
+ options?: KeyValueStoreUpdateOptions,
41
43
  ): AskResponse<TModel>;
42
44
 
43
45
  // Partition + sort key
@@ -46,6 +48,7 @@ function* askKeyValueStoreUpdatePartialProperties<TModel, PartitionKey extends k
46
48
  partitionKeyName: PartitionKey,
47
49
  partialProperties: Partial<TModel> & { [k in PartitionKey]: TModel[PartitionKey] } & { [k in SortKey]: TModel[SortKey] },
48
50
  sortKeyName: SortKey,
51
+ options?: KeyValueStoreUpdateOptions,
49
52
  ): AskResponse<TModel>;
50
53
  ```
51
54
 
@@ -57,6 +60,7 @@ function* askKeyValueStoreUpdatePartialProperties<TModel, PartitionKey extends k
57
60
  | `partitionKeyName` | `keyof TModel` | The property name that holds the partition key. That property must be present in `partialProperties`; it is used to address the record, not written. |
58
61
  | `partialProperties` | `Partial<TModel>` (with the key properties required) | The record patch. Each non-key property becomes a `Set`; a property explicitly set to `undefined` becomes a `Remove`. |
59
62
  | `sortKeyName` | `keyof TModel` | (Overload) The property name holding the sort key, when the store has one. Must be present in `partialProperties`. |
63
+ | `options` | `KeyValueStoreUpdateOptions` | Optional update options, passed straight through to the underlying update (see [`KeyValueStoreUpdateOptions`](./ask-key-value-store-update.md#keyvaluestoreupdateoptions)). In particular `scope` addresses the record written under that storage scope. |
60
64
 
61
65
  ## Returns
62
66
 
@@ -65,7 +69,7 @@ function* askKeyValueStoreUpdatePartialProperties<TModel, PartitionKey extends k
65
69
  ## Notes
66
70
 
67
71
  - The partition-key (and sort-key) properties are skipped when building the operations — they identify the record rather than mutate it.
68
- - A property whose value is `undefined` is turned into a `Remove`; a property with a valid value is turned into a `Set`. Values that aren't a supported store data type are silently skipped (validated internally via `isValidKvsAdvancedDataType`).
72
+ - A property whose value is `undefined` is turned into a `Remove`; a property with a valid value is turned into a `Set`. A value that isn't a supported store data type (validated via `isValidKvsAdvancedDataType`, e.g. a nested object) throws an `InvalidKvsPartialPropertyError` (code `unsupportedValueType`) before anything is written, rather than being silently skipped.
69
73
  - Errors surface the same as the underlying update (`KeyValueStoreUpdateErrorTypeEnum`); catch them with `askCatch`.
70
74
 
71
75
  ## Related
@@ -52,7 +52,13 @@ function* askKeyValueStoreUpdate<Value>(
52
52
  | `updates` | `KvsUpdate` | The list of update operations to apply. See [Update operations](#update-operations-kvsupdate). |
53
53
  | `key` | `KvsCoreDataType` | Partition key value of the record to update (`string \| number`). |
54
54
  | `sortKey` | `KvsCoreDataType` | Sort key value, required only if the store declares a sort key (`string \| number`). |
55
- | `options` | `KeyValueStoreUpdateOptions` | Reserved for future update options; currently empty. |
55
+ | `options` | `KeyValueStoreUpdateOptions` | Optional update options (see below). |
56
+
57
+ ### `KeyValueStoreUpdateOptions`
58
+
59
+ | Property | Type | Default | Description |
60
+ | --- | --- | --- | --- |
61
+ | `scope` | `string` | – | Optional storage scope. The processor composes it into the partition key value, so the update only addresses the record written under that scope (used by tenant/scoped features). Requires the store's partition key to be string-typed. |
56
62
 
57
63
  ## Update operations (`KvsUpdate`)
58
64
 
@@ -95,6 +101,8 @@ Each `path` is a `KvsAttributePath` — either a top-level attribute name (`'bal
95
101
  | --- | --- |
96
102
  | `KeyValueStoreUpdateErrorTypeEnum.ServiceUnavailable` | DynamoDB internal error or throttling. |
97
103
  | `KeyValueStoreUpdateErrorTypeEnum.ResourceNotFound` | The underlying table does not exist. |
104
+ | `KeyValueStoreUpdateErrorTypeEnum.InvalidScope` | The `scope` option is malformed (empty, `.`, over 128 characters, or containing path separators, `..`, `@`, or null bytes), the store's partition key is not string-typed, or the partition-key value contains the reserved `@@QPQSCOPE@@` delimiter (reserved on string-pk stores, so unscoped calls reject it too). |
105
+ | `KeyValueStoreUpdateErrorTypeEnum.StoreNotFound` | The key value store is not declared in the qpq config (misconfiguration, e.g. a wrong name or a missing `defineKeyValueStore`). |
98
106
 
99
107
  Catch errors with `askCatch` — it returns `{ success: true, result }` or `{ success: false, error }`.
100
108
 
@@ -42,7 +42,7 @@ function* askKeyValueStoreUpsertWithRetry<KvsItem>(
42
42
 
43
43
  ### `KeyValueStoreUpsertWithRetryOptions`
44
44
 
45
- Extends `KeyValueStoreUpsertOptions` (`ttlInSeconds`, `ifNotExists`) with:
45
+ Extends `KeyValueStoreUpsertOptions` (`ttlInSeconds`, `ifNotExists`, `scope`) with:
46
46
 
47
47
  | Property | Type | Default | Description |
48
48
  | --- | --- | --- | --- |
@@ -47,6 +47,7 @@ function* askKeyValueStoreUpsert<KvsItem>(
47
47
  | --- | --- | --- | --- |
48
48
  | `ttlInSeconds` | `number` | – | Time-to-live in seconds; sets the record's expiry (used with the store's `ttlAttribute`). |
49
49
  | `ifNotExists` | `boolean` | `false` | Conditional insert: only write when no item with the same key exists. A losing concurrent writer receives `KeyValueStoreUpsertErrorTypeEnum.Conflict` instead of silently overwriting — the primitive for optimistic-concurrency schemes (e.g. append-only event logs where the sort key is a claimed index). |
50
+ | `scope` | `string` | – | Optional storage scope. The processor composes it into the item's partition key value, so the record is written under that scope and only scoped reads see it (used by tenant/scoped features). Requires the store's partition key to be string-typed. |
50
51
 
51
52
  ## Returns
52
53
 
@@ -59,6 +60,8 @@ function* askKeyValueStoreUpsert<KvsItem>(
59
60
  | `KeyValueStoreUpsertErrorTypeEnum.ServiceUnavailable` | DynamoDB internal error or throttling. |
60
61
  | `KeyValueStoreUpsertErrorTypeEnum.ResourceNotFound` | The underlying table does not exist. |
61
62
  | `KeyValueStoreUpsertErrorTypeEnum.Conflict` | A conditional (`ifNotExists`) write lost to an existing item. Namespaced — not the generic `ErrorTypeEnum.Conflict` — so retry logic can target the write race specifically without also catching domain-level conflicts. |
63
+ | `KeyValueStoreUpsertErrorTypeEnum.InvalidScope` | The `scope` option is malformed (empty, `.`, over 128 characters, or containing path separators, `..`, `@`, or null bytes), the store's partition key is not string-typed, or the partition-key value contains the reserved `@@QPQSCOPE@@` delimiter (reserved on string-pk stores, so unscoped calls reject it too). |
64
+ | `KeyValueStoreUpsertErrorTypeEnum.StoreNotFound` | The key value store is not declared in the qpq config (misconfiguration, e.g. a wrong name or a missing `defineKeyValueStore`). |
62
65
 
63
66
  Catch errors with `askCatch` — it returns `{ success: true, result }` or `{ success: false, error }`.
64
67
 
@@ -88,7 +88,7 @@ if (outcome.success) {
88
88
 
89
89
  ## Notes
90
90
 
91
- - Requests abort after 25 seconds, surfacing as `NetworkRequestErrorTypeEnum.Timeout`.
91
+ - Requests abort after 30 seconds, surfacing as `NetworkRequestErrorTypeEnum.Timeout`. (30s deliberately outlasts API Gateway's 29s integration timeout, so a slow upstream shows up as a real gateway error rather than a client-side abort.)
92
92
 
93
93
  ## Related
94
94
 
@@ -157,4 +157,4 @@ Effects the reducer doesn't handle leave the state unchanged and are skipped. Us
157
157
 
158
158
  - [askStateDispatch](./ask-state-dispatch.md) — dispatch the effects this story reduces.
159
159
  - [askStateRead](./ask-state-read.md) — read the accumulated state mid-story.
160
- - [defineStateDispatchOverWebsockets](../../../config/webserver/state-dispatch-over-websockets.md) — stream reduced dispatches to a WebSocket client.
160
+ - [defineStateDispatchOverWebsockets](../../../config/features/state-dispatch-over-websockets.md) — stream reduced dispatches to a WebSocket client.
@@ -84,4 +84,4 @@ function* askStateDispatchEffect<E extends Effect<any, any>>(
84
84
 
85
85
  - [askStateRead](./ask-state-read.md) — read the state that dispatches accumulate into.
86
86
  - [askReduceState](./ask-reduce-state.md) — run a story purely for its dispatched effects and return the reduced state.
87
- - [defineStateDispatchOverWebsockets](../../../config/webserver/state-dispatch-over-websockets.md) — redirect these dispatches to a connected WebSocket client.
87
+ - [defineStateDispatchOverWebsockets](../../../config/features/state-dispatch-over-websockets.md) — redirect these dispatches to a connected WebSocket client.
@@ -61,7 +61,7 @@ function* askStreamMap<E extends StreamEncoding, T, R = StreamDataType<E, T>>(
61
61
  ## Notes
62
62
 
63
63
  - The `index` counts only chunks handed to your callback — chunks that are `skipped` or carry no `data` are dropped and do not advance it.
64
- - The handle is closed for you when the loop ends.
64
+ - The handle is closed for you when the loop ends. This includes failures: when a read or your callback fails, the stream is closed first and the original error is then rethrown, so a surrounding [askCatch](../system/ask-catch.md) can handle it without leaking the stream.
65
65
  - Because it buffers the entire stream in memory, prefer [askStreamProcess](./ask-stream-process.md) when you only need to react to each chunk and don't need the full list.
66
66
 
67
67
  ## Related
@@ -46,7 +46,8 @@ function* askStreamProcess<E extends StreamEncoding, T>(
46
46
  ## Notes
47
47
 
48
48
  - The `index` counts only chunks handed to your callback — chunks that are `skipped` or carry no `data` are dropped and do not advance it.
49
- - The handle is closed for you when the loop ends. If you need finer control — reading only part of a stream, or polling with `noWait` — drive [askStreamRead](./ask-stream-read.md) / [askStreamClose](./ask-stream-close.md) directly instead.
49
+ - The handle is closed for you when the loop ends. This includes failures: when a read or your callback fails, the stream is closed first and the original error is then rethrown, so a surrounding [askCatch](../system/ask-catch.md) can handle it without leaking the stream.
50
+ - If you need finer control — reading only part of a stream, or polling with `noWait` — drive [askStreamRead](./ask-stream-read.md) / [askStreamClose](./ask-stream-close.md) directly instead.
50
51
  - To build up a result rather than react to each chunk, use [askStreamMap](./ask-stream-map.md).
51
52
 
52
53
  ## Related
@@ -0,0 +1,66 @@
1
+ ---
2
+ title: askTraceStory
3
+ description: Replay a recorded story against its real code and capture a line-by-line execution trace.
4
+ ---
5
+
6
+ # askTraceStory
7
+
8
+ Replays a previously recorded story execution (a `StoryResult` from the logs) against the story's real code and records a **line-by-line execution trace**: every statement the story executed, with the values of its local variables at that moment.
9
+
10
+ - **Action type:** `SystemActionType.TraceStory`
11
+ - **Runtime support:** only node runtimes (AWS Lambda and the dev server) implement a processor. The tracer drives the V8 inspector, so browser runtimes cannot process this action.
12
+
13
+ The recorded `StoryResult` already contains every action result the original run saw, so the replay is deterministic: the story re-executes with the exact same inputs and answers, and the tracer captures what happened between the actions. The story's code is loaded through the runtime's dynamic module loader (which federates on Lambda), so the trace runs the same code that ran originally.
14
+
15
+ ```typescript
16
+ import { askTraceStory } from 'quidproquo-core';
17
+
18
+ export function* askDebugExecution(storyResult: StoryResult<any>) {
19
+ const trace = yield* askTraceStory(storyResult);
20
+
21
+ // trace.steps: one entry per executed statement, with locals captured at that point.
22
+ return trace;
23
+ }
24
+ ```
25
+
26
+ ## Signature
27
+
28
+ ```typescript
29
+ function* askTraceStory(
30
+ storyResult: StoryResult<any>,
31
+ scriptPatterns?: string[],
32
+ onlyOwnCode?: boolean,
33
+ ): AskResponse<QpqExecutionTrace>;
34
+ ```
35
+
36
+ ## Parameters
37
+
38
+ | Parameter | Type | Description |
39
+ | --- | --- | --- |
40
+ | `storyResult` | `StoryResult<any>` | The recorded execution to replay. Must carry `qpqFunctionRuntimeInfo` so the processor can locate the story's code. |
41
+ | `scriptPatterns` | `string[]` | Optional regex sources matched against script urls, tracing extra scripts in addition to the story function's own script (for bundles split across chunks). |
42
+ | `onlyOwnCode` | `boolean` | Optional. When true, breakpoints are only set on statements whose source-mapped origin is the service's own code; positions mapping into node_modules (framework and dependencies) are skipped, so the step budget is spent entirely on user statements. |
43
+
44
+ ## Returns
45
+
46
+ `QpqExecutionTrace`: the trace of the replay.
47
+
48
+ - `sources`: the original source files the trace maps into (with content when source maps shipped `sourcesContent`).
49
+ - `steps`: one entry per executed statement (source position, function name, captured locals, and the return value at function-return points).
50
+ - `truncated`: true when the step budget was hit; the replay still ran to completion but later steps were not recorded.
51
+ - `stats`: pause/breakpoint counts and timing, including the urls of every instrumented script.
52
+
53
+ ## Errors
54
+
55
+ | Error type | When |
56
+ | --- | --- |
57
+ | `ErrorTypeEnum.BadRequest` | The `storyResult` has no `qpqFunctionRuntimeInfo`, so the processor cannot locate its code. |
58
+ | `ErrorTypeEnum.NotFound` | The story's module could not be dynamically loaded. |
59
+ | `ErrorTypeEnum.GenericError` | The tracer itself failed while replaying the story. |
60
+
61
+ Wrap the call in [askCatch](./ask-catch.md) to handle these as values.
62
+
63
+ ## Related
64
+
65
+ - [askGetRuntimeCorrelation](./ask-get-runtime-correlation.md): the correlation id that identifies the recorded execution to trace.
66
+ - [askExecuteStory](./ask-execute-story.md): run a story fresh from its runtime reference (rather than replaying a recorded run).
@@ -65,7 +65,7 @@ function* askUserDirectoryAuthenticateUser(
65
65
  | `userDirectoryName` | `string` | Name of the directory to authenticate against — must match a directory declared with [defineUserDirectory](../../../config/core/user-directory.md) (or one shared via its `owner` option). |
66
66
  | `isCustom` | `boolean` | `false` for a standard email + password sign-in; `true` to start Cognito's custom auth flow (requires a [customAuthRuntime](../../../config/core/user-directory.md#custom-auth-runtime)). When `true`, `password` is not used. |
67
67
  | `email` | `string` | The user's email / username. |
68
- | `password` | `string` | The user's password. Required for a standard (`isCustom: false`) sign-in; omitted for custom auth. |
68
+ | `password` | `string` | The user's password. Required for a standard (`isCustom: false`) sign-in: a missing or empty password throws `InvalidPassword`. Omitted for custom auth. |
69
69
 
70
70
  ## Returns
71
71
 
@@ -98,8 +98,8 @@ interface AuthenticationInfo {
98
98
 
99
99
  | Error | Meaning |
100
100
  | --- | --- |
101
- | `UserDirectoryAuthenticateUserErrorTypeEnum.UserNotFound` | No user matches the supplied email. |
102
- | `UserDirectoryAuthenticateUserErrorTypeEnum.InvalidPassword` | The supplied password is incorrect. |
101
+ | `UserDirectoryAuthenticateUserErrorTypeEnum.UserNotFound` | The email or password is incorrect. Unknown users and wrong passwords are deliberately reported the same way so callers cannot probe which accounts exist. |
102
+ | `UserDirectoryAuthenticateUserErrorTypeEnum.InvalidPassword` | A standard (`isCustom: false`) sign-in was attempted without a password. Thrown before the request reaches the identity provider. |
103
103
 
104
104
  Errors thrown by actions can be caught with `askCatch` from quidproquo-core. It returns an `EitherActionResult` — `{ success: true, result }` on success, or `{ success: false, error }` on failure:
105
105
 
@@ -43,6 +43,7 @@ function* askUserDirectorySetAccessToken(
43
43
  ## Notes
44
44
 
45
45
  - The token is stored on the session only; sessions are not transferred across service boundaries (queues, event buses, service functions), so the identity does not leak beyond the current in-process runtime.
46
+ - A token that cannot be verified (missing, malformed, wrong pool, bad signature, or expired) fails the action with a generic `Unauthorized` error and leaves the session untouched. Catch it with `askCatch` from quidproquo-core when a failed load should not abort the story.
46
47
 
47
48
  ## Related
48
49
 
@@ -0,0 +1,47 @@
1
+ ---
2
+ title: askApplyEventDocEvent
3
+ description: Declaratively apply an event-doc event, leaving HOW it is applied (optimistic UI append, direct server append, ...) to a registered processor.
4
+ ---
5
+
6
+ # askApplyEventDocEvent
7
+
8
+ A **purely declarative** way to apply an event-doc event: the story yields an `eventType` and its `data`, and a registered processor decides how that turns into an actual write — for example an optimistic local append followed by a POST in a browser editor, or a direct [askEventDocEventAppend](./ask-event-doc-event-append.md) on the backend. Because it has no side effect of its own, verbs written in terms of it (like a domain's `askXSetY` action creators) run unchanged wherever a processor for it is registered — backend, tests, or transforms.
9
+
10
+ - **Action type:** `EventDocActionType.ApplyEvent`
11
+ - **quidproquo-features ships no default processor** for this action. Each consumer registers the implementation that fits its runtime via `defineActionProcessors`. Calling it with no processor registered fails the same way any unhandled action type does.
12
+ - The version stamped on the resulting event is not passed by the caller — the registered processor (the "editor") stamps its own configured schema version and provenance when it applies the event.
13
+
14
+ ```typescript
15
+ import { askApplyEventDocEvent } from 'quidproquo-features';
16
+
17
+ export function* askTenantSetBrand(data: TenantSetBrandData) {
18
+ yield* askApplyEventDocEvent(TenantEffect.setBrand, data);
19
+ }
20
+ ```
21
+
22
+ ## Signature
23
+
24
+ ```typescript
25
+ function* askApplyEventDocEvent(eventType: string, data: unknown): AskResponse<void>;
26
+ ```
27
+
28
+ ## Parameters
29
+
30
+ | Parameter | Type | Description |
31
+ | --- | --- | --- |
32
+ | `eventType` | `string` | The effect/event type discriminant, matched by the reducer that folds the document and by the registered processor. |
33
+ | `data` | `unknown` | The typed domain data for the event, opaque to this action itself. |
34
+
35
+ ## Returns
36
+
37
+ `AskResponse<void>` — applying an event never returns a value; a processor that fails to apply it surfaces the failure as its own runtime's error state (e.g. UI error state in a browser processor) rather than throwing back through this call.
38
+
39
+ ## Notes
40
+
41
+ - This is the contract only — it carries no default behavior. A service that yields it must also register a processor for `EventDocActionType.ApplyEvent` (via `defineActionProcessors`) appropriate to where the story runs.
42
+ - Prefer this over calling [askEventDocEventAppend](./ask-event-doc-event-append.md) directly when you want the same verb (e.g. a domain's action creator) to work identically in a browser editor with optimistic updates and on the backend with a direct append.
43
+
44
+ ## Related
45
+
46
+ - [askEventDocEventAppend](./ask-event-doc-event-append.md) — the store-backed backend append a processor for this action typically delegates to.
47
+ - [askEventDocAppendServerEvent](./ask-event-doc-append-server-event.md) — builds a server-authored event envelope; a natural implementation for a backend `ApplyEvent` processor.
@@ -91,6 +91,7 @@ What the client POSTs to append an event. `modelId` and the server-stamped prove
91
91
  ## Related
92
92
 
93
93
  - [askEventDocAppendServerEvent](./ask-event-doc-append-server-event.md) — the server-authored wrapper that builds the input envelope for you.
94
+ - [askApplyEventDocEvent](./ask-apply-event-doc-event.md) — a declarative, processor-dispatched alternative when the same verb must also run in a browser editor.
94
95
  - [askEventDocEventWrite](./ask-event-doc-event-write.md) — the low-level conditional write this composes.
95
96
  - [askEventDocEventList / EventListAll / EventLast](./ask-event-doc-event-list.md) — reading the log this appends to.
96
97
  - [askEventDocProvideStore](./ask-event-doc-provide-store.md) — provides the store context this requires.
@@ -5,7 +5,7 @@ description: The backend chat turn — persist the user message, stream the mode
5
5
 
6
6
  # askEventDocAiProcessSend
7
7
 
8
- Runs one conversational turn on the backend. It appends the user's message to the chat history, streams the assistant's reply through [askAiPromptStream](../../core/ai/ask-ai-prompt-stream.md) — dispatching each stream part to the browser live — then folds the completed reply into durable segments, saves it, and bumps the chat to the top of the list. This is the handler behind the `ChatSend` websocket method (`onChatSend`).
8
+ Runs one conversational turn on the backend. It appends the user's message to the chat history, then streams the assistant's reply through [askAiPromptStream](../../core/ai/ask-ai-prompt-stream.md) — dispatching each stream part to the browser live — folding each completed reply into durable segments and saving it. If a round stops with `finishReason: toolCalls` (the underlying step limit cut it off while the model still wanted to act), it automatically resends the updated history so the model continues the same turn, up to a bounded number of rounds. Once a round finishes naturally, it bumps the chat to the top of the list and returns. This is the handler behind the `ChatSend` websocket method (`onChatSend`).
9
9
 
10
10
  - Built from [askConfigGetGlobal](../../core/config/ask-config-get-global.md), [askAiPromptStream](../../core/ai/ask-ai-prompt-stream.md), [askStreamMap](../../core/stream/ask-stream-map.md), the history/list helpers, and the `askUIEventDocAi*` stream dispatches.
11
11
 
@@ -47,13 +47,16 @@ function* askEventDocAiProcessSend(
47
47
  1. Reads the AI name, model, and reasoning budget from the feature's globals, and resolves the system prompt — freshest source first: the configured `systemPromptGenerator` inline function (run per-turn with `{ docId }` so it can carry live document state), else the static `systemPrompt`, else a built-in default. The prompt is never persisted.
48
48
  2. [Validates the attachments](./ask-event-doc-ai-attachments-validate.md) against the document's storage drive and trusted `docId`.
49
49
  3. Loads the chat history, appends the new user message (attachments first, then text), and **saves** immediately — so a refresh mid-reply still shows the question.
50
- 4. Streams the reply with [askAiPromptStream](../../core/ai/ask-ai-prompt-stream.md). File segments in the history become drive-referenced file parts (no URLs), which the action processor resolves to contents at prompt time. Tools do **not** receive `docId` from the model — executors inherit the session context and read the trusted id there.
51
- 5. Maps over the stream with [askStreamMap](../../core/stream/ask-stream-map.md), dispatching each part to the UI as it arrives (the live typing view) and collecting the parts.
52
- 6. Folds the collected parts into durable segments (`text`/`reasoning` deltas merged, tool calls paired with results). A stream that produced no content (e.g. it errored before any text) saves no assistant message.
53
- 7. If there are segments, **saves** the finalized assistant message, dispatches it to the UI as the finalized message, then clears the UI's now-superseded live-stream buffer.
54
- 8. Touches the chat (bumps `updatedAt`) and returns `{ complete: true }`.
55
-
56
- Steps 5–7 use the `askUIEventDocAiAppendStreamChunk`, `askUIEventDocAiAppendChatMessage`, and `askUIEventDocAiClearStream` UI actions — the client renders the reply from those dispatches while this request is still in flight, then reconciles to the finalized message.
50
+ 4. Runs one or more rounds (bounded, currently up to 20):
51
+ 1. Streams the reply with [askAiPromptStream](../../core/ai/ask-ai-prompt-stream.md). File segments in the history become drive-referenced file parts (no URLs), which the action processor resolves to contents at prompt time. Tools do **not** receive `docId` from the model — executors inherit the session context and read the trusted id there. From the second round on, a transport-only nudge message (never saved to history) is appended so the conversation doesn't end on an assistant turn.
52
+ 2. Maps over the stream with [askStreamMap](../../core/stream/ask-stream-map.md), dispatching each part to the UI as it arrives (the live typing view) and collecting the parts.
53
+ 3. Folds the collected parts into durable segments (`text`/`reasoning` deltas merged, tool calls paired with results). A stream that produced no content (e.g. it errored before any text) saves no assistant message.
54
+ 4. If there are segments, **saves** the finalized assistant message and dispatches it to the UI as the finalized message.
55
+ 5. Clears the UI's now-superseded live-stream buffer.
56
+ 6. If the round's [`finishReason`](../../core/ai/ask-ai-prompt-stream.md#aistreamfinishreasonenum) was `toolCalls` (halted early) and it produced segments, loops back to stream another round from the updated history; otherwise stops.
57
+ 5. Touches the chat (bumps `updatedAt`) and returns `{ complete: true }`.
58
+
59
+ Steps 4.ii–4.v use the `askUIEventDocAiAppendStreamChunk`, `askUIEventDocAiAppendChatMessage`, and `askUIEventDocAiClearStream` UI actions — the client renders the reply from those dispatches while this request is still in flight, then reconciles to the finalized message. Because rounds can repeat, the UI may see more than one finalized assistant message appended for what is presented as a single turn.
57
60
 
58
61
  ## The chat model
59
62
 
@@ -0,0 +1 @@
1
+ { "label": "WebSocket queue", "link": { "type": "generated-index", "description": "Send RPC-style requests to another qpq service over the WebSocket queue." } }
@@ -11,7 +11,7 @@ Sends an **RPC-style request to another qpq service**, identified by a service n
11
11
  - The request is dispatched as a `qpq/serviceRequest/<serviceName>/<method>` message and its correlated response is awaited. In a browser (quidproquo-web-react) client this travels over the app's WebSocket queue connection; the response is returned when the correlated reply arrives.
12
12
 
13
13
  ```typescript
14
- import { askServiceRequest } from 'quidproquo-webserver';
14
+ import { askServiceRequest } from 'quidproquo-features';
15
15
 
16
16
  interface GetQuoteRequest { symbol: string; }
17
17
  interface GetQuoteResponse { symbol: string; price: number; }
@@ -30,7 +30,7 @@ export function* askGetQuote(symbol: string) {
30
30
  Prefer `createServiceRequester` for a typed, reusable wrapper:
31
31
 
32
32
  ```typescript
33
- import { createServiceRequester } from 'quidproquo-webserver';
33
+ import { createServiceRequester } from 'quidproquo-features';
34
34
 
35
35
  // Bind the service + method once, with request/response types
36
36
  const askGetQuote = createServiceRequester<GetQuoteRequest, GetQuoteResponse>('market', 'getQuote');
@@ -65,5 +65,6 @@ function* askServiceRequest<TPayload, TResponse>(
65
65
 
66
66
  ## Related
67
67
 
68
- - [askServiceFunctionExecute](../service-function/ask-service-function-execute.md) — direct Lambda-invoke of a named service function.
68
+ - [askServiceFunctionExecute](../../webserver/service-function/ask-service-function-execute.md) — direct Lambda-invoke of a named service function.
69
+ - [defineWebSocketQueue](../../../config/features/web-socket-queue.md) — the WebSocket queue this request travels over in a browser client.
69
70
  - [askCatch](../../core/system/ask-catch.md) — catch errors returned by the service handler.
@@ -9,6 +9,10 @@ Writes a single item into a **generic data resource** — a named table addresse
9
9
 
10
10
  - **Action type:** `GenericDataResourceActionTypeEnum.Put`
11
11
 
12
+ :::warning Not implemented by any runtime
13
+ No action processor currently implements this action on any platform (AWS, dev server, or browser). Yielding it from a story will fail at runtime with an unknown-action error. The action type exists so tooling (like the admin log viewer) can label historical log entries. Use a [key-value store](../../../config/core/key-value-store.md) for application data.
14
+ :::
15
+
12
16
  ```typescript
13
17
  import { askPutGenericDataResource } from 'quidproquo-webserver';
14
18
 
@@ -9,6 +9,10 @@ Reads items out of a **generic data resource** — a named table addressed direc
9
9
 
10
10
  - **Action type:** `GenericDataResourceActionTypeEnum.Scan`
11
11
 
12
+ :::warning Not implemented by any runtime
13
+ No action processor currently implements this action on any platform (AWS, dev server, or browser). Yielding it from a story will fail at runtime with an unknown-action error. The action type exists so tooling (like the admin log viewer) can label historical log entries. Use a [key-value store](../../../config/core/key-value-store.md) for application data.
14
+ :::
15
+
12
16
  ```typescript
13
17
  import { askScanGenericDataResource } from 'quidproquo-webserver';
14
18
 
@@ -35,7 +35,7 @@ function* askWebsocketSendMessage<T>(
35
35
 
36
36
  | Parameter | Type | Description |
37
37
  | --- | --- | --- |
38
- | `websocketApiName` | `string` | The `apiName` of the target [WebSocket API](../../../config/webserver/websocket.md) (the `apiName` option of [defineWebsocket](../../../config/webserver/websocket.md), or the `apiName` argument to [defineWebSocketQueue](../../../config/webserver/web-socket-queue.md)). Identifies which deployed API the connection belongs to. |
38
+ | `websocketApiName` | `string` | The `apiName` of the target [WebSocket API](../../../config/webserver/websocket.md) (the `apiName` option of [defineWebsocket](../../../config/webserver/websocket.md), or the `apiName` argument to [defineWebSocketQueue](../../../config/features/web-socket-queue.md)). Identifies which deployed API the connection belongs to. |
39
39
  | `connectionId` | `string` | Identifier of the client connection to send to. You receive this on the `WebsocketEvent` in your connect/message handlers; persist it (e.g. keyed by user) if you need to send outside the request that created it. |
40
40
  | `payload` | `T` | The message to send. Serialized to JSON before transmission, so any JSON-serializable value works. |
41
41
 
@@ -67,5 +67,5 @@ export function* askTrySend(connectionId: string, payload: unknown) {
67
67
  ## Related
68
68
 
69
69
  - [defineWebsocket](../../../config/webserver/websocket.md) — declares the WebSocket API and its connect/disconnect/message handlers.
70
- - [defineWebSocketQueue](../../../config/webserver/web-socket-queue.md) — managed messaging layer that tracks connections for you.
70
+ - [defineWebSocketQueue](../../../config/features/web-socket-queue.md) — managed messaging layer that tracks connections for you.
71
71
  - [askCatch](../../../actions/core/system/ask-catch.md) — catch the throttled/disconnected errors above.
@@ -332,6 +332,8 @@ interface StorySession {
332
332
  accessToken?: string; // Auth token (not transferred)
333
333
  decodedAccessToken?: DecodedAccessToken; // User info
334
334
  context: QpqContext<any>; // Contextual data
335
+ localContext?: QpqContext<any>; // Service-local context (never crosses a service boundary)
336
+ functionGlobals?: Record<string, unknown>; // Globals from the executing function's runtime definition
335
337
  }
336
338
  ```
337
339