@party-stack/ontology 0.3.0 → 0.5.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 (278) hide show
  1. package/lib/OntologyConfig.d.ts +3 -2
  2. package/lib/OntologyConfig.d.ts.map +1 -1
  3. package/lib/cli/generate.d.ts.map +1 -1
  4. package/lib/cli/generate.js +65 -47
  5. package/lib/cli/generate.js.map +1 -1
  6. package/lib/cli/index.js +7 -4
  7. package/lib/cli/index.js.map +1 -1
  8. package/lib/cli/pull.d.ts.map +1 -1
  9. package/lib/cli/pull.js +10 -4
  10. package/lib/cli/pull.js.map +1 -1
  11. package/lib/examples/blog.d.ts +214 -17
  12. package/lib/examples/blog.d.ts.map +1 -1
  13. package/lib/examples/blog.js +20 -0
  14. package/lib/examples/blog.js.map +1 -1
  15. package/lib/examples/blog.live-queries.d.ts.map +1 -1
  16. package/lib/examples/blog.live-queries.js +4 -2
  17. package/lib/examples/blog.live-queries.js.map +1 -1
  18. package/lib/examples/generated/live.d.ts +2 -2
  19. package/lib/examples/generated/live.d.ts.map +1 -1
  20. package/lib/examples/generated/live.js +2 -5
  21. package/lib/examples/generated/live.js.map +1 -1
  22. package/lib/examples/generated/types.d.ts +15 -10
  23. package/lib/examples/generated/types.d.ts.map +1 -1
  24. package/lib/generate/builders.d.ts +9 -0
  25. package/lib/generate/builders.d.ts.map +1 -0
  26. package/lib/generate/builders.js +60 -0
  27. package/lib/generate/builders.js.map +1 -0
  28. package/lib/generate/builders.test.d.ts +2 -0
  29. package/lib/generate/builders.test.d.ts.map +1 -0
  30. package/lib/generate/builders.test.js +49 -0
  31. package/lib/generate/builders.test.js.map +1 -0
  32. package/lib/generate/index.d.ts +5 -0
  33. package/lib/generate/index.d.ts.map +1 -0
  34. package/lib/generate/index.js +5 -0
  35. package/lib/generate/index.js.map +1 -0
  36. package/lib/generate/live.d.ts.map +1 -1
  37. package/lib/generate/live.js +13 -10
  38. package/lib/generate/live.js.map +1 -1
  39. package/lib/generate/ontology.d.ts.map +1 -1
  40. package/lib/generate/ontology.js +29 -8
  41. package/lib/generate/ontology.js.map +1 -1
  42. package/lib/generate/types.d.ts +11 -1
  43. package/lib/generate/types.d.ts.map +1 -1
  44. package/lib/generate/types.js +178 -118
  45. package/lib/generate/types.js.map +1 -1
  46. package/lib/generate/types.test.js +118 -8
  47. package/lib/generate/types.test.js.map +1 -1
  48. package/lib/generate/utils/buildJsDocs.d.ts +7 -0
  49. package/lib/generate/utils/buildJsDocs.d.ts.map +1 -0
  50. package/lib/generate/utils/buildJsDocs.js +12 -0
  51. package/lib/generate/utils/buildJsDocs.js.map +1 -0
  52. package/lib/index.d.ts +4 -2
  53. package/lib/index.d.ts.map +1 -1
  54. package/lib/index.js +2 -1
  55. package/lib/index.js.map +1 -1
  56. package/lib/infer.d.ts +231 -0
  57. package/lib/infer.d.ts.map +1 -0
  58. package/lib/infer.js +10 -0
  59. package/lib/infer.js.map +1 -0
  60. package/lib/infer.test.d.ts +2 -0
  61. package/lib/infer.test.d.ts.map +1 -0
  62. package/lib/infer.test.js +116 -0
  63. package/lib/infer.test.js.map +1 -0
  64. package/lib/ir/generated/builders.d.ts +180 -180
  65. package/lib/ir/generated/builders.d.ts.map +1 -1
  66. package/lib/ir/generated/builders.js +24 -75
  67. package/lib/ir/generated/builders.js.map +1 -1
  68. package/lib/ir/generated/types.d.ts +32 -5
  69. package/lib/ir/generated/types.d.ts.map +1 -1
  70. package/lib/ir/schema.d.ts +1498 -5
  71. package/lib/ir/schema.d.ts.map +1 -1
  72. package/lib/ir/schema.js +509 -97
  73. package/lib/ir/schema.js.map +1 -1
  74. package/lib/ir/validate.d.ts.map +1 -1
  75. package/lib/ir/validate.js +31 -0
  76. package/lib/ir/validate.js.map +1 -1
  77. package/lib/ir/validate.test.js +53 -3
  78. package/lib/ir/validate.test.js.map +1 -1
  79. package/lib/json/index.d.ts +14 -0
  80. package/lib/json/index.d.ts.map +1 -0
  81. package/lib/json/index.js +158 -0
  82. package/lib/json/index.js.map +1 -0
  83. package/lib/json/index.test.d.ts +2 -0
  84. package/lib/json/index.test.d.ts.map +1 -0
  85. package/lib/json/index.test.js +123 -0
  86. package/lib/json/index.test.js.map +1 -0
  87. package/lib/live/LiveOntology.d.ts +44 -18
  88. package/lib/live/LiveOntology.d.ts.map +1 -1
  89. package/lib/live/LiveOntology.js +64 -67
  90. package/lib/live/LiveOntology.js.map +1 -1
  91. package/lib/live/LiveOntology.test.d.ts +2 -0
  92. package/lib/live/LiveOntology.test.d.ts.map +1 -0
  93. package/lib/live/LiveOntology.test.js +155 -0
  94. package/lib/live/LiveOntology.test.js.map +1 -0
  95. package/lib/live/OntologyBackendAdapter.d.ts +53 -0
  96. package/lib/live/OntologyBackendAdapter.d.ts.map +1 -0
  97. package/lib/live/OntologyBackendAdapter.js +2 -0
  98. package/lib/live/OntologyBackendAdapter.js.map +1 -0
  99. package/lib/live/actions/createLiveOntologyAction.d.ts +20 -0
  100. package/lib/live/actions/createLiveOntologyAction.d.ts.map +1 -0
  101. package/lib/live/actions/createLiveOntologyAction.js +22 -0
  102. package/lib/live/actions/createLiveOntologyAction.js.map +1 -0
  103. package/lib/live/actions/createLiveOntologyActions.d.ts +23 -0
  104. package/lib/live/actions/createLiveOntologyActions.d.ts.map +1 -0
  105. package/lib/live/actions/createLiveOntologyActions.js +129 -0
  106. package/lib/live/actions/createLiveOntologyActions.js.map +1 -0
  107. package/lib/live/actions/prepareActionParameters.d.ts +16 -0
  108. package/lib/live/actions/prepareActionParameters.d.ts.map +1 -0
  109. package/lib/live/actions/prepareActionParameters.js +251 -0
  110. package/lib/live/actions/prepareActionParameters.js.map +1 -0
  111. package/lib/live/actions/prepareActionParameters.test.d.ts +2 -0
  112. package/lib/live/actions/prepareActionParameters.test.d.ts.map +1 -0
  113. package/lib/live/actions/prepareActionParameters.test.js +165 -0
  114. package/lib/live/actions/prepareActionParameters.test.js.map +1 -0
  115. package/lib/live/attachments/attachmentSources.d.ts +7 -0
  116. package/lib/live/attachments/attachmentSources.d.ts.map +1 -0
  117. package/lib/live/attachments/attachmentSources.js +80 -0
  118. package/lib/live/attachments/attachmentSources.js.map +1 -0
  119. package/lib/live/attachments/createLiveOntologyAttachments.d.ts +38 -0
  120. package/lib/live/attachments/createLiveOntologyAttachments.d.ts.map +1 -0
  121. package/lib/live/attachments/createLiveOntologyAttachments.js +59 -0
  122. package/lib/live/attachments/createLiveOntologyAttachments.js.map +1 -0
  123. package/lib/live/attachments/createLiveOntologyAttachments.test.d.ts +2 -0
  124. package/lib/live/attachments/createLiveOntologyAttachments.test.d.ts.map +1 -0
  125. package/lib/live/attachments/createLiveOntologyAttachments.test.js +244 -0
  126. package/lib/live/attachments/createLiveOntologyAttachments.test.js.map +1 -0
  127. package/lib/live/attachments/unsupportedOntologyAttachmentsAdapter.d.ts +3 -0
  128. package/lib/live/attachments/unsupportedOntologyAttachmentsAdapter.d.ts.map +1 -0
  129. package/lib/live/attachments/unsupportedOntologyAttachmentsAdapter.js +5 -0
  130. package/lib/live/attachments/unsupportedOntologyAttachmentsAdapter.js.map +1 -0
  131. package/lib/live/expression.d.ts +16 -3
  132. package/lib/live/expression.d.ts.map +1 -1
  133. package/lib/live/expression.js +54 -41
  134. package/lib/live/expression.js.map +1 -1
  135. package/lib/live/expression.test.d.ts +2 -0
  136. package/lib/live/expression.test.d.ts.map +1 -0
  137. package/lib/live/expression.test.js +76 -0
  138. package/lib/live/expression.test.js.map +1 -0
  139. package/lib/live/index.d.ts +4 -1
  140. package/lib/live/index.d.ts.map +1 -1
  141. package/lib/live/index.js +4 -1
  142. package/lib/live/index.js.map +1 -1
  143. package/lib/live/mutators/OntologyEdits.d.ts +11 -0
  144. package/lib/live/mutators/OntologyEdits.d.ts.map +1 -0
  145. package/lib/live/mutators/OntologyEdits.js +66 -0
  146. package/lib/live/mutators/OntologyEdits.js.map +1 -0
  147. package/lib/live/mutators/createMutatorTx.d.ts +9 -0
  148. package/lib/live/mutators/createMutatorTx.d.ts.map +1 -0
  149. package/lib/live/mutators/createMutatorTx.js +67 -0
  150. package/lib/live/mutators/createMutatorTx.js.map +1 -0
  151. package/lib/live/mutators/createMutatorTx.test.d.ts +2 -0
  152. package/lib/live/mutators/createMutatorTx.test.d.ts.map +1 -0
  153. package/lib/live/mutators/createMutatorTx.test.js +90 -0
  154. package/lib/live/mutators/createMutatorTx.test.js.map +1 -0
  155. package/lib/live/mutators/runOptimisticAction.d.ts +15 -0
  156. package/lib/live/mutators/runOptimisticAction.d.ts.map +1 -0
  157. package/lib/live/mutators/runOptimisticAction.js +29 -0
  158. package/lib/live/mutators/runOptimisticAction.js.map +1 -0
  159. package/lib/live/mutators/types.d.ts +24 -0
  160. package/lib/live/mutators/types.d.ts.map +1 -0
  161. package/lib/live/mutators/types.js +2 -0
  162. package/lib/live/mutators/types.js.map +1 -0
  163. package/lib/live/objects/OntologyObject.d.ts +2 -0
  164. package/lib/live/objects/OntologyObject.d.ts.map +1 -0
  165. package/lib/live/objects/OntologyObject.js +2 -0
  166. package/lib/live/objects/OntologyObject.js.map +1 -0
  167. package/lib/live/objects/createLiveOntologyObjectCollection.d.ts +14 -0
  168. package/lib/live/objects/createLiveOntologyObjectCollection.d.ts.map +1 -0
  169. package/lib/live/objects/createLiveOntologyObjectCollection.js +59 -0
  170. package/lib/live/objects/createLiveOntologyObjectCollection.js.map +1 -0
  171. package/lib/live/objects/createLiveOntologyObjectCollection.test.d.ts +2 -0
  172. package/lib/live/objects/createLiveOntologyObjectCollection.test.d.ts.map +1 -0
  173. package/lib/live/objects/createLiveOntologyObjectCollection.test.js +176 -0
  174. package/lib/live/objects/createLiveOntologyObjectCollection.test.js.map +1 -0
  175. package/lib/live/objects/ontologyObjectCollectionId.d.ts +2 -0
  176. package/lib/live/objects/ontologyObjectCollectionId.d.ts.map +1 -0
  177. package/lib/live/objects/ontologyObjectCollectionId.js +4 -0
  178. package/lib/live/objects/ontologyObjectCollectionId.js.map +1 -0
  179. package/lib/live/outbox/createOntologyOutbox.d.ts +19 -0
  180. package/lib/live/outbox/createOntologyOutbox.d.ts.map +1 -0
  181. package/lib/live/outbox/createOntologyOutbox.js +321 -0
  182. package/lib/live/outbox/createOntologyOutbox.js.map +1 -0
  183. package/lib/live/outbox/createOntologyOutbox.test.d.ts +2 -0
  184. package/lib/live/outbox/createOntologyOutbox.test.d.ts.map +1 -0
  185. package/lib/live/outbox/createOntologyOutbox.test.js +914 -0
  186. package/lib/live/outbox/createOntologyOutbox.test.js.map +1 -0
  187. package/lib/live/outbox/leaderExecutor.d.ts +16 -0
  188. package/lib/live/outbox/leaderExecutor.d.ts.map +1 -0
  189. package/lib/live/outbox/leaderExecutor.js +143 -0
  190. package/lib/live/outbox/leaderExecutor.js.map +1 -0
  191. package/lib/live/outbox/outboxService.d.ts +5 -0
  192. package/lib/live/outbox/outboxService.d.ts.map +1 -0
  193. package/lib/live/outbox/outboxService.js +73 -0
  194. package/lib/live/outbox/outboxService.js.map +1 -0
  195. package/lib/live/outbox/outboxValues.d.ts +7 -0
  196. package/lib/live/outbox/outboxValues.d.ts.map +1 -0
  197. package/lib/live/outbox/outboxValues.js +76 -0
  198. package/lib/live/outbox/outboxValues.js.map +1 -0
  199. package/lib/live/outbox/projectionManager.d.ts +29 -0
  200. package/lib/live/outbox/projectionManager.d.ts.map +1 -0
  201. package/lib/live/outbox/projectionManager.js +155 -0
  202. package/lib/live/outbox/projectionManager.js.map +1 -0
  203. package/lib/live/outbox/repository.d.ts +29 -0
  204. package/lib/live/outbox/repository.d.ts.map +1 -0
  205. package/lib/live/outbox/repository.js +245 -0
  206. package/lib/live/outbox/repository.js.map +1 -0
  207. package/lib/live/outbox/service.d.ts +59 -0
  208. package/lib/live/outbox/service.d.ts.map +1 -0
  209. package/lib/live/outbox/service.js +2 -0
  210. package/lib/live/outbox/service.js.map +1 -0
  211. package/lib/live/outbox/types.d.ts +45 -0
  212. package/lib/live/outbox/types.d.ts.map +1 -0
  213. package/lib/live/outbox/types.js +7 -0
  214. package/lib/live/outbox/types.js.map +1 -0
  215. package/lib/meta/generated/live.d.ts +5 -0
  216. package/lib/meta/generated/live.d.ts.map +1 -0
  217. package/lib/{ontology → meta}/generated/live.js +2 -5
  218. package/lib/meta/generated/live.js.map +1 -0
  219. package/lib/{ontology → meta}/generated/types.d.ts +32 -11
  220. package/lib/meta/generated/types.d.ts.map +1 -0
  221. package/lib/meta/generated/types.js.map +1 -0
  222. package/lib/{ontology → meta}/ontology.d.ts +1 -0
  223. package/lib/meta/ontology.d.ts.map +1 -0
  224. package/lib/{ontology → meta}/ontology.js +7 -0
  225. package/lib/meta/ontology.js.map +1 -0
  226. package/lib/meta/pull.d.ts +2 -1
  227. package/lib/meta/pull.d.ts.map +1 -1
  228. package/lib/meta/pull.js +20 -5
  229. package/lib/meta/pull.js.map +1 -1
  230. package/lib/utils/provide.d.ts +2 -0
  231. package/lib/utils/provide.d.ts.map +1 -0
  232. package/lib/utils/provide.js +11 -0
  233. package/lib/utils/provide.js.map +1 -0
  234. package/lib/utils/targets.d.ts +35 -0
  235. package/lib/utils/targets.d.ts.map +1 -0
  236. package/lib/utils/targets.js +2 -0
  237. package/lib/utils/targets.js.map +1 -0
  238. package/lib/utils/typeNames.d.ts +26 -0
  239. package/lib/utils/typeNames.d.ts.map +1 -0
  240. package/lib/utils/typeNames.js +2 -0
  241. package/lib/utils/typeNames.js.map +1 -0
  242. package/lib/utils/types.d.ts +6 -5
  243. package/lib/utils/types.d.ts.map +1 -1
  244. package/lib/utils/types.js +22 -1
  245. package/lib/utils/types.js.map +1 -1
  246. package/lib/utils/types.test.js +23 -3
  247. package/lib/utils/types.test.js.map +1 -1
  248. package/lib/utils/values.d.ts +25 -1
  249. package/lib/utils/values.d.ts.map +1 -1
  250. package/package.json +14 -8
  251. package/lib/ir/generated/validators.d.ts +0 -49
  252. package/lib/ir/generated/validators.d.ts.map +0 -1
  253. package/lib/ir/generated/validators.js +0 -173
  254. package/lib/ir/generated/validators.js.map +0 -1
  255. package/lib/live/OntologyAdapter.d.ts +0 -25
  256. package/lib/live/OntologyAdapter.d.ts.map +0 -1
  257. package/lib/live/OntologyAdapter.js +0 -2
  258. package/lib/live/OntologyAdapter.js.map +0 -1
  259. package/lib/live/applyActionLogic.d.ts +0 -10
  260. package/lib/live/applyActionLogic.d.ts.map +0 -1
  261. package/lib/live/applyActionLogic.js +0 -59
  262. package/lib/live/applyActionLogic.js.map +0 -1
  263. package/lib/live/createLiveOntologyAttachments.d.ts +0 -20
  264. package/lib/live/createLiveOntologyAttachments.d.ts.map +0 -1
  265. package/lib/live/createLiveOntologyAttachments.js +0 -30
  266. package/lib/live/createLiveOntologyAttachments.js.map +0 -1
  267. package/lib/live/materializeActionParameters.d.ts +0 -11
  268. package/lib/live/materializeActionParameters.d.ts.map +0 -1
  269. package/lib/live/materializeActionParameters.js +0 -86
  270. package/lib/live/materializeActionParameters.js.map +0 -1
  271. package/lib/ontology/generated/live.d.ts +0 -5
  272. package/lib/ontology/generated/live.d.ts.map +0 -1
  273. package/lib/ontology/generated/live.js.map +0 -1
  274. package/lib/ontology/generated/types.d.ts.map +0 -1
  275. package/lib/ontology/generated/types.js.map +0 -1
  276. package/lib/ontology/ontology.d.ts.map +0 -1
  277. package/lib/ontology/ontology.js.map +0 -1
  278. /package/lib/{ontology → meta}/generated/types.js +0 -0
