@peers-app/peers-sdk 0.26.6 → 0.27.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 (182) hide show
  1. package/README.md +3 -1
  2. package/bin/peers-package.js +13 -0
  3. package/define/package.json +4 -0
  4. package/dist/contracts/__tests__/action-dispatch.test.js +56 -76
  5. package/dist/contracts/__tests__/also-implements.test.d.ts +1 -0
  6. package/dist/contracts/__tests__/also-implements.test.js +110 -0
  7. package/dist/contracts/__tests__/builder.test.js +45 -0
  8. package/dist/contracts/__tests__/contract-resolvers.test.js +134 -4
  9. package/dist/contracts/__tests__/frozen-contracts.test.d.ts +1 -0
  10. package/dist/contracts/__tests__/frozen-contracts.test.js +191 -0
  11. package/dist/contracts/__tests__/generic-tool-dispatch.test.js +13 -56
  12. package/dist/contracts/__tests__/host-contract-client.test.d.ts +1 -0
  13. package/dist/contracts/__tests__/host-contract-client.test.js +793 -0
  14. package/dist/contracts/__tests__/host-contract-transport.test.js +15 -0
  15. package/dist/contracts/__tests__/registry.test.js +68 -0
  16. package/dist/contracts/__tests__/target-dispatch.d.ts +11 -0
  17. package/dist/contracts/__tests__/target-dispatch.js +24 -0
  18. package/dist/contracts/__tests__/tool-catalog-service.test.js +37 -1
  19. package/dist/contracts/__tests__/tool-catalog.test.js +12 -3
  20. package/dist/contracts/__tests__/validate.test.js +177 -7
  21. package/dist/contracts/__tests__/workflow-context.test.js +0 -11
  22. package/dist/contracts/builder.d.ts +16 -2
  23. package/dist/contracts/builder.js +44 -3
  24. package/dist/contracts/contract-definition-schema.d.ts +251 -0
  25. package/dist/contracts/contract-definition-schema.js +48 -0
  26. package/dist/contracts/contract-provider-router.d.ts +2 -0
  27. package/dist/contracts/contract-provider-router.js +5 -2
  28. package/dist/contracts/contract-proxy.d.ts +52 -18
  29. package/dist/contracts/contract-proxy.js +144 -203
  30. package/dist/contracts/contract-resolvers.d.ts +6 -4
  31. package/dist/contracts/contract-resolvers.js +32 -9
  32. package/dist/contracts/frozen-contracts.d.ts +59 -0
  33. package/dist/contracts/frozen-contracts.js +86 -0
  34. package/dist/contracts/host-contract-client.d.ts +134 -0
  35. package/dist/contracts/host-contract-client.js +396 -0
  36. package/dist/contracts/host-contract-transport.d.ts +2 -0
  37. package/dist/contracts/host-contract-transport.js +8 -0
  38. package/dist/contracts/index.d.ts +6 -4
  39. package/dist/contracts/index.js +11 -5
  40. package/dist/contracts/registry.d.ts +31 -1
  41. package/dist/contracts/registry.js +100 -6
  42. package/dist/contracts/system/contract-catalog-contract.d.ts +61 -6
  43. package/dist/contracts/system/contract-catalog-contract.js +17 -5
  44. package/dist/contracts/tool-catalog-service.js +26 -6
  45. package/dist/contracts/tool-catalog.d.ts +1 -1
  46. package/dist/contracts/tool-catalog.js +4 -1
  47. package/dist/contracts/types.d.ts +32 -2
  48. package/dist/contracts/validate.d.ts +16 -1
  49. package/dist/contracts/validate.js +139 -25
  50. package/dist/data/change-tracking.d.ts +17 -5
  51. package/dist/data/change-tracking.js +22 -6
  52. package/dist/data/change-tracking.test.d.ts +1 -0
  53. package/dist/data/change-tracking.test.js +62 -0
  54. package/dist/data/orm/sql.data-source.d.ts +9 -0
  55. package/dist/data/orm/sql.data-source.js +47 -2
  56. package/dist/data/orm/sql.data-source.test.js +142 -0
  57. package/dist/data/orm/table-container.js +9 -9
  58. package/dist/data/orm/table-container.test.js +44 -0
  59. package/dist/data/orm/types.d.ts +41 -1
  60. package/dist/data/orm/types.js +159 -28
  61. package/dist/data/orm/types.test.js +129 -0
  62. package/dist/data/peer-types.d.ts +8 -62
  63. package/dist/data/persistent-vars.d.ts +4 -1
  64. package/dist/data/persistent-vars.js +7 -2
  65. package/dist/data/tools.d.ts +29 -221
  66. package/dist/define/also-implements.test.d.ts +1 -0
  67. package/dist/define/also-implements.test.js +123 -0
  68. package/dist/define/context.d.ts +77 -0
  69. package/dist/define/context.js +2 -0
  70. package/dist/define/contract-ref.d.ts +177 -0
  71. package/dist/define/contract-ref.js +67 -0
  72. package/dist/define/declare-contract.d.ts +167 -0
  73. package/dist/define/declare-contract.js +220 -0
  74. package/dist/define/declare-contract.test.d.ts +1 -0
  75. package/dist/define/declare-contract.test.js +573 -0
  76. package/dist/define/define-package.d.ts +203 -0
  77. package/dist/define/define-package.js +289 -0
  78. package/dist/define/define.test.d.ts +1 -0
  79. package/dist/define/define.test.js +1010 -0
  80. package/dist/define/empty-fields.d.ts +15 -0
  81. package/dist/define/empty-fields.js +134 -0
  82. package/dist/define/empty-fields.test.d.ts +1 -0
  83. package/dist/define/empty-fields.test.js +55 -0
  84. package/dist/define/events-observables.test.d.ts +1 -0
  85. package/dist/define/events-observables.test.js +1013 -0
  86. package/dist/define/events.d.ts +81 -0
  87. package/dist/define/events.js +81 -0
  88. package/dist/define/generated-field.d.ts +20 -0
  89. package/dist/define/generated-field.js +25 -0
  90. package/dist/define/index.d.ts +28 -0
  91. package/dist/define/index.js +62 -0
  92. package/dist/define/lazy-members.d.ts +8 -0
  93. package/dist/define/lazy-members.js +38 -0
  94. package/dist/define/manifest.d.ts +63 -0
  95. package/dist/define/manifest.js +653 -0
  96. package/dist/define/observables.d.ts +146 -0
  97. package/dist/define/observables.js +187 -0
  98. package/dist/define/serve-members.d.ts +21 -0
  99. package/dist/define/serve-members.js +152 -0
  100. package/dist/define/serve-members.test.d.ts +1 -0
  101. package/dist/define/serve-members.test.js +630 -0
  102. package/dist/define/serve.d.ts +22 -0
  103. package/dist/define/serve.js +58 -0
  104. package/dist/define/system-contracts.d.ts +41 -0
  105. package/dist/define/system-contracts.js +21 -0
  106. package/dist/define/table-handle.d.ts +13 -0
  107. package/dist/define/table-handle.js +36 -0
  108. package/dist/define/table.d.ts +128 -0
  109. package/dist/define/table.js +122 -0
  110. package/dist/define/tool.d.ts +129 -0
  111. package/dist/define/tool.js +91 -0
  112. package/dist/define/vars.d.ts +82 -0
  113. package/dist/define/vars.js +256 -0
  114. package/dist/define/vars.test.d.ts +1 -0
  115. package/dist/define/vars.test.js +39 -0
  116. package/dist/define/wire-value.d.ts +12 -0
  117. package/dist/define/wire-value.js +76 -0
  118. package/dist/define/worker-state.d.ts +15 -0
  119. package/dist/define/worker-state.js +45 -0
  120. package/dist/device/tx-encoding.test.js +10 -0
  121. package/dist/index.d.ts +3 -0
  122. package/dist/index.js +5 -1
  123. package/dist/observable.d.ts +42 -2
  124. package/dist/observable.js +25 -7
  125. package/dist/package-build/build-package.d.ts +60 -0
  126. package/dist/package-build/build-package.js +312 -0
  127. package/dist/package-build/build-package.test.d.ts +1 -0
  128. package/dist/package-build/build-package.test.js +204 -0
  129. package/dist/package-build/cli.d.ts +7 -0
  130. package/dist/package-build/cli.js +218 -0
  131. package/dist/package-build/contract-add.d.ts +35 -0
  132. package/dist/package-build/contract-add.js +97 -0
  133. package/dist/package-build/contract-add.test.d.ts +1 -0
  134. package/dist/package-build/contract-add.test.js +467 -0
  135. package/dist/package-build/contract-source.d.ts +59 -0
  136. package/dist/package-build/contract-source.js +471 -0
  137. package/dist/package-build/freeze-contracts.d.ts +56 -0
  138. package/dist/package-build/freeze-contracts.js +198 -0
  139. package/dist/package-build/freeze-contracts.test.d.ts +1 -0
  140. package/dist/package-build/freeze-contracts.test.js +355 -0
  141. package/dist/package-installer/package-creator.d.ts +4 -0
  142. package/dist/package-installer/package-creator.js +57 -16
  143. package/dist/package-installer/package-creator.test.js +86 -0
  144. package/dist/package-loader/contract-members.d.ts +53 -0
  145. package/dist/package-loader/contract-members.js +31 -0
  146. package/dist/package-loader/contract-package-loader.js +5 -1
  147. package/dist/package-loader/contract-package-loader.test.js +11 -0
  148. package/dist/package-loader/host-authority-loading.test.js +9 -5
  149. package/dist/package-loader/index.d.ts +1 -0
  150. package/dist/package-loader/index.js +1 -0
  151. package/dist/package-loader/isolated-artifact.d.ts +80 -17
  152. package/dist/package-loader/isolated-artifact.js +257 -51
  153. package/dist/package-loader/isolated-artifact.test.js +294 -21
  154. package/dist/package-loader/isolated-runtime.d.ts +22 -7
  155. package/dist/package-loader/isolated-table-gateway.js +30 -11
  156. package/dist/package-loader/isolated-table-gateway.test.js +243 -0
  157. package/dist/package-loader/package-loader.d.ts +8 -1
  158. package/dist/package-loader/package-loader.js +39 -20
  159. package/dist/package-loader/package-loader.test.js +47 -0
  160. package/dist/peer-id.d.ts +25 -0
  161. package/dist/peer-id.js +99 -0
  162. package/dist/serial-json.compat.test.d.ts +1 -0
  163. package/dist/serial-json.compat.test.js +369 -0
  164. package/dist/serial-json.d.ts +41 -5
  165. package/dist/serial-json.js +216 -157
  166. package/dist/serial-json.oracle.d.ts +21 -0
  167. package/dist/serial-json.oracle.js +222 -0
  168. package/dist/serial-json.test.js +315 -0
  169. package/dist/testing/index.d.ts +4 -0
  170. package/dist/testing/index.js +8 -0
  171. package/dist/testing/mock-host-contract.d.ts +123 -0
  172. package/dist/testing/mock-host-contract.js +434 -0
  173. package/dist/testing/mock-host-contract.test.d.ts +1 -0
  174. package/dist/testing/mock-host-contract.test.js +782 -0
  175. package/dist/types/field-type.d.ts +35 -18
  176. package/dist/types/field-type.js +4 -1
  177. package/dist/types/zod-types.d.ts +9 -0
  178. package/dist/types/zod-types.js +8 -2
  179. package/dist/utils.d.ts +1 -25
  180. package/dist/utils.js +10 -90
  181. package/package.json +9 -2
  182. package/testing/package.json +4 -0
