@peers-app/peers-sdk 0.23.4 → 0.23.6

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 (75) hide show
  1. package/dist/context/data-context.d.ts +36 -5
  2. package/dist/context/data-context.js +44 -9
  3. package/dist/context/user-context.d.ts +44 -0
  4. package/dist/context/user-context.js +91 -12
  5. package/dist/contracts/__tests__/contract-provider-router.test.js +26 -0
  6. package/dist/contracts/__tests__/contract-resolvers.test.js +44 -0
  7. package/dist/contracts/__tests__/registry.test.js +104 -0
  8. package/dist/contracts/contract-provider-router.d.ts +21 -0
  9. package/dist/contracts/contract-provider-router.js +31 -5
  10. package/dist/contracts/contract-proxy.d.ts +3 -0
  11. package/dist/contracts/contract-proxy.js +3 -0
  12. package/dist/contracts/contract-resolvers.d.ts +21 -3
  13. package/dist/contracts/contract-resolvers.js +105 -32
  14. package/dist/contracts/index.d.ts +3 -3
  15. package/dist/contracts/registry.d.ts +59 -0
  16. package/dist/contracts/registry.js +155 -25
  17. package/dist/contracts/system-contracts.js +3 -1
  18. package/dist/data/index.d.ts +1 -0
  19. package/dist/data/index.js +1 -0
  20. package/dist/data/orm/types.d.ts +19 -0
  21. package/dist/data/orm/types.js +11 -0
  22. package/dist/data/package-table-identity.d.ts +46 -0
  23. package/dist/data/package-table-identity.js +71 -0
  24. package/dist/data/package-table-identity.test.d.ts +1 -0
  25. package/dist/data/package-table-identity.test.js +46 -0
  26. package/dist/data/package-version-resolver.d.ts +13 -0
  27. package/dist/data/package-version-resolver.js +142 -6
  28. package/dist/data/package-version-resolver.test.js +42 -0
  29. package/dist/data/persistent-var-identity.d.ts +37 -0
  30. package/dist/data/persistent-var-identity.js +69 -0
  31. package/dist/data/persistent-vars.d.ts +3 -5
  32. package/dist/data/persistent-vars.js +25 -13
  33. package/dist/data/persistent-vars.package-scope.test.d.ts +1 -0
  34. package/dist/data/persistent-vars.package-scope.test.js +27 -0
  35. package/dist/data/tools.d.ts +3 -3
  36. package/dist/package-installer/index.d.ts +1 -0
  37. package/dist/package-installer/index.js +1 -0
  38. package/dist/package-installer/package-cloner.d.ts +1 -1
  39. package/dist/package-installer/package-cloner.js +11 -16
  40. package/dist/package-installer/package-cloner.test.js +5 -3
  41. package/dist/package-installer/package-creator.d.ts +4 -3
  42. package/dist/package-installer/package-creator.js +13 -5
  43. package/dist/package-installer/package-creator.test.js +19 -0
  44. package/dist/package-installer/package-dev-tools.d.ts +30 -0
  45. package/dist/package-installer/package-dev-tools.js +63 -0
  46. package/dist/package-installer/package-dev-tools.test.d.ts +1 -0
  47. package/dist/package-installer/package-dev-tools.test.js +64 -0
  48. package/dist/package-loader/contract-package-loader.js +20 -8
  49. package/dist/package-loader/contract-package-loader.test.js +16 -0
  50. package/dist/package-loader/index.d.ts +4 -0
  51. package/dist/package-loader/index.js +4 -0
  52. package/dist/package-loader/isolated-artifact.d.ts +233 -0
  53. package/dist/package-loader/isolated-artifact.js +643 -0
  54. package/dist/package-loader/isolated-artifact.test.d.ts +1 -0
  55. package/dist/package-loader/isolated-artifact.test.js +270 -0
  56. package/dist/package-loader/isolated-persistent-var-gateway.d.ts +20 -0
  57. package/dist/package-loader/isolated-persistent-var-gateway.js +106 -0
  58. package/dist/package-loader/isolated-persistent-var-gateway.test.d.ts +1 -0
  59. package/dist/package-loader/isolated-persistent-var-gateway.test.js +56 -0
  60. package/dist/package-loader/isolated-runtime.d.ts +229 -0
  61. package/dist/package-loader/isolated-runtime.js +37 -0
  62. package/dist/package-loader/isolated-table-gateway.d.ts +16 -0
  63. package/dist/package-loader/isolated-table-gateway.js +375 -0
  64. package/dist/package-loader/isolated-table-gateway.test.d.ts +1 -0
  65. package/dist/package-loader/isolated-table-gateway.test.js +206 -0
  66. package/dist/package-loader/isolated-value.d.ts +14 -0
  67. package/dist/package-loader/isolated-value.js +63 -0
  68. package/dist/package-loader/package-loader.d.ts +123 -4
  69. package/dist/package-loader/package-loader.js +417 -46
  70. package/dist/package-loader/package-loader.test.js +478 -12
  71. package/dist/package-loader/resolve-consumed-contracts.d.ts +3 -3
  72. package/dist/package-loader/resolve-consumed-contracts.js +18 -10
  73. package/dist/utils.d.ts +21 -2
  74. package/dist/utils.js +46 -7
  75. package/package.json +1 -1
