@peers-app/peers-sdk 0.27.3 → 0.27.5

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 (272) hide show
  1. package/dist/contracts/contract-proxy.d.ts +7 -0
  2. package/dist/contracts/contract-proxy.js +19 -2
  3. package/dist/contracts/contract-transport.d.ts +2 -1
  4. package/dist/contracts/contract-transport.js +24 -2
  5. package/dist/contracts/create-local-consumer.js +16 -1
  6. package/dist/contracts/index.d.ts +2 -0
  7. package/dist/contracts/index.js +2 -0
  8. package/dist/contracts/system/index.d.ts +2 -0
  9. package/dist/contracts/system/index.js +2 -0
  10. package/dist/contracts/system/invites-contract.js +2 -1
  11. package/dist/contracts/system/sync-check-contract.d.ts +97 -0
  12. package/dist/contracts/system/sync-check-contract.js +115 -0
  13. package/dist/contracts/tool-arguments.d.ts +15 -0
  14. package/dist/contracts/tool-arguments.js +41 -0
  15. package/dist/contracts/tool-catalog-service.js +2 -12
  16. package/dist/contracts/tool-catalog.js +6 -2
  17. package/dist/data/change-tracking.d.ts +40 -0
  18. package/dist/data/change-tracking.js +89 -0
  19. package/dist/data/orm/types.d.ts +24 -7
  20. package/dist/data/orm/types.js +97 -24
  21. package/dist/define/manifest.js +6 -34
  22. package/dist/device/binary-peer-connection-v2.d.ts +7 -15
  23. package/dist/device/binary-peer-connection-v2.js +14 -30
  24. package/dist/device/connection.d.ts +34 -43
  25. package/dist/device/connection.js +188 -186
  26. package/dist/device/device.d.ts +1 -8
  27. package/dist/device/device.js +1 -30
  28. package/dist/device/socket.type.d.ts +6 -12
  29. package/dist/package-loader/package-loader.js +27 -22
  30. package/dist/system-ids.d.ts +5 -0
  31. package/dist/system-ids.js +6 -1
  32. package/package.json +4 -2
  33. package/dist/context/user-context-identity.test.d.ts +0 -1
  34. package/dist/context/user-context-identity.test.js +0 -220
  35. package/dist/contracts/__tests__/action-dispatch.test.d.ts +0 -1
  36. package/dist/contracts/__tests__/action-dispatch.test.js +0 -319
  37. package/dist/contracts/__tests__/also-implements.test.d.ts +0 -1
  38. package/dist/contracts/__tests__/also-implements.test.js +0 -110
  39. package/dist/contracts/__tests__/builder.test.d.ts +0 -1
  40. package/dist/contracts/__tests__/builder.test.js +0 -669
  41. package/dist/contracts/__tests__/contract-events.test.d.ts +0 -1
  42. package/dist/contracts/__tests__/contract-events.test.js +0 -199
  43. package/dist/contracts/__tests__/contract-observables.test.d.ts +0 -1
  44. package/dist/contracts/__tests__/contract-observables.test.js +0 -290
  45. package/dist/contracts/__tests__/contract-provider-router.test.d.ts +0 -1
  46. package/dist/contracts/__tests__/contract-provider-router.test.js +0 -315
  47. package/dist/contracts/__tests__/contract-proxy.test.d.ts +0 -1
  48. package/dist/contracts/__tests__/contract-proxy.test.js +0 -657
  49. package/dist/contracts/__tests__/contract-resolvers.test.d.ts +0 -1
  50. package/dist/contracts/__tests__/contract-resolvers.test.js +0 -758
  51. package/dist/contracts/__tests__/contract-subscription-lifecycle.test.d.ts +0 -1
  52. package/dist/contracts/__tests__/contract-subscription-lifecycle.test.js +0 -353
  53. package/dist/contracts/__tests__/contract-table-events.test.d.ts +0 -1
  54. package/dist/contracts/__tests__/contract-table-events.test.js +0 -209
  55. package/dist/contracts/__tests__/contract-transport.test.d.ts +0 -1
  56. package/dist/contracts/__tests__/contract-transport.test.js +0 -163
  57. package/dist/contracts/__tests__/extract.test.d.ts +0 -1
  58. package/dist/contracts/__tests__/extract.test.js +0 -145
  59. package/dist/contracts/__tests__/frozen-contracts.test.d.ts +0 -1
  60. package/dist/contracts/__tests__/frozen-contracts.test.js +0 -191
  61. package/dist/contracts/__tests__/generic-tool-dispatch.test.d.ts +0 -1
  62. package/dist/contracts/__tests__/generic-tool-dispatch.test.js +0 -130
  63. package/dist/contracts/__tests__/host-contract-client.test.d.ts +0 -1
  64. package/dist/contracts/__tests__/host-contract-client.test.js +0 -793
  65. package/dist/contracts/__tests__/host-contract-consumer.test.d.ts +0 -1
  66. package/dist/contracts/__tests__/host-contract-consumer.test.js +0 -163
  67. package/dist/contracts/__tests__/host-contract-transport.test.d.ts +0 -1
  68. package/dist/contracts/__tests__/host-contract-transport.test.js +0 -66
  69. package/dist/contracts/__tests__/integration.test.d.ts +0 -1
  70. package/dist/contracts/__tests__/integration.test.js +0 -348
  71. package/dist/contracts/__tests__/legacy-removal.test.d.ts +0 -1
  72. package/dist/contracts/__tests__/legacy-removal.test.js +0 -128
  73. package/dist/contracts/__tests__/persistent-registry.test.d.ts +0 -1
  74. package/dist/contracts/__tests__/persistent-registry.test.js +0 -109
  75. package/dist/contracts/__tests__/playground.test.d.ts +0 -1
  76. package/dist/contracts/__tests__/playground.test.js +0 -216
  77. package/dist/contracts/__tests__/registry.test.d.ts +0 -1
  78. package/dist/contracts/__tests__/registry.test.js +0 -238
  79. package/dist/contracts/__tests__/remote-contract-transport.test.d.ts +0 -1
  80. package/dist/contracts/__tests__/remote-contract-transport.test.js +0 -119
  81. package/dist/contracts/__tests__/system-contracts.test.d.ts +0 -1
  82. package/dist/contracts/__tests__/system-contracts.test.js +0 -247
  83. package/dist/contracts/__tests__/target-dispatch.d.ts +0 -11
  84. package/dist/contracts/__tests__/target-dispatch.js +0 -24
  85. package/dist/contracts/__tests__/tool-catalog-inventory.test.d.ts +0 -1
  86. package/dist/contracts/__tests__/tool-catalog-inventory.test.js +0 -44
  87. package/dist/contracts/__tests__/tool-catalog-service.test.d.ts +0 -1
  88. package/dist/contracts/__tests__/tool-catalog-service.test.js +0 -482
  89. package/dist/contracts/__tests__/tool-catalog.test.d.ts +0 -1
  90. package/dist/contracts/__tests__/tool-catalog.test.js +0 -1199
  91. package/dist/contracts/__tests__/validate.test.d.ts +0 -1
  92. package/dist/contracts/__tests__/validate.test.js +0 -991
  93. package/dist/contracts/__tests__/workflow-context.test.d.ts +0 -1
  94. package/dist/contracts/__tests__/workflow-context.test.js +0 -50
  95. package/dist/data/access-level.test.d.ts +0 -1
  96. package/dist/data/access-level.test.js +0 -53
  97. package/dist/data/change-tracking.test.d.ts +0 -1
  98. package/dist/data/change-tracking.test.js +0 -62
  99. package/dist/data/files/files.test.d.ts +0 -1
  100. package/dist/data/files/files.test.js +0 -770
  101. package/dist/data/group-members.test.d.ts +0 -1
  102. package/dist/data/group-members.test.js +0 -336
  103. package/dist/data/group-share.test.d.ts +0 -1
  104. package/dist/data/group-share.test.js +0 -110
  105. package/dist/data/groups.test.d.ts +0 -1
  106. package/dist/data/groups.test.js +0 -153
  107. package/dist/data/orm/cursor.test.d.ts +0 -1
  108. package/dist/data/orm/cursor.test.js +0 -314
  109. package/dist/data/orm/data-query.mongo.test.d.ts +0 -1
  110. package/dist/data/orm/data-query.mongo.test.js +0 -367
  111. package/dist/data/orm/data-query.sqlite.test.d.ts +0 -1
  112. package/dist/data/orm/data-query.sqlite.test.js +0 -365
  113. package/dist/data/orm/data-query.test.d.ts +0 -1
  114. package/dist/data/orm/data-query.test.js +0 -545
  115. package/dist/data/orm/dependency-injection.test.d.ts +0 -1
  116. package/dist/data/orm/dependency-injection.test.js +0 -171
  117. package/dist/data/orm/event-registry.test.d.ts +0 -1
  118. package/dist/data/orm/event-registry.test.js +0 -44
  119. package/dist/data/orm/multi-cursors.test.d.ts +0 -1
  120. package/dist/data/orm/multi-cursors.test.js +0 -463
  121. package/dist/data/orm/sql.data-source.test.d.ts +0 -1
  122. package/dist/data/orm/sql.data-source.test.js +0 -731
  123. package/dist/data/orm/table-container-events.test.d.ts +0 -1
  124. package/dist/data/orm/table-container-events.test.js +0 -101
  125. package/dist/data/orm/table-container.test.d.ts +0 -1
  126. package/dist/data/orm/table-container.test.js +0 -273
  127. package/dist/data/orm/table.event-source.test.d.ts +0 -1
  128. package/dist/data/orm/table.event-source.test.js +0 -331
  129. package/dist/data/orm/types.test.d.ts +0 -1
  130. package/dist/data/orm/types.test.js +0 -409
  131. package/dist/data/package-table-identity.test.d.ts +0 -1
  132. package/dist/data/package-table-identity.test.js +0 -46
  133. package/dist/data/package-version-permissions.test.d.ts +0 -1
  134. package/dist/data/package-version-permissions.test.js +0 -107
  135. package/dist/data/package-version-resolver.test.d.ts +0 -4
  136. package/dist/data/package-version-resolver.test.js +0 -543
  137. package/dist/data/package-versions.test.d.ts +0 -1
  138. package/dist/data/package-versions.test.js +0 -227
  139. package/dist/data/persistent-vars.conflict.test.d.ts +0 -1
  140. package/dist/data/persistent-vars.conflict.test.js +0 -126
  141. package/dist/data/persistent-vars.migration.test.d.ts +0 -1
  142. package/dist/data/persistent-vars.migration.test.js +0 -133
  143. package/dist/data/persistent-vars.package-scope.test.d.ts +0 -1
  144. package/dist/data/persistent-vars.package-scope.test.js +0 -27
  145. package/dist/data/sync-recovery-queue.test.d.ts +0 -1
  146. package/dist/data/sync-recovery-queue.test.js +0 -215
  147. package/dist/data/table-signing.test.d.ts +0 -1
  148. package/dist/data/table-signing.test.js +0 -118
  149. package/dist/data/tool-access.test.d.ts +0 -1
  150. package/dist/data/tool-access.test.js +0 -166
  151. package/dist/data/user-permissions.test.d.ts +0 -1
  152. package/dist/data/user-permissions.test.js +0 -305
  153. package/dist/data/user-trust-levels.test.d.ts +0 -1
  154. package/dist/data/user-trust-levels.test.js +0 -37
  155. package/dist/define/also-implements.test.d.ts +0 -1
  156. package/dist/define/also-implements.test.js +0 -123
  157. package/dist/define/declare-contract.test.d.ts +0 -1
  158. package/dist/define/declare-contract.test.js +0 -573
  159. package/dist/define/define.test.d.ts +0 -1
  160. package/dist/define/define.test.js +0 -1010
  161. package/dist/define/empty-fields.test.d.ts +0 -1
  162. package/dist/define/empty-fields.test.js +0 -55
  163. package/dist/define/events-observables.test.d.ts +0 -1
  164. package/dist/define/events-observables.test.js +0 -1013
  165. package/dist/define/serve-members.test.d.ts +0 -1
  166. package/dist/define/serve-members.test.js +0 -630
  167. package/dist/define/vars.test.d.ts +0 -1
  168. package/dist/define/vars.test.js +0 -39
  169. package/dist/device/binary-peer-connection-v2.test.d.ts +0 -1
  170. package/dist/device/binary-peer-connection-v2.test.js +0 -730
  171. package/dist/device/binary-peer-connection.d.ts +0 -58
  172. package/dist/device/binary-peer-connection.js +0 -516
  173. package/dist/device/binary-peer-connection.test.d.ts +0 -1
  174. package/dist/device/binary-peer-connection.test.js +0 -184
  175. package/dist/device/connection.test.d.ts +0 -1
  176. package/dist/device/connection.test.js +0 -613
  177. package/dist/device/device-election.test.d.ts +0 -1
  178. package/dist/device/device-election.test.js +0 -516
  179. package/dist/device/device.test.d.ts +0 -1
  180. package/dist/device/device.test.js +0 -243
  181. package/dist/device/frame-cipher.test.d.ts +0 -1
  182. package/dist/device/frame-cipher.test.js +0 -219
  183. package/dist/device/get-trust-level-fn.test.d.ts +0 -1
  184. package/dist/device/get-trust-level-fn.test.js +0 -685
  185. package/dist/device/streamed-socket.d.ts +0 -23
  186. package/dist/device/streamed-socket.js +0 -255
  187. package/dist/device/streamed-socket.test.d.ts +0 -1
  188. package/dist/device/streamed-socket.test.js +0 -216
  189. package/dist/device/tx-encoding.test.d.ts +0 -1
  190. package/dist/device/tx-encoding.test.js +0 -295
  191. package/dist/device-pairing/cpace.test.d.ts +0 -1
  192. package/dist/device-pairing/cpace.test.js +0 -116
  193. package/dist/device-pairing/device-pairing.test.d.ts +0 -1
  194. package/dist/device-pairing/device-pairing.test.js +0 -211
  195. package/dist/hlc.test.d.ts +0 -1
  196. package/dist/hlc.test.js +0 -45
  197. package/dist/host-credentials.test.d.ts +0 -1
  198. package/dist/host-credentials.test.js +0 -88
  199. package/dist/identity/account-email.test.d.ts +0 -1
  200. package/dist/identity/account-email.test.js +0 -59
  201. package/dist/identity/anchor-document.test.d.ts +0 -1
  202. package/dist/identity/anchor-document.test.js +0 -16
  203. package/dist/identity/anchor-resolution.test.d.ts +0 -1
  204. package/dist/identity/anchor-resolution.test.js +0 -130
  205. package/dist/identity/key-change.test.d.ts +0 -1
  206. package/dist/identity/key-change.test.js +0 -811
  207. package/dist/invites/group-approval.test.d.ts +0 -1
  208. package/dist/invites/group-approval.test.js +0 -223
  209. package/dist/invites/invite-token.test.d.ts +0 -1
  210. package/dist/invites/invite-token.test.js +0 -123
  211. package/dist/keys.test.d.ts +0 -1
  212. package/dist/keys.test.js +0 -336
  213. package/dist/logging/console-logger.test.d.ts +0 -1
  214. package/dist/logging/console-logger.test.js +0 -300
  215. package/dist/logging/console-logs.table.test.d.ts +0 -1
  216. package/dist/logging/console-logs.table.test.js +0 -156
  217. package/dist/observable.test.d.ts +0 -1
  218. package/dist/observable.test.js +0 -309
  219. package/dist/package-build/build-package.test.d.ts +0 -1
  220. package/dist/package-build/build-package.test.js +0 -204
  221. package/dist/package-build/contract-add.test.d.ts +0 -1
  222. package/dist/package-build/contract-add.test.js +0 -467
  223. package/dist/package-build/freeze-contracts.test.d.ts +0 -1
  224. package/dist/package-build/freeze-contracts.test.js +0 -355
  225. package/dist/package-installer/package-author-signing.test.d.ts +0 -1
  226. package/dist/package-installer/package-author-signing.test.js +0 -200
  227. package/dist/package-installer/package-cloner.test.d.ts +0 -1
  228. package/dist/package-installer/package-cloner.test.js +0 -278
  229. package/dist/package-installer/package-creator.test.d.ts +0 -1
  230. package/dist/package-installer/package-creator.test.js +0 -908
  231. package/dist/package-installer/package-dev-tools.test.d.ts +0 -1
  232. package/dist/package-installer/package-dev-tools.test.js +0 -64
  233. package/dist/package-installer/package-installer.test.d.ts +0 -1
  234. package/dist/package-installer/package-installer.test.js +0 -882
  235. package/dist/package-installer/package-propagation.test.d.ts +0 -1
  236. package/dist/package-installer/package-propagation.test.js +0 -1269
  237. package/dist/package-installer/package-publisher.test.d.ts +0 -1
  238. package/dist/package-installer/package-publisher.test.js +0 -153
  239. package/dist/package-installer/package-remote-checker.test.d.ts +0 -1
  240. package/dist/package-installer/package-remote-checker.test.js +0 -334
  241. package/dist/package-installer/package-seed-installer.test.d.ts +0 -1
  242. package/dist/package-installer/package-seed-installer.test.js +0 -123
  243. package/dist/package-installer/package-tarball.test.d.ts +0 -1
  244. package/dist/package-installer/package-tarball.test.js +0 -75
  245. package/dist/package-installer/peers-core-installer.test.d.ts +0 -1
  246. package/dist/package-installer/peers-core-installer.test.js +0 -49
  247. package/dist/package-installer/peers-core-trust-anchor.test.d.ts +0 -1
  248. package/dist/package-installer/peers-core-trust-anchor.test.js +0 -155
  249. package/dist/package-loader/contract-package-loader.test.d.ts +0 -1
  250. package/dist/package-loader/contract-package-loader.test.js +0 -274
  251. package/dist/package-loader/host-authority-loading.test.d.ts +0 -1
  252. package/dist/package-loader/host-authority-loading.test.js +0 -104
  253. package/dist/package-loader/host-dispatch-policy.test.d.ts +0 -1
  254. package/dist/package-loader/host-dispatch-policy.test.js +0 -56
  255. package/dist/package-loader/isolated-artifact.test.d.ts +0 -1
  256. package/dist/package-loader/isolated-artifact.test.js +0 -688
  257. package/dist/package-loader/isolated-persistent-var-gateway.test.d.ts +0 -1
  258. package/dist/package-loader/isolated-persistent-var-gateway.test.js +0 -56
  259. package/dist/package-loader/isolated-table-gateway.test.d.ts +0 -1
  260. package/dist/package-loader/isolated-table-gateway.test.js +0 -633
  261. package/dist/package-loader/package-loader.test.d.ts +0 -1
  262. package/dist/package-loader/package-loader.test.js +0 -1764
  263. package/dist/package-loader/resolve-consumed-contracts.test.d.ts +0 -1
  264. package/dist/package-loader/resolve-consumed-contracts.test.js +0 -166
  265. package/dist/serial-json.compat.test.d.ts +0 -1
  266. package/dist/serial-json.compat.test.js +0 -369
  267. package/dist/serial-json.test.d.ts +0 -1
  268. package/dist/serial-json.test.js +0 -401
  269. package/dist/testing/mock-host-contract.test.d.ts +0 -1
  270. package/dist/testing/mock-host-contract.test.js +0 -782
  271. package/dist/utils.test.d.ts +0 -1
  272. package/dist/utils.test.js +0 -232