@@ -0,0 +1,82 @@
1
+ import type { PersistentVarScope } from "../data/persistent-var-identity";
2
+ import { type Observable } from "../observable";
3
+ import type { IPackageContext } from "./context";
4
+ /**
5
+ * A package's own persistent variable: an observable kept in step with the stored value. Read it
6
+ * with `v()`, write it with `v(value)`, and `subscribe` to changes, including changes from other
7
+ * devices. Use it at module scope; it needs no `ctx`.
8
+ */
9
+ export type PersistentVar<T> = Observable<T> & {
10
+ /**
11
+ * Resolves once the stored value is loaded, and again once the writes made so far are saved.
12
+ * In a package worker, variables declared at module scope are loaded before the first call.
13
+ */
14
+ loadingPromise: Promise<PersistentVar<T>>;
15
+ /** Clear the stored value and go back to the default. */
16
+ delete: () => Promise<void>;
17
+ };
18
+ /** Options for a package variable with a default. */
19
+ export interface IPackageVarOptions<T> {
20
+ /** Read while the variable is unset (or `null`). It isn't stored by itself. */
21
+ defaultValue: T;
22
+ }
23
+ /** Declares a package variable in one scope. */
24
+ export interface IPackageVarFactory {
25
+ /** A variable that reads `defaultValue` while unset. */
26
+ <T = string>(name: string, opts: IPackageVarOptions<T>): PersistentVar<T>;
27
+ /** A variable that reads `undefined` while unset. */
28
+ <T = string>(name: string, opts?: {
29
+ defaultValue?: undefined;
30
+ }): PersistentVar<T | undefined>;
31
+ }
32
+ /** Where a package variable is stored, for an observable it backs. */
33
+ export interface IPackageVarBinding {
34
+ scope: PersistentVarScope;
35
+ name: string;
36
+ /** Undefined when the variable was declared without one. */
37
+ defaultValue: unknown;
38
+ }
39
+ /**
40
+ * Read a package variable, refreshing its stored value and propagating
41
+ * storage failures to the caller. Returns this read's value even if a newer refresh is still
42
+ * pending. Background variable operations still log their failures.
43
+ *
44
+ * @param variable - A package variable; also used by contract observable handlers.
45
+ */
46
+ export declare function readPackageVar<T>(variable: PersistentVar<T>): Promise<T>;
47
+ /**
48
+ * Write a package variable and wait for persistence. Unlike callable background
49
+ * writes, a failed save rejects, including when the value was unchanged.
50
+ * Replies with this write's value, independent of later concurrent writes.
51
+ *
52
+ * @param variable - A package variable; also used by contract observable handlers.
53
+ * @param value - The value to store; contract handlers parse it with the observable's schema first.
54
+ */
55
+ export declare function writePackageVar<T>(variable: PersistentVar<T>, value: T): Promise<T>;
56
+ /**
57
+ * Load every package variable and keep it current through `ctx`. `startPackage` runs this when
58
+ * the worker starts, before any call. Variables declared later bind as they're declared.
59
+ *
60
+ * @param ctx - The worker's context, bound to no call.
61
+ */
62
+ export declare function bindPackageVars(ctx: IPackageContext): Promise<void>;
63
+ /**
64
+ * Where `value` is stored, if it's a variable from {@link deviceVar} or another scope.
65
+ *
66
+ * @param value - Anything; usually an observable's source.
67
+ */
68
+ export declare function packageVarBinding(value: unknown): IPackageVarBinding | undefined;
69
+ /**
70
+ * A variable stored only on this device.
71
+ *
72
+ * `const refreshSchedule = deviceVar("refreshSchedule", { defaultValue: "" });`
73
+ */
74
+ export declare const deviceVar: IPackageVarFactory;
75
+ /** A variable stored for the user and synced across all of their devices. */
76
+ export declare const userVar: IPackageVarFactory;
77
+ /** A variable shared by everyone in the group, synced across all of its users and devices. */
78
+ export declare const groupVar: IPackageVarFactory;
79
+ /** A variable for this device in the group: a different value per group, not synced. */
80
+ export declare const groupDeviceVar: IPackageVarFactory;
81
+ /** A variable for the user in the group, synced across the user's devices. */
82
+ export declare const groupUserVar: IPackageVarFactory;
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.groupUserVar = exports.groupDeviceVar = exports.groupVar = exports.userVar = exports.deviceVar = void 0;
4
+ exports.readPackageVar = readPackageVar;
5
+ exports.writePackageVar = writePackageVar;
6
+ exports.bindPackageVars = bindPackageVars;
7
+ exports.packageVarBinding = packageVarBinding;
8
+ const observable_1 = require("../observable");
9
+ const wire_value_1 = require("./wire-value");
10
+ const worker_state_1 = require("./worker-state");
11
+ const registered = new Map();
12
+ const bindings = new WeakMap();
13
+ const contractOperations = new WeakMap();
14
+ /**
15
+ * Read a package variable, refreshing its stored value and propagating
16
+ * storage failures to the caller. Returns this read's value even if a newer refresh is still
17
+ * pending. Background variable operations still log their failures.
18
+ *
19
+ * @param variable - A package variable; also used by contract observable handlers.
20
+ */
21
+ async function readPackageVar(variable) {
22
+ const operations = contractOperations.get(variable);
23
+ if (!operations)
24
+ throw new Error("readPackageVar takes a package variable");
25
+ return (await operations.read());
26
+ }
27
+ /**
28
+ * Write a package variable and wait for persistence. Unlike callable background
29
+ * writes, a failed save rejects, including when the value was unchanged.
30
+ * Replies with this write's value, independent of later concurrent writes.
31
+ *
32
+ * @param variable - A package variable; also used by contract observable handlers.
33
+ * @param value - The value to store; contract handlers parse it with the observable's schema first.
34
+ */
35
+ async function writePackageVar(variable, value) {
36
+ const operations = contractOperations.get(variable);
37
+ if (!operations)
38
+ throw new Error("writePackageVar takes a package variable");
39
+ return (await operations.write(value));
40
+ }
41
+ /** Equal JSON-like values (what the host stores), so an unchanged value notifies no one. */
42
+ function sameValue(left, right) {
43
+ if (left === right) {
44
+ return true;
45
+ }
46
+ if (left instanceof Date || right instanceof Date) {
47
+ return left instanceof Date && right instanceof Date && left.getTime() === right.getTime();
48
+ }
49
+ if (!left || !right || typeof left !== "object" || typeof right !== "object") {
50
+ return false;
51
+ }
52
+ if (Array.isArray(left) !== Array.isArray(right)) {
53
+ return false;
54
+ }
55
+ const leftRecord = left;
56
+ const rightRecord = right;
57
+ const keys = Object.keys(leftRecord);
58
+ return (keys.length === Object.keys(rightRecord).length &&
59
+ keys.every((key) => Object.hasOwn(rightRecord, key) && sameValue(leftRecord[key], rightRecord[key])));
60
+ }
61
+ function packageVar(scope, name, defaultValue) {
62
+ if (typeof name !== "string" || name.trim() === "") {
63
+ throw new Error("A package variable needs a name");
64
+ }
65
+ const key = JSON.stringify([scope, name]);
66
+ const existing = registered.get(key);
67
+ if (existing) {
68
+ return existing.variable;
69
+ }
70
+ const variable = (0, observable_1.observable)(defaultValue);
71
+ // Outside a worker (tests, the manifest build) the variable is in memory and already loaded.
72
+ variable.loadingPromise = Promise.resolve(variable);
73
+ let ctx;
74
+ /** True while a stored value is written into the observable, so it isn't saved back. */
75
+ let applying = false;
76
+ /** Writes not yet saved. A stored value read meanwhile is stale. */
77
+ let pending = 0;
78
+ /** Reads issued so far; only the latest one applies. */
79
+ let reads = 0;
80
+ /** Capture the first save issued by a contract write, before subscribers can write again. */
81
+ let contractWrite;
82
+ const apply = (stored) => {
83
+ const next = (stored === null || stored === undefined ? defaultValue : stored);
84
+ if (sameValue(next, variable())) {
85
+ return;
86
+ }
87
+ applying = true;
88
+ try {
89
+ variable(next);
90
+ }
91
+ finally {
92
+ applying = false;
93
+ }
94
+ };
95
+ // Notifications carry no value and can arrive out of order, so read the value again.
96
+ const refresh = async () => {
97
+ const bound = ctx;
98
+ if (!bound) {
99
+ return { failed: false, value: variable() };
100
+ }
101
+ const read = ++reads;
102
+ try {
103
+ const stored = await bound.getOwnedPersistentVar(scope, name);
104
+ if (read === reads && pending === 0) {
105
+ apply(stored);
106
+ }
107
+ return { failed: false, value: stored ?? defaultValue };
108
+ }
109
+ catch (error) {
110
+ console.error(`Reading package variable "${name}" failed`, error);
111
+ return { failed: true, error };
112
+ }
113
+ };
114
+ const queueSave = (stored) => {
115
+ const bound = ctx;
116
+ if (!bound) {
117
+ return;
118
+ }
119
+ // Capture before waiting for earlier saves: the package may mutate its source again.
120
+ // Report an unsupported value through the usual save result, including for background writes.
121
+ let captured;
122
+ try {
123
+ captured = { value: (0, wire_value_1.snapshotPackageValue)(stored) };
124
+ }
125
+ catch (error) {
126
+ captured = { error };
127
+ }
128
+ pending += 1;
129
+ const saved = variable.loadingPromise.then(async () => {
130
+ let result = { failed: false };
131
+ try {
132
+ if ("error" in captured)
133
+ throw captured.error;
134
+ await bound.setOwnedPersistentVar(scope, name, captured.value);
135
+ }
136
+ catch (error) {
137
+ console.error(`Saving package variable "${name}" failed`, error);
138
+ result = { failed: true, error };
139
+ }
140
+ finally {
141
+ pending -= 1;
142
+ }
143
+ // The outcome is the save's. A failed refresh after it only leaves the mirror stale,
144
+ // and refresh logs that itself.
145
+ if (pending === 0) {
146
+ await refresh();
147
+ }
148
+ return result;
149
+ });
150
+ if (contractWrite && !contractWrite.saved)
151
+ contractWrite.saved = saved;
152
+ variable.loadingPromise = saved.then(() => variable);
153
+ };
154
+ // Registered first, so it sees the applied value before any package subscriber. Only that
155
+ // value is skipped; a package subscriber's write in response is saved like any other.
156
+ variable.subscribe((value) => {
157
+ if (applying) {
158
+ applying = false;
159
+ return;
160
+ }
161
+ queueSave(value ?? null);
162
+ });
163
+ variable.delete = async () => {
164
+ await variable.loadingPromise;
165
+ queueSave(null);
166
+ apply(null);
167
+ await variable.loadingPromise;
168
+ };
169
+ const bind = (bound) => {
170
+ ctx = bound;
171
+ const load = (async () => {
172
+ try {
173
+ // Watch first, so a change between the read and the watch isn't missed.
174
+ await bound.watchOwnedPersistentVar(scope, name, refresh);
175
+ }
176
+ catch (error) {
177
+ console.error(`Watching package variable "${name}" failed`, error);
178
+ }
179
+ return refresh();
180
+ })();
181
+ variable.loadingPromise = load.then(() => variable);
182
+ return load.then(() => undefined);
183
+ };
184
+ contractOperations.set(variable, {
185
+ async read() {
186
+ await variable.loadingPromise;
187
+ const result = await refresh();
188
+ if (result.failed)
189
+ throw result.error;
190
+ return result.value;
191
+ },
192
+ async write(value) {
193
+ const written = (0, wire_value_1.snapshotPackageValue)(value);
194
+ const previous = contractWrite;
195
+ const operation = {};
196
+ contractWrite = operation;
197
+ try {
198
+ variable(value);
199
+ // Observables need not notify for an unchanged value. An explicit contract write
200
+ // still persists it, and can recover from an earlier failed save.
201
+ if (!operation.saved)
202
+ queueSave(value ?? null);
203
+ }
204
+ finally {
205
+ contractWrite = previous;
206
+ }
207
+ // A subscriber may have queued more saves synchronously. Only this write's save
208
+ // determines its result; later package writes have their own outcomes.
209
+ const result = await operation.saved;
210
+ if (result?.failed)
211
+ throw result.error;
212
+ return written;
213
+ },
214
+ });
215
+ registered.set(key, { variable: variable, bind });
216
+ bindings.set(variable, Object.freeze({ scope, name, defaultValue }));
217
+ const started = (0, worker_state_1.packageContext)();
218
+ if (started) {
219
+ void bind(started);
220
+ }
221
+ return variable;
222
+ }
223
+ function scopedVar(scope) {
224
+ return ((name, opts) => packageVar(scope, name, opts?.defaultValue));
225
+ }
226
+ /**
227
+ * Load every package variable and keep it current through `ctx`. `startPackage` runs this when
228
+ * the worker starts, before any call. Variables declared later bind as they're declared.
229
+ *
230
+ * @param ctx - The worker's context, bound to no call.
231
+ */
232
+ async function bindPackageVars(ctx) {
233
+ await Promise.all([...registered.values()].map((entry) => entry.bind(ctx)));
234
+ }
235
+ /**
236
+ * Where `value` is stored, if it's a variable from {@link deviceVar} or another scope.
237
+ *
238
+ * @param value - Anything; usually an observable's source.
239
+ */
240
+ function packageVarBinding(value) {
241
+ return typeof value === "function" ? bindings.get(value) : undefined;
242
+ }
243
+ /**
244
+ * A variable stored only on this device.
245
+ *
246
+ * `const refreshSchedule = deviceVar("refreshSchedule", { defaultValue: "" });`
247
+ */
248
+ exports.deviceVar = scopedVar("device");
249
+ /** A variable stored for the user and synced across all of their devices. */
250
+ exports.userVar = scopedVar("user");
251
+ /** A variable shared by everyone in the group, synced across all of its users and devices. */
252
+ exports.groupVar = scopedVar("group");
253
+ /** A variable for this device in the group: a different value per group, not synced. */
254
+ exports.groupDeviceVar = scopedVar("groupDevice");
255
+ /** A variable for the user in the group, synced across the user's devices. */
256
+ exports.groupUserVar = scopedVar("groupUser");
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const index_1 = require("./index");
4
+ const vars_1 = require("./vars");
5
+ test("the public variable helpers retain value types and reject persistence failures", async () => {
6
+ const variable = (0, index_1.deviceVar)("awaited", { defaultValue: 0 });
7
+ let stored = 0;
8
+ let failRead = false;
9
+ let failWrite = false;
10
+ const ctx = {
11
+ watchOwnedPersistentVar: async () => () => undefined,
12
+ getOwnedPersistentVar: async () => {
13
+ if (failRead)
14
+ throw new Error("durable read failed");
15
+ return stored;
16
+ },
17
+ setOwnedPersistentVar: async (_scope, _name, value) => {
18
+ if (failWrite)
19
+ throw new Error("durable save failed");
20
+ stored = value;
21
+ },
22
+ };
23
+ await (0, vars_1.bindPackageVars)(ctx);
24
+ const saved = await (0, index_1.writePackageVar)(variable, 7);
25
+ const read = await (0, index_1.readPackageVar)(variable);
26
+ expect([saved, read]).toEqual([7, 7]);
27
+ const errors = jest.spyOn(console, "error").mockImplementation(() => undefined);
28
+ try {
29
+ failWrite = true;
30
+ await expect((0, index_1.writePackageVar)(variable, 8)).rejects.toThrow("durable save failed");
31
+ failWrite = false;
32
+ await expect((0, index_1.writePackageVar)(variable, 9)).resolves.toBe(9);
33
+ failRead = true;
34
+ await expect((0, index_1.readPackageVar)(variable)).rejects.toThrow("durable read failed");
35
+ }
36
+ finally {
37
+ errors.mockRestore();
38
+ }
39
+ });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Snapshot a package value for the worker bridge, as the bridge carries
3
+ * it. Missing object fields are omitted, and missing array entries (or a missing root value)
4
+ * become null, as in JSON. Dates and Uint8Arrays are copied. A value the bridge can't carry (a
5
+ * function, symbol, bigint, non-finite number, cycle, or an object other than a plain object,
6
+ * array, Date, or Uint8Array) throws here, so it fails the same way in a worker and in tests.
7
+ * The source value is never changed. This doesn't use `structuredClone`, which some test
8
+ * environments (jsdom) lack.
9
+ *
10
+ * @param value - The value to send: arguments, a result, a row, an event, or an observable value.
11
+ */
12
+ export declare function snapshotPackageValue(value: unknown): unknown;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.snapshotPackageValue = snapshotPackageValue;
4
+ /**
5
+ * Snapshot a package value for the worker bridge, as the bridge carries
6
+ * it. Missing object fields are omitted, and missing array entries (or a missing root value)
7
+ * become null, as in JSON. Dates and Uint8Arrays are copied. A value the bridge can't carry (a
8
+ * function, symbol, bigint, non-finite number, cycle, or an object other than a plain object,
9
+ * array, Date, or Uint8Array) throws here, so it fails the same way in a worker and in tests.
10
+ * The source value is never changed. This doesn't use `structuredClone`, which some test
11
+ * environments (jsdom) lack.
12
+ *
13
+ * @param value - The value to send: arguments, a result, a row, an event, or an observable value.
14
+ */
15
+ function snapshotPackageValue(value) {
16
+ const ancestors = new Set();
17
+ const copy = (current, path) => {
18
+ if (current === null || typeof current === "string" || typeof current === "boolean") {
19
+ return current;
20
+ }
21
+ if (typeof current === "number") {
22
+ if (!Number.isFinite(current)) {
23
+ throw new TypeError(`${path} is ${current}; only finite numbers can be sent`);
24
+ }
25
+ return current;
26
+ }
27
+ if (typeof current !== "object") {
28
+ throw new TypeError(`${path} is a ${typeof current}, which can't be sent`);
29
+ }
30
+ const tag = Object.prototype.toString.call(current);
31
+ if (tag === "[object Date]") {
32
+ const time = Date.prototype.getTime.call(current);
33
+ if (!Number.isFinite(time)) {
34
+ throw new TypeError(`${path} is an invalid Date, which can't be sent`);
35
+ }
36
+ return new Date(time);
37
+ }
38
+ if (tag === "[object Uint8Array]") {
39
+ return new Uint8Array(current);
40
+ }
41
+ if (ancestors.has(current)) {
42
+ throw new TypeError(`${path} contains itself; cycles can't be sent`);
43
+ }
44
+ ancestors.add(current);
45
+ try {
46
+ if (Array.isArray(current)) {
47
+ const items = [];
48
+ for (let index = 0; index < current.length; index += 1) {
49
+ const item = current[index];
50
+ items.push(item === undefined ? null : copy(item, `${path}[${index}]`));
51
+ }
52
+ return items;
53
+ }
54
+ if (tag !== "[object Object]") {
55
+ throw new TypeError(`${path} is a ${tag.slice(8, -1)}, which can't be sent`);
56
+ }
57
+ const record = {};
58
+ for (const [key, field] of Object.entries(current)) {
59
+ if (field !== undefined) {
60
+ // Define rather than assign, so a "__proto__" key stays a field.
61
+ Object.defineProperty(record, key, {
62
+ value: copy(field, `${path}.${key}`),
63
+ enumerable: true,
64
+ writable: true,
65
+ configurable: true,
66
+ });
67
+ }
68
+ }
69
+ return record;
70
+ }
71
+ finally {
72
+ ancestors.delete(current);
73
+ }
74
+ };
75
+ return value === undefined ? null : copy(value, "value");
76
+ }
@@ -0,0 +1,15 @@
1
+ import type { IPackageContext } from "./context";
2
+ /**
3
+ * Record the context the worker started with, for the package's variables, events, and
4
+ * observables. Called by `startPackage`.
5
+ *
6
+ * @param ctx - The worker's context, bound to no call.
7
+ */
8
+ export declare function setPackageContext(ctx: IPackageContext): void;
9
+ /** The started worker's context, or undefined before the start and outside a worker. */
10
+ export declare function packageContext(): IPackageContext | undefined;
11
+ /**
12
+ * Resolves with the worker's context once it starts. Undefined outside a worker, where there's
13
+ * nothing to wait for.
14
+ */
15
+ export declare function whenPackageStarts(): Promise<IPackageContext> | undefined;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setPackageContext = setPackageContext;
4
+ exports.packageContext = packageContext;
5
+ exports.whenPackageStarts = whenPackageStarts;
6
+ /** The context the package worker started with; undefined until then, and outside a worker. */
7
+ let context;
8
+ let started;
9
+ let resolveStarted;
10
+ /** True in a package worker: the Runtime defines `onWorkerStart` before the package's code runs. */
11
+ function inPackageWorker() {
12
+ return typeof globalThis.onWorkerStart === "function";
13
+ }
14
+ /**
15
+ * Record the context the worker started with, for the package's variables, events, and
16
+ * observables. Called by `startPackage`.
17
+ *
18
+ * @param ctx - The worker's context, bound to no call.
19
+ */
20
+ function setPackageContext(ctx) {
21
+ context = ctx;
22
+ resolveStarted?.(ctx);
23
+ resolveStarted = undefined;
24
+ started = Promise.resolve(ctx);
25
+ }
26
+ /** The started worker's context, or undefined before the start and outside a worker. */
27
+ function packageContext() {
28
+ return context;
29
+ }
30
+ /**
31
+ * Resolves with the worker's context once it starts. Undefined outside a worker, where there's
32
+ * nothing to wait for.
33
+ */
34
+ function whenPackageStarts() {
35
+ if (context) {
36
+ return Promise.resolve(context);
37
+ }
38
+ if (!inPackageWorker()) {
39
+ return undefined;
40
+ }
41
+ started ??= new Promise((resolve) => {
42
+ resolveStarted = resolve;
43
+ });
44
+ return started;
45
+ }
@@ -149,6 +149,16 @@ describe("tx-encode-decode", () => {
149
149
  expect(() => (0, tx_encoding_1.txDecode)(invalidPayload)).toThrow("Unknown tx flag: 255");
150
150
  });
