@superpanel-sdk/runtime 0.5.0 → 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.
Files changed (189) hide show
  1. package/README.md +3 -3
  2. package/dist/builder/codegen/emit.d.ts +5 -10
  3. package/dist/builder/codegen/emit.js +100 -106
  4. package/dist/builder/codegen/generate.d.ts +3 -5
  5. package/dist/builder/codegen/generate.js +13 -32
  6. package/dist/builder/company-imports.d.ts +5 -0
  7. package/dist/builder/company-imports.js +9 -1
  8. package/dist/builder/compile/compile-sequence.d.ts +13 -11
  9. package/dist/builder/compile/compile-sequence.js +135 -83
  10. package/dist/builder/compile/function-code.d.ts +15 -9
  11. package/dist/builder/compile/function-code.js +59 -29
  12. package/dist/builder/compile/registry.d.ts +4 -4
  13. package/dist/builder/compile/registry.js +14 -12
  14. package/dist/builder/errors.d.ts +9 -0
  15. package/dist/builder/errors.js +17 -0
  16. package/dist/builder/function-params.d.ts +10 -0
  17. package/dist/builder/function-params.js +203 -0
  18. package/dist/builder/load.d.ts +17 -17
  19. package/dist/builder/load.js +27 -25
  20. package/dist/builder/paths.d.ts +6 -1
  21. package/dist/builder/paths.js +16 -2
  22. package/dist/builder/schedules.d.ts +9 -7
  23. package/dist/builder/schedules.js +20 -21
  24. package/dist/builder/typecheck.d.ts +1 -1
  25. package/dist/builder/typecheck.js +3 -6
  26. package/dist/builder/types.d.ts +41 -5
  27. package/dist/builder/types.js +19 -0
  28. package/dist/cli/calls/index.d.ts +177 -0
  29. package/dist/cli/calls/index.js +96 -0
  30. package/dist/cli/debug/index.d.ts +31 -0
  31. package/dist/cli/debug/index.js +119 -0
  32. package/dist/cli/exec/index.d.ts +39 -0
  33. package/dist/cli/exec/index.js +138 -0
  34. package/dist/cli/exec/session.d.ts +48 -0
  35. package/dist/cli/exec/session.js +82 -0
  36. package/dist/cli/exec/trace.d.ts +18 -0
  37. package/dist/cli/exec/trace.js +52 -0
  38. package/dist/cli/index.js +84 -1
  39. package/dist/cli/logs/index.d.ts +47 -0
  40. package/dist/cli/logs/index.js +113 -0
  41. package/dist/cli/mobile/index.d.ts +12 -0
  42. package/dist/cli/mobile/index.js +405 -0
  43. package/dist/cli/serve/index.d.ts +40 -4
  44. package/dist/cli/serve/index.js +167 -28
  45. package/dist/cli/turns/index.d.ts +105 -0
  46. package/dist/cli/turns/index.js +128 -0
  47. package/dist/cli/turns/run.d.ts +13 -0
  48. package/dist/cli/turns/run.js +57 -0
  49. package/dist/cli/turns/shadow.d.ts +112 -0
  50. package/dist/cli/turns/shadow.js +54 -0
  51. package/dist/cli/type-gen/index.js +0 -1
  52. package/dist/compute-host/compute-host.d.ts +5 -6
  53. package/dist/compute-host/compute-host.js +4 -13
  54. package/dist/compute-host/source-compute-host.d.ts +5 -7
  55. package/dist/compute-host/source-compute-host.js +12 -33
  56. package/dist/core/case-orchestrator/contact-hours.d.ts +26 -6
  57. package/dist/core/case-orchestrator/contact-hours.js +27 -8
  58. package/dist/core/case-orchestrator/index.d.ts +1 -4
  59. package/dist/core/case-orchestrator/index.js +1 -4
  60. package/dist/core/case-orchestrator/render.d.ts +1 -1
  61. package/dist/core/case-orchestrator/render.js +15 -10
  62. package/dist/core/case-orchestrator/types.d.ts +83 -128
  63. package/dist/core/case-orchestrator/types.js +0 -4
  64. package/dist/core/context/email-message.d.ts +40 -1
  65. package/dist/core/context/email-message.js +27 -0
  66. package/dist/core/context/email.d.ts +13 -1
  67. package/dist/core/context/email.js +2 -3
  68. package/dist/core/context/exchanges.d.ts +25 -0
  69. package/dist/core/context/exchanges.js +25 -7
  70. package/dist/core/context/instances.d.ts +31 -2
  71. package/dist/core/context/instances.js +18 -4
  72. package/dist/core/context/platform-registry.d.ts +35 -0
  73. package/dist/core/context/sms.d.ts +30 -0
  74. package/dist/core/context/sms.js +20 -0
  75. package/dist/core/context/types.d.ts +29 -1
  76. package/dist/core/conversation/index.d.ts +58 -4
  77. package/dist/core/conversation/index.js +123 -4
  78. package/dist/core/functions/errors.d.ts +74 -0
  79. package/dist/core/functions/errors.js +26 -0
  80. package/dist/core/functions/events.d.ts +23 -78
  81. package/dist/core/functions/events.js +8 -17
  82. package/dist/core/functions/schedules.d.ts +9 -10
  83. package/dist/core/functions/schedules.js +2 -2
  84. package/dist/core/functions/types.d.ts +17 -72
  85. package/dist/core/functions/types.js +1 -9
  86. package/dist/core/page/define.d.ts +230 -160
  87. package/dist/core/page/define.js +145 -104
  88. package/dist/core/page/envelope.d.ts +55 -0
  89. package/dist/core/page/envelope.js +4 -0
  90. package/dist/core/page/fns.d.ts +448 -0
  91. package/dist/core/page/fns.js +539 -0
  92. package/dist/core/page/memory-reader.d.ts +27 -0
  93. package/dist/core/page/memory-reader.js +299 -0
  94. package/dist/core/page/page-host.d.ts +20 -0
  95. package/dist/core/page/page-host.js +57 -0
  96. package/dist/core/page/records-query.d.ts +234 -0
  97. package/dist/core/page/records-query.js +780 -0
  98. package/dist/core/page/render.d.ts +67 -25
  99. package/dist/core/page/render.js +492 -272
  100. package/dist/core/page/sandbox.d.ts +85 -0
  101. package/dist/core/page/sandbox.js +357 -0
  102. package/dist/core/page/time-format.d.ts +27 -0
  103. package/dist/core/page/time-format.js +84 -0
  104. package/dist/core/page/typed.d.ts +165 -159
  105. package/dist/core/page/workspace.d.ts +25 -1
  106. package/dist/core/page/workspace.js +48 -13
  107. package/dist/core/party/schema.d.ts +74 -3
  108. package/dist/core/party/schema.js +39 -6
  109. package/dist/core/party/types.d.ts +8 -13
  110. package/dist/core/sequence/define.d.ts +1 -2
  111. package/dist/core/slot/company-metadata.d.ts +11 -22
  112. package/dist/core/slot/company-metadata.js +12 -39
  113. package/dist/core/slot/compile-schema.js +6 -6
  114. package/dist/core/slot/field.d.ts +1 -2
  115. package/dist/core/slot/field.js +0 -2
  116. package/dist/core/slot/index.d.ts +3 -3
  117. package/dist/core/slot/index.js +3 -3
  118. package/dist/core/slot/naming.d.ts +1 -1
  119. package/dist/core/slot/naming.js +1 -1
  120. package/dist/core/slot/platform-fields.d.ts +4 -4
  121. package/dist/core/slot/platform-fields.js +5 -5
  122. package/dist/core/slot/schema-compat.d.ts +8 -7
  123. package/dist/core/slot/schema-compat.js +22 -31
  124. package/dist/core/slot/schema.d.ts +3 -59
  125. package/dist/core/slot/schema.js +0 -15
  126. package/dist/core/slot/types.d.ts +4 -10
  127. package/dist/core/slot/value.d.ts +2 -0
  128. package/dist/core/slot/value.js +6 -2
  129. package/dist/dev-overlay/protocol.d.ts +38 -5
  130. package/dist/fn-context/build-context.js +10 -4
  131. package/dist/fn-context/fake/index.d.ts +2 -0
  132. package/dist/fn-context/fake/index.js +5 -3
  133. package/dist/fn-context/fn-params.d.ts +2 -5
  134. package/dist/fn-context/fn-params.js +6 -9
  135. package/dist/fn-context/index.d.ts +1 -1
  136. package/dist/fn-context/make-ctx.js +5 -4
  137. package/dist/fn-context/rpc/case-orchestrator-tools.d.ts +11 -3
  138. package/dist/fn-context/rpc/case-orchestrator-tools.js +0 -1
  139. package/dist/fn-context/rpc/errors.d.ts +13 -0
  140. package/dist/fn-context/rpc/errors.js +22 -0
  141. package/dist/fn-context/rpc/index.d.ts +1 -1
  142. package/dist/fn-context/rpc/index.js +1 -1
  143. package/dist/fn-context/rpc/integration/index.d.ts +11 -1
  144. package/dist/fn-context/rpc/log.d.ts +11 -1
  145. package/dist/fn-context/rpc/platform.d.ts +11 -1
  146. package/dist/fn-context/rpc/records.d.ts +13 -6
  147. package/dist/fn-context/rpc/records.js +2 -4
  148. package/dist/fn-context/rpc/trpc-client.d.ts +42 -0
  149. package/dist/fn-context/rpc/trpc-client.js +151 -22
  150. package/dist/fn-context/rpc/trpc-init.d.ts +19 -1
  151. package/dist/fn-context/rpc/trpc-init.js +10 -4
  152. package/dist/fn-context/rpc/trpc-router-factory.d.ts +66 -8
  153. package/dist/fn-context/run-scope.js +1 -1
  154. package/dist/fn-context/types.d.ts +20 -6
  155. package/dist/index.d.ts +11 -12
  156. package/dist/index.js +5 -8
  157. package/dist/logger.d.ts +4 -0
  158. package/dist/logger.js +8 -0
  159. package/dist/platform.d.ts +32 -33
  160. package/dist/platform.js +23 -22
  161. package/dist/testing-compile-child.d.ts +2 -0
  162. package/dist/testing-compile-child.js +20 -0
  163. package/dist/testing.d.ts +368 -0
  164. package/dist/testing.js +439 -0
  165. package/dist/voice-service/actions.d.ts +12 -35
  166. package/dist/voice-service/index.d.ts +2 -0
  167. package/dist/voice-service/index.js +2 -0
  168. package/dist/voice-service/protocol.d.ts +604 -0
  169. package/dist/voice-service/protocol.js +67 -0
  170. package/dist/voice-service/types.d.ts +16 -6
  171. package/dist/worker.d.ts +2 -1
  172. package/dist/worker.js +2 -1
  173. package/package.json +20 -16
  174. package/dist/core/case-orchestrator/follow-ups.d.ts +0 -31
  175. package/dist/core/case-orchestrator/follow-ups.js +0 -103
  176. package/dist/core/case-orchestrator/tools.d.ts +0 -29
  177. package/dist/core/case-orchestrator/tools.js +0 -31
  178. package/dist/core/case-orchestrator/validate.d.ts +0 -11
  179. package/dist/core/case-orchestrator/validate.js +0 -63
  180. package/dist/core/functions/define/bind.d.ts +0 -25
  181. package/dist/core/functions/define/bind.js +0 -14
  182. package/dist/core/functions/define/case.d.ts +0 -9
  183. package/dist/core/functions/define/case.js +0 -13
  184. package/dist/core/functions/define/events.d.ts +0 -20
  185. package/dist/core/functions/define/events.js +0 -21
  186. package/dist/core/functions/define/no-case.d.ts +0 -4
  187. package/dist/core/functions/define/no-case.js +0 -9
  188. package/dist/core/functions/func-ref.d.ts +0 -25
  189. package/dist/core/functions/func-ref.js +0 -20
