@prestack/thread 0.0.1

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 (248) hide show
  1. package/CHANGELOG.md +192 -0
  2. package/dist/attach.d.ts +1 -0
  3. package/dist/attach.js +1 -0
  4. package/dist/core/actor.d.ts +12 -0
  5. package/dist/core/actor.js +8 -0
  6. package/dist/core/asking.d.ts +180 -0
  7. package/dist/core/asking.js +1109 -0
  8. package/dist/core/attach.d.ts +54 -0
  9. package/dist/core/attach.js +510 -0
  10. package/dist/core/chain.d.ts +16 -0
  11. package/dist/core/chain.js +114 -0
  12. package/dist/core/cli.d.ts +2 -0
  13. package/dist/core/cli.js +1682 -0
  14. package/dist/core/condition.d.ts +52 -0
  15. package/dist/core/condition.js +198 -0
  16. package/dist/core/descend.d.ts +81 -0
  17. package/dist/core/descend.js +879 -0
  18. package/dist/core/finding.d.ts +12 -0
  19. package/dist/core/finding.js +23 -0
  20. package/dist/core/generators/meta.d.ts +2 -0
  21. package/dist/core/generators/meta.js +823 -0
  22. package/dist/core/init.d.ts +15 -0
  23. package/dist/core/init.js +134 -0
  24. package/dist/core/invoked.d.ts +9 -0
  25. package/dist/core/invoked.js +41 -0
  26. package/dist/core/round.d.ts +1 -0
  27. package/dist/core/round.js +57 -0
  28. package/dist/core/rows.d.ts +26 -0
  29. package/dist/core/rows.js +354 -0
  30. package/dist/core/scope.d.ts +6 -0
  31. package/dist/core/scope.js +12 -0
  32. package/dist/core/thread.d.ts +26 -0
  33. package/dist/core/thread.js +5 -0
  34. package/dist/core/tree.d.ts +12 -0
  35. package/dist/core/tree.js +56 -0
  36. package/dist/core/types.d.ts +49 -0
  37. package/dist/core/types.js +311 -0
  38. package/dist/derivation/attach.d.ts +2 -0
  39. package/dist/derivation/attach.js +61 -0
  40. package/dist/derivation/generators/build.d.ts +16 -0
  41. package/dist/derivation/generators/build.js +217 -0
  42. package/dist/derivation/generators/check.d.ts +18 -0
  43. package/dist/derivation/generators/check.js +78 -0
  44. package/dist/derivation/generators/derivation.d.ts +3 -0
  45. package/dist/derivation/generators/derivation.js +273 -0
  46. package/dist/derivation/generators/determinism.d.ts +20 -0
  47. package/dist/derivation/generators/determinism.js +110 -0
  48. package/dist/derivation/generators/release.d.ts +3 -0
  49. package/dist/derivation/generators/release.js +183 -0
  50. package/dist/derivation/makers/release.d.ts +1 -0
  51. package/dist/derivation/makers/release.js +107 -0
  52. package/dist/derivation/release.d.ts +10 -0
  53. package/dist/derivation/release.js +12 -0
  54. package/dist/integrations/attach.d.ts +6 -0
  55. package/dist/integrations/attach.js +177 -0
  56. package/dist/integrations/generators/read.d.ts +1 -0
  57. package/dist/integrations/generators/read.js +92 -0
  58. package/dist/integrations/generators/sdk.d.ts +1 -0
  59. package/dist/integrations/generators/sdk.js +148 -0
  60. package/dist/integrations/generators/secret.d.ts +1 -0
  61. package/dist/integrations/generators/secret.js +233 -0
  62. package/dist/integrations/generators/target.d.ts +1 -0
  63. package/dist/integrations/generators/target.js +765 -0
  64. package/dist/integrations/generators/vault.d.ts +1 -0
  65. package/dist/integrations/generators/vault.js +164 -0
  66. package/dist/integrations/sdk.d.ts +6 -0
  67. package/dist/integrations/sdk.js +151 -0
  68. package/dist/integrations/secret.d.ts +16 -0
  69. package/dist/integrations/secret.js +30 -0
  70. package/dist/integrations/target.d.ts +49 -0
  71. package/dist/integrations/target.js +225 -0
  72. package/dist/integrations/vault.d.ts +12 -0
  73. package/dist/integrations/vault.js +104 -0
  74. package/dist/integrity/attach.d.ts +2 -0
  75. package/dist/integrity/attach.js +213 -0
  76. package/dist/integrity/check-threads.d.ts +10 -0
  77. package/dist/integrity/check-threads.js +153 -0
  78. package/dist/integrity/constraint.d.ts +16 -0
  79. package/dist/integrity/constraint.js +1105 -0
  80. package/dist/integrity/generators/compiler.d.ts +1 -0
  81. package/dist/integrity/generators/compiler.js +130 -0
  82. package/dist/integrity/generators/gate.d.ts +1 -0
  83. package/dist/integrity/generators/gate.js +172 -0
  84. package/dist/integrity/generators/issues.d.ts +1 -0
  85. package/dist/integrity/generators/issues.js +100 -0
  86. package/dist/integrity/generators/publish.d.ts +1 -0
  87. package/dist/integrity/generators/publish.js +186 -0
  88. package/dist/integrity/generators/rule.d.ts +2 -0
  89. package/dist/integrity/generators/rule.js +213 -0
  90. package/dist/integrity/generators/seal.d.ts +4 -0
  91. package/dist/integrity/generators/seal.js +101 -0
  92. package/dist/integrity/generators/trial.d.ts +39 -0
  93. package/dist/integrity/generators/trial.js +254 -0
  94. package/dist/integrity/lint.d.ts +50 -0
  95. package/dist/integrity/lint.js +161 -0
  96. package/dist/integrity/lsp/bin.d.ts +2 -0
  97. package/dist/integrity/lsp/bin.js +35 -0
  98. package/dist/integrity/lsp/diagnostics.d.ts +25 -0
  99. package/dist/integrity/lsp/diagnostics.js +57 -0
  100. package/dist/integrity/lsp/findings.d.ts +20 -0
  101. package/dist/integrity/lsp/findings.js +39 -0
  102. package/dist/integrity/lsp/lint.d.ts +7 -0
  103. package/dist/integrity/lsp/lint.js +95 -0
  104. package/dist/integrity/lsp/protocol.d.ts +20 -0
  105. package/dist/integrity/lsp/protocol.js +57 -0
  106. package/dist/integrity/lsp/server.d.ts +15 -0
  107. package/dist/integrity/lsp/server.js +171 -0
  108. package/dist/integrity/sealed.d.ts +1 -0
  109. package/dist/integrity/sealed.js +56 -0
  110. package/dist/integrity/slopnator.d.ts +90 -0
  111. package/dist/integrity/slopnator.js +267 -0
  112. package/dist/integrity/slopnators/sl_broken_lineage.d.ts +2 -0
  113. package/dist/integrity/slopnators/sl_broken_lineage.js +35 -0
  114. package/dist/integrity/slopnators/sl_declared_elsewhere.d.ts +2 -0
  115. package/dist/integrity/slopnators/sl_declared_elsewhere.js +33 -0
  116. package/dist/integrity/slopnators/sl_derived_drift.d.ts +8 -0
  117. package/dist/integrity/slopnators/sl_derived_drift.js +91 -0
  118. package/dist/integrity/slopnators/sl_hardcoded_style.d.ts +2 -0
  119. package/dist/integrity/slopnators/sl_hardcoded_style.js +46 -0
  120. package/dist/integrity/slopnators/sl_inert_island.d.ts +2 -0
  121. package/dist/integrity/slopnators/sl_inert_island.js +85 -0
  122. package/dist/integrity/slopnators/sl_inert_slopnator.d.ts +2 -0
  123. package/dist/integrity/slopnators/sl_inert_slopnator.js +76 -0
  124. package/dist/integrity/slopnators/sl_missing_row.d.ts +2 -0
  125. package/dist/integrity/slopnators/sl_missing_row.js +180 -0
  126. package/dist/integrity/slopnators/sl_no_barrel.d.ts +2 -0
  127. package/dist/integrity/slopnators/sl_no_barrel.js +41 -0
  128. package/dist/integrity/slopnators/sl_no_child_process.d.ts +2 -0
  129. package/dist/integrity/slopnators/sl_no_child_process.js +61 -0
  130. package/dist/integrity/slopnators/sl_no_comment.d.ts +2 -0
  131. package/dist/integrity/slopnators/sl_no_comment.js +41 -0
  132. package/dist/integrity/slopnators/sl_no_dispatch_bypass.d.ts +2 -0
  133. package/dist/integrity/slopnators/sl_no_dispatch_bypass.js +301 -0
  134. package/dist/integrity/slopnators/sl_no_import_cycle.d.ts +2 -0
  135. package/dist/integrity/slopnators/sl_no_import_cycle.js +42 -0
  136. package/dist/integrity/slopnators/sl_no_such_command.d.ts +2 -0
  137. package/dist/integrity/slopnators/sl_no_such_command.js +55 -0
  138. package/dist/integrity/slopnators/sl_only_takes_a_literal.d.ts +2 -0
  139. package/dist/integrity/slopnators/sl_only_takes_a_literal.js +59 -0
  140. package/dist/integrity/slopnators/sl_secret_literal.d.ts +2 -0
  141. package/dist/integrity/slopnators/sl_secret_literal.js +89 -0
  142. package/dist/integrity/slopnators/sl_stale_path.d.ts +2 -0
  143. package/dist/integrity/slopnators/sl_stale_path.js +92 -0
  144. package/dist/integrity/slopnators/sl_twice_meant.d.ts +2 -0
  145. package/dist/integrity/slopnators/sl_twice_meant.js +30 -0
  146. package/dist/integrity/slopnators/sl_twice_parented.d.ts +2 -0
  147. package/dist/integrity/slopnators/sl_twice_parented.js +34 -0
  148. package/dist/integrity/slopnators/sl_undeclared_crossing.d.ts +2 -0
  149. package/dist/integrity/slopnators/sl_undeclared_crossing.js +78 -0
  150. package/dist/integrity/slopnators/sl_undeclared_destination.d.ts +2 -0
  151. package/dist/integrity/slopnators/sl_undeclared_destination.js +56 -0
  152. package/dist/integrity/slopnators/sl_undeclared_element.d.ts +2 -0
  153. package/dist/integrity/slopnators/sl_undeclared_element.js +34 -0
  154. package/dist/integrity/slopnators/sl_undeclared_node.d.ts +2 -0
  155. package/dist/integrity/slopnators/sl_undeclared_node.js +59 -0
  156. package/dist/integrity/slopnators/sl_undeclared_operation.d.ts +2 -0
  157. package/dist/integrity/slopnators/sl_undeclared_operation.js +120 -0
  158. package/dist/integrity/slopnators/sl_undeclared_variant.d.ts +2 -0
  159. package/dist/integrity/slopnators/sl_undeclared_variant.js +117 -0
  160. package/dist/integrity/slopnators/sl_undescribed.d.ts +2 -0
  161. package/dist/integrity/slopnators/sl_undescribed.js +54 -0
  162. package/dist/integrity/slopnators/sl_ungoverned_island.d.ts +2 -0
  163. package/dist/integrity/slopnators/sl_ungoverned_island.js +58 -0
  164. package/dist/integrity/slopnators/sl_unkept_rows.d.ts +2 -0
  165. package/dist/integrity/slopnators/sl_unkept_rows.js +104 -0
  166. package/dist/integrity/slopnators/sl_unnamed_island.d.ts +2 -0
  167. package/dist/integrity/slopnators/sl_unnamed_island.js +37 -0
  168. package/dist/integrity/slopnators/sl_unpaired_member.d.ts +2 -0
  169. package/dist/integrity/slopnators/sl_unpaired_member.js +114 -0
  170. package/dist/integrity/slopnators/sl_unpaired_slopnator.d.ts +2 -0
  171. package/dist/integrity/slopnators/sl_unpaired_slopnator.js +60 -0
  172. package/dist/integrity/slopnators/sl_unprefixed_member.d.ts +2 -0
  173. package/dist/integrity/slopnators/sl_unprefixed_member.js +126 -0
  174. package/dist/integrity/slopnators/sl_unseamed_reach.d.ts +2 -0
  175. package/dist/integrity/slopnators/sl_unseamed_reach.js +73 -0
  176. package/dist/integrity/slopnators/sl_unthreaded.d.ts +56 -0
  177. package/dist/integrity/slopnators/sl_unthreaded.js +352 -0
  178. package/dist/integrity/slopnators/sl_untranslated_key.d.ts +2 -0
  179. package/dist/integrity/slopnators/sl_untranslated_key.js +157 -0
  180. package/dist/integrity/slopnators/sl_unversioned.d.ts +2 -0
  181. package/dist/integrity/slopnators/sl_unversioned.js +31 -0
  182. package/dist/integrity/sweep.d.ts +24 -0
  183. package/dist/integrity/sweep.js +264 -0
  184. package/dist/knowledge/attach.d.ts +2 -0
  185. package/dist/knowledge/attach.js +339 -0
  186. package/dist/knowledge/generators/docsgen.d.ts +1 -0
  187. package/dist/knowledge/generators/docsgen.js +145 -0
  188. package/dist/knowledge/generators/glossary.d.ts +1 -0
  189. package/dist/knowledge/generators/glossary.js +56 -0
  190. package/dist/knowledge/generators/skills.d.ts +1 -0
  191. package/dist/knowledge/generators/skills.js +222 -0
  192. package/dist/knowledge/glossary.d.ts +6 -0
  193. package/dist/knowledge/glossary.js +1 -0
  194. package/dist/operations/access.d.ts +27 -0
  195. package/dist/operations/access.js +61 -0
  196. package/dist/operations/attach.d.ts +6 -0
  197. package/dist/operations/attach.js +414 -0
  198. package/dist/operations/capabilities.d.ts +37 -0
  199. package/dist/operations/capabilities.js +35 -0
  200. package/dist/operations/cost.d.ts +14 -0
  201. package/dist/operations/cost.js +37 -0
  202. package/dist/operations/dispatch.d.ts +14 -0
  203. package/dist/operations/dispatch.js +327 -0
  204. package/dist/operations/error-results.d.ts +4 -0
  205. package/dist/operations/error-results.js +25 -0
  206. package/dist/operations/errors/catalog.d.ts +58 -0
  207. package/dist/operations/errors/catalog.js +91 -0
  208. package/dist/operations/errors/classes.d.ts +73 -0
  209. package/dist/operations/errors/classes.js +22 -0
  210. package/dist/operations/errors/errors.d.ts +52 -0
  211. package/dist/operations/errors/errors.js +257 -0
  212. package/dist/operations/generators/capability.d.ts +5 -0
  213. package/dist/operations/generators/capability.js +603 -0
  214. package/dist/operations/generators/compile.d.ts +2 -0
  215. package/dist/operations/generators/compile.js +541 -0
  216. package/dist/operations/generators/grant.d.ts +1 -0
  217. package/dist/operations/generators/grant.js +267 -0
  218. package/dist/operations/generators/operation.d.ts +1 -0
  219. package/dist/operations/generators/operation.js +811 -0
  220. package/dist/operations/generators/permits.d.ts +1 -0
  221. package/dist/operations/generators/permits.js +196 -0
  222. package/dist/operations/generators/seed.d.ts +20 -0
  223. package/dist/operations/generators/seed.js +285 -0
  224. package/dist/operations/generators/trial.d.ts +53 -0
  225. package/dist/operations/generators/trial.js +376 -0
  226. package/dist/operations/http.d.ts +30 -0
  227. package/dist/operations/http.js +121 -0
  228. package/dist/operations/makers/capability.d.ts +1 -0
  229. package/dist/operations/makers/capability.js +117 -0
  230. package/dist/operations/makers/trial.d.ts +1 -0
  231. package/dist/operations/makers/trial.js +413 -0
  232. package/dist/operations/memory.d.ts +104 -0
  233. package/dist/operations/memory.js +365 -0
  234. package/dist/operations/metering.d.ts +55 -0
  235. package/dist/operations/metering.js +247 -0
  236. package/dist/operations/nodes.d.ts +35 -0
  237. package/dist/operations/nodes.js +15 -0
  238. package/dist/operations/operations.d.ts +18 -0
  239. package/dist/operations/operations.js +5 -0
  240. package/dist/operations/queue-policy.d.ts +3 -0
  241. package/dist/operations/queue-policy.js +8 -0
  242. package/dist/operations/result.d.ts +19 -0
  243. package/dist/operations/result.js +10 -0
  244. package/dist/operations/seams.d.ts +239 -0
  245. package/dist/operations/seams.js +45 -0
  246. package/dist/operations/store.d.ts +14 -0
  247. package/dist/operations/store.js +1 -0
  248. package/package.json +16 -0