151
151
  });
152
+ describe("untrusted input", () => {
153
+ afterEach(() => {
154
+ delete Object.prototype.polluted;
155
+ });
156
+ it("does not let a decoded message write onto Object.prototype", () => {
157
+ const decoded = (0, tx_encoding_1.txDecode)((0, tx_encoding_1.txEncode)({ __this_ref: "__proto__", polluted: "yes" }));
158
+ expect({}.polluted).toBeUndefined();
159
+ expect(decoded).toEqual({ polluted: "yes" });
160
+ });
161
+ });
152
162
  describe("round-trip consistency", () => {
153
163
  it("should handle empty objects", () => {
154
164
  const emptyObj = {};
package/dist/index.d.ts CHANGED
@@ -5,6 +5,9 @@ export * as data from "./data";
5
5
  export * from "./data/orm";
6
6
  export * as orm from "./data/orm";
7
7
  export { getLogger, type IWorkflowLog, WorkflowLogs, workflowLogSchema, } from "./data/workflow-logs";
8
+ export { type AnyContractTools, type ContractClientTool, type ContractClientTools, type ContractEventPayloadsOf, type ContractIdOf, type ContractObservableValuesOf, type ContractObservableWritabilityOf, type ContractTableInputsOf, type ContractTableRowsOf, type ContractToolsOf, type ContractVersionOf, contractRef, type IContractClient, type IContractRef, } from "./define/contract-ref";
9
+ export type { IPackageContract, IPackageDefinition, } from "./define/define-package";
10
+ export { buildPackageManifest } from "./define/manifest";
8
11
  export * from "./device/binary-peer-connection-v2";
9
12
  export * from "./device/connection";
10
13
  export * from "./device/device";
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.workflowLogSchema = exports.WorkflowLogs = exports.getLogger = exports.orm = exports.data = void 0;
17
+ exports.buildPackageManifest = exports.contractRef = exports.workflowLogSchema = exports.WorkflowLogs = exports.getLogger = exports.orm = exports.data = void 0;
18
18
  __exportStar(require("./context"), exports);
19
19
  __exportStar(require("./contracts"), exports);
20
20
  __exportStar(require("./data"), exports);
@@ -26,6 +26,10 @@ var workflow_logs_1 = require("./data/workflow-logs");
26
26
  Object.defineProperty(exports, "getLogger", { enumerable: true, get: function () { return workflow_logs_1.getLogger; } });
27
27
  Object.defineProperty(exports, "WorkflowLogs", { enumerable: true, get: function () { return workflow_logs_1.WorkflowLogs; } });
28
28
  Object.defineProperty(exports, "workflowLogSchema", { enumerable: true, get: function () { return workflow_logs_1.workflowLogSchema; } });
29
+ var contract_ref_1 = require("./define/contract-ref");
30
+ Object.defineProperty(exports, "contractRef", { enumerable: true, get: function () { return contract_ref_1.contractRef; } });
31
+ var manifest_1 = require("./define/manifest");
32
+ Object.defineProperty(exports, "buildPackageManifest", { enumerable: true, get: function () { return manifest_1.buildPackageManifest; } });
29
33
  __exportStar(require("./device/binary-peer-connection-v2"), exports);
30
34
  __exportStar(require("./device/connection"), exports);
31
35
  __exportStar(require("./device/device"), exports);
@@ -2,6 +2,7 @@ type ObservableFn<T> = {
2
2
  (): T;
3
3
  (newValue: T): T;
4
4
  };
5
+ /** Handle returned by `subscribe`; `dispose` stops the notifications. */
5
6
  export type Subscription = {
6
7
  dispose: () => void;
7
8
  };
@@ -10,14 +11,53 @@ type ObservableProps<T> = {
10
11
  notifySubscribers: () => void;
11
12
  subscriberCount: () => number;
12
13
  };
13
- export type Observable<T> = ObservableFn<T> & ObservableProps<T>;
14
+ declare const writableObservable: unique symbol;
15
+ declare const observableValue: unique symbol;
16
+ /** A subscribable value that consumers can read with `obs()`. */
17
+ export type ReadonlyObservable<T> = (() => T) & ObservableProps<T> & {
18
+ readonly [writableObservable]?: boolean;
19
+ };
20
+ /** A readable, writable, subscribable value: `obs()` reads it and `obs(value)` writes it. */
21
+ export type Observable<T> = ObservableFn<T> & ReadonlyObservable<T> & {
22
+ readonly [writableObservable]?: true;
23
+ readonly [observableValue]?: {
24
+ accept(value: T): T;
25
+ };
26
+ };
27
+ /**
28
+ * A writable observable that must accept exactly `T`, including in its subscribers. Use for
29
+ * a declared writable source; unlike ordinary observable assignments, this checks writes
30
+ * strictly and rejects a source whose value type is narrower than the declaration.
31
+ */
32
+ export type InvariantObservable<T> = Observable<T> & {
33
+ readonly [observableValue]?: {
34
+ accept: (value: T) => T;
35
+ };
36
+ };
37
+ /**
38
+ * Create an observable. Writing a value that isn't `===` the current one notifies subscribers.
39
+ *
40
+ * @param initialValue - The starting value.
41
+ */
14
42
  export declare function observable<T>(initialValue: T): Observable<T>;
43
+ /** Create an observable that starts `undefined`. */
15
44
  export declare function observable<T = undefined>(): Observable<T | undefined>;
45
+ /**
46
+ * An observable derived from other observables: `read` runs again when any observable it read
47
+ * changes. Pass `{ read, write }` to make it writable.
48
+ *
49
+ * @param fn - The read function, or read and write functions.
50
+ */
16
51
  export declare function computed<T>(fn: {
17
52
  read: () => T;
18
53
  write?: (val: T) => void;
19
54
  } | (() => T)): Observable<T>;
55
+ /** Test an observable while preserving a known read-only view. */
56
+ export declare function isSubscribable<T>(obj: ReadonlyObservable<T>): obj is ReadonlyObservable<T>;
57
+ /** True when an untyped value is an observable. */
20
58
  export declare function isSubscribable(obj: any): obj is Observable<any>;
21
- export declare function unwrapObservable<T>(obj: T | Observable<T>): T;
59
+ /** The observable's current value, or `obj` itself when it isn't an observable. */
60
+ export declare function unwrapObservable<T>(obj: T | ReadonlyObservable<T>): T;
61
+ /** Keep two observables equal: a change to either is written to the other. */
22
62
  export declare function linkObservables<T>(obs1: Observable<T>, obs2: Observable<T>): Subscription;
23
63
  export {};