@@ -0,0 +1,270 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const access_level_1 = require("../data/access-level");
4
+ const field_type_1 = require("../types/field-type");
5
+ const utils_1 = require("../utils");
6
+ const isolated_artifact_1 = require("./isolated-artifact");
7
+ function makeArtifact(overrides = {}) {
8
+ const packageId = (0, utils_1.newid)();
9
+ const contractId = (0, utils_1.newid)();
10
+ return {
11
+ packageId,
12
+ contractId,
13
+ artifact: {
14
+ manifest: {
15
+ packageId,
16
+ version: "1.0.0",
17
+ provides: [
18
+ {
19
+ contractId,
20
+ version: 1,
21
+ name: "Greeter",
22
+ description: "Hello",
23
+ tools: [
24
+ {
25
+ name: "greet",
26
+ usageDescription: "Say hello",
27
+ inputFields: [{ name: "name", type: field_type_1.FieldType.string, optional: true }],
28
+ outputFields: [{ name: "greeting", type: field_type_1.FieldType.string }],
29
+ suggestedAccessLevel: access_level_1.AccessLevel.Writer,
30
+ },
31
+ ],
32
+ },
33
+ ],
34
+ consumes: [],
35
+ tables: [],
36
+ },
37
+ providerSource: "registerContractTool('x', 1, 'greet', function () {})",
38
+ ...overrides,
39
+ },
40
+ };
41
+ }
42
+ describe("isolated package artifact", () => {
43
+ test("serializes and parses a valid envelope", () => {
44
+ const { artifact, contractId } = makeArtifact();
45
+ const bundle = (0, isolated_artifact_1.serializeIsolatedPackageArtifact)(artifact);
46
+ expect(bundle.startsWith(`${isolated_artifact_1.ISOLATED_PACKAGE_MAGIC}\n`)).toBe(true);
47
+ expect((0, isolated_artifact_1.looksLikeIsolatedPackageArtifact)(bundle)).toBe(true);
48
+ expect((0, isolated_artifact_1.looksLikeIsolatedPackageArtifact)("module.exports = {}")).toBe(false);
49
+ const parsed = (0, isolated_artifact_1.parseIsolatedPackageArtifact)(bundle);
50
+ expect(parsed.manifest.packageId).toBe(artifact.manifest.packageId);
51
+ expect(parsed.manifest.provides[0].tools[0].inputFields).toEqual([
52
+ { name: "name", type: field_type_1.FieldType.string, optional: true },
53
+ ]);
54
+ const definition = (0, isolated_artifact_1.isolatedContractToDefinition)(parsed.manifest.provides[0], parsed.manifest.tables ?? []);
55
+ expect(definition.contractId).toBe(contractId);
56
+ expect(definition.tools[0].name).toBe("greet");
57
+ expect(definition.tables).toEqual([]);
58
+ });
59
+ test("defaults omitted tables to an empty array", () => {
60
+ const { artifact } = makeArtifact();
61
+ delete artifact.manifest.tables;
62
+ const manifest = (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact);
63
+ expect(manifest.tables).toEqual([]);
64
+ });
65
+ test("accepts a package-private table without adding it to the contract definition", () => {
66
+ const { artifact } = makeArtifact();
67
+ artifact.manifest.tables = [
68
+ {
69
+ name: "State",
70
+ description: "Owned state",
71
+ primaryKeyName: "stateId",
72
+ fields: [
73
+ { name: "stateId", type: field_type_1.FieldType.id },
74
+ { name: "count", type: field_type_1.FieldType.number },
75
+ ],
76
+ },
77
+ ];
78
+ const manifest = (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact);
79
+ expect(manifest.tables?.[0].versionNumber).toBe(1);
80
+ const definition = (0, isolated_artifact_1.isolatedContractToDefinition)(manifest.provides[0], manifest.tables ?? []);
81
+ expect(definition.tables).toEqual([]);
82
+ });
83
+ test("maps table references and pvar-backed observables to public contract shapes", () => {
84
+ const { artifact } = makeArtifact();
85
+ artifact.manifest.tables = [
86
+ {
87
+ name: "SharedState",
88
+ description: "Consumer-facing state",
89
+ primaryKeyName: "stateId",
90
+ fields: [
91
+ { name: "stateId", type: field_type_1.FieldType.id },
92
+ { name: "count", type: field_type_1.FieldType.number },
93
+ ],
94
+ },
95
+ ];
96
+ artifact.manifest.provides[0].tables = [{ name: "SharedState" }];
97
+ artifact.manifest.provides[0].observables = [
98
+ {
99
+ name: "sharedCount",
100
+ description: "Consumer-facing count",
101
+ valueType: field_type_1.FieldType.number,
102
+ writable: true,
103
+ persistentVar: {
104
+ scope: "device",
105
+ name: "sharedCount",
106
+ defaultValue: 0,
107
+ },
108
+ },
109
+ ];
110
+ const manifest = (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact);
111
+ const definition = (0, isolated_artifact_1.isolatedContractToDefinition)(manifest.provides[0], manifest.tables ?? []);
112
+ expect(definition.tables).toEqual([
113
+ {
114
+ name: "SharedState",
115
+ description: "Consumer-facing state",
116
+ primaryKeyName: "stateId",
117
+ fields: [
118
+ { name: "stateId", type: field_type_1.FieldType.id },
119
+ { name: "count", type: field_type_1.FieldType.number },
120
+ ],
121
+ },
122
+ ]);
123
+ expect(definition.observables).toEqual([
124
+ {
125
+ name: "sharedCount",
126
+ description: "Consumer-facing count",
127
+ valueType: field_type_1.FieldType.number,
128
+ writable: true,
129
+ },
130
+ ]);
131
+ });
132
+ test("rejects malformed isolated tables", () => {
133
+ const { artifact } = makeArtifact();
134
+ artifact.manifest.tables = [
135
+ {
136
+ name: "State",
137
+ primaryKeyName: "stateId",
138
+ fields: [{ name: "stateId", type: field_type_1.FieldType.id }],
139
+ tableId: (0, utils_1.newid)(),
140
+ },
141
+ ];
142
+ expect(() => (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact)).toThrow(/must not declare tableId/);
143
+ artifact.manifest.tables = [
144
+ {
145
+ name: "State",
146
+ primaryKeyName: "stateId",
147
+ fields: [{ name: "stateId", type: field_type_1.FieldType.id }],
148
+ physicalName: "SystemTable",
149
+ },
150
+ ];
151
+ expect(() => (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact)).toThrow(/physicalName/);
152
+ artifact.manifest.tables = [
153
+ {
154
+ name: "State",
155
+ primaryKeyName: "missing",
156
+ fields: [{ name: "stateId", type: field_type_1.FieldType.id }],
157
+ },
158
+ ];
159
+ expect(() => (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact)).toThrow(/primaryKeyName/);
160
+ artifact.manifest.tables = [
161
+ {
162
+ name: "State",
163
+ primaryKeyName: "stateId",
164
+ fields: [{ name: "stateId", type: field_type_1.FieldType.string }],
165
+ },
166
+ ];
167
+ expect(() => (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact)).toThrow(/type id/);
168
+ artifact.manifest.tables = [
169
+ {
170
+ name: "State",
171
+ primaryKeyName: "stateId",
172
+ fields: [{ name: "stateId", type: field_type_1.FieldType.id }],
173
+ },
174
+ ];
175
+ artifact.manifest.provides[0].tables = [
176
+ {
177
+ name: "State",
178
+ primaryKeyName: "stateId",
179
+ fields: [{ name: "stateId", type: field_type_1.FieldType.id }],
180
+ },
181
+ ];
182
+ expect(() => (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact)).toThrow(/only a package table name/);
183
+ });
184
+ test("requires table names to be unique inside the package", () => {
185
+ const { artifact } = makeArtifact();
186
+ artifact.manifest.tables = [
187
+ {
188
+ name: "State",
189
+ primaryKeyName: "stateId",
190
+ fields: [{ name: "stateId", type: field_type_1.FieldType.id }],
191
+ },
192
+ {
193
+ name: "State",
194
+ primaryKeyName: "otherStateId",
195
+ fields: [{ name: "otherStateId", type: field_type_1.FieldType.id }],
196
+ },
197
+ ];
198
+ expect(() => (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact)).toThrow(/duplicate table "State"/);
199
+ });
200
+ test("rejects a recognized but malformed envelope", () => {
201
+ expect(() => (0, isolated_artifact_1.parseIsolatedPackageArtifact)(`${isolated_artifact_1.ISOLATED_PACKAGE_MAGIC}\n{not-json`)).toThrow(/JSON is invalid/);
202
+ expect(() => (0, isolated_artifact_1.validateIsolatedPackageArtifact)({
203
+ manifest: {
204
+ packageId: "not-an-id",
205
+ version: "1.0.0",
206
+ provides: [],
207
+ consumes: [],
208
+ },
209
+ providerSource: "x",
210
+ })).toThrow(/peer id|provide at least one/i);
211
+ });
212
+ test("defaults missing tool field arrays", () => {
213
+ const { artifact } = makeArtifact();
214
+ artifact.manifest.provides[0].tools[0] = {
215
+ name: "greet",
216
+ usageDescription: "Say hello",
217
+ };
218
+ const manifest = (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact);
219
+ expect(manifest.provides[0].tools[0].inputFields).toEqual([]);
220
+ expect(manifest.provides[0].tools[0].outputFields).toEqual([]);
221
+ expect(manifest.provides[0].tables).toEqual([]);
222
+ expect(manifest.provides[0].observables).toEqual([]);
223
+ expect(manifest.provides[0].alsoImplements).toEqual([]);
224
+ });
225
+ test("validates and preserves alsoImplements declarations", () => {
226
+ const { artifact } = makeArtifact();
227
+ const targetContractId = (0, utils_1.newid)();
228
+ artifact.manifest.provides[0].alsoImplements = [
229
+ { contractId: targetContractId, version: 1 },
230
+ { contractId: targetContractId, version: { from: 2, to: 4 } },
231
+ ];
232
+ const manifest = (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact);
233
+ expect(manifest.provides[0].alsoImplements).toEqual([
234
+ { contractId: targetContractId, version: 1 },
235
+ { contractId: targetContractId, version: { from: 2, to: 4 } },
236
+ ]);
237
+ });
238
+ test("rejects unsupported contract features instead of dropping them", () => {
239
+ const { artifact } = makeArtifact();
240
+ artifact.manifest.provides[0].events = [
241
+ { name: "changed", payloadFields: [] },
242
+ ];
243
+ expect(() => (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact)).toThrow(/events is not supported/);
244
+ });
245
+ test("rejects missing public table references and malformed observable bindings", () => {
246
+ const { artifact } = makeArtifact();
247
+ artifact.manifest.provides[0].tables = [{ name: "Missing" }];
248
+ expect(() => (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact)).toThrow(/undeclared package table/);
249
+ artifact.manifest.provides[0].tables = [];
250
+ artifact.manifest.provides[0].observables = [
251
+ {
252
+ name: "sharedCount",
253
+ description: "Shared count",
254
+ valueType: field_type_1.FieldType.number,
255
+ writable: true,
256
+ persistentVar: {
257
+ scope: "device",
258
+ name: "sharedCount",
259
+ defaultValue: "zero",
260
+ },
261
+ },
262
+ ];
263
+ expect(() => (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact)).toThrow(/must match.*number/);
264
+ artifact.manifest.provides[0].observables[0].valueType = field_type_1.FieldType.object;
265
+ artifact.manifest.provides[0].observables[0].persistentVar.defaultValue = {
266
+ callback: () => undefined,
267
+ };
268
+ expect(() => (0, isolated_artifact_1.validateIsolatedPackageArtifact)(artifact)).toThrow(/JSON-compatible/);
269
+ });
270
+ });
@@ -0,0 +1,20 @@
1
+ import type { DataContext } from "../context/data-context";
2
+ import type { IIsolatedPackagePersistentVarGateway } from "./isolated-runtime";
3
+ /**
4
+ * In-memory package-prefixed pvar gateway for isolated runtime tests.
5
+ *
6
+ * Values survive repeated access through the same gateway and are copied on
7
+ * both read and write.
8
+ */
9
+ export declare function createMemoryIsolatedPackagePersistentVarGateway(): IIsolatedPackagePersistentVarGateway;
10
+ /**
11
+ * Create a host-owned pvar gateway for one trusted data-context route.
12
+ *
13
+ * This is a thin async adapter over the canonical Peers pvar constructors.
14
+ * Every logical name is prefixed with trusted package identity before the
15
+ * canonical constructor derives its deterministic storage identity.
16
+ *
17
+ * @param dataContext - Authorized personal or group context for this runtime.
18
+ * @returns A package-scoped gateway whose reads and writes await pvar persistence.
19
+ */
20
+ export declare function createIsolatedPackagePersistentVarGateway(dataContext: DataContext): IIsolatedPackagePersistentVarGateway;
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMemoryIsolatedPackagePersistentVarGateway = createMemoryIsolatedPackagePersistentVarGateway;
4
+ exports.createIsolatedPackagePersistentVarGateway = createIsolatedPackagePersistentVarGateway;
5
+ const persistent_var_identity_1 = require("../data/persistent-var-identity");
6
+ const isolated_value_1 = require("./isolated-value");
7
+ function memoryKey(ref) {
8
+ return `${ref.packageId}:${ref.scope}:${ref.name}`;
9
+ }
10
+ async function resolvePersistentVar(dataContext, ref) {
11
+ const { deviceVar, groupDeviceVar, groupUserVar, groupVar, userVar } = await Promise.resolve().then(() => require("../data/persistent-vars"));
12
+ const name = (0, persistent_var_identity_1.packageScopedPersistentVarName)(ref.packageId, ref.name);
13
+ const userContext = dataContext.userContext;
14
+ switch (ref.scope) {
15
+ case "device":
16
+ return deviceVar(name, { userContext });
17
+ case "user":
18
+ return userVar(name, { userContext });
19
+ case "group":
20
+ return groupVar(name, { dataContext, userContext });
21
+ case "groupDevice":
22
+ return groupDeviceVar(name, { dataContext, userContext });
23
+ case "groupUser":
24
+ return groupUserVar(name, { dataContext, userContext });
25
+ }
26
+ }
27
+ /**
28
+ * In-memory package-prefixed pvar gateway for isolated runtime tests.
29
+ *
30
+ * Values survive repeated access through the same gateway and are copied on
31
+ * both read and write.
32
+ */
33
+ function createMemoryIsolatedPackagePersistentVarGateway() {
34
+ const values = new Map();
35
+ const subscribers = new Map();
36
+ return {
37
+ async get(ref) {
38
+ const value = values.get(memoryKey(ref));
39
+ return value === undefined ? undefined : (0, isolated_value_1.copyIsolatedValue)(value);
40
+ },
41
+ async set(ref, value) {
42
+ if (value === undefined) {
43
+ throw new Error("Persistent variable value must be copyable");
44
+ }
45
+ const copy = (0, isolated_value_1.copyIsolatedValue)(value);
46
+ const key = memoryKey(ref);
47
+ values.set(key, copy);
48
+ for (const handler of subscribers.get(key) ?? []) {
49
+ handler((0, isolated_value_1.copyIsolatedValue)(copy));
50
+ }
51
+ return (0, isolated_value_1.copyIsolatedValue)(copy);
52
+ },
53
+ async subscribe(ref, handler) {
54
+ const key = memoryKey(ref);
55
+ const handlers = subscribers.get(key) ?? new Set();
56
+ handlers.add(handler);
57
+ subscribers.set(key, handlers);
58
+ return () => {
59
+ handlers.delete(handler);
60
+ if (handlers.size === 0) {
61
+ subscribers.delete(key);
62
+ }
63
+ };
64
+ },
65
+ };
66
+ }
67
+ /**
68
+ * Create a host-owned pvar gateway for one trusted data-context route.
69
+ *
70
+ * This is a thin async adapter over the canonical Peers pvar constructors.
71
+ * Every logical name is prefixed with trusted package identity before the
72
+ * canonical constructor derives its deterministic storage identity.
73
+ *
74
+ * @param dataContext - Authorized personal or group context for this runtime.
75
+ * @returns A package-scoped gateway whose reads and writes await pvar persistence.
76
+ */
77
+ function createIsolatedPackagePersistentVarGateway(dataContext) {
78
+ return {
79
+ async get(ref) {
80
+ const persistentVar = await resolvePersistentVar(dataContext, ref);
81
+ await persistentVar.loadingPromise;
82
+ const value = persistentVar();
83
+ return value === undefined ? undefined : (0, isolated_value_1.copyIsolatedValue)(value);
84
+ },
85
+ async set(ref, value) {
86
+ if (value === undefined) {
87
+ throw new Error("Persistent variable value must be copyable");
88
+ }
89
+ const persistentVar = await resolvePersistentVar(dataContext, ref);
90
+ const copy = (0, isolated_value_1.copyIsolatedValue)(value);
91
+ await persistentVar.loadingPromise;
92
+ persistentVar(copy);
93
+ await persistentVar.loadingPromise;
94
+ const saved = persistentVar();
95
+ return saved === undefined ? undefined : (0, isolated_value_1.copyIsolatedValue)(saved);
96
+ },
97
+ async subscribe(ref, handler) {
98
+ const persistentVar = await resolvePersistentVar(dataContext, ref);
99
+ await persistentVar.loadingPromise;
100
+ const subscription = persistentVar.subscribe((value) => {
101
+ handler((0, isolated_value_1.copyIsolatedValue)(value));
102
+ });
103
+ return () => subscription.dispose();
104
+ },
105
+ };
106
+ }
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("../utils");
4
+ const isolated_persistent_var_gateway_1 = require("./isolated-persistent-var-gateway");
5
+ describe("isolated package persistent variable gateway", () => {
6
+ test("separates the same logical name by trusted package and scope", async () => {
7
+ const gateway = (0, isolated_persistent_var_gateway_1.createMemoryIsolatedPackagePersistentVarGateway)();
8
+ const packageA = { packageId: (0, utils_1.newid)(), scope: "device", name: "settings" };
9
+ const packageB = { packageId: (0, utils_1.newid)(), scope: "device", name: "settings" };
10
+ const packageAUser = { ...packageA, scope: "user" };
11
+ await gateway.set(packageA, { count: 1 });
12
+ await gateway.set(packageB, { count: 2 });
13
+ await gateway.set(packageAUser, { count: 3 });
14
+ await expect(gateway.get(packageA)).resolves.toEqual({ count: 1 });
15
+ await expect(gateway.get(packageB)).resolves.toEqual({ count: 2 });
16
+ await expect(gateway.get(packageAUser)).resolves.toEqual({ count: 3 });
17
+ });
18
+ test("copies supported values and rejects unsafe writes", async () => {
19
+ const gateway = (0, isolated_persistent_var_gateway_1.createMemoryIsolatedPackagePersistentVarGateway)();
20
+ const ref = { packageId: (0, utils_1.newid)(), scope: "group", name: "state" };
21
+ const input = {
22
+ nested: { count: 1 },
23
+ savedAt: new Date("2026-08-24T12:00:00.000Z"),
24
+ bytes: new Uint8Array([1, 2, 3]),
25
+ };
26
+ await gateway.set(ref, input);
27
+ input.nested.count = 9;
28
+ const stored = (await gateway.get(ref));
29
+ expect(stored).toEqual({
30
+ nested: { count: 1 },
31
+ savedAt: new Date("2026-08-24T12:00:00.000Z"),
32
+ bytes: new Uint8Array([1, 2, 3]),
33
+ });
34
+ stored.nested.count = 7;
35
+ await expect(gateway.get(ref)).resolves.toEqual({
36
+ nested: { count: 1 },
37
+ savedAt: new Date("2026-08-24T12:00:00.000Z"),
38
+ bytes: new Uint8Array([1, 2, 3]),
39
+ });
40
+ await expect(gateway.set(ref, undefined)).rejects.toThrow(/copyable/);
41
+ await expect(gateway.set(ref, { callback: () => undefined })).rejects.toThrow(/unsupported/);
42
+ await expect(gateway.set(ref, Number.POSITIVE_INFINITY)).rejects.toThrow(/finite/);
43
+ });
44
+ test("streams detached values until the host subscription is removed", async () => {
45
+ const gateway = (0, isolated_persistent_var_gateway_1.createMemoryIsolatedPackagePersistentVarGateway)();
46
+ const ref = { packageId: (0, utils_1.newid)(), scope: "group", name: "shared" };
47
+ const received = [];
48
+ const unsubscribe = await gateway.subscribe(ref, (value) => received.push(value));
49
+ const value = { count: 1 };
50
+ await gateway.set(ref, value);
51
+ value.count = 2;
52
+ unsubscribe();
53
+ await gateway.set(ref, { count: 3 });
54
+ expect(received).toEqual([{ count: 1 }]);
55
+ });
56
+ });