@@ -0,0 +1,299 @@
1
+ /**
2
+ * A page's `db` over rows a test gives (the runtime's page unit tests, `testing.ts`): the same reads the
3
+ * platform's reader (services `slot/page/records.ts`) answers from the database, answered from memory.
4
+ * What a where, orderBy, paging, include and select mean, and which reads are refused, is the shared
5
+ * `records-query.ts`; only where the candidate rows come from differs (every row of the table the test
6
+ * gave, in the order it gave them). Two things it does not do: limits on bytes and rows read (a test's
7
+ * data is small), and putting a phone operand in stored form (write test data and where values in
8
+ * E.164, `+14155550123`).
9
+ */
10
+ import { parentOf, SESSION_TABLE } from '../slot/company-metadata.js';
11
+ import { getCompiledTable, getTableField, relationsOf } from '../slot/compile-schema.js';
12
+ import { emptyFieldValue, makeFieldValue } from '../slot/field.js';
13
+ import { slotFieldNames } from '../slot/row.js';
14
+ import { afterOf, checkListSizes, findAnchors, isPlainObject, limitExceeded, matches, optionalCount, orderKeys, orderRows, pageRows, RECORDS_LIMITS, RecordsQueryError, refuseSearch, relatedKey, relationFilters, RELATION_INCLUDE_KEYS, selectFields, toSlot, unstartableWhy, wayBackFilters, } from './records-query.js';
15
+ const SLOT_STATES = ['filled', 'empty', 'asked_unknown', 'declined'];
16
+ /** A field given as `{ $slot: { value, state, reason } }`, or null for a plain value. */
17
+ function slotGiven(value) {
18
+ if (!isPlainObject(value) || !('$slot' in value))
19
+ return null;
20
+ const slot = value.$slot;
21
+ if (!isPlainObject(slot) || !SLOT_STATES.includes(slot.state)) {
22
+ throw new Error(`renderPage: a $slot is { value, state, reason } with state one of ${SLOT_STATES.join(', ')}`);
23
+ }
24
+ return slot;
25
+ }
26
+ export function createMemoryReader(input) {
27
+ const { schema, rows: given, now } = input;
28
+ const read = new Map();
29
+ const truth = new Map();
30
+ const parents = new Map();
31
+ const calls = [];
32
+ const tableOf = (name) => {
33
+ if (name === SESSION_TABLE)
34
+ throw new RecordsQueryError(`records.${name}: the session is ctx.session, not a table`);
35
+ const table = getCompiledTable(schema, name);
36
+ if (!table)
37
+ throw new RecordsQueryError(`records.${name}: no such table in this sequence`);
38
+ return table;
39
+ };
40
+ // The test's data is checked whole, up front: a mistake in a row the page never reads still shows.
41
+ for (const [name, rows] of Object.entries(given)) {
42
+ const table = getCompiledTable(schema, name);
43
+ if (!table)
44
+ throw new Error(`renderPage: data.${name}: no such table in the company schema`);
45
+ for (const row of rows) {
46
+ if (typeof row.id !== 'string' || !row.id)
47
+ throw new Error(`renderPage: data.${name}: every row needs a string id`);
48
+ for (const key of Object.keys(row)) {
49
+ if (key !== 'id' && key !== 'createdAt' && key !== 'updatedAt' && !getTableField(table, key)) {
50
+ throw new Error(`renderPage: data.${name} row ${row.id}: ${key} is not a field of ${name}`);
51
+ }
52
+ }
53
+ }
54
+ }
55
+ const relationOf = (tableName, name) => relationsOf(schema, tableName).find((r) => r.name === name);
56
+ /** A given row as `db` hands it out: each schema field a slot, filled when the test gave a value. */
57
+ const toRowOut = (tableName, data) => {
58
+ const table = tableOf(tableName);
59
+ const id = String(data.id);
60
+ const out = { id };
61
+ for (const key of ['createdAt', 'updatedAt'])
62
+ if (typeof data[key] === 'string')
63
+ out[key] = data[key];
64
+ for (const field of slotFieldNames(table.fields)) {
65
+ const value = data[field];
66
+ const slot = slotGiven(value);
67
+ const fv = slot
68
+ ? makeFieldValue({ value: (slot.value ?? null), state: slot.state, reason: slot.reason ?? null, updatedAt: now })
69
+ : value === undefined || value === null
70
+ ? emptyFieldValue(now)
71
+ : makeFieldValue({ value: value, state: 'filled', updatedAt: now });
72
+ out[field] = toSlot(tableName, id, field, fv);
73
+ }
74
+ if (!read.has(id))
75
+ read.set(id, { table: tableName });
76
+ truth.set(id, out);
77
+ return out;
78
+ };
79
+ const allRows = (tableName) => (given[tableName] ?? []).map((data) => toRowOut(tableName, data));
80
+ const noteParents = (tableName, table, field, ids) => {
81
+ if (parentOf(table)?.field !== field)
82
+ return;
83
+ const set = parents.get(tableName) ?? new Set();
84
+ for (const id of ids)
85
+ set.add(id);
86
+ parents.set(tableName, set);
87
+ };
88
+ /** The rows of a way back for each parent, in the order the test gave them. */
89
+ const childrenOf = (relation, parentIds) => {
90
+ const children = allRows(relation.table);
91
+ const out = new Map();
92
+ for (const id of parentIds)
93
+ out.set(id, children.filter((child) => child[relation.field].value === id));
94
+ return out;
95
+ };
96
+ const filterRows = (tableName, rows, where) => {
97
+ const table = tableOf(tableName);
98
+ const relations = relationFilters(table, where);
99
+ const ways = wayBackFilters(tableName, where, relationOf);
100
+ const nested = (target, sub) => sub.some((w) => isPlainObject(w) && (relationFilters(tableOf(target), w).length > 0 || wayBackFilters(target, w, relationOf).length > 0));
101
+ for (const { field, target, sub } of relations) {
102
+ if (nested(target, sub)) {
103
+ throw new RecordsQueryError(`records.${tableName}: where.${field}.is filters one level only; read the related rows first, then filter by their ids (in)`);
104
+ }
105
+ }
106
+ for (const { relation, sub } of ways) {
107
+ if (nested(relation.table, sub)) {
108
+ throw new RecordsQueryError(`records.${tableName}: where.${relation.name} filters one level only; read those rows first, then filter by their ids (in)`);
109
+ }
110
+ }
111
+ const targets = new Map();
112
+ for (const { target } of relations)
113
+ for (const row of allRows(target))
114
+ targets.set(row.id, row);
115
+ const related = new Map();
116
+ for (const { relation } of ways) {
117
+ const key = relatedKey(tableName, relation.name);
118
+ if (!related.has(key))
119
+ related.set(key, childrenOf(relation, rows.map((row) => row.id)));
120
+ }
121
+ const context = { schema, targets, related, relationOf };
122
+ return rows.filter((row) => matches(context, tableName, row, where));
123
+ };
124
+ const includeRelation = (tableName, rows, relation, spec, depth) => {
125
+ const options = isPlainObject(spec) ? spec : {};
126
+ for (const key of Object.keys(options)) {
127
+ if (!RELATION_INCLUDE_KEYS.has(key)) {
128
+ throw new RecordsQueryError(`records.${tableName}: include.${relation.name}.${key} is not supported (where, orderBy, skip, take, include)`);
129
+ }
130
+ }
131
+ if (options.where !== undefined)
132
+ refuseSearch(relation.table, options.where, `include.${relation.name}.where`);
133
+ const children = childrenOf(relation, rows.map((row) => row.id));
134
+ const all = [...children.values()].flat();
135
+ const kept = options.where === undefined ? null : new Set(filterRows(relation.table, all, options.where).map((row) => row.id));
136
+ const childTable = tableOf(relation.table);
137
+ const skip = optionalCount(options.skip, `include.${relation.name}.skip`) ?? 0;
138
+ const take = optionalCount(options.take, `include.${relation.name}.take`);
139
+ const shown = [];
140
+ for (const row of rows) {
141
+ let list = (children.get(row.id) ?? []).filter((child) => !kept || kept.has(child.id));
142
+ list = orderRows(orderKeys(childTable, options.orderBy), list).slice(skip, take === undefined ? undefined : skip + take);
143
+ row[relation.name] = list;
144
+ shown.push(...list);
145
+ }
146
+ include(relation.table, shown, options.include, depth + 1);
147
+ };
148
+ const include = (tableName, rows, spec, depth) => {
149
+ if (spec === undefined || spec === null || spec === false)
150
+ return;
151
+ if (!isPlainObject(spec))
152
+ throw new RecordsQueryError(`records.${tableName}: include must be an object`);
153
+ if (depth > RECORDS_LIMITS.includeDepth)
154
+ throw limitExceeded(`records.${tableName}: include goes deeper than ${RECORDS_LIMITS.includeDepth} levels`);
155
+ const table = tableOf(tableName);
156
+ for (const [field, nested] of Object.entries(spec)) {
157
+ if (nested === false || nested === undefined)
158
+ continue;
159
+ const ref = getTableField(table, field)?.ref;
160
+ if (!ref) {
161
+ const relation = relationOf(tableName, field);
162
+ if (relation) {
163
+ includeRelation(tableName, rows, relation, nested, depth);
164
+ continue;
165
+ }
166
+ throw new RecordsQueryError(`records.${tableName}: include.${field} is not a lookup nor a way back (relatedName) of ${tableName}`);
167
+ }
168
+ if (ref.kind !== 'lookup')
169
+ throw new RecordsQueryError(`records.${tableName}: include.${field} is the parent; read the parent by id`);
170
+ tableOf(ref.table);
171
+ const byId = new Map(allRows(ref.table).map((row) => [row.id, row]));
172
+ const nestedInclude = isPlainObject(nested) ? nested.include : undefined;
173
+ include(ref.table, [...byId.values()], nestedInclude, depth + 1);
174
+ for (const row of rows) {
175
+ const slot = row[field];
176
+ slot.row = typeof slot.value === 'string' ? (byId.get(slot.value) ?? null) : null;
177
+ }
178
+ }
179
+ };
180
+ /** The where as the platform takes it: an email operand lower-cased, as it is stored. */
181
+ const normalizeWhere = (table, where) => {
182
+ const out = {};
183
+ for (const [field, cond] of Object.entries(where)) {
184
+ if (field === 'AND' || field === 'OR' || field === 'NOT') {
185
+ out[field] = Array.isArray(cond) ? cond.map((part) => (isPlainObject(part) ? normalizeWhere(table, part) : part)) : isPlainObject(cond) ? normalizeWhere(table, cond) : cond;
186
+ continue;
187
+ }
188
+ const def = Object.hasOwn(table.fields, field) ? table.fields[field] : undefined;
189
+ out[field] = def?.dataType === 'email' ? lowerEmail(cond) : cond;
190
+ }
191
+ return out;
192
+ };
193
+ const findMany = (tableName, args) => {
194
+ const table = tableOf(tableName);
195
+ const where = normalizeWhere(table, (args.where ?? {}));
196
+ refuseSearch(tableName, where);
197
+ checkListSizes(tableName, where);
198
+ const keys = orderKeys(table, args.orderBy);
199
+ const place = afterOf(tableName, keys, args);
200
+ const anchors = findAnchors(table, where);
201
+ if (anchors.length === 0) {
202
+ const why = unstartableWhy(tableName, table, where);
203
+ if (why)
204
+ throw new RecordsQueryError(why);
205
+ }
206
+ else {
207
+ noteParents(tableName, table, anchors[0].field, anchors[0].ids);
208
+ }
209
+ let rows = filterRows(tableName, allRows(tableName), where);
210
+ rows = orderRows(keys, rows);
211
+ rows = pageRows(keys, rows, place, args);
212
+ include(tableName, rows, args.include, 1);
213
+ return rows.map((row) => selectFields(tableName, table, row, args.select));
214
+ };
215
+ const run = (call) => {
216
+ calls.push(call);
217
+ const args = (call.args ?? {});
218
+ if (!isPlainObject(args))
219
+ throw new RecordsQueryError(`records.${call.table}.${call.method}: arguments must be an object`);
220
+ switch (call.method) {
221
+ case 'findMany':
222
+ return findMany(call.table, args);
223
+ case 'findFirst':
224
+ return findMany(call.table, { ...args, take: 1 })[0] ?? null;
225
+ case 'findUnique': {
226
+ const where = (args.where ?? {});
227
+ const keys = Object.keys(where);
228
+ if (keys.length !== 1 || keys[0] !== 'id' || typeof where.id !== 'string') {
229
+ throw new RecordsQueryError(`records.${call.table}.findUnique: where is { id }`);
230
+ }
231
+ return findMany(call.table, { ...args, take: 1 })[0] ?? null;
232
+ }
233
+ case 'count':
234
+ return findMany(call.table, { where: args.where, take: RECORDS_LIMITS.rowsPerCall }).length;
235
+ default:
236
+ throw new RecordsQueryError(`records.${call.table}: no method ${String(call.method)} (records is read-only)`);
237
+ }
238
+ };
239
+ return {
240
+ calls,
241
+ async execute(batch) {
242
+ return batch.map((call) => {
243
+ try {
244
+ return { ok: true, value: run(call) };
245
+ }
246
+ catch (error) {
247
+ if (error instanceof RecordsQueryError)
248
+ return { ok: false, error: error.message, code: error.code };
249
+ throw error;
250
+ }
251
+ });
252
+ },
253
+ row(tableName, id) {
254
+ tableOf(tableName);
255
+ const data = (given[tableName] ?? []).find((row) => String(row.id) === id);
256
+ return data ? toRowOut(tableName, data) : null;
257
+ },
258
+ rowInfo(id) {
259
+ const seen = read.get(id);
260
+ const row = truth.get(id);
261
+ if (!seen || !row)
262
+ return undefined;
263
+ const slots = {};
264
+ for (const [field, slot] of Object.entries(row)) {
265
+ if (!slot || typeof slot !== 'object' || Array.isArray(slot))
266
+ continue;
267
+ const { value, state, reason, updatedAt } = slot;
268
+ slots[field] = { value, state, reason, updatedAt };
269
+ }
270
+ return { table: seen.table, slots };
271
+ },
272
+ parentsOf(table) {
273
+ return [...(parents.get(table) ?? [])];
274
+ },
275
+ adoptRow(table, row) {
276
+ read.set(row.id, { table });
277
+ truth.set(row.id, row);
278
+ },
279
+ };
280
+ }
281
+ function lowerEmail(cond) {
282
+ if (typeof cond === 'string')
283
+ return cond.trim().toLowerCase();
284
+ if (!isPlainObject(cond))
285
+ return cond;
286
+ const out = {};
287
+ for (const [op, operand] of Object.entries(cond)) {
288
+ if (op === 'equals' || op === 'startsWith')
289
+ out[op] = typeof operand === 'string' ? operand.trim().toLowerCase() : operand;
290
+ else if (op === 'in' || op === 'notIn')
291
+ out[op] = Array.isArray(operand) ? operand.map((v) => (typeof v === 'string' ? v.trim().toLowerCase() : v)) : operand;
292
+ else if (op === 'not')
293
+ out[op] = lowerEmail(operand);
294
+ else
295
+ out[op] = operand;
296
+ }
297
+ return out;
298
+ }
299
+ //# sourceMappingURL=memory-reader.js.map
@@ -0,0 +1,20 @@
1
+ import type { HostResult } from './sandbox.js';
2
+ /** The zone a page reads time in when the contact gives none. */
3
+ export declare const DEFAULT_PAGE_ZONE = "America/New_York";
4
+ /** A row as `db` hands it out, as far as its time zone goes: each field a slot with a `value`. */
5
+ type CallerRow = Record<string, unknown> | null;
6
+ /** The contact's zone (their `timezone`, else from postal code, state or phone) and today there (25 §11). */
7
+ export declare function timeOfCaller(caller: CallerRow, now: Date): {
8
+ zone: string;
9
+ today: string;
10
+ };
11
+ /** `ctx.time.format` (25 §11). */
12
+ export declare function timeCall(args: unknown[]): HostResult;
13
+ /** `ctx.time.day` (25 §11). */
14
+ export declare function dayCall(args: unknown[]): HostResult;
15
+ /** `ctx.time.at` (74 §4.7): a day and a wall time in a zone, as an instant. */
16
+ export declare function atCall(args: unknown[]): HostResult;
17
+ /** `ctx.time.parts` (74 §4.7): an instant's wall-clock parts in a zone. */
18
+ export declare function partsCall(args: unknown[]): HostResult;
19
+ export {};
20
+ //# sourceMappingURL=page-host.d.ts.map
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The page script's host calls that read no data (25 §11, 74 §4.7): `ctx.time.format`, `.day`, `.at` and `.parts`, done
3
+ * outside the sandbox where `Intl` is, and the caller's zone and today. The platform's render and the
4
+ * runtime's page unit tests answer them with these.
5
+ */
6
+ import { contactTimezone } from '../slot/contact-timezone.js';
7
+ import { dayIn, formatTime, timeAt, timeParts } from './time-format.js';
8
+ /** The zone a page reads time in when the contact gives none. */
9
+ export const DEFAULT_PAGE_ZONE = 'America/New_York';
10
+ /** The contact's zone (their `timezone`, else from postal code, state or phone) and today there (25 §11). */
11
+ export function timeOfCaller(caller, now) {
12
+ const value = (name) => caller?.[name]?.value;
13
+ const zone = contactTimezone({ timezone: value('timezone'), postal_code: value('postal_code'), state: value('state'), country: value('country'), phone: value('phone') }, DEFAULT_PAGE_ZONE).timezone;
14
+ return { zone, today: dayIn(now, zone) };
15
+ }
16
+ /** `ctx.time.format` (25 §11). */
17
+ export function timeCall(args) {
18
+ const [at, options] = args;
19
+ try {
20
+ if (typeof at !== 'string' && typeof at !== 'number')
21
+ throw new Error('time.format: give an ISO time');
22
+ return { ok: true, value: formatTime(at, { timeZone: String(options?.timeZone ?? DEFAULT_PAGE_ZONE), ...(options?.style ? { style: options.style } : {}) }) };
23
+ }
24
+ catch (error) {
25
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
26
+ }
27
+ }
28
+ /** `ctx.time.day` (25 §11). */
29
+ export function dayCall(args) {
30
+ const [at, timeZone] = args;
31
+ try {
32
+ if (typeof at !== 'string' && typeof at !== 'number')
33
+ throw new Error('time.day: give an ISO time');
34
+ return { ok: true, value: dayIn(at, String(timeZone ?? DEFAULT_PAGE_ZONE)) };
35
+ }
36
+ catch (error) {
37
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
38
+ }
39
+ }
40
+ /** `ctx.time.at` / `ctx.time.parts` (74 §4.7). */
41
+ function hostTime(work) {
42
+ try {
43
+ return { ok: true, value: work() };
44
+ }
45
+ catch (error) {
46
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
47
+ }
48
+ }
49
+ /** `ctx.time.at` (74 §4.7): a day and a wall time in a zone, as an instant. */
50
+ export function atCall(args) {
51
+ return hostTime(() => timeAt(String(args[0]), String(args[1]), String(args[2] ?? DEFAULT_PAGE_ZONE)));
52
+ }
53
+ /** `ctx.time.parts` (74 §4.7): an instant's wall-clock parts in a zone. */
54
+ export function partsCall(args) {
55
+ return hostTime(() => timeParts(args[0], String(args[1] ?? DEFAULT_PAGE_ZONE)));
56
+ }
57
+ //# sourceMappingURL=page-host.js.map
@@ -0,0 +1,234 @@
1
+ /**
2
+ * What a `db` read means, apart from where its rows are stored (15 §10.4, 18-record-search-index): the
3
+ * rows a page script and `ctx.api.db` see (`RowOut`, one `SlotOut` per field), what a `where`,
4
+ * `orderBy`, `after` / `skip` / `take` and `select` do to them, what a read may not say, and which of its
5
+ * conditions an index answers. The platform's reader (services `slot/page/records.ts`) loads its
6
+ * candidate rows from the database and applies these; the runtime's page unit tests
7
+ * (`testing.ts`) load them from the test's data and apply the same functions.
8
+ */
9
+ import { type TableRelation } from '../slot/compile-schema.js';
10
+ import { type DataType } from '../slot/schema.js';
11
+ import type { CompiledSlotSchema, FieldValue, RuntimeFieldDef, RuntimeTableDef } from '../slot/types.js';
12
+ export declare const RECORDS_LIMITS: {
13
+ /** Rows one call may reach through its anchor or the index, before any other filter. */
14
+ readonly rowsPerCall: 500;
15
+ /** `findMany` without `take`. */
16
+ readonly defaultTake: 100;
17
+ /** `skip`: further on, page with `after`. */
18
+ readonly maxSkip: 1000;
19
+ /** Values of one `in` / `notIn`. */
20
+ readonly listValues: 500;
21
+ /** `count` counts up to this many; more answer this. */
22
+ readonly countCap: 10000;
23
+ /** Rows read by one reader (a page run; one `ctx.api.db` call). */
24
+ readonly rowsPerRun: 5000;
25
+ /**
26
+ * Stored row data (its JSON, UTF-8 bytes) one batch of calls may read (one `ctx.api.db` call, one
27
+ * sandbox slice), and one reader: what keeps a read from filling this process's memory, checked
28
+ * before the rows are loaded (34 §8).
29
+ */
30
+ readonly bytesPerCall: number;
31
+ readonly bytesPerRun: number;
32
+ /** One statement of a read, in milliseconds: past it the read fails as `query_timeout` (34 §8). */
33
+ readonly statementMs: 3000;
34
+ readonly includeDepth: 3;
35
+ };
36
+ export type SlotState = FieldValue['state'];
37
+ /** One field as a page script sees it (15 §10.4). */
38
+ export type SlotOut = {
39
+ value: unknown;
40
+ state: SlotState;
41
+ reason: string | null;
42
+ updatedAt: string | null;
43
+ ref: {
44
+ table: string;
45
+ id: string;
46
+ field: string;
47
+ };
48
+ /** The target row, when the lookup was `include`d. */
49
+ row?: RowOut | null;
50
+ };
51
+ /**
52
+ * A row: its id and times (ISO 8601; a stored row's, not the session's), one slot per field, and the
53
+ * rows of each way back `include`d (`relatedName`).
54
+ */
55
+ export type RowOut = {
56
+ id: string;
57
+ createdAt?: string;
58
+ updatedAt?: string;
59
+ [field: string]: SlotOut | RowOut[] | string | undefined;
60
+ };
61
+ export type RecordsMethod = 'findUnique' | 'findFirst' | 'findMany' | 'count';
62
+ export type RecordsCall = {
63
+ table: string;
64
+ method: RecordsMethod;
65
+ args: Record<string, unknown>;
66
+ };
67
+ /** Why a read was refused: a read this API does not take, or one past a limit (18-record-search-index §4.10). */
68
+ export type RecordsQueryErrorCode = 'invalid_read' | 'limit_exceeded';
69
+ export declare class RecordsQueryError extends Error {
70
+ readonly code: RecordsQueryErrorCode;
71
+ constructor(message: string, code?: RecordsQueryErrorCode);
72
+ }
73
+ export declare function limitExceeded(message: string): RecordsQueryError;
74
+ export type WhereInput = Record<string, unknown>;
75
+ export type FindArgs = {
76
+ where?: unknown;
77
+ include?: unknown;
78
+ select?: unknown;
79
+ orderBy?: unknown;
80
+ take?: unknown;
81
+ skip?: unknown;
82
+ /** A row as read: the rows strictly after it in this read's order ({@link afterOf}). */
83
+ after?: unknown;
84
+ /** Replaced by `after`: refused, saying so. */
85
+ cursor?: unknown;
86
+ };
87
+ export declare const COMBINATORS: Set<string>;
88
+ /** Prisma's list filters, on a way back (`relatedName`). */
89
+ export declare const WAY_BACK_FILTERS: Set<string>;
90
+ /** What an `include` of a way back may say of its rows. */
91
+ export declare const RELATION_INCLUDE_KEYS: Set<string>;
92
+ /**
93
+ * Refuses `contains` / `endsWith` as an operator anywhere in a where (under AND / OR / NOT, a `not`, a
94
+ * relation's filter), saying what to write instead; before anything is read, whatever path the read
95
+ * takes. A field may be named `contains`: the where's own keys are fields, only a condition's are
96
+ * operators.
97
+ */
98
+ export declare function refuseSearch(tableName: string, where: unknown, path?: string): void;
99
+ /** An anchor: `ids` the field is equal to / in. */
100
+ export type Anchor = {
101
+ field: string;
102
+ ids: string[];
103
+ };
104
+ /** The fields of a read's conditions and order that are index entries (not row times): their dictionary ids are needed. */
105
+ export declare function indexedFieldsOf(conditions: readonly IndexCondition[], order: IndexOrder | null | undefined): string[];
106
+ /**
107
+ * The conditions of a where that pin it to some rows (top level and top-level AND): `id` or a ref
108
+ * field equal to / in some ids (15 §10.6.1).
109
+ */
110
+ export declare function findAnchors(table: RuntimeTableDef, where: WhereInput): Anchor[];
111
+ export declare function relationFilters(table: RuntimeTableDef, where: WhereInput): Array<{
112
+ field: string;
113
+ target: string;
114
+ sub: unknown[];
115
+ }>;
116
+ /** What a `where` is judged against beyond the row: `is` targets and the rows of each way back, by parent. */
117
+ export type MatchContext = {
118
+ schema: CompiledSlotSchema;
119
+ targets: ReadonlyMap<string, RowOut>;
120
+ /** `relatedKey(table, relatedName)` → parent id → its rows. */
121
+ related: ReadonlyMap<string, ReadonlyMap<string, RowOut[]>>;
122
+ relationOf: (tableName: string, name: string) => TableRelation | undefined;
123
+ };
124
+ /** The ways back a `where` filters by (top level and inside AND / OR / NOT), with their sub-wheres. */
125
+ export declare function wayBackFilters(tableName: string, where: WhereInput, relationOf: MatchContext['relationOf']): Array<{
126
+ relation: TableRelation;
127
+ sub: unknown[];
128
+ }>;
129
+ export declare function relatedKey(tableName: string, name: string): string;
130
+ export declare function matches(context: MatchContext, tableName: string, row: RowOut, where: unknown): boolean;
131
+ /**
132
+ * One key of `orderBy`: the field (a field, `id` or a row time), its direction, how its values compare,
133
+ * and a field's definition (none for `id` or a row time).
134
+ */
135
+ export type OrderKey = {
136
+ field: string;
137
+ sign: 1 | -1;
138
+ order: (a: unknown, b: unknown) => number;
139
+ def?: RuntimeFieldDef;
140
+ };
141
+ export declare function orderKeys(table: RuntimeTableDef, orderBy: unknown): OrderKey[];
142
+ /**
143
+ * A row's place in a read's order: its value of each order key (null or undefined: none), then its
144
+ * creation (ms) and id, which break ties.
145
+ */
146
+ export type Place = {
147
+ values: unknown[];
148
+ created: number;
149
+ id: string;
150
+ };
151
+ /** Rows in the read's order; without keys, as they come (creation order). */
152
+ export declare function orderRows(keys: readonly OrderKey[], rows: RowOut[]): RowOut[];
153
+ /**
154
+ * `orderBy` as the database can order: null without one, the field when it is one indexed field or a
155
+ * row time; undefined when it cannot (several keys, `id`, a field not indexed).
156
+ */
157
+ export declare function indexOrder(table: RuntimeTableDef, orderBy: unknown): IndexOrder | null | undefined;
158
+ /** Rows in the read's order, paged: from strictly after `place`, then `skip`, then `take`. */
159
+ export declare function pageRows(keys: readonly OrderKey[], rows: RowOut[], place: Place | undefined, args: FindArgs): RowOut[];
160
+ /**
161
+ * `after`: the place a page starts after, read off a row as the reader returned it (normally the last
162
+ * of the page before). Taken by value, never looked up: its id only breaks ties, so a row since deleted,
163
+ * changed or of another tenant is only a place and discloses nothing. It needs the row's `id` and
164
+ * `createdAt`, and its value of each order key: `updatedAt`, or the field's slot (`{ value }`, a null
165
+ * value for none). A field's value must be of its type ({@link fitsField}): one the index cannot hold
166
+ * would otherwise place as "no value" in SQL and compare as text in memory.
167
+ */
168
+ export declare function afterOf(tableName: string, keys: readonly OrderKey[], args: FindArgs): Place | undefined;
169
+ export declare function skipOf(value: unknown): number;
170
+ /** Every `in` / `notIn` of a where (and its AND / OR / NOT, way-back and `is` filters) within `listValues`. */
171
+ export declare function checkListSizes(tableName: string, where: unknown): void;
172
+ export declare function optionalCount(value: unknown, name: string): number | undefined;
173
+ export declare function selectFields(tableName: string, table: RuntimeTableDef, row: RowOut, select: unknown): RowOut;
174
+ export declare function toSlot(table: string, id: string, field: string, value: FieldValue): SlotOut;
175
+ export declare function isUuid(value: unknown): value is string;
176
+ export declare function asArray(value: unknown): unknown[];
177
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
178
+ /** A projected field as its schema declares it. */
179
+ export type IndexedField = {
180
+ dataType: DataType;
181
+ unique: boolean;
182
+ };
183
+ /** The fields of one table's definition that go to the index. */
184
+ export declare function tableIndexedFields(fields: Record<string, unknown>): Map<string, IndexedField>;
185
+ /** An index entry's typed value columns. */
186
+ export type IndexValue = {
187
+ valueText: string | null;
188
+ valueNum: number | null;
189
+ valueTime: Date | null;
190
+ };
191
+ /** A stored value's index columns; null when it has none to index (empty, or not of its type). */
192
+ export declare function indexValueOf(dataType: DataType, value: unknown): IndexValue | null;
193
+ /**
194
+ * One condition the database can answer: a field (through the index) or a row time (`row`, a column of
195
+ * `Record`), its type, and the operators on it.
196
+ */
197
+ export type IndexCondition = {
198
+ field: string;
199
+ dataType: DataType;
200
+ ops: Record<string, unknown>;
201
+ insensitive: boolean;
202
+ row?: true;
203
+ /** No operators: every row with a value, in its order (a walk along the order, 34 §7). */
204
+ walk?: true;
205
+ };
206
+ /** The order a read asks for, as the index can order: one field (or a row time) and its direction. */
207
+ export type IndexOrder = {
208
+ field: string;
209
+ dataType: DataType;
210
+ desc: boolean;
211
+ row?: true;
212
+ };
213
+ /**
214
+ * The conditions of a where the database can answer (top level and top-level AND): on indexed fields
215
+ * and row times. And whether they are the whole where (nothing else to check in memory). A condition
216
+ * on a field that is not indexed, an empty value, `not`, `notIn`, `state`, `OR` and `NOT` are left to
217
+ * the in-memory filter.
218
+ */
219
+ export declare function indexConditions(table: RuntimeTableDef, where: WhereInput): {
220
+ conditions: IndexCondition[];
221
+ whole: boolean;
222
+ };
223
+ /** The conditions that can start a query; when none can, why. */
224
+ export declare function startableConditions(conditions: readonly IndexCondition[]): {
225
+ candidates: IndexCondition[];
226
+ why: string | null;
227
+ };
228
+ /**
229
+ * Why a `where` with no anchor (`id` or a ref field equal to / in some ids) cannot be read: no condition
230
+ * on an indexed field or a row time starts it. Null when one does. The platform's reader and the page
231
+ * unit tests refuse the same reads, with this message.
232
+ */
233
+ export declare function unstartableWhy(tableName: string, table: RuntimeTableDef, where: WhereInput): string | null;
234
+ //# sourceMappingURL=records-query.d.ts.map