@@ -0,0 +1,914 @@
1
+ import { MemoryBlobBytesStore, SingleProcessCoordination, } from "@party-stack/runtime";
2
+ import { Temporal } from "temporal-polyfill";
3
+ import { describe, expect, it, vi, } from "vitest";
4
+ import { createOntologyOutbox } from "./createOntologyOutbox.js";
5
+ import { OUTBOX_COORDINATION_SERVICE } from "./service.js";
6
+ import { NonRetryableError } from "./types.js";
7
+ class TestNetworkConnectivity {
8
+ connected;
9
+ listeners = new Set();
10
+ constructor(connected = true) {
11
+ this.connected = connected;
12
+ }
13
+ get isConnected() {
14
+ return this.connected;
15
+ }
16
+ setConnected(isConnected) {
17
+ if (isConnected === this.connected)
18
+ return;
19
+ this.connected = isConnected;
20
+ for (const listener of this.listeners) {
21
+ listener(isConnected);
22
+ }
23
+ }
24
+ subscribe(listener) {
25
+ this.listeners.add(listener);
26
+ return () => {
27
+ this.listeners.delete(listener);
28
+ };
29
+ }
30
+ close() {
31
+ this.listeners.clear();
32
+ }
33
+ }
34
+ function memoryPersistenceAdapter() {
35
+ const rows = new Map();
36
+ return {
37
+ loadSubset: () => Promise.resolve([...rows].map(([key, value]) => ({
38
+ key,
39
+ value,
40
+ }))),
41
+ applyCommittedTx: (_collectionId, transaction) => {
42
+ if (transaction.truncate)
43
+ rows.clear();
44
+ for (const mutation of transaction.mutations) {
45
+ if (mutation.type === "delete") {
46
+ rows.delete(mutation.key);
47
+ }
48
+ else {
49
+ rows.set(mutation.key, mutation.value);
50
+ }
51
+ }
52
+ return Promise.resolve();
53
+ },
54
+ ensureIndex: () => Promise.resolve(),
55
+ };
56
+ }
57
+ function coordinatedOutboxRuntime(options) {
58
+ const runtime = {
59
+ owner: "test-user",
60
+ namespace: "outbox-test",
61
+ blobBytes: new MemoryBlobBytesStore(),
62
+ connectivity: options.connectivity,
63
+ persistence: options.adapter,
64
+ coordination: options.coordination,
65
+ };
66
+ return {
67
+ runtime,
68
+ coordination: options.coordination,
69
+ };
70
+ }
71
+ function createSingleProcessRuntime(options, scope) {
72
+ const coordination = new SingleProcessCoordination({
73
+ scope,
74
+ });
75
+ return {
76
+ runtime: {
77
+ ...options,
78
+ owner: options.owner ?? "test-user",
79
+ namespace: options.namespace ?? scope,
80
+ coordination,
81
+ },
82
+ coordination,
83
+ };
84
+ }
85
+ function clientView(host) {
86
+ return {
87
+ role: "client",
88
+ service: (namespace) => host.service(namespace),
89
+ close: () => Promise.resolve(),
90
+ };
91
+ }
92
+ class TestLeadershipCoordination {
93
+ role = "host";
94
+ delegate;
95
+ leading = true;
96
+ active;
97
+ waiters = new Set();
98
+ constructor(scope) {
99
+ this.delegate = new SingleProcessCoordination({
100
+ scope,
101
+ });
102
+ }
103
+ get isLeader() {
104
+ return this.leading;
105
+ }
106
+ get waitingForLeadership() {
107
+ return this.waiters.size > 0;
108
+ }
109
+ service(namespace) {
110
+ return this.delegate.service(namespace);
111
+ }
112
+ serve(namespace, handlers) {
113
+ return this.delegate.serve(namespace, handlers);
114
+ }
115
+ async runAsLeader(callback, options) {
116
+ await this.waitForLeadership(options?.signal);
117
+ const controller = new AbortController();
118
+ const abort = () => controller.abort(options?.signal?.reason);
119
+ options?.signal?.addEventListener("abort", abort, {
120
+ once: true,
121
+ });
122
+ this.active = controller;
123
+ try {
124
+ return await callback({
125
+ signal: controller.signal,
126
+ });
127
+ }
128
+ finally {
129
+ options?.signal?.removeEventListener("abort", abort);
130
+ if (this.active === controller) {
131
+ this.active = undefined;
132
+ }
133
+ }
134
+ }
135
+ loseLeadership() {
136
+ this.leading = false;
137
+ this.active?.abort(new Error("Test leadership lost."));
138
+ }
139
+ regainLeadership() {
140
+ this.leading = true;
141
+ for (const wake of this.waiters)
142
+ wake();
143
+ this.waiters.clear();
144
+ }
145
+ async close() {
146
+ this.loseLeadership();
147
+ await this.delegate.close();
148
+ }
149
+ waitForLeadership(signal) {
150
+ if (this.leading)
151
+ return Promise.resolve();
152
+ return new Promise((resolve, reject) => {
153
+ const wake = () => {
154
+ signal?.removeEventListener("abort", abort);
155
+ resolve();
156
+ };
157
+ const abort = () => {
158
+ this.waiters.delete(wake);
159
+ reject(signal?.reason instanceof Error ? signal.reason : new Error("Leadership wait aborted."));
160
+ };
161
+ this.waiters.add(wake);
162
+ signal?.addEventListener("abort", abort, {
163
+ once: true,
164
+ });
165
+ if (signal?.aborted)
166
+ abort();
167
+ });
168
+ }
169
+ }
170
+ function setup(isConnected = true) {
171
+ const connectivity = new TestNetworkConnectivity(isConnected);
172
+ const execute = vi.fn(() => Promise.resolve("done"));
173
+ const { runtime, coordination } = createSingleProcessRuntime({
174
+ blobBytes: new MemoryBlobBytesStore(),
175
+ connectivity,
176
+ }, `setup-${crypto.randomUUID()}`);
177
+ const outbox = createOntologyOutbox({
178
+ runtime,
179
+ execute,
180
+ });
181
+ return {
182
+ connectivity,
183
+ coordination,
184
+ execute,
185
+ outbox,
186
+ };
187
+ }
188
+ describe("createOntologyOutbox", () => {
189
+ it("persists before executing and removes completed work", async () => {
190
+ const { execute, outbox } = setup();
191
+ await outbox.ready;
192
+ const action = await outbox.enqueue({
193
+ actionTypeName: "createTask",
194
+ parameters: { title: "Test" },
195
+ });
196
+ await expect(action.completed).resolves.toBe("done");
197
+ expect(execute).toHaveBeenCalledOnce();
198
+ await vi.waitFor(() => {
199
+ expect(outbox.collection.size).toBe(0);
200
+ });
201
+ await outbox.cleanup();
202
+ });
203
+ it("waits until connectivity is restored", async () => {
204
+ const { connectivity, execute, outbox } = setup(false);
205
+ await outbox.ready;
206
+ const action = await outbox.enqueue({
207
+ actionTypeName: "createTask",
208
+ parameters: {},
209
+ });
210
+ await Promise.resolve();
211
+ expect(execute).not.toHaveBeenCalled();
212
+ connectivity.setConnected(true);
213
+ await expect(action.completed).resolves.toBe("done");
214
+ expect(execute).toHaveBeenCalledOnce();
215
+ await outbox.cleanup();
216
+ });
217
+ it("edits and removes queued work", async () => {
218
+ const { connectivity, outbox } = setup(false);
219
+ await outbox.ready;
220
+ const action = await outbox.enqueue({
221
+ actionTypeName: "createTask",
222
+ parameters: { title: "Before" },
223
+ });
224
+ await outbox.edit(action.entry.id, (request) => {
225
+ request.parameters.title = "After";
226
+ });
227
+ expect(outbox.collection.get(action.entry.id)?.request.parameters.title).toBe("After");
228
+ await outbox.remove(action.entry.id);
229
+ await expect(action.completed).rejects.toThrow("removed");
230
+ expect(outbox.collection.has(action.entry.id)).toBe(false);
231
+ connectivity.close();
232
+ await outbox.cleanup();
233
+ });
234
+ it("installs one projection when enqueue and collection change race", async () => {
235
+ const project = vi.fn(async () => {
236
+ await Promise.resolve();
237
+ return {
238
+ settle: vi.fn(),
239
+ };
240
+ });
241
+ const { runtime } = createSingleProcessRuntime({
242
+ blobBytes: new MemoryBlobBytesStore(),
243
+ connectivity: new TestNetworkConnectivity(false),
244
+ }, "projection-race");
245
+ const outbox = createOntologyOutbox({
246
+ runtime,
247
+ execute: () => Promise.resolve(),
248
+ project,
249
+ });
250
+ await outbox.ready;
251
+ const action = await outbox.enqueue({
252
+ actionTypeName: "createTask",
253
+ parameters: { id: "one" },
254
+ });
255
+ void action.completed.catch(() => undefined);
256
+ expect(project).toHaveBeenCalledOnce();
257
+ await outbox.cleanup();
258
+ });
259
+ it("keeps durable intent when local projection fails", async () => {
260
+ const { runtime } = createSingleProcessRuntime({
261
+ blobBytes: new MemoryBlobBytesStore(),
262
+ connectivity: new TestNetworkConnectivity(false),
263
+ }, "projection-discard");
264
+ const outbox = createOntologyOutbox({
265
+ runtime,
266
+ execute: () => Promise.resolve(),
267
+ project: () => Promise.reject(new Error("cannot project")),
268
+ });
269
+ await outbox.ready;
270
+ const action = await outbox.enqueue({
271
+ actionTypeName: "createTask",
272
+ parameters: {},
273
+ });
274
+ void action.completed.catch(() => undefined);
275
+ const entry = outbox.collection.get(action.entry.id);
276
+ expect(entry?.status).toBe("queued");
277
+ expect(entry?.lastError).toBeUndefined();
278
+ await outbox.cleanup();
279
+ });
280
+ it("requires manual retry after execution failure", async () => {
281
+ let attempts = 0;
282
+ const { runtime } = createSingleProcessRuntime({
283
+ blobBytes: new MemoryBlobBytesStore(),
284
+ connectivity: new TestNetworkConnectivity(),
285
+ }, "manual-retry");
286
+ const outbox = createOntologyOutbox({
287
+ runtime,
288
+ maxRetries: 0,
289
+ failureStrategy: "pause",
290
+ execute: () => {
291
+ attempts += 1;
292
+ return attempts === 1 ? Promise.reject(new Error("failed")) : Promise.resolve("done");
293
+ },
294
+ });
295
+ await outbox.ready;
296
+ const action = await outbox.enqueue({
297
+ actionTypeName: "createTask",
298
+ parameters: {},
299
+ });
300
+ await vi.waitFor(() => {
301
+ expect(outbox.collection.get(action.entry.id)?.status).toBe("failed");
302
+ });
303
+ expect(attempts).toBe(1);
304
+ await outbox.retry(action.entry.id);
305
+ await expect(action.completed).resolves.toBe("done");
306
+ expect(attempts).toBe(2);
307
+ await outbox.cleanup();
308
+ });
309
+ it("automatically retries ordinary execution failures with backoff", async () => {
310
+ let attempts = 0;
311
+ const { runtime } = createSingleProcessRuntime({
312
+ blobBytes: new MemoryBlobBytesStore(),
313
+ connectivity: new TestNetworkConnectivity(),
314
+ }, "automatic-retry");
315
+ const outbox = createOntologyOutbox({
316
+ runtime,
317
+ maxRetries: 3,
318
+ execute: () => {
319
+ attempts += 1;
320
+ return attempts < 3
321
+ ? Promise.reject(new Error("temporary"))
322
+ : Promise.resolve("done");
323
+ },
324
+ });
325
+ await outbox.ready;
326
+ const action = await outbox.enqueue({
327
+ actionTypeName: "createTask",
328
+ parameters: {},
329
+ });
330
+ await expect(action.completed).resolves.toBe("done");
331
+ expect(attempts).toBe(3);
332
+ expect(outbox.collection.get(action.entry.id)).toBeUndefined();
333
+ await outbox.cleanup();
334
+ });
335
+ it("applies the failure strategy after automatic retries are exhausted", async () => {
336
+ let attempts = 0;
337
+ const { runtime } = createSingleProcessRuntime({
338
+ blobBytes: new MemoryBlobBytesStore(),
339
+ connectivity: new TestNetworkConnectivity(),
340
+ }, "automatic-retry-exhausted");
341
+ const outbox = createOntologyOutbox({
342
+ runtime,
343
+ maxRetries: 2,
344
+ execute: () => {
345
+ attempts += 1;
346
+ return Promise.reject(new Error("still failing"));
347
+ },
348
+ });
349
+ await outbox.ready;
350
+ const action = await outbox.enqueue({
351
+ actionTypeName: "createTask",
352
+ parameters: {},
353
+ });
354
+ await expect(action.completed).rejects.toThrow("still failing");
355
+ expect(attempts).toBe(3);
356
+ expect(outbox.collection.get(action.entry.id)).toBeUndefined();
357
+ await outbox.cleanup();
358
+ });
359
+ it("keeps a failed head ahead of later queued entries", async () => {
360
+ const executed = [];
361
+ let firstAttempts = 0;
362
+ const { runtime } = createSingleProcessRuntime({
363
+ blobBytes: new MemoryBlobBytesStore(),
364
+ connectivity: new TestNetworkConnectivity(),
365
+ }, "strict-fifo");
366
+ const outbox = createOntologyOutbox({
367
+ runtime,
368
+ maxRetries: 0,
369
+ failureStrategy: "pause",
370
+ execute: (entry) => {
371
+ const name = String(entry.request.parameters.name);
372
+ executed.push(name);
373
+ if (name === "first" && firstAttempts++ === 0) {
374
+ return Promise.reject(new Error("first failed"));
375
+ }
376
+ return Promise.resolve(name);
377
+ },
378
+ });
379
+ await outbox.ready;
380
+ const first = await outbox.enqueue({
381
+ actionTypeName: "createTask",
382
+ parameters: { name: "first" },
383
+ });
384
+ await vi.waitFor(() => {
385
+ expect(outbox.collection.get(first.entry.id)?.status).toBe("failed");
386
+ });
387
+ const second = await outbox.enqueue({
388
+ actionTypeName: "createTask",
389
+ parameters: { name: "second" },
390
+ });
391
+ await Promise.resolve();
392
+ expect(executed).toEqual(["first"]);
393
+ expect(outbox.collection.get(second.entry.id)?.status).toBe("queued");
394
+ await outbox.retry(first.entry.id);
395
+ await expect(first.completed).resolves.toBe("first");
396
+ await expect(second.completed).resolves.toBe("second");
397
+ expect(executed).toEqual(["first", "first", "second"]);
398
+ await outbox.cleanup();
399
+ });
400
+ it("releases a failed FIFO head when it is removed", async () => {
401
+ const { runtime } = createSingleProcessRuntime({
402
+ blobBytes: new MemoryBlobBytesStore(),
403
+ connectivity: new TestNetworkConnectivity(),
404
+ }, "remove-failed-head");
405
+ const outbox = createOntologyOutbox({
406
+ runtime,
407
+ maxRetries: 0,
408
+ failureStrategy: "pause",
409
+ execute: (entry) => {
410
+ const name = String(entry.request.parameters.name);
411
+ return name === "first" ? Promise.reject(new Error("blocked")) : Promise.resolve(name);
412
+ },
413
+ });
414
+ await outbox.ready;
415
+ const first = await outbox.enqueue({
416
+ actionTypeName: "createTask",
417
+ parameters: { name: "first" },
418
+ });
419
+ await vi.waitFor(() => {
420
+ expect(outbox.collection.get(first.entry.id)?.status).toBe("failed");
421
+ });
422
+ const second = await outbox.enqueue({
423
+ actionTypeName: "createTask",
424
+ parameters: { name: "second" },
425
+ });
426
+ await outbox.remove(first.entry.id);
427
+ await expect(first.completed).rejects.toThrow("removed");
428
+ await expect(second.completed).resolves.toBe("second");
429
+ await outbox.cleanup();
430
+ await runtime.coordination.close();
431
+ });
432
+ it("rolls back and pauses on non-retryable failures when configured", async () => {
433
+ const { runtime } = createSingleProcessRuntime({
434
+ blobBytes: new MemoryBlobBytesStore(),
435
+ connectivity: new TestNetworkConnectivity(),
436
+ }, "non-retriable");
437
+ const outbox = createOntologyOutbox({
438
+ runtime,
439
+ execute: () => Promise.reject(new NonRetryableError("rejected")),
440
+ failureStrategy: "pause",
441
+ });
442
+ await outbox.ready;
443
+ const action = await outbox.enqueue({
444
+ actionTypeName: "createTask",
445
+ parameters: {},
446
+ });
447
+ await expect(action.completed).rejects.toThrow("rejected");
448
+ await vi.waitFor(() => {
449
+ expect(outbox.collection.get(action.entry.id)?.retryable).toBe(false);
450
+ });
451
+ await expect(outbox.retry(action.entry.id)).rejects.toThrow("not retriable");
452
+ await outbox.cleanup();
453
+ });
454
+ it("discards the remaining queue on non-retryable failures by default", async () => {
455
+ const connectivity = new TestNetworkConnectivity(false);
456
+ const { runtime } = createSingleProcessRuntime({
457
+ blobBytes: new MemoryBlobBytesStore(),
458
+ connectivity,
459
+ }, "non-retryable-discard");
460
+ const settled = new Set();
461
+ const outbox = createOntologyOutbox({
462
+ runtime,
463
+ execute: () => Promise.reject(new NonRetryableError("rejected")),
464
+ project: (entry) => Promise.resolve({
465
+ settle(error) {
466
+ expect(error).toBeDefined();
467
+ settled.add(String(entry.request.parameters
468
+ .order));
469
+ },
470
+ }),
471
+ });
472
+ await outbox.ready;
473
+ const first = await outbox.enqueue({
474
+ actionTypeName: "createTask",
475
+ parameters: { order: 1 },
476
+ });
477
+ const second = await outbox.enqueue({
478
+ actionTypeName: "createTask",
479
+ parameters: { order: 2 },
480
+ });
481
+ const deleteEntries = vi.spyOn(outbox.collection, "delete");
482
+ connectivity.setConnected(true);
483
+ await expect(first.completed).rejects.toThrow("rejected");
484
+ await expect(second.completed).rejects.toThrow("failed permanently");
485
+ await vi.waitFor(() => {
486
+ expect(outbox.collection.get(first.entry.id)).toBeUndefined();
487
+ expect(outbox.collection.get(second.entry.id)).toBeUndefined();
488
+ });
489
+ expect(settled).toEqual(new Set(["1", "2"]));
490
+ expect(deleteEntries).toHaveBeenCalledOnce();
491
+ expect(deleteEntries).toHaveBeenCalledWith([first.entry.id, second.entry.id], { optimistic: false });
492
+ await outbox.cleanup();
493
+ });
494
+ it("discards previously interrupted work during recovery by default", async () => {
495
+ const adapter = memoryPersistenceAdapter();
496
+ const { runtime, coordination } = createSingleProcessRuntime({
497
+ blobBytes: new MemoryBlobBytesStore(),
498
+ connectivity: new TestNetworkConnectivity(false),
499
+ persistence: adapter,
500
+ }, "interrupted-recovery-discard");
501
+ const first = createOntologyOutbox({
502
+ runtime,
503
+ execute: () => Promise.resolve(),
504
+ });
505
+ await first.ready;
506
+ const action = await first.enqueue({
507
+ actionTypeName: "createTask",
508
+ parameters: {},
509
+ });
510
+ void action.completed.catch(() => undefined);
511
+ await first.collection.update(action.entry.id, { optimistic: false }, (draft) => {
512
+ draft.status = "failed";
513
+ draft.retryable = true;
514
+ draft.lastError = {
515
+ name: "InterruptedExecution",
516
+ message: "Execution was interrupted; retry manually.",
517
+ };
518
+ }).isPersisted.promise;
519
+ await first.cleanup();
520
+ const restored = createOntologyOutbox({
521
+ runtime,
522
+ execute: () => Promise.resolve(),
523
+ });
524
+ await restored.ready;
525
+ await vi.waitFor(() => {
526
+ expect(restored.collection.get(action.entry.id)).toBeUndefined();
527
+ });
528
+ await restored.cleanup();
529
+ await coordination.close();
530
+ });
531
+ it("resumes retryable failed work during recovery", async () => {
532
+ const adapter = memoryPersistenceAdapter();
533
+ const connectivity = new TestNetworkConnectivity(false);
534
+ const { runtime, coordination } = createSingleProcessRuntime({
535
+ blobBytes: new MemoryBlobBytesStore(),
536
+ connectivity: connectivity,
537
+ persistence: adapter,
538
+ }, "failed-recovery-retry");
539
+ const first = createOntologyOutbox({
540
+ runtime,
541
+ execute: () => Promise.resolve(),
542
+ });
543
+ await first.ready;
544
+ const action = await first.enqueue({
545
+ actionTypeName: "createTask",
546
+ parameters: {},
547
+ });
548
+ void action.completed.catch(() => undefined);
549
+ await first.collection.update(action.entry.id, { optimistic: false }, (draft) => {
550
+ draft.status = "failed";
551
+ draft.retryable = true;
552
+ draft.attempts = 1;
553
+ draft.lastError = {
554
+ name: "Error",
555
+ message: "temporary",
556
+ };
557
+ }).isPersisted.promise;
558
+ await first.cleanup();
559
+ const execute = vi.fn(() => Promise.resolve("done"));
560
+ connectivity.setConnected(true);
561
+ const restored = createOntologyOutbox({
562
+ runtime,
563
+ execute,
564
+ });
565
+ await restored.ready;
566
+ await vi.waitFor(() => {
567
+ expect(execute).toHaveBeenCalledOnce();
568
+ expect(restored.collection.get(action.entry.id)).toBeUndefined();
569
+ });
570
+ await restored.cleanup();
571
+ await coordination.close();
572
+ });
573
+ it("keeps restored intent when optimistic projection is unavailable", async () => {
574
+ const adapter = memoryPersistenceAdapter();
575
+ const connectivity = new TestNetworkConnectivity(false);
576
+ const { runtime } = createSingleProcessRuntime({
577
+ blobBytes: new MemoryBlobBytesStore(),
578
+ connectivity,
579
+ persistence: adapter,
580
+ }, "projection-restart");
581
+ const first = createOntologyOutbox({
582
+ runtime,
583
+ execute: () => Promise.resolve(),
584
+ });
585
+ await first.ready;
586
+ const action = await first.enqueue({
587
+ actionTypeName: "deleteTask",
588
+ parameters: { task: "missing" },
589
+ });
590
+ void action.completed.catch(() => undefined);
591
+ await first.cleanup();
592
+ const restored = createOntologyOutbox({
593
+ runtime,
594
+ execute: () => Promise.resolve(),
595
+ project: () => Promise.reject(new Error("Collection.delete was called with key 'missing' but there is no item in the collection with this key")),
596
+ });
597
+ await expect(restored.ready).resolves.toBeUndefined();
598
+ const entry = restored.collection.get(action.entry.id);
599
+ expect(entry?.status).toBe("queued");
600
+ expect(entry?.lastError).toBeUndefined();
601
+ await restored.remove(action.entry.id);
602
+ expect(restored.collection.has(action.entry.id)).toBe(false);
603
+ await restored.cleanup();
604
+ await runtime.coordination.close();
605
+ connectivity.close();
606
+ });
607
+ it("uses a client-only coordination value without serving", async () => {
608
+ const adapter = memoryPersistenceAdapter();
609
+ const hostCoordination = new SingleProcessCoordination({
610
+ scope: "client-only",
611
+ });
612
+ const clientCoordination = clientView(hostCoordination);
613
+ const hostRuntime = {
614
+ owner: "test-user",
615
+ namespace: "client-only",
616
+ blobBytes: new MemoryBlobBytesStore(),
617
+ connectivity: new TestNetworkConnectivity(false),
618
+ persistence: adapter,
619
+ coordination: hostCoordination,
620
+ };
621
+ const clientRuntime = {
622
+ owner: "test-user",
623
+ namespace: "client-only",
624
+ blobBytes: new MemoryBlobBytesStore(),
625
+ connectivity: new TestNetworkConnectivity(false),
626
+ persistence: adapter,
627
+ coordination: clientCoordination,
628
+ };
629
+ const host = createOntologyOutbox({
630
+ runtime: hostRuntime,
631
+ execute: () => Promise.resolve("done"),
632
+ });
633
+ await host.ready;
634
+ const client = createOntologyOutbox({
635
+ runtime: clientRuntime,
636
+ execute: () => Promise.reject(new Error("Client execution must not run.")),
637
+ });
638
+ await client.ready;
639
+ const action = await client.enqueue({
640
+ actionTypeName: "createTask",
641
+ parameters: { id: "client" },
642
+ });
643
+ await vi.waitFor(() => {
644
+ expect(host.collection.has(action.entry.id)).toBe(true);
645
+ });
646
+ await host.remove(action.entry.id);
647
+ await expect(action.completed).rejects.toThrow("Outbox entry removed");
648
+ await client.cleanup();
649
+ await host.cleanup();
650
+ await hostCoordination.close();
651
+ });
652
+ it("discards interrupted execution when leadership is regained", async () => {
653
+ const coordination = new TestLeadershipCoordination("leadership-cancellation");
654
+ const runtime = {
655
+ owner: "test-user",
656
+ namespace: "leadership-cancellation",
657
+ blobBytes: new MemoryBlobBytesStore(),
658
+ connectivity: new TestNetworkConnectivity(true),
659
+ coordination,
660
+ };
661
+ const never = new Promise(() => undefined);
662
+ const execute = vi.fn(() => never);
663
+ const outbox = createOntologyOutbox({
664
+ runtime,
665
+ execute,
666
+ });
667
+ await outbox.ready;
668
+ const action = await outbox.enqueue({
669
+ actionTypeName: "createTask",
670
+ parameters: {},
671
+ });
672
+ void action.completed.catch(() => undefined);
673
+ await vi.waitFor(() => {
674
+ expect(outbox.collection.get(action.entry.id)?.status).toBe("executing");
675
+ expect(execute).toHaveBeenCalledOnce();
676
+ });
677
+ coordination.loseLeadership();
678
+ await vi.waitFor(() => {
679
+ expect(coordination.waitingForLeadership).toBe(true);
680
+ });
681
+ coordination.regainLeadership();
682
+ await expect(action.completed).rejects.toThrow("execution was interrupted");
683
+ expect(outbox.collection.get(action.entry.id)).toBeUndefined();
684
+ await outbox.cleanup();
685
+ await coordination.close();
686
+ });
687
+ it("rejects a completion from a stale execution claim", async () => {
688
+ const { runtime, coordination } = createSingleProcessRuntime({
689
+ blobBytes: new MemoryBlobBytesStore(),
690
+ connectivity: new TestNetworkConnectivity(false),
691
+ }, "stale-claim");
692
+ const outbox = createOntologyOutbox({
693
+ runtime,
694
+ execute: () => Promise.resolve(),
695
+ });
696
+ await outbox.ready;
697
+ const service = coordination.service(OUTBOX_COORDINATION_SERVICE);
698
+ const action = await outbox.enqueue({
699
+ actionTypeName: "createTask",
700
+ parameters: {},
701
+ });
702
+ const stale = await service.methods.claim({});
703
+ expect(stale?.executionId).toBeDefined();
704
+ await service.methods.recover({
705
+ failureStrategy: "pause",
706
+ maxRetries: 3,
707
+ });
708
+ await service.methods.retry({
709
+ id: action.entry.id,
710
+ });
711
+ const current = await service.methods.claim({});
712
+ expect(current?.executionId).not.toBe(stale?.executionId);
713
+ await expect(service.methods.complete({
714
+ id: action.entry.id,
715
+ executionId: stale.executionId,
716
+ result: "stale",
717
+ })).rejects.toMatchObject({
718
+ code: "STALE_EXECUTION",
719
+ });
720
+ await service.methods.complete({
721
+ id: action.entry.id,
722
+ executionId: current.executionId,
723
+ result: "current",
724
+ });
725
+ await expect(action.completed).resolves.toBe("current");
726
+ await outbox.cleanup();
727
+ await coordination.close();
728
+ });
729
+ it("rolls back and replays later projections after edits and removals", async () => {
730
+ const projections = [];
731
+ const { runtime, coordination } = createSingleProcessRuntime({
732
+ blobBytes: new MemoryBlobBytesStore(),
733
+ connectivity: new TestNetworkConnectivity(false),
734
+ }, "projection-replay");
735
+ const outbox = createOntologyOutbox({
736
+ runtime,
737
+ execute: () => Promise.resolve(),
738
+ project: (entry) => {
739
+ const projection = {
740
+ name: String(entry.request.parameters.name),
741
+ settle: vi.fn(),
742
+ };
743
+ projections.push(projection);
744
+ return Promise.resolve(projection);
745
+ },
746
+ });
747
+ await outbox.ready;
748
+ const first = await outbox.enqueue({
749
+ actionTypeName: "createTask",
750
+ parameters: { name: "first" },
751
+ });
752
+ const second = await outbox.enqueue({
753
+ actionTypeName: "createTask",
754
+ parameters: { name: "second" },
755
+ });
756
+ void first.completed.catch(() => undefined);
757
+ void second.completed.catch(() => undefined);
758
+ await outbox.edit(first.entry.id, (request) => {
759
+ request.parameters.name = "first-edited";
760
+ });
761
+ await vi.waitFor(() => {
762
+ expect(projections.map((projection) => projection.name)).toEqual([
763
+ "first",
764
+ "second",
765
+ "first-edited",
766
+ "second",
767
+ ]);
768
+ });
769
+ expect(projections[0].settle).toHaveBeenCalledOnce();
770
+ expect(projections[1].settle).toHaveBeenCalledOnce();
771
+ await outbox.remove(first.entry.id);
772
+ await vi.waitFor(() => {
773
+ expect(projections.map((projection) => projection.name)).toEqual([
774
+ "first",
775
+ "second",
776
+ "first-edited",
777
+ "second",
778
+ "second",
779
+ ]);
780
+ });
781
+ await outbox.remove(second.entry.id);
782
+ await outbox.cleanup();
783
+ await coordination.close();
784
+ });
785
+ it("preserves Temporal parameters across coordinated contexts", async () => {
786
+ const adapter = memoryPersistenceAdapter();
787
+ const hostCoordination = new SingleProcessCoordination({
788
+ scope: "temporal",
789
+ });
790
+ const firstRuntime = coordinatedOutboxRuntime({
791
+ adapter,
792
+ coordination: hostCoordination,
793
+ connectivity: new TestNetworkConnectivity(true),
794
+ });
795
+ const secondRuntime = coordinatedOutboxRuntime({
796
+ adapter,
797
+ coordination: clientView(hostCoordination),
798
+ connectivity: new TestNetworkConnectivity(true),
799
+ });
800
+ let observed;
801
+ const execute = (entry) => {
802
+ observed =
803
+ entry.request.parameters.__now;
804
+ return Promise.resolve("done");
805
+ };
806
+ const create = ({ runtime, }) => createOntologyOutbox({
807
+ runtime,
808
+ execute,
809
+ });
810
+ const first = create(firstRuntime);
811
+ const second = create(secondRuntime);
812
+ await Promise.all([first.ready, second.ready]);
813
+ const origin = second;
814
+ const instant = Temporal.Instant.from("2026-07-27T12:00:00Z");
815
+ const action = await origin.enqueue({
816
+ actionTypeName: "createTask",
817
+ parameters: { __now: instant },
818
+ });
819
+ await expect(action.completed).resolves.toBe("done");
820
+ expect(observed).toBeInstanceOf(Temporal.Instant);
821
+ expect(String(observed)).toBe("2026-07-27T12:00:00Z");
822
+ await first.cleanup();
823
+ await second.cleanup();
824
+ await hostCoordination.close();
825
+ });
826
+ it("serializes cross-context removal through the coordination leader", async () => {
827
+ const adapter = memoryPersistenceAdapter();
828
+ const hostCoordination = new SingleProcessCoordination({
829
+ scope: "coordinated",
830
+ });
831
+ const firstRuntime = coordinatedOutboxRuntime({
832
+ adapter,
833
+ coordination: hostCoordination,
834
+ connectivity: new TestNetworkConnectivity(false),
835
+ });
836
+ const secondRuntime = coordinatedOutboxRuntime({
837
+ adapter,
838
+ coordination: clientView(hostCoordination),
839
+ connectivity: new TestNetworkConnectivity(false),
840
+ });
841
+ const create = ({ runtime }) => createOntologyOutbox({
842
+ runtime,
843
+ execute: () => Promise.resolve("done"),
844
+ });
845
+ const first = create(firstRuntime);
846
+ const second = create(secondRuntime);
847
+ await Promise.all([first.ready, second.ready]);
848
+ const origin = second;
849
+ const other = first;
850
+ const action = await origin.enqueue({
851
+ actionTypeName: "deleteTask",
852
+ parameters: { task: "one" },
853
+ });
854
+ await vi.waitFor(() => {
855
+ expect(first.collection.has(action.entry.id)).toBe(true);
856
+ expect(second.collection.has(action.entry.id)).toBe(true);
857
+ });
858
+ await other.remove(action.entry.id);
859
+ await expect(action.completed).rejects.toThrow("Outbox entry removed");
860
+ await vi.waitFor(() => {
861
+ expect(first.collection.has(action.entry.id)).toBe(false);
862
+ expect(second.collection.has(action.entry.id)).toBe(false);
863
+ });
864
+ await first.cleanup();
865
+ await second.cleanup();
866
+ await hostCoordination.close();
867
+ });
868
+ it("rejects removal after the leader has claimed an entry", async () => {
869
+ const adapter = memoryPersistenceAdapter();
870
+ const hostCoordination = new SingleProcessCoordination({
871
+ scope: "claimed",
872
+ });
873
+ const firstRuntime = coordinatedOutboxRuntime({
874
+ adapter,
875
+ coordination: hostCoordination,
876
+ connectivity: new TestNetworkConnectivity(true),
877
+ });
878
+ const secondRuntime = coordinatedOutboxRuntime({
879
+ adapter,
880
+ coordination: clientView(hostCoordination),
881
+ connectivity: new TestNetworkConnectivity(true),
882
+ });
883
+ let finishExecution;
884
+ const execution = new Promise((resolve) => {
885
+ finishExecution = resolve;
886
+ });
887
+ const execute = vi.fn(() => execution);
888
+ const create = ({ runtime }) => createOntologyOutbox({
889
+ runtime,
890
+ execute,
891
+ });
892
+ const first = create(firstRuntime);
893
+ const second = create(secondRuntime);
894
+ await Promise.all([first.ready, second.ready]);
895
+ const origin = second;
896
+ const other = first;
897
+ const action = await origin.enqueue({
898
+ actionTypeName: "deleteTask",
899
+ parameters: { task: "one" },
900
+ });
901
+ await vi.waitFor(() => {
902
+ expect(first.collection.get(action.entry.id)?.status).toBe("executing");
903
+ expect(second.collection.get(action.entry.id)?.status).toBe("executing");
904
+ });
905
+ await expect(other.remove(action.entry.id)).rejects.toThrow("cannot be removed while it is executing");
906
+ finishExecution("done");
907
+ await expect(action.completed).resolves.toBe("done");
908
+ expect(execute).toHaveBeenCalledOnce();
909
+ await first.cleanup();
910
+ await second.cleanup();
911
+ await hostCoordination.close();
912
+ });
913
+ });
914
+ //# sourceMappingURL=createOntologyOutbox.test.js.map