@superpanel-sdk/runtime 0.2.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -10
- package/dist/builder/codegen/emit.d.ts +25 -12
- package/dist/builder/codegen/emit.js +339 -78
- package/dist/builder/codegen/generate.d.ts +21 -2
- package/dist/builder/codegen/generate.js +147 -34
- package/dist/builder/company-import-graph.d.ts +2 -0
- package/dist/builder/company-import-graph.js +40 -8
- package/dist/builder/company-imports.d.ts +29 -0
- package/dist/builder/company-imports.js +122 -0
- package/dist/builder/compile/compile-sequence.d.ts +61 -8
- package/dist/builder/compile/compile-sequence.js +334 -80
- package/dist/builder/compile/function-code.d.ts +19 -3
- package/dist/builder/compile/function-code.js +123 -45
- package/dist/builder/compile/registry.d.ts +7 -3
- package/dist/builder/compile/registry.js +30 -27
- package/dist/builder/errors.d.ts +9 -0
- package/dist/builder/errors.js +17 -0
- package/dist/builder/function-params.d.ts +10 -0
- package/dist/builder/function-params.js +203 -0
- package/dist/builder/load.d.ts +70 -10
- package/dist/builder/load.js +258 -67
- package/dist/builder/paths.d.ts +9 -1
- package/dist/builder/paths.js +32 -2
- package/dist/builder/schedules.d.ts +25 -0
- package/dist/builder/schedules.js +101 -0
- package/dist/builder/slot-schema-validation.d.ts +4 -0
- package/dist/builder/slot-schema-validation.js +10 -0
- package/dist/builder/typecheck.d.ts +8 -0
- package/dist/builder/typecheck.js +102 -0
- package/dist/builder/types.d.ts +127 -19
- package/dist/builder/types.js +21 -1
- package/dist/cli/auth/auth.d.ts +37 -0
- package/dist/cli/auth/auth.js +211 -0
- package/dist/cli/auth/backend-url.d.ts +5 -0
- package/dist/cli/auth/backend-url.js +25 -0
- package/dist/cli/auth/clipboard.d.ts +9 -0
- package/dist/cli/auth/clipboard.js +38 -0
- package/dist/cli/auth/http.d.ts +20 -0
- package/dist/cli/auth/http.js +27 -0
- package/dist/cli/auth/index.d.ts +10 -0
- package/dist/cli/auth/index.js +27 -0
- package/dist/cli/auth/progress.d.ts +12 -0
- package/dist/cli/auth/progress.js +15 -0
- package/dist/cli/auth/serve-login.d.ts +8 -0
- package/dist/cli/auth/serve-login.js +24 -0
- package/dist/cli/calls/index.d.ts +177 -0
- package/dist/cli/calls/index.js +96 -0
- package/dist/cli/compile/index.js +3 -0
- package/dist/cli/config.d.ts +10 -0
- package/dist/cli/config.js +52 -0
- package/dist/cli/debug/index.d.ts +31 -0
- package/dist/cli/debug/index.js +119 -0
- package/dist/cli/exec/index.d.ts +39 -0
- package/dist/cli/exec/index.js +138 -0
- package/dist/cli/exec/session.d.ts +48 -0
- package/dist/cli/exec/session.js +82 -0
- package/dist/cli/exec/trace.d.ts +18 -0
- package/dist/cli/exec/trace.js +52 -0
- package/dist/cli/index.js +112 -9
- package/dist/cli/logs/index.d.ts +47 -0
- package/dist/cli/logs/index.js +113 -0
- package/dist/cli/mobile/index.d.ts +12 -0
- package/dist/cli/mobile/index.js +405 -0
- package/dist/cli/serve/index.d.ts +109 -3
- package/dist/cli/serve/index.js +506 -118
- package/dist/cli/turns/index.d.ts +105 -0
- package/dist/cli/turns/index.js +128 -0
- package/dist/cli/turns/run.d.ts +13 -0
- package/dist/cli/turns/run.js +57 -0
- package/dist/cli/turns/shadow.d.ts +112 -0
- package/dist/cli/turns/shadow.js +54 -0
- package/dist/cli/type-gen/index.js +9 -9
- package/dist/compiler.d.ts +7 -0
- package/dist/compiler.js +7 -0
- package/dist/compute-host/compute-host.d.ts +14 -23
- package/dist/compute-host/compute-host.js +18 -30
- package/dist/compute-host/index.d.ts +2 -4
- package/dist/compute-host/index.js +1 -2
- package/dist/compute-host/source-compute-host.d.ts +10 -10
- package/dist/compute-host/source-compute-host.js +33 -40
- package/dist/core/case-orchestrator/contact-hours.d.ts +70 -0
- package/dist/core/case-orchestrator/contact-hours.js +127 -0
- package/dist/core/case-orchestrator/index.d.ts +3 -0
- package/dist/core/case-orchestrator/index.js +3 -0
- package/dist/core/case-orchestrator/render.d.ts +75 -0
- package/dist/core/case-orchestrator/render.js +244 -0
- package/dist/core/case-orchestrator/time.d.ts +11 -0
- package/dist/core/case-orchestrator/time.js +29 -0
- package/dist/core/case-orchestrator/types.d.ts +708 -0
- package/dist/core/case-orchestrator/types.js +42 -0
- package/dist/core/context/call.d.ts +291 -0
- package/dist/core/context/call.js +94 -0
- package/dist/core/context/channels.d.ts +60 -0
- package/dist/core/context/channels.js +35 -0
- package/dist/core/context/contact.d.ts +96 -6
- package/dist/core/context/contact.js +48 -2
- package/dist/core/context/email-message.d.ts +198 -0
- package/dist/core/context/email-message.js +86 -0
- package/dist/core/context/email.d.ts +109 -0
- package/dist/core/context/email.js +90 -0
- package/dist/core/context/exchanges.d.ts +167 -0
- package/dist/core/context/exchanges.js +81 -0
- package/dist/core/context/instances.d.ts +307 -0
- package/dist/core/context/instances.js +137 -0
- package/dist/core/context/kv.d.ts +113 -0
- package/dist/core/context/kv.js +60 -0
- package/dist/core/context/platform-registry.d.ts +544 -4
- package/dist/core/context/platform-registry.js +14 -2
- package/dist/core/context/platform.d.ts +2 -1
- package/dist/core/context/sms.d.ts +177 -0
- package/dist/core/context/sms.js +77 -0
- package/dist/core/context/types.d.ts +79 -48
- package/dist/core/context/types.js +1 -7
- package/dist/core/conversation/index.d.ts +85 -0
- package/dist/core/conversation/index.js +133 -0
- package/dist/core/functions/errors.d.ts +74 -0
- package/dist/core/functions/errors.js +26 -0
- package/dist/core/functions/events.d.ts +192 -0
- package/dist/core/functions/events.js +23 -0
- package/dist/core/functions/schedules.d.ts +50 -0
- package/dist/core/functions/schedules.js +20 -0
- package/dist/core/functions/types.d.ts +29 -73
- package/dist/core/functions/types.js +1 -4
- package/dist/core/office-hours/index.d.ts +39 -0
- package/dist/core/office-hours/index.js +187 -0
- package/dist/core/page/define.d.ts +904 -0
- package/dist/core/page/define.js +518 -0
- package/dist/core/page/envelope.d.ts +55 -0
- package/dist/core/page/envelope.js +4 -0
- package/dist/core/page/fns.d.ts +448 -0
- package/dist/core/page/fns.js +539 -0
- package/dist/core/page/memory-reader.d.ts +27 -0
- package/dist/core/page/memory-reader.js +299 -0
- package/dist/core/page/ops.d.ts +58 -0
- package/dist/core/page/ops.js +220 -0
- package/dist/core/page/page-host.d.ts +20 -0
- package/dist/core/page/page-host.js +57 -0
- package/dist/core/page/records-query.d.ts +234 -0
- package/dist/core/page/records-query.js +780 -0
- package/dist/core/page/render.d.ts +295 -0
- package/dist/core/page/render.js +1060 -0
- package/dist/core/page/sandbox.d.ts +85 -0
- package/dist/core/page/sandbox.js +357 -0
- package/dist/core/page/time-format.d.ts +46 -0
- package/dist/core/page/time-format.js +125 -0
- package/dist/core/page/timeline.d.ts +40 -0
- package/dist/core/page/timeline.js +2 -0
- package/dist/core/page/typed.d.ts +648 -0
- package/dist/core/page/typed.js +2 -0
- package/dist/core/page/workspace.d.ts +57 -0
- package/dist/core/page/workspace.js +266 -0
- package/dist/core/party/index.d.ts +3 -0
- package/dist/core/party/index.js +3 -0
- package/dist/core/party/schema.d.ts +208 -0
- package/dist/core/party/schema.js +113 -0
- package/dist/core/party/types.d.ts +35 -0
- package/dist/core/party/types.js +15 -0
- package/dist/core/sequence/define.d.ts +56 -0
- package/dist/core/sequence/define.js +10 -0
- package/dist/core/sequence/index.d.ts +63 -0
- package/dist/core/sequence/index.js +231 -0
- package/dist/core/slot/binding.d.ts +40 -0
- package/dist/core/slot/binding.js +2 -0
- package/dist/core/slot/company-metadata.d.ts +102 -0
- package/dist/core/slot/company-metadata.js +536 -0
- package/dist/core/slot/compile-schema.d.ts +19 -13
- package/dist/core/slot/compile-schema.js +51 -248
- package/dist/core/slot/contact-timezone.d.ts +110 -0
- package/dist/core/slot/contact-timezone.js +186 -0
- package/dist/core/slot/field.d.ts +2 -25
- package/dist/core/slot/field.js +0 -71
- package/dist/core/slot/id-ref.d.ts +17 -2
- package/dist/core/slot/id-ref.js +16 -8
- package/dist/core/slot/index.d.ts +12 -13
- package/dist/core/slot/index.js +11 -12
- package/dist/core/slot/naming.d.ts +15 -0
- package/dist/core/slot/naming.js +96 -0
- package/dist/core/slot/platform-fields.d.ts +119 -0
- package/dist/core/slot/platform-fields.js +121 -0
- package/dist/core/slot/ref.d.ts +16 -0
- package/dist/core/slot/ref.js +14 -0
- package/dist/core/slot/row.d.ts +2 -9
- package/dist/core/slot/row.js +13 -23
- package/dist/core/slot/schema-compat.d.ts +17 -0
- package/dist/core/slot/schema-compat.js +103 -0
- package/dist/core/slot/schema.d.ts +353 -76
- package/dist/core/slot/schema.js +163 -10
- package/dist/core/slot/types.d.ts +30 -103
- package/dist/core/slot/value.d.ts +28 -3
- package/dist/core/slot/value.js +93 -57
- package/dist/dev-overlay/protocol.d.ts +74 -8
- package/dist/fn-context/build-context.d.ts +3 -1
- package/dist/fn-context/build-context.js +26 -10
- package/dist/fn-context/fake/empty-api.js +9 -28
- package/dist/fn-context/fake/index.d.ts +13 -4
- package/dist/fn-context/fake/index.js +29 -2
- package/dist/fn-context/fn-params.d.ts +3 -0
- package/dist/fn-context/fn-params.js +10 -0
- package/dist/fn-context/index.d.ts +5 -5
- package/dist/fn-context/index.js +4 -4
- package/dist/fn-context/log.js +2 -2
- package/dist/fn-context/make-ctx.d.ts +11 -0
- package/dist/fn-context/make-ctx.js +20 -9
- package/dist/fn-context/rpc/case-orchestrator-tools.d.ts +32 -0
- package/dist/fn-context/rpc/case-orchestrator-tools.js +12 -0
- package/dist/fn-context/rpc/errors.d.ts +13 -0
- package/dist/fn-context/rpc/errors.js +22 -0
- package/dist/fn-context/rpc/index.d.ts +4 -2
- package/dist/fn-context/rpc/index.js +4 -2
- package/dist/fn-context/rpc/integration/index.d.ts +15 -5
- package/dist/fn-context/rpc/integration/index.js +1 -1
- package/dist/fn-context/rpc/integration/list-surfaces.d.ts +21 -0
- package/dist/fn-context/rpc/integration/{list-bindings.js → list-surfaces.js} +4 -4
- package/dist/fn-context/rpc/log.d.ts +11 -1
- package/dist/fn-context/rpc/platform.d.ts +13 -3
- package/dist/fn-context/rpc/platform.js +1 -1
- package/dist/fn-context/rpc/records.d.ts +85 -0
- package/dist/fn-context/rpc/records.js +24 -0
- package/dist/fn-context/rpc/trpc-client.d.ts +44 -2
- package/dist/fn-context/rpc/trpc-client.js +222 -12
- package/dist/fn-context/rpc/trpc-init.d.ts +21 -3
- package/dist/fn-context/rpc/trpc-init.js +10 -4
- package/dist/fn-context/rpc/trpc-router-factory.d.ts +111 -7
- package/dist/fn-context/rpc/trpc-router-factory.js +2 -0
- package/dist/fn-context/run-scope.d.ts +4 -0
- package/dist/fn-context/run-scope.js +9 -0
- package/dist/fn-context/types.d.ts +52 -5
- package/dist/index.d.ts +36 -46
- package/dist/index.js +30 -33
- package/dist/logger.d.ts +11 -0
- package/dist/logger.js +49 -0
- package/dist/platform.d.ts +75 -0
- package/dist/platform.js +57 -0
- package/dist/testing-compile-child.d.ts +2 -0
- package/dist/testing-compile-child.js +20 -0
- package/dist/testing.d.ts +368 -0
- package/dist/testing.js +439 -0
- package/dist/voice-service/actions.d.ts +50 -6
- package/dist/voice-service/index.d.ts +2 -0
- package/dist/voice-service/index.js +2 -0
- package/dist/voice-service/protocol.d.ts +604 -0
- package/dist/voice-service/protocol.js +67 -0
- package/dist/voice-service/types.d.ts +55 -8
- package/dist/worker.d.ts +11 -0
- package/dist/worker.js +11 -0
- package/package.json +38 -15
- package/dist/builder/compile/compile-agenda.d.ts +0 -15
- package/dist/builder/compile/compile-agenda.js +0 -64
- package/dist/builder/merge.d.ts +0 -4
- package/dist/builder/merge.js +0 -19
- package/dist/compute-host/read-only-cache.d.ts +0 -20
- package/dist/compute-host/read-only-cache.js +0 -61
- package/dist/core/context/db.d.ts +0 -69
- package/dist/core/context/db.js +0 -44
- package/dist/core/context/docusign.d.ts +0 -134
- package/dist/core/context/docusign.js +0 -108
- package/dist/core/context/filevine.d.ts +0 -323
- package/dist/core/context/filevine.js +0 -254
- package/dist/core/context/http.d.ts +0 -9
- package/dist/core/context/http.js +0 -4
- package/dist/core/context/hubspot.d.ts +0 -250
- package/dist/core/context/hubspot.js +0 -191
- package/dist/core/context/twilio.d.ts +0 -50
- package/dist/core/context/twilio.js +0 -42
- package/dist/core/functions/define/case-context.d.ts +0 -14
- package/dist/core/functions/define/case-context.js +0 -15
- package/dist/core/functions/define/compute.d.ts +0 -7
- package/dist/core/functions/define/compute.js +0 -17
- package/dist/core/functions/define/custom.d.ts +0 -4
- package/dist/core/functions/define/custom.js +0 -8
- package/dist/core/functions/define/system.d.ts +0 -7
- package/dist/core/functions/define/system.js +0 -12
- package/dist/core/functions/func-ref.d.ts +0 -16
- package/dist/core/functions/func-ref.js +0 -20
- package/dist/core/slot/deps.d.ts +0 -15
- package/dist/core/slot/deps.js +0 -88
- package/dist/core/slot/derived.d.ts +0 -32
- package/dist/core/slot/derived.js +0 -115
- package/dist/core/slot/platform-session-fields.d.ts +0 -38
- package/dist/core/slot/platform-session-fields.js +0 -48
- package/dist/core/slot/sf-schema.d.ts +0 -23
- package/dist/core/slot/sf-schema.js +0 -125
- package/dist/core/slot/sf-validate.d.ts +0 -14
- package/dist/core/slot/sf-validate.js +0 -132
- package/dist/core/slot/snapshot.d.ts +0 -18
- package/dist/core/slot/snapshot.js +0 -138
- package/dist/core/slot/write.d.ts +0 -48
- package/dist/core/slot/write.js +0 -426
- package/dist/core/stage/builders.d.ts +0 -54
- package/dist/core/stage/builders.js +0 -31
- package/dist/core/stage/condition.d.ts +0 -17
- package/dist/core/stage/condition.js +0 -198
- package/dist/core/stage/drive.d.ts +0 -43
- package/dist/core/stage/drive.js +0 -78
- package/dist/core/stage/finish-when.d.ts +0 -20
- package/dist/core/stage/finish-when.js +0 -82
- package/dist/core/stage/flatten.d.ts +0 -27
- package/dist/core/stage/flatten.js +0 -466
- package/dist/core/stage/index.d.ts +0 -15
- package/dist/core/stage/index.js +0 -13
- package/dist/core/stage/interrupts.d.ts +0 -28
- package/dist/core/stage/interrupts.js +0 -120
- package/dist/core/stage/ivr-fields.d.ts +0 -9
- package/dist/core/stage/ivr-fields.js +0 -75
- package/dist/core/stage/simulate.d.ts +0 -24
- package/dist/core/stage/simulate.js +0 -33
- package/dist/core/stage/transfer-fields.d.ts +0 -19
- package/dist/core/stage/transfer-fields.js +0 -129
- package/dist/core/stage/transfer-spec.d.ts +0 -48
- package/dist/core/stage/transfer-spec.js +0 -113
- package/dist/core/stage/types.d.ts +0 -208
- package/dist/core/stage/types.js +0 -2
- package/dist/core/stage/validate.d.ts +0 -11
- package/dist/core/stage/validate.js +0 -330
- package/dist/core/stage/walk.d.ts +0 -39
- package/dist/core/stage/walk.js +0 -119
- package/dist/fn-context/rpc/integration/list-bindings.d.ts +0 -21
- package/dist/fn-context/system-args.d.ts +0 -6
- package/dist/fn-context/system-args.js +0 -14
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pages written with functions (76): what `context.page.ts` is written with. Every node and every action
|
|
3
|
+
* is a platform function returning its own type: `page()`, `block()`, `list()`, `field()`, `text()`,
|
|
4
|
+
* `data()`, `button()`, `doNow()`, `toolbox()`; actions `run()`, `createRow()`, `warmTransfer()`, …
|
|
5
|
+
* A node carries a mark no JSON can carry (31 §8), so data from outside never becomes a field or a
|
|
6
|
+
* button. The page module turns the tree into the envelope (`toFnPageResult`), the one thing the
|
|
7
|
+
* platform reads (`render.ts`): what the AI reads of the page does not change (76 §2.20).
|
|
8
|
+
*
|
|
9
|
+
* The types here are the generic ones; `typed.ts` binds `page`'s extraction, `field`'s slot, `createRow`,
|
|
10
|
+
* `deleteRow` and `run` to one sequence's tables, functions (`fn`) and page actions (`act`) (`__gen/index.ts`).
|
|
11
|
+
*
|
|
12
|
+
* Runs in the page's QuickJS sandbox like the rest of the page module: no imports at runtime but the
|
|
13
|
+
* page module's own.
|
|
14
|
+
*/
|
|
15
|
+
import { announceAction, cancelFollowUpAction, createRowAction, deleteRowAction, endAction, fieldCell, followUpAction, isChoose, keypadAction, refFieldCell, runFunction, transferTo, voicemailAction, } from './define.js';
|
|
16
|
+
// ---------------------------------------------------------------------------------------------
|
|
17
|
+
// Marks
|
|
18
|
+
/** The mark of a node: `Symbol.for`, the same in every copy of the page module a bundle may hold. */
|
|
19
|
+
export const NODE = Symbol.for('superpanel.page.node');
|
|
20
|
+
/** The mark of an action (what a button or `doNow` does). */
|
|
21
|
+
export const ACTION = Symbol.for('superpanel.page.action');
|
|
22
|
+
/** The mark of a handle `run()` takes: a company function (`fn.x`) or a page action (`act.x`). */
|
|
23
|
+
export const HANDLE = Symbol.for('superpanel.page.handle');
|
|
24
|
+
/** The mark of a platform tool (`pressKeypad()`, `convertTime()`). */
|
|
25
|
+
export const TOOL = Symbol.for('superpanel.page.tool');
|
|
26
|
+
const KINDS = new Set(['page', 'block', 'list', 'field', 'text', 'data', 'button', 'toolbox', 'do']);
|
|
27
|
+
/** Whether `value` is a node a platform function made. */
|
|
28
|
+
export function isNode(value) {
|
|
29
|
+
return typeof value === 'object' && value !== null && KINDS.has(value[NODE]);
|
|
30
|
+
}
|
|
31
|
+
function isAction(value) {
|
|
32
|
+
return typeof value === 'object' && value !== null && typeof value[ACTION] === 'string';
|
|
33
|
+
}
|
|
34
|
+
const node = (value) => value;
|
|
35
|
+
// ---------------------------------------------------------------------------------------------
|
|
36
|
+
// Nodes
|
|
37
|
+
/** The page (76 §2.7): settings, then content. What a page function returns, unless it returns `doNow()`. */
|
|
38
|
+
export function page(settings, content) {
|
|
39
|
+
settingsOf('page', settings, ['title', 'description', 'guide', 'extraction', 'voice']);
|
|
40
|
+
contentOf('page', content);
|
|
41
|
+
return node({ [NODE]: 'page', settings, content });
|
|
42
|
+
}
|
|
43
|
+
/** A block (76 §2.7): settings (`{}` for none), then its content by name. */
|
|
44
|
+
export function block(settings, content) {
|
|
45
|
+
settingsOf('block', settings, ['title', 'description']);
|
|
46
|
+
contentOf('block', content);
|
|
47
|
+
return node({ [NODE]: 'block', settings, content });
|
|
48
|
+
}
|
|
49
|
+
/** A list (76 §2.10): settings, then items, all one kind of node (`rows.map((r) => block({}, { … }))`). */
|
|
50
|
+
export function list(settings, items) {
|
|
51
|
+
settingsOf('list', settings, ['title', 'description']);
|
|
52
|
+
if (!Array.isArray(items))
|
|
53
|
+
throw new Error('list(settings, items): items is an array of nodes, all one kind (rows.map((r) => block({}, { … })))');
|
|
54
|
+
return node({ [NODE]: 'list', settings, items });
|
|
55
|
+
}
|
|
56
|
+
/** A field the AI may write, or read only (`readOnly`). A ref field is `refField()`. */
|
|
57
|
+
export function field(slot, options = {}) {
|
|
58
|
+
checkOptions('field()', options, ['readOnly', 'expand']);
|
|
59
|
+
const cell = fieldCell(slot, options);
|
|
60
|
+
return node({ [NODE]: 'field', make: () => cell });
|
|
61
|
+
}
|
|
62
|
+
/** A ref field: writable, the rows it may be set to (`options`); read-only, who its value is (`label`). */
|
|
63
|
+
export function refField(slot, options) {
|
|
64
|
+
checkOptions('refField()', options, ['options', 'readOnly', 'label']);
|
|
65
|
+
const given = options;
|
|
66
|
+
if (given.readOnly && given.options !== undefined)
|
|
67
|
+
throw new Error('refField(): read-only, it says who the current value is with label, not options');
|
|
68
|
+
if (!given.readOnly && given.label !== undefined)
|
|
69
|
+
throw new Error('refField(): label is for a read-only ref field; a writable one offers options');
|
|
70
|
+
const readOnly = given.readOnly === true;
|
|
71
|
+
const listed = readOnly && typeof given.label === 'string' && typeof slot?.value === 'string' ? [{ id: slot.value, label: given.label }] : given.options;
|
|
72
|
+
const cell = refFieldCell(slot, { ...(listed ? { options: listed } : {}), ...(readOnly ? { readOnly: true } : {}) });
|
|
73
|
+
return node({ [NODE]: 'field', make: () => cell });
|
|
74
|
+
}
|
|
75
|
+
/** One text, read only. A value that may be empty: `x ? text(x) : undefined`. */
|
|
76
|
+
export function text(value) {
|
|
77
|
+
if (typeof value === 'number')
|
|
78
|
+
value = String(value);
|
|
79
|
+
if (typeof value !== 'string') {
|
|
80
|
+
throw new Error(`text(): takes a string or a number, not ${describe(value)}${isNode(value) ? '' : ': data is data(…)'}`);
|
|
81
|
+
}
|
|
82
|
+
if (!value.trim())
|
|
83
|
+
throw new Error('text() is empty: leave it out when there is nothing to show (note ? text(note) : undefined)');
|
|
84
|
+
return node({ [NODE]: 'text', text: value });
|
|
85
|
+
}
|
|
86
|
+
/** Data shown as it is, whatever keys it has (`$action` too): never a field or a button (31 §4). */
|
|
87
|
+
export function data(value) {
|
|
88
|
+
// `undefined` is nothing to show: the item is left out, as an `undefined` value of an object was.
|
|
89
|
+
return node({ [NODE]: 'data', value: value === undefined ? undefined : plain('data()', value) });
|
|
90
|
+
}
|
|
91
|
+
/** What the AI can press (31 §5): its words, and what it does (`action`). */
|
|
92
|
+
export function button(options) {
|
|
93
|
+
const given = options;
|
|
94
|
+
checkOptions('button()', given, ['action', 'title', 'does']);
|
|
95
|
+
if (!isAction(given.action))
|
|
96
|
+
throw new Error(`button(): action is what it does, an action function's result (run(…), createRow(…), warmTransfer(…), …), not ${describe(given.action)}`);
|
|
97
|
+
const verb = given.action[ACTION];
|
|
98
|
+
if (verb === 'announce')
|
|
99
|
+
throw new Error('button(): announce() is only for doNow(): the AI never presses it');
|
|
100
|
+
for (const key of ['title', 'does']) {
|
|
101
|
+
if (given[key] !== undefined && typeof given[key] !== 'string')
|
|
102
|
+
throw new Error(`button(): ${key} is text`);
|
|
103
|
+
}
|
|
104
|
+
// What the AI needs to tell buttons apart (30 §7.4): a function or a page action says what it is and
|
|
105
|
+
// when to press it (its name is the code's); a transfer who it goes to (the AI sees no number).
|
|
106
|
+
if (verb === 'run' && (!given.title || !given.does))
|
|
107
|
+
throw new Error('button(): a run() button needs title and does: what it is and when to press it');
|
|
108
|
+
if (verb === 'transfer' && !given.title)
|
|
109
|
+
throw new Error('button(): a transfer needs a title: the AI sees no number, the title is who it goes to');
|
|
110
|
+
const words = { ...(given.title ? { label: given.title } : {}), ...(given.does ? { description: given.does } : {}) };
|
|
111
|
+
return node({ [NODE]: 'button', action: given.action, words });
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* What the page does now (31 §6), instead of a page: the AI neither sees nor presses it. The action has
|
|
115
|
+
* its run key, the name `tried(ctx.runs, …)` checks (76 §2.14); `wait: false` only for `run()`: an action
|
|
116
|
+
* that may be one of several (`flag ? run(…) : end()`) only when every one is a `run()`.
|
|
117
|
+
*/
|
|
118
|
+
export function doNow(action, options = {}) {
|
|
119
|
+
if (!isAction(action))
|
|
120
|
+
throw new Error(`doNow(): takes an action (run(…), keypad(…), announce(…), …), not ${describe(action)}`);
|
|
121
|
+
checkOptions('doNow()', options, ['wait']);
|
|
122
|
+
const verb = action[ACTION];
|
|
123
|
+
if (verb === 'scheduleFollowUp' || verb === 'cancelFollowUp')
|
|
124
|
+
throw new Error(`doNow(): ${verb}() is a button: the AI books it with the customer`);
|
|
125
|
+
if (!action.runKey)
|
|
126
|
+
throw new Error(`doNow(${verb}()): give the action a runKey, the name tried(ctx.runs, …) checks (76 §2.14)`);
|
|
127
|
+
const wait = options.wait;
|
|
128
|
+
if (wait !== undefined) {
|
|
129
|
+
if (typeof wait !== 'boolean')
|
|
130
|
+
throw new Error('doNow(): wait is true or false');
|
|
131
|
+
if (verb !== 'run' && wait === false)
|
|
132
|
+
throw new Error(`doNow(${verb}()): only run() may go on in the background (wait: false)`);
|
|
133
|
+
}
|
|
134
|
+
return node({ [NODE]: 'do', action, ...(wait !== undefined ? { wait } : {}) });
|
|
135
|
+
}
|
|
136
|
+
/** The platform's tools the AI may use on this page (76 §2.18): one toolbox a page, each tool once. */
|
|
137
|
+
export function toolbox(tools) {
|
|
138
|
+
if (!Array.isArray(tools))
|
|
139
|
+
throw new Error('toolbox([...]): a list of tools (pressKeypad(), convertTime())');
|
|
140
|
+
const names = tools.map((tool) => {
|
|
141
|
+
const name = typeof tool === 'object' && tool !== null ? tool[TOOL] : undefined;
|
|
142
|
+
if (typeof name !== 'string')
|
|
143
|
+
throw new Error(`toolbox(): ${describe(tool)} is not a tool: pressKeypad(), convertTime()`);
|
|
144
|
+
return name;
|
|
145
|
+
});
|
|
146
|
+
const twice = names.find((name, i) => names.indexOf(name) !== i);
|
|
147
|
+
if (twice)
|
|
148
|
+
throw new Error(`toolbox(): ${twice}() is in it twice`);
|
|
149
|
+
return node({ [NODE]: 'toolbox', tools: names });
|
|
150
|
+
}
|
|
151
|
+
/** Press keys on the other side's automated phone menu (76 §2.18): a call only. */
|
|
152
|
+
export function pressKeypad() {
|
|
153
|
+
return { [TOOL]: 'pressKeypad' };
|
|
154
|
+
}
|
|
155
|
+
/** Convert between a local time in a time zone and an instant, UTC (76 §2.18, §2.19). */
|
|
156
|
+
export function convertTime() {
|
|
157
|
+
return { [TOOL]: 'convertTime' };
|
|
158
|
+
}
|
|
159
|
+
// ---------------------------------------------------------------------------------------------
|
|
160
|
+
// Actions
|
|
161
|
+
const action = (verb, runKey, build) => ({ [ACTION]: verb, build, ...(runKey !== undefined ? { runKey } : {}) });
|
|
162
|
+
const keyed = (built, runKey) => {
|
|
163
|
+
// The run key is the action's own (30 §7.3): the same whoever runs it, the page or the AI.
|
|
164
|
+
if (runKey)
|
|
165
|
+
built.key = runKey;
|
|
166
|
+
return { action: built, ...(runKey ? { key: runKey } : {}) };
|
|
167
|
+
};
|
|
168
|
+
function checkRunKey(at, runKey) {
|
|
169
|
+
if (runKey === undefined)
|
|
170
|
+
return undefined;
|
|
171
|
+
if (typeof runKey !== 'string' || !runKey.trim())
|
|
172
|
+
throw new Error(`${at}: runKey is a name (text)`);
|
|
173
|
+
if (runKey === '__proto__')
|
|
174
|
+
throw new Error(`${at}: runKey __proto__: pick another name (JavaScript keeps it for an object's prototype)`);
|
|
175
|
+
return runKey;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* What the page fixed (`prefill`) and the rows the AI chooses among (`options`, 35 §2), as one map the
|
|
179
|
+
* platform tells apart: a `choose(...)` is the AI's choice, anything else is fixed.
|
|
180
|
+
*/
|
|
181
|
+
function givenRows(at, options, mode) {
|
|
182
|
+
const prefill = (options.prefill ?? {});
|
|
183
|
+
if (typeof prefill !== 'object' || Array.isArray(prefill))
|
|
184
|
+
throw new Error(`${at}: prefill is an object, parameter → value`);
|
|
185
|
+
for (const [key, value] of Object.entries(prefill)) {
|
|
186
|
+
if (isChoose(value))
|
|
187
|
+
throw new Error(`${at}: prefill is what the page fixed: the rows the AI chooses ${key} among go in options: { ${key}: choose(...) }`);
|
|
188
|
+
}
|
|
189
|
+
if (options.options === undefined)
|
|
190
|
+
return { ...prefill };
|
|
191
|
+
if (mode === 'do')
|
|
192
|
+
throw new Error(`${at}: in doNow() nobody chooses: no options, prefill every parameter`);
|
|
193
|
+
const chosen = options.options;
|
|
194
|
+
for (const [key, value] of Object.entries(chosen)) {
|
|
195
|
+
if (!isChoose(value))
|
|
196
|
+
throw new Error(`${at}: options.${key} is choose(rows, label): the rows the AI chooses among, each with what it sees of it`);
|
|
197
|
+
if (key in prefill)
|
|
198
|
+
throw new Error(`${at}: ${key} is both prefilled and in options: fix it, or let the AI choose`);
|
|
199
|
+
}
|
|
200
|
+
return { ...chosen, ...prefill };
|
|
201
|
+
}
|
|
202
|
+
export function run(handle, options = {}) {
|
|
203
|
+
const kind = typeof handle === 'object' && handle !== null ? handle[HANDLE] : undefined;
|
|
204
|
+
const name = handle?.name;
|
|
205
|
+
if ((kind !== 'function' && kind !== 'action') || typeof name !== 'string') {
|
|
206
|
+
throw new Error(typeof handle === 'function'
|
|
207
|
+
? `run(${handle.name || '…'}): a plain function is not a page action: put it in actions.ts (export const actions = { name: async (ctx: ActionCtx, args) => … }) and run(act.name) (76 §2.11)`
|
|
208
|
+
: `run(): takes fn.<name> (a company function) or act.<name> (a page action), not ${describe(handle)}`);
|
|
209
|
+
}
|
|
210
|
+
const at = `run(${kind === 'function' ? 'fn' : 'act'}.${name})`;
|
|
211
|
+
checkOptions(at, options, ['prefill', 'options', 'args', 'say', 'runKey']);
|
|
212
|
+
const runKey = checkRunKey(at, options.runKey);
|
|
213
|
+
if (kind === 'action' && options.say !== undefined)
|
|
214
|
+
throw new Error(`${at}: say is for a company function: a page action runs at once`);
|
|
215
|
+
return action('run', runKey, (words, mode) => {
|
|
216
|
+
if (mode === 'do' && options.args !== undefined)
|
|
217
|
+
throw new Error(`${at}: in doNow() the AI fills nothing: no args`);
|
|
218
|
+
const prefill = givenRows(at, options, mode);
|
|
219
|
+
const args = options.args === undefined ? undefined : plain(`${at} args`, options.args);
|
|
220
|
+
if (kind === 'function') {
|
|
221
|
+
const built = runFunction(name, { ...words, ...(options.say !== undefined ? { say: options.say } : {}) });
|
|
222
|
+
if (Object.keys(prefill).length)
|
|
223
|
+
built.prefill = prefill;
|
|
224
|
+
if (args)
|
|
225
|
+
built.args = args;
|
|
226
|
+
return keyed(built, runKey);
|
|
227
|
+
}
|
|
228
|
+
const built = { $action: 'custom', name, prefill: Object.fromEntries(Object.entries(prefill).filter(([, value]) => value !== undefined)) };
|
|
229
|
+
if (words.label)
|
|
230
|
+
built.label = words.label;
|
|
231
|
+
if (words.description)
|
|
232
|
+
built.description = words.description;
|
|
233
|
+
if (args)
|
|
234
|
+
built.args = args;
|
|
235
|
+
return keyed(built, runKey);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/** Add a row (15 §10.6): `fields` are what the AI fills, `prefill` what the page fixed, `options` a ref field's rows. */
|
|
239
|
+
export function createRow(table, options) {
|
|
240
|
+
const at = `createRow('${table}')`;
|
|
241
|
+
if (typeof table !== 'string' || !table)
|
|
242
|
+
throw new Error('createRow(table, …): the table is its name');
|
|
243
|
+
checkOptions(at, options, ['fields', 'required', 'prefill', 'options', 'runKey']);
|
|
244
|
+
if (!Array.isArray(options.fields))
|
|
245
|
+
throw new Error(`${at}: fields is a list of the fields the AI fills`);
|
|
246
|
+
const runKey = checkRunKey(at, options.runKey);
|
|
247
|
+
return action('createRow', runKey, (words, mode) => keyed(createRowAction(table, { fields: options.fields, ...(options.required ? { required: options.required } : {}), ...words })(givenRows(at, options, mode)), runKey));
|
|
248
|
+
}
|
|
249
|
+
/** Delete a row and everything under it (15 §10.6.1). */
|
|
250
|
+
export function deleteRow(row, options = {}) {
|
|
251
|
+
checkOptions('deleteRow()', options, ['reason', 'runKey']);
|
|
252
|
+
const runKey = checkRunKey('deleteRow()', options.runKey);
|
|
253
|
+
const reason = options.reason;
|
|
254
|
+
// Checked now: a row that is not one fails where it is written.
|
|
255
|
+
deleteRowAction(row);
|
|
256
|
+
return action('deleteRow', runKey, (words) => keyed(deleteRowAction(row, { ...words, ...(reason !== undefined ? { reason } : {}) }), runKey));
|
|
257
|
+
}
|
|
258
|
+
const TRANSFER_KEYS = ['to', 'briefing', 'language', 'sipAuth', 'callerId', 'runKey'];
|
|
259
|
+
function transfer(mode, fnName, options, extra) {
|
|
260
|
+
const at = `${fnName}()`;
|
|
261
|
+
if (typeof options !== 'object' || options === null)
|
|
262
|
+
throw new Error(`${at}: takes { to, … }`);
|
|
263
|
+
checkOptions(at, options, [...TRANSFER_KEYS, ...extra]);
|
|
264
|
+
if (typeof options.to !== 'string' || !options.to.trim())
|
|
265
|
+
throw new Error(`${at}: to is the number or SIP URI to call`);
|
|
266
|
+
if (options.whisper !== undefined && (typeof options.whisper !== 'string' || !options.whisper.trim()))
|
|
267
|
+
throw new Error(`${at}: whisper is text: who it is and what about`);
|
|
268
|
+
const runKey = checkRunKey(at, options.runKey);
|
|
269
|
+
const spec = { ...pickDefined(options, ['sipAuth', 'callerId', 'briefing', 'bridge', 'language', 'whisper']), to: options.to, mode };
|
|
270
|
+
transferTo(spec);
|
|
271
|
+
return action('transfer', runKey, (words) => keyed(transferTo({ ...spec, ...words }), runKey));
|
|
272
|
+
}
|
|
273
|
+
/** Put the customer through; the person hears the briefing first and does not confirm (76 §2.9). */
|
|
274
|
+
export function coldTransfer(options) {
|
|
275
|
+
return transfer('cold', 'coldTransfer', options, []);
|
|
276
|
+
}
|
|
277
|
+
/** The customer waits; the person confirms, hears the briefing, then is put through (76 §2.9). */
|
|
278
|
+
export function warmTransfer(options) {
|
|
279
|
+
return transfer('warm', 'warmTransfer', options, ['whisper']);
|
|
280
|
+
}
|
|
281
|
+
/** The AI keeps talking with the customer while the person is called; put through once they take it (76 §2.9). */
|
|
282
|
+
export function backgroundTransfer(options) {
|
|
283
|
+
return transfer('background', 'backgroundTransfer', options, ['whisper', 'bridge']);
|
|
284
|
+
}
|
|
285
|
+
/** End the conversation (a call hangs up once the last line has played). */
|
|
286
|
+
export function end(options = {}) {
|
|
287
|
+
checkOptions('end()', options, ['runKey']);
|
|
288
|
+
const runKey = checkRunKey('end()', options.runKey);
|
|
289
|
+
return action('end', runKey, (words) => keyed(endAction(words), runKey));
|
|
290
|
+
}
|
|
291
|
+
/** Leave a message on the voicemail, word for word, then hang up (25 §13). */
|
|
292
|
+
export function voicemail(options) {
|
|
293
|
+
checkOptions('voicemail()', options, ['text', 'runKey']);
|
|
294
|
+
const runKey = checkRunKey('voicemail()', options.runKey);
|
|
295
|
+
voicemailAction(options.text);
|
|
296
|
+
return action('voicemail', runKey, (words) => keyed(voicemailAction(options.text, words), runKey));
|
|
297
|
+
}
|
|
298
|
+
/** Ask the customer to press a key; what they press is written to `write` (through `map`). */
|
|
299
|
+
export function keypad(options) {
|
|
300
|
+
checkOptions('keypad()', options, ['runKey', 'say', 'digits', 'timeoutMs', 'write', 'map']);
|
|
301
|
+
const runKey = checkRunKey('keypad()', options.runKey);
|
|
302
|
+
if (!runKey)
|
|
303
|
+
throw new Error('keypad(): runKey is required, the name tried(ctx.runs, …) checks');
|
|
304
|
+
const spec = pickDefined(options, ['say', 'digits', 'timeoutMs', 'write', 'map']);
|
|
305
|
+
keypadAction(runKey, spec);
|
|
306
|
+
return action('keypad', runKey, (words) => ({ action: keypadAction(runKey, { ...spec, ...words }), key: runKey }));
|
|
307
|
+
}
|
|
308
|
+
/** Book the case's next step (40 §2.2), or move one already booked (51 §2). The AI gives when, in UTC (76 §2.19). */
|
|
309
|
+
export function scheduleFollowUp(options) {
|
|
310
|
+
checkOptions('scheduleFollowUp()', options, ['channels', 'prefill', 'options', 'runKey']);
|
|
311
|
+
const runKey = checkRunKey('scheduleFollowUp()', options.runKey);
|
|
312
|
+
return action('scheduleFollowUp', runKey, (words) => {
|
|
313
|
+
const prefill = givenRows('scheduleFollowUp()', options, 'button');
|
|
314
|
+
return keyed(followUpAction({ channels: options.channels, ...(runKey ? { key: runKey } : {}), ...words, ...(Object.keys(prefill).length ? { prefill } : {}) }), runKey);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
/** Cancel the case's next step (40 §2.2): the AI says why. */
|
|
318
|
+
export function cancelFollowUp(options = {}) {
|
|
319
|
+
checkOptions('cancelFollowUp()', options, ['runKey']);
|
|
320
|
+
const runKey = checkRunKey('cancelFollowUp()', options.runKey);
|
|
321
|
+
return action('cancelFollowUp', runKey, (words) => keyed(cancelFollowUpAction({ ...(runKey ? { key: runKey } : {}), ...words }), runKey));
|
|
322
|
+
}
|
|
323
|
+
/** Say a fixed text, word for word (a compliance statement): `doNow()` only. `answers`: it answers what the customer said. */
|
|
324
|
+
export function announce(options) {
|
|
325
|
+
checkOptions('announce()', options, ['runKey', 'text', 'answers']);
|
|
326
|
+
const runKey = checkRunKey('announce()', options.runKey);
|
|
327
|
+
if (!runKey)
|
|
328
|
+
throw new Error('announce(): runKey is required, the name tried(ctx.runs, …) checks');
|
|
329
|
+
announceAction(runKey, options.text);
|
|
330
|
+
return action('announce', runKey, () => ({ action: announceAction(runKey, options.text, { answers: options.answers === true }), key: runKey }));
|
|
331
|
+
}
|
|
332
|
+
// ---------------------------------------------------------------------------------------------
|
|
333
|
+
// Page actions (76 §2.11) and company functions (§2.15)
|
|
334
|
+
/** `fn` of `__gen` (76 §2.15): a handle per company function, by name; the types say which names there are. */
|
|
335
|
+
export function functionHandles() {
|
|
336
|
+
return new Proxy({}, {
|
|
337
|
+
get(_, name) {
|
|
338
|
+
if (typeof name !== 'string')
|
|
339
|
+
return undefined;
|
|
340
|
+
return { [HANDLE]: 'function', name };
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
/** `act` of `__gen` (76 §2.11): a handle per page action of `actions.ts`, by name; the types say which names there are. */
|
|
345
|
+
export function actionHandles() {
|
|
346
|
+
return new Proxy({}, {
|
|
347
|
+
get(_, name) {
|
|
348
|
+
if (typeof name !== 'string')
|
|
349
|
+
return undefined;
|
|
350
|
+
return { [HANDLE]: 'action', name };
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
// ---------------------------------------------------------------------------------------------
|
|
355
|
+
// The envelope: what crosses out of the sandbox (31 §8)
|
|
356
|
+
/** What a page function returned, as the platform reads it: `page()` or `doNow()`, as its envelope. */
|
|
357
|
+
export function toFnPageResult(value) {
|
|
358
|
+
if (!isNode(value) || (value[NODE] !== 'page' && value[NODE] !== 'do')) {
|
|
359
|
+
throw new Error(`a page function returns page(…) or doNow(…), not ${isNode(value) ? `${value[NODE]}()` : describe(value)} (76 §2.1)`);
|
|
360
|
+
}
|
|
361
|
+
if (value[NODE] === 'do') {
|
|
362
|
+
const built = value.action.build({}, 'do');
|
|
363
|
+
return { $envelope: 1, do: { action: built.action, ...(built.key ? { key: built.key } : {}), ...(value.wait !== undefined ? { wait: value.wait } : {}) } };
|
|
364
|
+
}
|
|
365
|
+
const settings = value.settings;
|
|
366
|
+
const out = { children: [] };
|
|
367
|
+
for (const key of ['title', 'description', 'guide']) {
|
|
368
|
+
if (settings[key] !== undefined && settings[key] !== null)
|
|
369
|
+
out[key] = settings[key];
|
|
370
|
+
}
|
|
371
|
+
if (settings.extraction !== undefined)
|
|
372
|
+
out.extraction = plain('page() extraction', settings.extraction);
|
|
373
|
+
if (settings.voice !== undefined)
|
|
374
|
+
out.voice = plain('page() voice', settings.voice);
|
|
375
|
+
const toolboxes = { count: 0 };
|
|
376
|
+
out.children = contentNodes('page', value.content, 1, toolboxes);
|
|
377
|
+
return { $envelope: 1, page: out };
|
|
378
|
+
}
|
|
379
|
+
const MAX_DEPTH = 32;
|
|
380
|
+
/** A block's or the page's content: each entry a node, named by its key; a skipped one left out. */
|
|
381
|
+
function contentNodes(where, content, depth, toolboxes) {
|
|
382
|
+
if (depth > MAX_DEPTH)
|
|
383
|
+
throw new Error(`${where}: nested deeper than ${MAX_DEPTH}`);
|
|
384
|
+
const nodes = [];
|
|
385
|
+
for (const [key, value] of Object.entries(content)) {
|
|
386
|
+
const out = itemNode(`${where}.${key}`, key, value, depth, toolboxes);
|
|
387
|
+
if (out)
|
|
388
|
+
nodes.push(out);
|
|
389
|
+
}
|
|
390
|
+
return nodes;
|
|
391
|
+
}
|
|
392
|
+
function itemNode(where, key, value, depth, toolboxes) {
|
|
393
|
+
if (value === undefined || value === null || value === false)
|
|
394
|
+
return null;
|
|
395
|
+
if (!isNode(value))
|
|
396
|
+
throw new Error(notANode(where, value));
|
|
397
|
+
switch (value[NODE]) {
|
|
398
|
+
case 'page':
|
|
399
|
+
throw new Error(`${where}: page() is the whole page, never inside a block`);
|
|
400
|
+
case 'do':
|
|
401
|
+
throw new Error(`${where}: doNow() is what a page function returns instead of a page, never on it`);
|
|
402
|
+
case 'block': {
|
|
403
|
+
const out = { k: 'block', key, list: false, children: contentNodes(where, value.content, depth + 1, toolboxes) };
|
|
404
|
+
wordsOf(out, value.settings);
|
|
405
|
+
return out;
|
|
406
|
+
}
|
|
407
|
+
case 'list': {
|
|
408
|
+
const items = value.items.filter((item) => item !== undefined && item !== null && item !== false);
|
|
409
|
+
const kinds = new Set(items.map((item) => (isNode(item) ? item[NODE] : describe(item))));
|
|
410
|
+
if (kinds.size > 1)
|
|
411
|
+
throw new Error(`${where}: list items are all one kind: all block(), all field(), all text() or all button() (it has ${[...kinds].join(', ')})`);
|
|
412
|
+
const kind = [...kinds][0];
|
|
413
|
+
if (kind !== undefined && !['block', 'field', 'text', 'button'].includes(kind)) {
|
|
414
|
+
throw new Error(`${where}: a list holds block(), field(), text() or button(), not ${isNode(items[0]) ? `${kind}()` : kind}`);
|
|
415
|
+
}
|
|
416
|
+
const children = items.map((item, i) => itemNode(`${where}.${i}`, String(i), item, depth + 1, toolboxes)).filter((n) => n !== null);
|
|
417
|
+
const out = { k: 'block', key, list: true, children };
|
|
418
|
+
wordsOf(out, value.settings);
|
|
419
|
+
return out;
|
|
420
|
+
}
|
|
421
|
+
case 'field':
|
|
422
|
+
return { k: 'field', key, cell: value.make() };
|
|
423
|
+
case 'text':
|
|
424
|
+
return { k: 'data', key, value: value.text };
|
|
425
|
+
case 'data':
|
|
426
|
+
return value.value === undefined ? null : { k: 'data', key, value: value.value };
|
|
427
|
+
case 'button':
|
|
428
|
+
// The platform gives a button its id, from what it does and on which rows (46 §2.1): its key is the code's.
|
|
429
|
+
return { k: 'button', key: '', action: value.action.build(value.words, 'button').action };
|
|
430
|
+
case 'toolbox':
|
|
431
|
+
if (++toolboxes.count > 1)
|
|
432
|
+
throw new Error(`${where}: a page has one toolbox()`);
|
|
433
|
+
return { k: 'toolbox', key, tools: [...value.tools] };
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
function wordsOf(out, settings) {
|
|
437
|
+
if (out.k !== 'block')
|
|
438
|
+
return;
|
|
439
|
+
if (typeof settings.title === 'string')
|
|
440
|
+
out.title = settings.title;
|
|
441
|
+
if (typeof settings.description === 'string')
|
|
442
|
+
out.description = settings.description;
|
|
443
|
+
}
|
|
444
|
+
function notANode(where, value) {
|
|
445
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
446
|
+
return `${where}: ${describe(value)} is not a node: a text is text(…), data is data(…); a value that may be empty is x ? text(x) : undefined, not x && text(x)`;
|
|
447
|
+
}
|
|
448
|
+
if (typeof value === 'function')
|
|
449
|
+
return `${where}: a function is not page content (a button is button({ … }))`;
|
|
450
|
+
if (isAction(value))
|
|
451
|
+
return `${where}: an action is placed with button({ title, does, action }) or doNow(action)`;
|
|
452
|
+
if (isSlotLike(value))
|
|
453
|
+
return `${where}: a slot is placed with field(slot) (read only: field(slot, { readOnly: true }))`;
|
|
454
|
+
return `${where}: ${describe(value)} is not a node: data is data(…), a group of nodes block({}, { … })`;
|
|
455
|
+
}
|
|
456
|
+
// ---------------------------------------------------------------------------------------------
|
|
457
|
+
function settingsOf(at, settings, allowed) {
|
|
458
|
+
if (typeof settings !== 'object' || settings === null || Array.isArray(settings) || isNode(settings)) {
|
|
459
|
+
throw new Error(`${at}(settings, content): settings come first, an object (${at}({}, { … }) when there are none)`);
|
|
460
|
+
}
|
|
461
|
+
checkOptions(`${at}() settings`, settings, allowed);
|
|
462
|
+
for (const key of ['title', 'description', 'guide']) {
|
|
463
|
+
const value = settings[key];
|
|
464
|
+
if (value !== undefined && typeof value !== 'string')
|
|
465
|
+
throw new Error(`${at}() ${key}: must be text`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
function contentOf(at, content) {
|
|
469
|
+
if (typeof content !== 'object' || content === null || Array.isArray(content) || isNode(content)) {
|
|
470
|
+
throw new Error(`${at}(settings, content): content is an object, its items by name (${at}({}, { name: field(…) }))`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
/** An option a function does not take is an error (31 §4): a misspelt one would otherwise do nothing, silently. */
|
|
474
|
+
function checkOptions(at, options, allowed) {
|
|
475
|
+
if (options === undefined)
|
|
476
|
+
return;
|
|
477
|
+
if (typeof options !== 'object' || options === null || Array.isArray(options))
|
|
478
|
+
throw new Error(`${at}: takes an object`);
|
|
479
|
+
const unknown = Object.keys(options).filter((key) => !allowed.includes(key) && options[key] !== undefined);
|
|
480
|
+
if (unknown.length)
|
|
481
|
+
throw new Error(`${at}: has no ${unknown.join(', ')} (it takes ${allowed.join(', ')})`);
|
|
482
|
+
}
|
|
483
|
+
function pickDefined(props, keys) {
|
|
484
|
+
const out = {};
|
|
485
|
+
for (const key of keys)
|
|
486
|
+
if (props[key] !== undefined)
|
|
487
|
+
out[key] = props[key];
|
|
488
|
+
return out;
|
|
489
|
+
}
|
|
490
|
+
function isPlainObject(value) {
|
|
491
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value) && !isNode(value);
|
|
492
|
+
}
|
|
493
|
+
function isSlotLike(value) {
|
|
494
|
+
if (!isPlainObject(value) || !isPlainObject(value.ref) || !('state' in value))
|
|
495
|
+
return false;
|
|
496
|
+
const ref = value.ref;
|
|
497
|
+
return typeof ref.table === 'string' && typeof ref.id === 'string' && typeof ref.field === 'string';
|
|
498
|
+
}
|
|
499
|
+
function describe(value) {
|
|
500
|
+
if (Array.isArray(value))
|
|
501
|
+
return 'a list';
|
|
502
|
+
if (value === null)
|
|
503
|
+
return 'null';
|
|
504
|
+
if (typeof value === 'string')
|
|
505
|
+
return `a string ${JSON.stringify(value.length > 40 ? `${value.slice(0, 40)}…` : value)}`;
|
|
506
|
+
return typeof value === 'object' ? 'an object' : `a ${typeof value}`;
|
|
507
|
+
}
|
|
508
|
+
/** Data as JSON keeps it: no functions, no slots, no nodes, `undefined` left out. */
|
|
509
|
+
function plain(where, value, depth = 0) {
|
|
510
|
+
if (depth > MAX_DEPTH)
|
|
511
|
+
throw new Error(`${where}: nested deeper than ${MAX_DEPTH}`);
|
|
512
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
513
|
+
return value;
|
|
514
|
+
if (typeof value === 'number')
|
|
515
|
+
return Number.isFinite(value) ? value : String(value);
|
|
516
|
+
if (typeof value === 'function')
|
|
517
|
+
throw new Error(`${where}: a function is not data`);
|
|
518
|
+
if (isNode(value))
|
|
519
|
+
throw new Error(`${where}: data() holds data only: put page nodes in block() (found ${value[NODE]}())`);
|
|
520
|
+
if (isAction(value))
|
|
521
|
+
throw new Error(`${where}: data() holds data only: an action goes in button() or doNow()`);
|
|
522
|
+
if (isSlotLike(value))
|
|
523
|
+
throw new Error(`${where}: a slot is placed with field(slot) (read only: field(slot, { readOnly: true }))`);
|
|
524
|
+
if (value instanceof Date)
|
|
525
|
+
return value.toISOString();
|
|
526
|
+
if (Array.isArray(value))
|
|
527
|
+
return value.map((item, i) => plain(`${where}.${i}`, item, depth + 1));
|
|
528
|
+
if (typeof value === 'object') {
|
|
529
|
+
const out = {};
|
|
530
|
+
for (const [key, item] of Object.entries(value)) {
|
|
531
|
+
if (item === undefined)
|
|
532
|
+
continue;
|
|
533
|
+
out[key] = plain(`${where}.${key}`, item, depth + 1);
|
|
534
|
+
}
|
|
535
|
+
return out;
|
|
536
|
+
}
|
|
537
|
+
return String(value);
|
|
538
|
+
}
|
|
539
|
+
//# sourceMappingURL=fns.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CompiledSlotSchema } from '../slot/types.js';
|
|
2
|
+
import { type RecordsCall, type RowOut, type SlotOut } from './records-query.js';
|
|
3
|
+
import type { HostResult } from './sandbox.js';
|
|
4
|
+
/**
|
|
5
|
+
* A test's rows, by table: each an `id` and its fields' values (a missing or null field is empty). A field
|
|
6
|
+
* asked and not known, declined, or with a reason is `{ $slot: { value, state, reason } }` (a turn's
|
|
7
|
+
* export writes them so, 68 §7.4).
|
|
8
|
+
*/
|
|
9
|
+
export type MemoryRows = Readonly<Record<string, ReadonlyArray<Readonly<Record<string, unknown>>>>>;
|
|
10
|
+
export type MemoryReader = {
|
|
11
|
+
execute(calls: RecordsCall[]): Promise<HostResult[]>;
|
|
12
|
+
row(table: string, id: string): RowOut | null;
|
|
13
|
+
rowInfo(id: string): {
|
|
14
|
+
table: string;
|
|
15
|
+
slots: Record<string, Omit<SlotOut, 'ref' | 'row'>>;
|
|
16
|
+
} | undefined;
|
|
17
|
+
parentsOf(table: string): string[];
|
|
18
|
+
adoptRow(table: string, row: RowOut): void;
|
|
19
|
+
/** Every read, in order: `table.method` and its arguments, for a test that checks what the page read. */
|
|
20
|
+
readonly calls: RecordsCall[];
|
|
21
|
+
};
|
|
22
|
+
export declare function createMemoryReader(input: {
|
|
23
|
+
schema: CompiledSlotSchema;
|
|
24
|
+
rows: MemoryRows;
|
|
25
|
+
now: number;
|
|
26
|
+
}): MemoryReader;
|
|
27
|
+
//# sourceMappingURL=memory-reader.d.ts.map
|