@@ -1,1013 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const isolated_artifact_1 = require("../package-loader/isolated-artifact");
4
- const peer_id_1 = require("../peer-id");
5
- const index_1 = require("./index");
6
- const manifest_1 = require("./manifest");
7
- const packageId = (0, peer_id_1.newid)();
8
- const contractId = (0, peer_id_1.newid)();
9
- const otherContractId = (0, peer_id_1.newid)();
10
- /** A worker ctx that records emits and publishes, and keeps the observable write handlers. */
11
- function workerHost(publish = async () => null) {
12
- const calls = [];
13
- const saves = [];
14
- const setters = new Map();
15
- const ctx = {
16
- emitContractEvent: async (...args) => {
17
- calls.push(["emit", ...args]);
18
- return null;
19
- },
20
- publishOwnedObservable: async (...args) => {
21
- calls.push(["publish", ...args]);
22
- return publish(args[3]);
23
- },
24
- onOwnedObservableSet: (id, version, name, handler) => {
25
- setters.set(`${id}@${version}:${name}`, handler);
26
- },
27
- getOwnedPersistentVar: async () => null,
28
- setOwnedPersistentVar: async (...args) => {
29
- saves.push(args);
30
- return args[2];
31
- },
32
- watchOwnedPersistentVar: async () => () => undefined,
33
- };
34
- return { ctx, calls, saves, setters };
35
- }
36
- const settle = () => new Promise((resolve) => setTimeout(resolve, 0));
37
- const ping = (0, index_1.defineTool)({
38
- name: "ping",
39
- usageDescription: "Answers.",
40
- accessLevel: index_1.AccessLevel.Reader,
41
- inputSchema: index_1.z.object({}),
42
- outputSchema: index_1.z.object({}),
43
- handler: () => ({}),
44
- });
45
- const alertEvent = (0, index_1.defineEvent)({
46
- name: "alert",
47
- description: "Something finished.",
48
- payloadSchema: index_1.z.object({
49
- title: index_1.z.string().describe("Short title."),
50
- kind: index_1.z.string().default("info").describe("Alert kind."),
51
- }),
52
- });
53
- /** Declare a package with `fill` adding members to two provided contracts. */
54
- function define(fill) {
55
- const main = { events: [], observables: [] };
56
- const other = { events: [], observables: [] };
57
- fill(main, other);
58
- return (0, index_1.definePackage)((pkg) => {
59
- pkg.packageId = packageId;
60
- pkg.version = "1.0.0";
61
- pkg.provides((0, index_1.defineContract)({
62
- contractId,
63
- version: 1,
64
- name: "Main",
65
- description: "The main contract.",
66
- tools: [ping],
67
- events: main.events,
68
- observables: main.observables,
69
- }));
70
- pkg.provides((0, index_1.defineContract)({
71
- contractId: otherContractId,
72
- version: 1,
73
- name: "Other",
74
- description: "Another contract.",
75
- tools: [ping],
76
- events: other.events,
77
- observables: other.observables,
78
- }));
79
- });
80
- }
81
- describe("outside a worker", () => {
82
- test("emit checks the payload and sends nothing", async () => {
83
- await expect(alertEvent.emit({ title: "Done" })).resolves.toBeUndefined();
84
- await expect(alertEvent.emit({ title: 5 })).rejects.toThrow(/Expected string/);
85
- const heartbeat = (0, index_1.defineEvent)({ name: "heartbeat", description: "No payload." });
86
- await expect(heartbeat.emit()).resolves.toBeUndefined();
87
- });
88
- test("emit checks what the bridge can carry, without structuredClone (jsdom lacks it)", async () => {
89
- const event = (0, index_1.defineEvent)({
90
- name: "anything",
91
- description: "An untyped payload.",
92
- payloadSchema: index_1.z.object({ value: index_1.z.any() }),
93
- });
94
- const global = globalThis;
95
- const original = global.structuredClone;
96
- delete global.structuredClone;
97
- try {
98
- const shared = { label: "same" };
99
- await expect(event.emit({ value: { a: shared, b: [shared], when: new Date(0) } })).resolves.toBeUndefined();
100
- await expect(event.emit({ value: new Map() })).rejects.toThrow("value.value is a Map, which can't be sent");
101
- await expect(event.emit({ value: [() => 1] })).rejects.toThrow("value.value[0] is a function, which can't be sent");
102
- await expect(event.emit({ value: Number.NaN })).rejects.toThrow(/only finite numbers/);
103
- const cycle = {};
104
- cycle.self = cycle;
105
- await expect(event.emit({ value: cycle })).rejects.toThrow(/cycles can't be sent/);
106
- }
107
- finally {
108
- global.structuredClone = original;
109
- }
110
- });
111
- test("an object payload schema rejects null instead of treating it as an empty object", async () => {
112
- const event = (0, index_1.defineEvent)({
113
- name: "defaults",
114
- description: "An object payload.",
115
- payloadSchema: index_1.z.object({ kind: index_1.z.string().default("info") }),
116
- });
117
- await expect(event.emit(null)).rejects.toThrow(/Expected object/);
118
- });
119
- test("an event or observable is checked when it's declared", () => {
120
- expect(() => (0, index_1.defineEvent)({ name: "", description: "" })).toThrow(/requires a name/);
121
- expect(() => (0, index_1.defineEvent)({
122
- name: "x",
123
- description: "",
124
- payloadSchema: index_1.z.object({}),
125
- payloadFields: [],
126
- })).toThrow(/either payloadSchema or payloadFields/);
127
- const source = (0, index_1.observable)(0);
128
- expect(() => (0, index_1.defineObservable)({ name: "count", description: "Count.", source })).toThrow(/either a schema or a valueType/);
129
- expect(() => (0, index_1.defineObservable)({
130
- name: "count",
131
- description: "Count.",
132
- schema: index_1.z.number(),
133
- source: 0,
134
- })).toThrow(/needs a source/);
135
- expect(() => (0, index_1.defineObservable)({
136
- name: "count",
137
- description: "Count.",
138
- schema: index_1.z.number(),
139
- source: (0, index_1.deviceVar)(`no-default-${(0, peer_id_1.newid)()}`),
140
- })).toThrow(/needs a defaultValue/);
141
- });
142
- });
143
- test("an emit made before the worker starts waits for the start", async () => {
144
- const definition = define((main) => {
145
- main.events = [alertEvent];
146
- });
147
- const global = globalThis;
148
- global.onWorkerStart = () => undefined;
149
- try {
150
- const host = workerHost();
151
- const pending = alertEvent.emit({ title: "Early" });
152
- await settle();
153
- expect(host.calls).toEqual([]);
154
- await (0, index_1.startPackage)(definition, host.ctx);
155
- await pending;
156
- expect(host.calls).toEqual([
157
- ["emit", contractId, 1, "alert", { title: "Early", kind: "info" }],
158
- ]);
159
- }
160
- finally {
161
- delete global.onWorkerStart;
162
- }
163
- });
164
- describe("the manifest", () => {
165
- test.each([
166
- index_1.z.object({ note: index_1.z.string().optional() }).nullable().readonly(),
167
- index_1.z.object({ note: index_1.z.string().optional() }).nullable().brand(),
168
- index_1.z
169
- .object({ note: index_1.z.string().optional() })
170
- .nullable()
171
- .refine(() => true),
172
- index_1.z.object({ note: index_1.z.string().optional() }).nullable().default(null),
173
- ])("a wrapped nullable whole observable value is allowed (%#)", (schema) => {
174
- const definition = define((main) => {
175
- main.observables = [
176
- (0, index_1.defineObservable)({
177
- name: "maybe",
178
- description: "May have no value.",
179
- schema,
180
- source: (0, index_1.observable)(null),
181
- }),
182
- ];
183
- });
184
- const manifest = (0, manifest_1.buildPackageManifest)(definition);
185
- expect(manifest.provides[0].observables?.[0].value).toMatchObject({
186
- type: "object",
187
- optional: true,
188
- fields: [{ name: "note", type: "string", optional: true }],
189
- });
190
- expect(() => (0, isolated_artifact_1.validateIsolatedPackageArtifact)({ manifest, providerSource: "void 0;" }, { strict: true })).not.toThrow();
191
- });
192
- test("nullable fields inside a nullable observable still fail the build", () => {
193
- const definition = define((main) => {
194
- main.observables = [
195
- (0, index_1.defineObservable)({
196
- name: "maybe",
197
- description: "May have no value.",
198
- schema: index_1.z.object({ note: index_1.z.string().nullable() }).nullable().readonly(),
199
- source: (0, index_1.observable)(null),
200
- }),
201
- ];
202
- });
203
- expect(() => (0, manifest_1.buildPackageManifest)(definition)).toThrow('Observable maybe value: Field "value.note" uses .nullable()');
204
- });
205
- test("an IR event configuration may leave payloadSchema undefined", () => {
206
- const options = {
207
- name: "configured",
208
- description: "Configured from IR.",
209
- payloadSchema: undefined,
210
- payloadFields: [{ name: "title", type: index_1.FieldType.string }],
211
- };
212
- const event = (0, index_1.defineEvent)(options);
213
- const definition = define((main) => {
214
- main.events = [event];
215
- });
216
- expect((0, manifest_1.buildPackageManifest)(definition).provides[0].events).toEqual([
217
- {
218
- name: options.name,
219
- description: options.description,
220
- payloadFields: options.payloadFields,
221
- },
222
- ]);
223
- });
224
- test("event fields describe parsed output through wrappers, transforms, and arrays", () => {
225
- const event = (0, index_1.defineEvent)({
226
- name: "shapes",
227
- description: "Parsed event values.",
228
- payloadSchema: index_1.z.object({
229
- maybe: index_1.z.string().optional().brand().readonly(),
230
- detail: index_1.z.string().nullish(),
231
- size: index_1.z.string().transform((value) => value.length),
232
- matrix: index_1.z.array(index_1.z.array(index_1.z.number())),
233
- nullableItems: index_1.z.array(index_1.z.string().nullable()),
234
- }),
235
- });
236
- const definition = define((main) => {
237
- main.events = [event];
238
- });
239
- expect((0, manifest_1.buildPackageManifest)(definition).provides[0].events?.[0].payloadFields).toEqual([
240
- { name: "maybe", type: "string", optional: true },
241
- { name: "detail", type: "string", optional: true },
242
- { name: "size", type: "any", optional: true },
243
- { name: "matrix", type: "any", isArray: true },
244
- { name: "nullableItems", type: "any", isArray: true },
245
- ]);
246
- });
247
- test("nested event fields use parsed output rules at every level", () => {
248
- const event = (0, index_1.defineEvent)({
249
- name: "nested",
250
- description: "Nested parsed values.",
251
- payloadSchema: index_1.z.object({
252
- detail: index_1.z.object({
253
- changed: index_1.z.string().transform((value) => value.length),
254
- piped: index_1.z.string().transform(Number).pipe(index_1.z.number()),
255
- items: index_1.z.array(index_1.z.object({ matrix: index_1.z.array(index_1.z.array(index_1.z.number())) })),
256
- modes: index_1.z.array(index_1.z.enum(["on", "off"]).nullable()),
257
- }),
258
- }),
259
- });
260
- const definition = define((main) => {
261
- main.events = [event];
262
- });
263
- const manifest = (0, manifest_1.buildPackageManifest)(definition);
264
- expect(manifest.provides[0].events?.[0].payloadFields).toEqual([
265
- {
266
- name: "detail",
267
- type: "object",
268
- fields: [
269
- { name: "changed", type: "any", optional: true },
270
- { name: "piped", type: "number" },
271
- {
272
- name: "items",
273
- type: "object",
274
- isArray: true,
275
- fields: [{ name: "matrix", type: "any", isArray: true }],
276
- },
277
- { name: "modes", type: "any", isArray: true },
278
- ],
279
- },
280
- ]);
281
- expect(() => (0, isolated_artifact_1.validateIsolatedPackageArtifact)({ manifest, providerSource: "void 0;" }, { strict: true })).not.toThrow();
282
- });
283
- test("carries events and observables as IR", () => {
284
- const location = (0, index_1.groupVar)(`location-${(0, peer_id_1.newid)()}`, {
285
- defaultValue: { query: "", unit: "F" },
286
- });
287
- const conditions = (0, index_1.observable)(null);
288
- const definition = define((main, other) => {
289
- main.events = [
290
- alertEvent,
291
- (0, index_1.defineEvent)({ name: "ping", description: "No payload." }),
292
- { name: "raw", description: "IR as is.", payloadFields: [] },
293
- ];
294
- main.observables = [
295
- (0, index_1.defineObservable)({
296
- name: "location",
297
- description: "Saved city.",
298
- schema: index_1.z.object({ query: index_1.z.string(), unit: index_1.z.enum(["F", "C"]) }),
299
- writable: true,
300
- source: location,
301
- }),
302
- (0, index_1.defineObservable)({
303
- name: "conditions",
304
- description: "Latest reading.",
305
- schema: index_1.z.object({ temp: index_1.z.number() }).nullable(),
306
- source: conditions,
307
- }),
308
- (0, index_1.defineObservable)({
309
- name: "recent",
310
- description: "Recent cities.",
311
- schema: index_1.z.array(index_1.z.string()),
312
- source: (0, index_1.observable)([]),
313
- }),
314
- (0, index_1.defineObservable)({
315
- name: "status",
316
- description: "IR value type.",
317
- valueType: index_1.FieldType.string,
318
- source: (0, index_1.observable)("idle"),
319
- }),
320
- (0, index_1.defineObservable)({
321
- name: "mode",
322
- description: "An enum.",
323
- schema: index_1.z.enum(["a", "b"]).default("a"),
324
- source: (0, index_1.observable)("a"),
325
- }),
326
- (0, index_1.defineObservable)({
327
- name: "answer",
328
- description: "A literal.",
329
- schema: index_1.z.literal(42),
330
- source: (0, index_1.observable)(42),
331
- }),
332
- (0, index_1.defineObservable)({
333
- name: "owner",
334
- description: "A peer id.",
335
- schema: index_1.zodPeerId,
336
- source: (0, index_1.observable)((0, peer_id_1.newid)()),
337
- }),
338
- ];
339
- other.observables = [
340
- {
341
- name: "skill",
342
- description: "IR with its variable.",
343
- valueType: index_1.FieldType.object,
344
- writable: false,
345
- persistentVar: { scope: "device", name: "skill", defaultValue: {} },
346
- },
347
- ];
348
- });
349
- const manifest = (0, manifest_1.buildPackageManifest)(definition);
350
- const [main, other] = manifest.provides;
351
- expect(main.events).toEqual([
352
- {
353
- name: "alert",
354
- description: "Something finished.",
355
- payloadFields: [
356
- { name: "title", type: "string", description: "Short title." },
357
- { name: "kind", type: "string", description: "Alert kind." },
358
- ],
359
- },
360
- { name: "ping", description: "No payload.", payloadFields: [] },
361
- { name: "raw", description: "IR as is.", payloadFields: [] },
362
- ]);
363
- expect(main.observables).toEqual([
364
- {
365
- name: "location",
366
- description: "Saved city.",
367
- valueType: "object",
368
- value: {
369
- type: "object",
370
- fields: [
371
- { name: "query", type: "string" },
372
- { name: "unit", type: "string", enumValues: ["F", "C"] },
373
- ],
374
- },
375
- writable: true,
376
- persistentVar: {
377
- scope: "group",
378
- name: expect.stringMatching(/^location-/),
379
- defaultValue: { query: "", unit: "F" },
380
- },
381
- },
382
- {
383
- name: "conditions",
384
- description: "Latest reading.",
385
- valueType: "any",
386
- // The full shape says what `valueType` can't: an object, or empty (null).
387
- value: { type: "object", optional: true, fields: [{ name: "temp", type: "number" }] },
388
- writable: false,
389
- },
390
- {
391
- name: "recent",
392
- description: "Recent cities.",
393
- valueType: "object",
394
- value: { type: "string", isArray: true },
395
- writable: false,
396
- },
397
- { name: "status", description: "IR value type.", valueType: "string", writable: false },
398
- {
399
- name: "mode",
400
- description: "An enum.",
401
- valueType: "string",
402
- value: { type: "string", enumValues: ["a", "b"] },
403
- writable: false,
404
- },
405
- {
406
- name: "answer",
407
- description: "A literal.",
408
- valueType: "number",
409
- value: { type: "number", enumValues: [42] },
410
- writable: false,
411
- },
412
- {
413
- name: "owner",
414
- description: "A peer id.",
415
- valueType: "id",
416
- value: { type: "id" },
417
- writable: false,
418
- },
419
- ]);
420
- expect(other.observables).toEqual([
421
- {
422
- name: "skill",
423
- description: "IR with its variable.",
424
- valueType: "object",
425
- writable: false,
426
- persistentVar: { scope: "device", name: "skill", defaultValue: {} },
427
- },
428
- ]);
429
- expect(() => (0, isolated_artifact_1.validateIsolatedPackageArtifact)({ manifest, providerSource: "x" })).not.toThrow();
430
- });
431
- test("transforms use any without executing schema code to infer the wire type", () => {
432
- const transform = jest.fn((value) => value.length);
433
- const refine = jest.fn(() => true);
434
- const definition = define((main) => {
435
- main.observables = [
436
- (0, index_1.defineObservable)({
437
- name: "length",
438
- description: "A transformed value.",
439
- schema: index_1.z.string().transform(transform),
440
- writable: true,
441
- source: (0, index_1.observable)(0),
442
- }),
443
- (0, index_1.defineObservable)({
444
- name: "optional",
445
- description: "An optional refined value.",
446
- schema: index_1.z.string().optional().refine(refine),
447
- source: (0, index_1.observable)(),
448
- }),
449
- ];
450
- });
451
- expect((0, manifest_1.buildPackageManifest)(definition).provides[0].observables?.map((o) => o.valueType)).toEqual([index_1.FieldType.any, index_1.FieldType.any]);
452
- expect(transform).not.toHaveBeenCalled();
453
- expect(refine).not.toHaveBeenCalled();
454
- });
455
- test.each([
456
- index_1.z.date().nullable(),
457
- index_1.z.bigint().optional(),
458
- index_1.z.array(index_1.z.date()),
459
- index_1.z.object({ nested: index_1.z.map(index_1.z.string(), index_1.z.number()) }),
460
- index_1.z.record(index_1.z.set(index_1.z.string())),
461
- index_1.z.tuple([index_1.z.string(), index_1.z.symbol()]),
462
- index_1.z.union([index_1.z.string(), index_1.z.date()]),
463
- index_1.z.lazy(() => index_1.z.object({ nested: index_1.z.date() })),
464
- index_1.z.literal(1n),
465
- index_1.z.nan(),
466
- ])("rejects non-JSON schemas inside wrappers and containers (%#)", (schema) => {
467
- const definition = define((main) => {
468
- main.observables = [
469
- (0, index_1.defineObservable)({
470
- name: "invalid",
471
- description: "Non-JSON.",
472
- schema,
473
- source: (0, index_1.observable)(null),
474
- }),
475
- ];
476
- });
477
- expect(() => (0, manifest_1.buildPackageManifest)(definition)).toThrow(/its value must be JSON/);
478
- });
479
- test("rejects what an observable can't hold or a contract can't list", () => {
480
- const withObservable = (item) => define((main) => {
481
- main.observables = [item];
482
- });
483
- expect(() => (0, manifest_1.buildPackageManifest)(withObservable((0, index_1.defineObservable)({
484
- name: "when",
485
- description: "A date.",
486
- schema: index_1.z.date(),
487
- source: (0, index_1.observable)(new Date()),
488
- })))).toThrow(/ZodDate schema; its value must be JSON/);
489
- expect(() => (0, manifest_1.buildPackageManifest)(withObservable((0, index_1.defineObservable)({
490
- name: "unit",
491
- description: "Unit.",
492
- schema: index_1.z.enum(["F", "C"]),
493
- source: (0, index_1.deviceVar)(`unit-${(0, peer_id_1.newid)()}`, { defaultValue: "K" }),
494
- })))).toThrow(/defaultValue doesn't match the schema/);
495
- // The worker publishes an in-memory source's value at start; one the host would reject
496
- // fails the build rather than every worker start.
497
- expect(() => (0, manifest_1.buildPackageManifest)(withObservable((0, index_1.defineObservable)({
498
- name: "label",
499
- description: "Unset label.",
500
- schema: index_1.z.string(),
501
- source: (0, index_1.observable)(),
502
- })))).toThrow(/label's source starts as undefined, which doesn't match its valueType string/);
503
- expect(() => (0, manifest_1.buildPackageManifest)(withObservable((0, index_1.defineObservable)({
504
- name: "reading",
505
- description: "No reading yet.",
506
- schema: index_1.z.object({ temp: index_1.z.number() }),
507
- source: (0, index_1.observable)(null),
508
- })))).toThrow(/reading's source starts as null, which doesn't match its valueType object/);
509
- expect(() => (0, manifest_1.buildPackageManifest)(withObservable((0, index_1.defineObservable)({
510
- name: "count",
511
- description: "IR value type.",
512
- valueType: index_1.FieldType.number,
513
- source: (0, index_1.observable)("zero"),
514
- })))).toThrow(/count's source starts as a string, which doesn't match its valueType number/);
515
- expect(() => withObservable({ name: "status", description: "No binding.", valueType: "string" })).toThrow(/IR without a persistentVar/);
516
- expect(() => withObservable({ description: "No name." })).toThrow(/isn't from defineObservable/);
517
- const status = (0, index_1.defineObservable)({
518
- name: "status",
519
- description: "Status.",
520
- valueType: index_1.FieldType.string,
521
- source: (0, index_1.observable)("idle"),
522
- });
523
- expect(() => define((main) => {
524
- main.observables = [status, status];
525
- })).toThrow(/observable "status" twice/);
526
- expect(() => define((main) => {
527
- main.events = [alertEvent, { name: "alert", description: "Again." }];
528
- })).toThrow(/event "alert" twice/);
529
- });
530
- });
531
- describe("in a worker", () => {
532
- test("emits omit undefined fields and snapshot optional array entries without losing wire types", async () => {
533
- const event = (0, index_1.defineEvent)({
534
- name: "optional",
535
- description: "Optional payload values.",
536
- payloadSchema: index_1.z.object({
537
- extra: index_1.z.string().optional(),
538
- nested: index_1.z.object({ extra: index_1.z.string().optional() }),
539
- items: index_1.z.array(index_1.z.string().optional()),
540
- when: index_1.z.date(),
541
- bytes: index_1.z.instanceof(Uint8Array),
542
- }),
543
- });
544
- const definition = define((main) => {
545
- main.events = [event];
546
- });
547
- const host = workerHost();
548
- await (0, index_1.startPackage)(definition, host.ctx);
549
- const when = new Date("2026-10-21T00:00:00Z");
550
- const bytes = new Uint8Array([1, 2]);
551
- const payload = {
552
- extra: undefined,
553
- nested: { extra: undefined },
554
- items: [undefined],
555
- when,
556
- bytes,
557
- };
558
- const sent = event.emit(payload);
559
- bytes[0] = 3;
560
- await sent;
561
- expect(host.calls).toEqual([
562
- [
563
- "emit",
564
- contractId,
565
- 1,
566
- "optional",
567
- { nested: {}, items: [null], when, bytes: new Uint8Array([1, 2]) },
568
- ],
569
- ]);
570
- expect(Object.hasOwn(payload, "extra")).toBe(true);
571
- expect(payload.nested).toEqual({ extra: undefined });
572
- expect(payload.items).toEqual([undefined]);
573
- });
574
- test("emits, writes, and publications leave out empty optional fields", async () => {
575
- const event = (0, index_1.defineEvent)({
576
- name: "rated",
577
- description: "A rating changed.",
578
- payloadSchema: index_1.z.object({
579
- note: index_1.z.string().optional(),
580
- where: index_1.z.object({ zip: index_1.z.string().optional() }),
581
- count: index_1.z.number().default(1),
582
- }),
583
- });
584
- const place = (0, index_1.observable)({
585
- query: "a",
586
- unit: null,
587
- });
588
- const definition = define((main) => {
589
- main.events = [event];
590
- main.observables = [
591
- (0, index_1.defineObservable)({
592
- name: "place",
593
- description: "Saved place.",
594
- schema: index_1.z.object({ query: index_1.z.string(), unit: index_1.z.enum(["F", "C"]).optional() }),
595
- writable: true,
596
- source: place,
597
- }),
598
- ];
599
- });
600
- const host = workerHost();
601
- await (0, index_1.startPackage)(definition, host.ctx);
602
- expect(host.calls).toEqual([["publish", contractId, 1, "place", { query: "a" }]]);
603
- await event.emit({ note: null, where: { zip: null }, count: null });
604
- expect(host.calls.at(-1)).toEqual(["emit", contractId, 1, "rated", { where: {}, count: 1 }]);
605
- const write = host.setters.get(`${contractId}@1:place`);
606
- await expect(Promise.resolve(write?.({ query: "b", unit: null }))).resolves.toEqual({
607
- query: "b",
608
- });
609
- expect(place()).toEqual({ query: "b" });
610
- });
611
- test("array observables clean their object fields on publication and write", async () => {
612
- const source = (0, index_1.observable)([{ note: null }]);
613
- const definition = define((main) => {
614
- main.observables = [
615
- (0, index_1.defineObservable)({
616
- name: "items",
617
- description: "Items.",
618
- schema: index_1.z.array(index_1.z.object({ note: index_1.z.string().optional() })),
619
- writable: true,
620
- source,
621
- }),
622
- ];
623
- });
624
- const host = workerHost();
625
- await (0, index_1.startPackage)(definition, host.ctx);
626
- expect(host.calls).toEqual([["publish", contractId, 1, "items", [{}]]]);
627
- const write = host.setters.get(`${contractId}@1:items`);
628
- await expect(Promise.resolve(write?.([{ note: null }]))).resolves.toStrictEqual([{}]);
629
- expect(source()).toStrictEqual([{}]);
630
- });
631
- test("variables load before the initial publication of sources derived from them", async () => {
632
- const saved = (0, index_1.deviceVar)(`derived-${(0, peer_id_1.newid)()}`, { defaultValue: 0 });
633
- const source = (0, index_1.observable)(0);
634
- const subscription = saved.subscribe(source);
635
- const definition = define((main) => {
636
- main.observables = [
637
- (0, index_1.defineObservable)({
638
- name: "derived",
639
- description: "Derived from stored state.",
640
- schema: index_1.z.number(),
641
- source,
642
- }),
643
- ];
644
- });
645
- let release = () => { };
646
- const gate = new Promise((resolve) => {
647
- release = resolve;
648
- });
649
- const host = workerHost(async (value) => {
650
- if (value === 7)
651
- await gate;
652
- });
653
- host.ctx.getOwnedPersistentVar = async () => 7;
654
- let started = false;
655
- const starting = (0, index_1.startPackage)(definition, host.ctx).then(() => {
656
- started = true;
657
- });
658
- try {
659
- await settle();
660
- expect(host.calls).toEqual([["publish", contractId, 1, "derived", 7]]);
661
- expect(started).toBe(false);
662
- }
663
- finally {
664
- release();
665
- await starting;
666
- subscription.dispose();
667
- }
668
- });
669
- test("onStart hooks run in order after variables load, before the first publication", async () => {
670
- const saved = (0, index_1.deviceVar)(`hooked-${(0, peer_id_1.newid)()}`, { defaultValue: 0 });
671
- const source = (0, index_1.observable)(0);
672
- const seen = [];
673
- const host = workerHost();
674
- host.ctx.getOwnedPersistentVar = async () => 7;
675
- const definition = (0, index_1.definePackage)((pkg) => {
676
- pkg.packageId = packageId;
677
- pkg.version = "1.0.0";
678
- pkg.provides((0, index_1.defineContract)({
679
- contractId,
680
- version: 1,
681
- name: "Main",
682
- description: "The main contract.",
683
- tools: [ping],
684
- observables: [
685
- (0, index_1.defineObservable)({
686
- name: "loaded",
687
- description: "Set at the start.",
688
- schema: index_1.z.number(),
689
- source,
690
- }),
691
- ],
692
- }));
693
- pkg.onStart(async (ctx) => {
694
- seen.push(["first", saved(), ctx === host.ctx]);
695
- await settle();
696
- source(saved() + 1);
697
- });
698
- pkg.onStart(() => {
699
- seen.push(["second", source()]);
700
- });
701
- });
702
- await (0, index_1.startPackage)(definition, host.ctx);
703
- expect(seen).toEqual([
704
- ["first", 7, true],
705
- ["second", 8],
706
- ]);
707
- // Readers' first value is the one the hook set.
708
- expect(host.calls).toEqual([["publish", contractId, 1, "loaded", 8]]);
709
- });
710
- test("a failing onStart hook fails the start, and onStart takes only functions", async () => {
711
- const definition = (0, index_1.definePackage)((pkg) => {
712
- pkg.packageId = packageId;
713
- pkg.version = "1.0.0";
714
- pkg.onStart(() => {
715
- throw new Error("no cache");
716
- });
717
- });
718
- await expect((0, index_1.startPackage)(definition, workerHost().ctx)).rejects.toThrow("no cache");
719
- expect(() => (0, index_1.definePackage)((pkg) => {
720
- pkg.packageId = packageId;
721
- pkg.version = "1.0.0";
722
- pkg.onStart("later");
723
- })).toThrow("pkg.onStart takes a function");
724
- });
725
- test("emit parses the payload and sends it on every contract that lists the event, in order", async () => {
726
- const unlisted = (0, index_1.defineEvent)({ name: "unlisted", description: "On no contract." });
727
- const definition = define((main, other) => {
728
- main.events = [alertEvent];
729
- other.events = [alertEvent];
730
- });
731
- const host = workerHost();
732
- await (0, index_1.startPackage)(definition, host.ctx);
733
- const first = alertEvent.emit({ title: "One" });
734
- const second = alertEvent.emit({ title: "Two", kind: "timer" });
735
- await Promise.all([first, second]);
736
- expect(host.calls).toEqual([
737
- ["emit", contractId, 1, "alert", { title: "One", kind: "info" }],
738
- ["emit", otherContractId, 1, "alert", { title: "One", kind: "info" }],
739
- ["emit", contractId, 1, "alert", { title: "Two", kind: "timer" }],
740
- ["emit", otherContractId, 1, "alert", { title: "Two", kind: "timer" }],
741
- ]);
742
- await expect(alertEvent.emit({})).rejects.toThrow(/Required/);
743
- await expect(unlisted.emit(undefined)).rejects.toThrow(/isn't listed on a contract/);
744
- });
745
- test("an in-memory observable publishes at the start and after each change, one at a time", async () => {
746
- const count = (0, index_1.observable)(0);
747
- const stored = (0, index_1.deviceVar)(`stored-${(0, peer_id_1.newid)()}`, { defaultValue: 1 });
748
- const definition = define((main, other) => {
749
- const counter = (0, index_1.defineObservable)({
750
- name: "count",
751
- description: "A count.",
752
- schema: index_1.z.number(),
753
- source: count,
754
- });
755
- main.observables = [
756
- counter,
757
- (0, index_1.defineObservable)({
758
- name: "stored",
759
- description: "Served by the host.",
760
- schema: index_1.z.number(),
761
- source: stored,
762
- }),
763
- ];
764
- other.observables = [counter];
765
- });
766
- let active = 0;
767
- let overlapped = false;
768
- const host = workerHost(async () => {
769
- active += 1;
770
- overlapped ||= active > 2;
771
- await settle();
772
- active -= 1;
773
- return null;
774
- });
775
- await (0, index_1.startPackage)(definition, host.ctx);
776
- expect(host.calls).toEqual([
777
- ["publish", contractId, 1, "count", 0],
778
- ["publish", otherContractId, 1, "count", 0],
779
- ]);
780
- count(1);
781
- count(2);
782
- stored(5);
783
- await settle();
784
- await settle();
785
- await settle();
786
- expect(host.calls.slice(2)).toEqual([
787
- ["publish", contractId, 1, "count", 1],
788
- ["publish", otherContractId, 1, "count", 1],
789
- ["publish", contractId, 1, "count", 2],
790
- ["publish", otherContractId, 1, "count", 2],
791
- ]);
792
- // Each value's publishes run together; the next value waits for them.
793
- expect(overlapped).toBe(false);
794
- // The variable-backed observable is saved like any variable; the host serves it.
795
- expect(host.saves).toEqual([["device", expect.stringMatching(/^stored-/), 5]]);
796
- // Read-only: no write handler.
797
- expect(host.setters.size).toBe(0);
798
- });
799
- test("queued publications capture each object before later mutation", async () => {
800
- const value = (0, index_1.observable)({ count: 0 });
801
- const definition = define((main) => {
802
- main.observables = [
803
- (0, index_1.defineObservable)({
804
- name: "snapshot",
805
- description: "A snapshot.",
806
- schema: index_1.z.object({ count: index_1.z.number() }),
807
- source: value,
808
- }),
809
- ];
810
- });
811
- const host = workerHost();
812
- await (0, index_1.startPackage)(definition, host.ctx);
813
- host.calls.length = 0;
814
- const next = { count: 1 };
815
- value(next);
816
- next.count = 2;
817
- value.notifySubscribers();
818
- await settle();
819
- expect(host.calls).toEqual([
820
- ["publish", contractId, 1, "snapshot", { count: 1 }],
821
- ["publish", contractId, 1, "snapshot", { count: 2 }],
822
- ]);
823
- });
824
- test("publications and parsed write replies normalize optional values without changing the source", async () => {
825
- const source = (0, index_1.observable)({
826
- extra: undefined,
827
- nested: { extra: undefined },
828
- items: [undefined],
829
- });
830
- const definition = define((main) => {
831
- main.observables = [
832
- (0, index_1.defineObservable)({
833
- name: "optional",
834
- description: "Optional values.",
835
- schema: index_1.z.string().transform(() => ({
836
- extra: undefined,
837
- nested: { extra: undefined },
838
- items: [undefined],
839
- })),
840
- writable: true,
841
- source,
842
- }),
843
- ];
844
- });
845
- const host = workerHost();
846
- await (0, index_1.startPackage)(definition, host.ctx);
847
- expect(host.calls).toEqual([
848
- ["publish", contractId, 1, "optional", { nested: {}, items: [null] }],
849
- ]);
850
- const write = host.setters.get(`${contractId}@1:optional`);
851
- await expect(Promise.resolve(write?.("next"))).resolves.toEqual({ nested: {}, items: [null] });
852
- expect(host.calls.at(-1)).toEqual([
853
- "publish",
854
- contractId,
855
- 1,
856
- "optional",
857
- { nested: {}, items: [null] },
858
- ]);
859
- expect(Object.hasOwn(source(), "extra")).toBe(true);
860
- expect(source().nested).toEqual({ extra: undefined });
861
- expect(source().items).toEqual([undefined]);
862
- });
863
- test("a write is parsed, set on the source, and resolves once the stored value is published", async () => {
864
- const level = (0, index_1.observable)(1);
865
- level.subscribe((value) => {
866
- if (value > 10)
867
- level(10);
868
- });
869
- const definition = define((main) => {
870
- main.observables = [
871
- (0, index_1.defineObservable)({
872
- name: "level",
873
- description: "Level, at most 10.",
874
- schema: index_1.z.number().int(),
875
- writable: true,
876
- source: level,
877
- }),
878
- ];
879
- });
880
- const host = workerHost();
881
- await (0, index_1.startPackage)(definition, host.ctx);
882
- const write = host.setters.get(`${contractId}@1:level`);
883
- expect(write).toBeDefined();
884
- host.calls.length = 0;
885
- await expect(Promise.resolve(write?.(1.5))).rejects.toThrow(/integer/);
886
- expect(level()).toBe(1);
887
- expect(host.calls).toEqual([]);
888
- await expect(Promise.resolve(write?.(4))).resolves.toBe(4);
889
- expect(level()).toBe(4);
890
- expect(host.calls).toEqual([["publish", contractId, 1, "level", 4]]);
891
- // The source's own subscriber clamps the value; what's published and returned is the result.
892
- host.calls.length = 0;
893
- await expect(Promise.resolve(write?.(50))).resolves.toBe(10);
894
- expect(host.calls).toEqual([["publish", contractId, 1, "level", 10]]);
895
- });
896
- test("publications need no structuredClone and keep a __proto__ key as a field", async () => {
897
- const source = (0, index_1.observable)(JSON.parse('{"__proto__": 1}'));
898
- const definition = define((main) => {
899
- main.observables = [
900
- (0, index_1.defineObservable)({
901
- name: "counts",
902
- description: "Counts by name.",
903
- valueType: index_1.FieldType.object,
904
- writable: true,
905
- source,
906
- }),
907
- ];
908
- });
909
- const global = globalThis;
910
- const original = global.structuredClone;
911
- delete global.structuredClone;
912
- try {
913
- const host = workerHost();
914
- await (0, index_1.startPackage)(definition, host.ctx);
915
- const published = host.calls[0]?.[4];
916
- expect(Object.getPrototypeOf(published)).toBe(Object.prototype);
917
- expect(Object.getOwnPropertyDescriptor(published, "__proto__")?.value).toBe(1);
918
- const write = host.setters.get(`${contractId}@1:counts`);
919
- await expect(Promise.resolve(write?.({ total: 2 }))).resolves.toEqual({ total: 2 });
920
- }
921
- finally {
922
- global.structuredClone = original;
923
- }
924
- });
925
- test("an object write returns the published snapshot even if its source mutates meanwhile", async () => {
926
- const source = (0, index_1.observable)({ count: 0 });
927
- const definition = define((main) => {
928
- main.observables = [
929
- (0, index_1.defineObservable)({
930
- name: "snapshot",
931
- description: "A snapshot.",
932
- writable: true,
933
- schema: index_1.z.object({ count: index_1.z.number() }),
934
- source,
935
- }),
936
- ];
937
- });
938
- let release = () => { };
939
- const gate = new Promise((resolve) => {
940
- release = resolve;
941
- });
942
- const host = workerHost(async (value) => {
943
- if (value.count === 1)
944
- await gate;
945
- });
946
- await (0, index_1.startPackage)(definition, host.ctx);
947
- const write = host.setters.get(`${contractId}@1:snapshot`);
948
- const pending = Promise.resolve(write?.({ count: 1 }));
949
- try {
950
- await settle();
951
- source().count = 2;
952
- }
953
- finally {
954
- release();
955
- }
956
- await expect(pending).resolves.toEqual({ count: 1 });
957
- });
958
- test("an initial publish failure rejects startup; later background failures are logged", async () => {
959
- const status = (0, index_1.observable)("idle");
960
- const definition = define((main) => {
961
- main.observables = [
962
- (0, index_1.defineObservable)({
963
- name: "status",
964
- description: "Status.",
965
- schema: index_1.z.string(),
966
- source: status,
967
- }),
968
- ];
969
- });
970
- const errors = jest.spyOn(console, "error").mockImplementation(() => undefined);
971
- let fail = true;
972
- const host = workerHost(async () => {
973
- if (fail)
974
- throw new Error("host said no");
975
- return null;
976
- });
977
- try {
978
- await expect((0, index_1.startPackage)(definition, host.ctx)).rejects.toThrow("host said no");
979
- fail = false;
980
- await (0, index_1.startPackage)(definition, host.ctx);
981
- fail = true;
982
- status("broken");
983
- await settle();
984
- expect(errors).toHaveBeenCalledWith('Publishing observable "status" failed', expect.any(Error));
985
- fail = false;
986
- status("busy");
987
- await settle();
988
- expect(host.calls.at(-1)).toEqual(["publish", contractId, 1, "status", "busy"]);
989
- }
990
- finally {
991
- errors.mockRestore();
992
- }
993
- });
994
- test("starting again serves the new ctx only", async () => {
995
- const mood = (0, index_1.observable)("calm");
996
- const definition = define((main) => {
997
- main.observables = [
998
- (0, index_1.defineObservable)({ name: "mood", description: "Mood.", schema: index_1.z.string(), source: mood }),
999
- ];
1000
- });
1001
- const first = workerHost();
1002
- const second = workerHost();
1003
- await (0, index_1.startPackage)(definition, first.ctx);
1004
- await (0, index_1.startPackage)(definition, second.ctx);
1005
- mood("busy");
1006
- await settle();
1007
- expect(first.calls).toEqual([["publish", contractId, 1, "mood", "calm"]]);
1008
- expect(second.calls).toEqual([
1009
- ["publish", contractId, 1, "mood", "calm"],
1010
- ["publish", contractId, 1, "mood", "busy"],
1011
- ]);
1012
- });
1013
- });