@@ -0,0 +1,365 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { backoffMs, isExhausted } from "./queue-policy.js";
3
+ function projectRow(row, fields, key) {
4
+ const keep = new Set([key, 'version', ...fields]);
5
+ const projected = {};
6
+ for (const [name, value] of Object.entries(row)) {
7
+ if (keep.has(name))
8
+ projected[name] = value;
9
+ }
10
+ return projected;
11
+ }
12
+ export class UnknownColumnError extends Error {
13
+ constructor(entity, key) {
14
+ super(`${entity} does not declare "${key}", so writing it would be silently discarded. ` +
15
+ 'Declare the field or relation on the entity, or stop writing the key.');
16
+ this.name = 'UnknownColumnError';
17
+ }
18
+ }
19
+ export class VersionConflictError extends Error {
20
+ constructor(entity, id, expectedVersion) {
21
+ super(`${entity} ${id} was not at version ${expectedVersion} — someone else changed it first. ` +
22
+ 're-read the row and retry with its current ETag.');
23
+ this.name = 'VersionConflictError';
24
+ }
25
+ }
26
+ export class MemoryStore {
27
+ data = new Map();
28
+ declared;
29
+ keys;
30
+ constructor(declared, keys) {
31
+ this.declared = declared ?? new Map();
32
+ this.keys = keys ?? new Map();
33
+ }
34
+ keyOf(entity) {
35
+ const held = this.keys.get(entity);
36
+ if (held === undefined) {
37
+ throw new Error(`nothing told this store which value identifies a ${entity}, so it cannot find one row rather than all of ` +
38
+ 'them. An entity states that with a `unique` invariant over one of its values, and whatever builds this ' +
39
+ 'store passes those along with the columns each entity declares.');
40
+ }
41
+ return held;
42
+ }
43
+ static SYSTEM = new Set(['version']);
44
+ assertDeclared(entity, row) {
45
+ const allowed = this.declared.get(entity);
46
+ if (allowed === undefined)
47
+ return;
48
+ for (const key of Object.keys(row)) {
49
+ if (allowed.has(key) || MemoryStore.SYSTEM.has(key))
50
+ continue;
51
+ throw new UnknownColumnError(entity, key);
52
+ }
53
+ }
54
+ depth = 0;
55
+ async transaction(_actor, fn) {
56
+ if (this.depth > 0)
57
+ return fn();
58
+ const snapshot = new Map();
59
+ for (const [key, rows] of this.data)
60
+ snapshot.set(key, rows.map((r) => ({ ...r })));
61
+ this.depth += 1;
62
+ try {
63
+ return await fn();
64
+ }
65
+ catch (err) {
66
+ this.data = snapshot;
67
+ throw err;
68
+ }
69
+ finally {
70
+ this.depth -= 1;
71
+ }
72
+ }
73
+ async insert(_actor, entity, row) {
74
+ this.assertDeclared(entity, row);
75
+ const rows = this.data.get(entity) ?? [];
76
+ const rec = { ...row, version: 1 };
77
+ rows.push(rec);
78
+ this.data.set(entity, rows);
79
+ return { ...rec };
80
+ }
81
+ async get(_actor, entity, key, fields) {
82
+ const named = this.keyOf(entity);
83
+ const row = this.data.get(entity)?.find((r) => r[named] === key) ?? null;
84
+ return row === null ? null : projectRow(row, fields, named);
85
+ }
86
+ async list(_actor, entity, opts) {
87
+ const all = this.data.get(entity) ?? [];
88
+ const where = Object.entries(opts?.where ?? {});
89
+ for (const [field] of where)
90
+ this.assertDeclared(entity, { [field]: null });
91
+ const matched = where.length === 0 ? all : all.filter((row) => where.every(([f, v]) => (row[f] ?? null) === v));
92
+ return opts?.limit ? matched.slice(0, opts.limit).map((r) => ({ ...r })) : matched.map((r) => ({ ...r }));
93
+ }
94
+ async update(_actor, entity, key, patch, expectedVersion) {
95
+ this.assertDeclared(entity, patch);
96
+ const named = this.keyOf(entity);
97
+ const rows = this.data.get(entity) ?? [];
98
+ const i = rows.findIndex((r) => r[named] === key);
99
+ const cur = rows[i];
100
+ if (i < 0 || !cur)
101
+ throw new Error(`record ${String(key)} not found`);
102
+ const currentVersion = typeof cur['version'] === 'number' ? cur['version'] : 1;
103
+ if (expectedVersion !== undefined && expectedVersion !== currentVersion) {
104
+ throw new VersionConflictError(entity, String(key), expectedVersion);
105
+ }
106
+ const next = { ...cur, ...patch, [named]: key, version: currentVersion + 1 };
107
+ rows[i] = next;
108
+ return { ...next };
109
+ }
110
+ async remove(_actor, entity, key) {
111
+ const named = this.keyOf(entity);
112
+ this.data.set(entity, (this.data.get(entity) ?? []).filter((r) => r[named] !== key));
113
+ }
114
+ }
115
+ export class MemoryQueue {
116
+ jobs = new Map();
117
+ dead = [];
118
+ seq = 0;
119
+ async enqueue(actor, kind, payload, opts) {
120
+ if (opts?.dedupeKey) {
121
+ for (const [id, job] of this.jobs) {
122
+ if (job.dedupeKey === opts.dedupeKey)
123
+ return id;
124
+ }
125
+ }
126
+ const id = `job_${++this.seq}`;
127
+ this.jobs.set(id, {
128
+ id,
129
+ kind,
130
+ payload,
131
+ attempts: 0,
132
+ runAt: opts?.runAt ?? 0,
133
+ claimed: false,
134
+ dedupeKey: opts?.dedupeKey,
135
+ actor,
136
+ });
137
+ return id;
138
+ }
139
+ async poll(kinds, limit, now) {
140
+ const due = [];
141
+ for (const job of this.jobs.values()) {
142
+ if (due.length >= limit)
143
+ break;
144
+ if (job.claimed || !kinds.includes(job.kind) || job.runAt > now)
145
+ continue;
146
+ job.claimed = true;
147
+ due.push({ ...job });
148
+ }
149
+ return due;
150
+ }
151
+ async ack(id) {
152
+ this.jobs.delete(id);
153
+ }
154
+ async fail(id, error, now) {
155
+ const job = this.jobs.get(id);
156
+ if (!job)
157
+ return;
158
+ job.attempts += 1;
159
+ job.claimed = false;
160
+ if (isExhausted(job.attempts)) {
161
+ this.dead.push({ ...job });
162
+ this.jobs.delete(id);
163
+ return;
164
+ }
165
+ job.runAt = now + backoffMs(job.attempts);
166
+ void error;
167
+ }
168
+ async deadLetter(id, error) {
169
+ const job = this.jobs.get(id);
170
+ if (!job)
171
+ return;
172
+ this.dead.push({ ...job });
173
+ this.jobs.delete(id);
174
+ void error;
175
+ }
176
+ async deadLetters() {
177
+ return this.dead.map((j) => ({ ...j }));
178
+ }
179
+ }
180
+ export class MemoryIdempotency {
181
+ records;
182
+ onCheck;
183
+ constructor(opts) {
184
+ this.records = opts.records ?? new Map();
185
+ this.onCheck = opts.onCheck;
186
+ }
187
+ keyFor(key, action) {
188
+ return `${key}:${action}`;
189
+ }
190
+ async check(key, action) {
191
+ this.onCheck?.();
192
+ if (!key)
193
+ return null;
194
+ const k = this.keyFor(key, action);
195
+ return this.records.has(k) ? (this.records.get(k) ?? null) : null;
196
+ }
197
+ async store(key, action, output) {
198
+ if (!key)
199
+ return;
200
+ const k = this.keyFor(key, action);
201
+ if (!this.records.has(k))
202
+ this.records.set(k, output);
203
+ }
204
+ }
205
+ export function memoryVault(seed = {}) {
206
+ return {
207
+ async resolve(refs) {
208
+ const found = {};
209
+ for (const ref of refs) {
210
+ const value = seed[ref];
211
+ if (value === undefined)
212
+ throw new Error(`vault has no value for "${ref}"`);
213
+ found[ref] = value;
214
+ }
215
+ return found;
216
+ },
217
+ };
218
+ }
219
+ export const arrived = () => Date.now();
220
+ export const fresh = () => randomUUID();
221
+ export function inMemorySeams(opts) {
222
+ const sink = {
223
+ logs: [], events: [], audits: [], errors: [], costs: [], indexed: [], spans: [],
224
+ outbox: [], gateOrder: [], quota: { reserved: [], committed: [], released: 0 },
225
+ files: [], queries: [],
226
+ };
227
+ const store = new MemoryStore(opts?.entities, opts?.keys);
228
+ const queue = new MemoryQueue();
229
+ const searchIdx = new Map();
230
+ const telemetry = {
231
+ start(action) {
232
+ let status = 'ok';
233
+ return {
234
+ ok() { status = 'ok'; },
235
+ error() { status = 'error'; },
236
+ end() { sink.spans.push({ action, status }); },
237
+ };
238
+ },
239
+ };
240
+ const log = { write(level, action, _actor, data) { sink.logs.push({ level, action, data }); } };
241
+ const audit = { async append(_actor, record) { sink.audits.push(record); } };
242
+ const errors = { async record(_actor, record) { sink.errors.push(record); } };
243
+ const cost = { meter(action) { sink.costs.push(action); } };
244
+ const events = { async publish(e) { sink.events.push(e); } };
245
+ const rateLimiter = {
246
+ async check() {
247
+ sink.gateOrder.push('rate');
248
+ return opts?.allowRate ?? true;
249
+ },
250
+ async checkPolicy() {
251
+ sink.gateOrder.push('rate');
252
+ return opts?.allowRate ?? true;
253
+ },
254
+ };
255
+ const featureGate = opts?.features === undefined
256
+ ? undefined
257
+ : {
258
+ async check(_actor, feature) {
259
+ sink.gateOrder.push('feature');
260
+ return (opts.features ?? []).includes(feature);
261
+ },
262
+ };
263
+ const used = new Map();
264
+ const quotas = opts?.quota === undefined
265
+ ? undefined
266
+ : {
267
+ async reserve(_actor, meter, estimate) {
268
+ sink.gateOrder.push('quota');
269
+ sink.quota.reserved.push(estimate);
270
+ const allowance = opts.quota?.[meter] ?? Number.POSITIVE_INFINITY;
271
+ const before = used.get(meter) ?? 0;
272
+ if (before + estimate > allowance) {
273
+ return {
274
+ ok: false,
275
+ behavior: opts.onExceed ?? 'block',
276
+ retryAfterSeconds: 60,
277
+ async commit() { },
278
+ async release() { },
279
+ };
280
+ }
281
+ used.set(meter, before + estimate);
282
+ let settled = false;
283
+ return {
284
+ ok: true,
285
+ behavior: null,
286
+ retryAfterSeconds: 0,
287
+ async commit(actual) {
288
+ if (settled)
289
+ return;
290
+ settled = true;
291
+ sink.quota.committed.push(actual);
292
+ used.set(meter, (used.get(meter) ?? 0) - estimate + actual);
293
+ },
294
+ async release() {
295
+ if (settled)
296
+ return;
297
+ settled = true;
298
+ sink.quota.released += 1;
299
+ used.set(meter, (used.get(meter) ?? 0) - estimate);
300
+ },
301
+ };
302
+ },
303
+ };
304
+ const outbox = {
305
+ async emit(_actor, event) {
306
+ sink.outbox.push(event);
307
+ },
308
+ };
309
+ const idempotency = new MemoryIdempotency({
310
+ onCheck: () => sink.gateOrder.push('idempotency'),
311
+ });
312
+ const search = {
313
+ async index(_actor, entity, row) {
314
+ sink.indexed.push({ entity, row });
315
+ searchIdx.set(entity, [...(searchIdx.get(entity) ?? []), row]);
316
+ },
317
+ async query(_actor, entity, q) {
318
+ const needle = q.toLowerCase();
319
+ return (searchIdx.get(entity) ?? []).filter((r) => JSON.stringify(r).toLowerCase().includes(needle));
320
+ },
321
+ };
322
+ const vault = opts?.secrets === undefined ? undefined : memoryVault(opts.secrets);
323
+ const stored = new Map();
324
+ const files = {
325
+ async put(_actor, key, body, contentType) {
326
+ stored.set(key, { body, contentType });
327
+ sink.files.push({ action: 'put', key, bytes: body.byteLength });
328
+ return { size: body.byteLength };
329
+ },
330
+ async get(_actor, key) {
331
+ const found = stored.get(key);
332
+ sink.files.push({ action: 'get', key, bytes: found === undefined ? null : found.body.byteLength });
333
+ return found === undefined ? null : { body: found.body, contentType: found.contentType };
334
+ },
335
+ async signedUrl(_actor, key, expiresInSeconds) {
336
+ sink.files.push({ action: 'signed', key, bytes: null });
337
+ return `https://files.invalid/unsigned-in-memory/${encodeURIComponent(key)}?expires_in=${String(expiresInSeconds)}`;
338
+ },
339
+ async remove(_actor, key) {
340
+ stored.delete(key);
341
+ sink.files.push({ action: 'remove', key, bytes: null });
342
+ },
343
+ };
344
+ const answers = opts?.views;
345
+ const views = answers === undefined
346
+ ? undefined
347
+ : {
348
+ async query(_actor, sql, params) {
349
+ const found = (answers[sql] ?? []);
350
+ sink.queries.push({ sql, params, rows: found.length });
351
+ return found;
352
+ },
353
+ };
354
+ return {
355
+ seams: {
356
+ store, telemetry, log, audit, errors, search, cost, idempotency, events, rateLimiter, queue,
357
+ outbox, files, clock: { now: arrived }, random: { next: Math.random, uuid: fresh },
358
+ ...(featureGate ? { featureGate } : {}),
359
+ ...(quotas ? { quotas } : {}),
360
+ ...(vault ? { vault } : {}),
361
+ ...(views ? { views } : {}),
362
+ },
363
+ sink, store, queue,
364
+ };
365
+ }
@@ -0,0 +1,55 @@
1
+ import { PlatformError } from './errors/catalog.ts';
2
+ import type { PropertyValue, Search, Seams, Store, Views } from './seams.ts';
3
+ export interface ActionBudget {
4
+ readonly roundTrips: number;
5
+ }
6
+ export interface WriteAmplification {
7
+ readonly entity: string;
8
+ readonly id: string;
9
+ readonly columns: readonly string[];
10
+ readonly bytes: number;
11
+ }
12
+ export interface DatabaseUsage {
13
+ readonly roundTrips: number;
14
+ readonly rowsRead: number;
15
+ readonly rowsWritten: number;
16
+ readonly bytesRead: number;
17
+ readonly bytesWritten: number;
18
+ readonly amplified: readonly WriteAmplification[];
19
+ }
20
+ export declare class RoundTripBudgetExceededError extends PlatformError {
21
+ readonly budget: number;
22
+ readonly refusedStatement: string;
23
+ constructor(budget: number, statement: string);
24
+ }
25
+ export declare const BUDGET_EXCEEDED_BECAUSE = "round_trip_budget_exceeded";
26
+ export declare const DB_USAGE_PROPERTY = "db";
27
+ export declare const DB_USAGE_ATTRIBUTES: {
28
+ readonly roundTrips: "thread.db.round_trips";
29
+ readonly rowsRead: "thread.db.rows_read";
30
+ readonly rowsWritten: "thread.db.rows_written";
31
+ readonly bytesRead: "thread.db.bytes_read";
32
+ readonly bytesWritten: "thread.db.bytes_written";
33
+ readonly budgetRoundTrips: "thread.db.budget_round_trips";
34
+ };
35
+ export declare const DB_AMPLIFICATION_PROPERTY = "db_amplification";
36
+ export declare const DB_AMPLIFICATION_ATTRIBUTES: {
37
+ readonly entities: "thread.db.amplified_entities";
38
+ readonly columns: "thread.db.amplified_columns";
39
+ readonly bytes: "thread.db.amplified_bytes";
40
+ readonly writes: "thread.db.amplified_writes";
41
+ };
42
+ export declare function databaseUsageValues(usage: DatabaseUsage, budget?: ActionBudget): Readonly<Record<string, PropertyValue>>;
43
+ export declare function writeAmplificationValues(usage: DatabaseUsage): Readonly<Record<string, PropertyValue>> | undefined;
44
+ export declare function payloadBytes(value: unknown, depth?: number): number;
45
+ export interface MeterableSeams {
46
+ readonly store?: Store;
47
+ readonly search?: Search;
48
+ readonly views?: Views;
49
+ }
50
+ export interface Metered<S> {
51
+ readonly seams: S;
52
+ usage(): DatabaseUsage;
53
+ }
54
+ export type MeteredSeams = Metered<Seams>;
55
+ export declare function meterDatabase<S extends MeterableSeams>(seams: S, budget?: ActionBudget): Metered<S>;
@@ -0,0 +1,247 @@
1
+ import { PlatformError } from "./errors/catalog.js";
2
+ import { DbRoundTripBudgetExceeded } from "./errors/errors.js";
3
+ import { isTransactional } from "./seams.js";
4
+ export class RoundTripBudgetExceededError extends PlatformError {
5
+ budget;
6
+ refusedStatement;
7
+ constructor(budget, statement) {
8
+ const detail = `this operation declared a budget of ${budget} database round trip(s) and asked for ` +
9
+ `"${statement}" as number ${budget + 1}`;
10
+ super(DbRoundTripBudgetExceeded.def, { detail, because: BUDGET_EXCEEDED_BECAUSE });
11
+ this.name = 'RoundTripBudgetExceededError';
12
+ this.message = detail;
13
+ this.budget = budget;
14
+ this.refusedStatement = statement;
15
+ }
16
+ }
17
+ export const BUDGET_EXCEEDED_BECAUSE = 'round_trip_budget_exceeded';
18
+ export const DB_USAGE_PROPERTY = 'db';
19
+ export const DB_USAGE_ATTRIBUTES = {
20
+ roundTrips: 'thread.db.round_trips',
21
+ rowsRead: 'thread.db.rows_read',
22
+ rowsWritten: 'thread.db.rows_written',
23
+ bytesRead: 'thread.db.bytes_read',
24
+ bytesWritten: 'thread.db.bytes_written',
25
+ budgetRoundTrips: 'thread.db.budget_round_trips',
26
+ };
27
+ export const DB_AMPLIFICATION_PROPERTY = 'db_amplification';
28
+ export const DB_AMPLIFICATION_ATTRIBUTES = {
29
+ entities: 'thread.db.amplified_entities',
30
+ columns: 'thread.db.amplified_columns',
31
+ bytes: 'thread.db.amplified_bytes',
32
+ writes: 'thread.db.amplified_writes',
33
+ };
34
+ export function databaseUsageValues(usage, budget) {
35
+ return {
36
+ [DB_USAGE_ATTRIBUTES.roundTrips]: usage.roundTrips,
37
+ [DB_USAGE_ATTRIBUTES.rowsRead]: usage.rowsRead,
38
+ [DB_USAGE_ATTRIBUTES.rowsWritten]: usage.rowsWritten,
39
+ [DB_USAGE_ATTRIBUTES.bytesRead]: usage.bytesRead,
40
+ [DB_USAGE_ATTRIBUTES.bytesWritten]: usage.bytesWritten,
41
+ ...(budget === undefined ? {} : { [DB_USAGE_ATTRIBUTES.budgetRoundTrips]: budget.roundTrips }),
42
+ };
43
+ }
44
+ export function writeAmplificationValues(usage) {
45
+ if (usage.amplified.length === 0)
46
+ return undefined;
47
+ const entities = [...new Set(usage.amplified.map((write) => write.entity))];
48
+ const columns = [...new Set(usage.amplified.flatMap((write) => write.columns))];
49
+ return {
50
+ [DB_AMPLIFICATION_ATTRIBUTES.entities]: entities.join(','),
51
+ [DB_AMPLIFICATION_ATTRIBUTES.columns]: columns.join(','),
52
+ [DB_AMPLIFICATION_ATTRIBUTES.bytes]: usage.amplified.reduce((total, write) => total + write.bytes, 0),
53
+ [DB_AMPLIFICATION_ATTRIBUTES.writes]: usage.amplified.length,
54
+ };
55
+ }
56
+ const MAX_WALK_DEPTH = 24;
57
+ function utf8Length(text) {
58
+ let bytes = 0;
59
+ for (let index = 0; index < text.length; index += 1) {
60
+ const code = text.charCodeAt(index);
61
+ if (code < 0x80)
62
+ bytes += 1;
63
+ else if (code < 0x800)
64
+ bytes += 2;
65
+ else if (code >= 0xd800 && code <= 0xdbff && index + 1 < text.length) {
66
+ bytes += 4;
67
+ index += 1;
68
+ }
69
+ else
70
+ bytes += 3;
71
+ }
72
+ return bytes;
73
+ }
74
+ export function payloadBytes(value, depth = 0) {
75
+ if (value === null || value === undefined)
76
+ return 0;
77
+ if (depth > MAX_WALK_DEPTH)
78
+ return 0;
79
+ switch (typeof value) {
80
+ case 'string':
81
+ return utf8Length(value);
82
+ case 'number':
83
+ case 'boolean':
84
+ case 'bigint':
85
+ return 8;
86
+ case 'object':
87
+ break;
88
+ default:
89
+ return 0;
90
+ }
91
+ if (value instanceof Date)
92
+ return 8;
93
+ if (value instanceof Uint8Array)
94
+ return value.byteLength;
95
+ if (Array.isArray(value)) {
96
+ let total = 0;
97
+ for (const item of value)
98
+ total += payloadBytes(item, depth + 1);
99
+ return total;
100
+ }
101
+ let total = 0;
102
+ for (const [key, nested] of Object.entries(value)) {
103
+ total += utf8Length(key) + payloadBytes(nested, depth + 1);
104
+ }
105
+ return total;
106
+ }
107
+ function sameStoredValue(left, right, depth = 0) {
108
+ if (left === right)
109
+ return true;
110
+ if (depth > MAX_WALK_DEPTH)
111
+ return false;
112
+ if (left instanceof Date || right instanceof Date) {
113
+ return left instanceof Date && right instanceof Date && left.getTime() === right.getTime();
114
+ }
115
+ if (typeof left !== 'object' || typeof right !== 'object' || left === null || right === null)
116
+ return false;
117
+ if (Array.isArray(left) !== Array.isArray(right))
118
+ return false;
119
+ if (Array.isArray(left) && Array.isArray(right)) {
120
+ return left.length === right.length && left.every((item, index) => sameStoredValue(item, right[index], depth + 1));
121
+ }
122
+ const a = left;
123
+ const b = right;
124
+ const keys = Object.keys(a);
125
+ if (keys.length !== Object.keys(b).length)
126
+ return false;
127
+ return keys.every((key) => key in b && sameStoredValue(a[key], b[key], depth + 1));
128
+ }
129
+ export function meterDatabase(seams, budget) {
130
+ let roundTrips = 0;
131
+ let rowsRead = 0;
132
+ let rowsWritten = 0;
133
+ let bytesRead = 0;
134
+ let bytesWritten = 0;
135
+ const amplified = [];
136
+ const seen = new Map();
137
+ const remember = (entity, row) => {
138
+ seen.set(`${entity}:${row.id}`, row);
139
+ };
140
+ const spend = (statement) => {
141
+ if (budget !== undefined && roundTrips >= budget.roundTrips) {
142
+ throw new RoundTripBudgetExceededError(budget.roundTrips, statement);
143
+ }
144
+ roundTrips += 1;
145
+ };
146
+ const readRows = (rows) => {
147
+ rowsRead += rows.length;
148
+ for (const row of rows)
149
+ bytesRead += payloadBytes(row);
150
+ };
151
+ const base = seams.store;
152
+ const store = base === undefined ? undefined : {
153
+ async insert(actor, entity, row) {
154
+ spend(`insert ${entity}`);
155
+ bytesWritten += payloadBytes(row);
156
+ rowsWritten += 1;
157
+ const written = await base.insert(actor, entity, row);
158
+ remember(entity, written);
159
+ return written;
160
+ },
161
+ async get(actor, entity, id, fields) {
162
+ spend(`get ${entity}`);
163
+ const row = await base.get(actor, entity, id, fields);
164
+ if (row !== null) {
165
+ rowsRead += 1;
166
+ bytesRead += payloadBytes(row);
167
+ remember(entity, row);
168
+ }
169
+ return row;
170
+ },
171
+ async list(actor, entity, opts) {
172
+ spend(`list ${entity}`);
173
+ const rows = await base.list(actor, entity, opts);
174
+ readRows(rows);
175
+ for (const row of rows)
176
+ remember(entity, row);
177
+ return rows;
178
+ },
179
+ async update(actor, entity, id, patch, expectedVersion) {
180
+ spend(`update ${entity}`);
181
+ recordAmplification(entity, id, patch);
182
+ bytesWritten += payloadBytes(patch);
183
+ rowsWritten += 1;
184
+ const row = await base.update(actor, entity, id, patch, expectedVersion);
185
+ remember(entity, row);
186
+ return row;
187
+ },
188
+ async remove(actor, entity, id) {
189
+ spend(`remove ${entity}`);
190
+ rowsWritten += 1;
191
+ await base.remove(actor, entity, id);
192
+ seen.delete(`${entity}:${id}`);
193
+ },
194
+ };
195
+ if (base !== undefined && store !== undefined && isTransactional(base)) {
196
+ store.transaction = (actor, fn) => base.transaction(actor, fn);
197
+ }
198
+ function recordAmplification(entity, id, patch) {
199
+ const stored = seen.get(`${entity}:${id}`);
200
+ if (stored === undefined)
201
+ return;
202
+ const columns = [];
203
+ let bytes = 0;
204
+ for (const [column, value] of Object.entries(patch)) {
205
+ if (!(column in stored))
206
+ continue;
207
+ if (!sameStoredValue(value, stored[column]))
208
+ continue;
209
+ columns.push(column);
210
+ bytes += payloadBytes(value);
211
+ }
212
+ if (columns.length === 0)
213
+ return;
214
+ amplified.push({ entity, id: String(id), columns, bytes });
215
+ }
216
+ const views = seams.views === undefined
217
+ ? undefined
218
+ : {
219
+ async query(actor, sql, params) {
220
+ spend('view');
221
+ const rows = await seams.views.query(actor, sql, params);
222
+ readRows(rows);
223
+ return rows;
224
+ },
225
+ };
226
+ const baseSearch = seams.search;
227
+ const search = baseSearch === undefined
228
+ ? undefined
229
+ : {
230
+ index: (actor, entity, row) => baseSearch.index(actor, entity, row),
231
+ async query(actor, entity, q) {
232
+ spend(`search ${entity}`);
233
+ const rows = await baseSearch.query(actor, entity, q);
234
+ readRows(rows);
235
+ return rows;
236
+ },
237
+ };
238
+ const metered = {
239
+ ...(store === undefined ? {} : { store }),
240
+ ...(search === undefined ? {} : { search }),
241
+ ...(views === undefined ? {} : { views }),
242
+ };
243
+ return {
244
+ seams: { ...seams, ...metered },
245
+ usage: () => ({ roundTrips, rowsRead, rowsWritten, bytesRead, bytesWritten, amplified }),
246
+ };
247
+ }
@@ -0,0 +1,35 @@
1
+ import type { Threaded } from '../core/thread.ts';
2
+ import type { Operation } from './operations.ts';
3
+ import type { Handed, Reads, Supplied } from './seams.ts';
4
+ export interface Node extends Threaded {
5
+ readonly version: number;
6
+ }
7
+ export type Held<I = unknown> = Parameters<Operation<I>['run']>[1];
8
+ export declare const STOPPED: unique symbol;
9
+ export declare function stopped(thrown: unknown): {
10
+ readonly value: unknown;
11
+ } | undefined;
12
+ export declare const nd_read: <I, R extends string = Supplied>(block: (hands: Reads<Handed<R>>, held: Held<I>) => Promise<unknown>) => {
13
+ type: "nd_read";
14
+ };
15
+ export declare const nd_action: <I, R extends string = Supplied>(block: (hands: Handed<R>, held: Held<I>) => Promise<unknown>) => {
16
+ type: "nd_action";
17
+ };
18
+ export declare const nd_branch: <I, R extends string = Supplied>(choose: (hands: Reads<Handed<R>>, held: Held<I>) => Promise<string>, ways: Readonly<Record<string, (hands: Handed<R>, held: Held<I>) => Promise<unknown>>>) => {
19
+ type: "nd_branch";
20
+ };
21
+ export declare const nd_each: <I, R extends string = Supplied>(over: (held: Held<I>) => readonly unknown[], block: (item: unknown, hands: Handed<R>, held: Held<I>) => Promise<unknown>) => {
22
+ type: "nd_each";
23
+ };
24
+ export declare const nd_parallel: <I, R extends string = Supplied>(blocks: readonly ((hands: Handed<R>, held: Held<I>) => Promise<unknown>)[]) => {
25
+ type: "nd_parallel";
26
+ };
27
+ export declare const nd_repeat: <I, R extends string = Supplied>(ceiling: number, block: (pass: number, carried: unknown, hands: Handed<R>, held: Held<I>) => Promise<{
28
+ readonly done: boolean;
29
+ readonly value: unknown;
30
+ }>) => {
31
+ type: "nd_repeat";
32
+ };
33
+ export declare const nd_stop: <I, R extends string = Supplied>(block: (hands: Reads<Handed<R>>, held: Held<I>) => Promise<unknown>) => {
34
+ type: "nd_stop";
35
+ };