@ng-org/alien-deepsignals 0.1.2-alpha.11 → 0.1.2-alpha.12
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.
- package/dist/core.d.ts +5 -1
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +11 -11
- package/dist/deepSignal.d.ts +1 -1
- package/dist/deepSignal.d.ts.map +1 -1
- package/dist/deepSignal.js +16 -27
- package/dist/hooks/react/index.js +2 -8
- package/dist/hooks/react/useDeepSignal.d.ts +2 -2
- package/dist/hooks/react/useDeepSignal.d.ts.map +1 -1
- package/dist/hooks/react/useDeepSignal.js +11 -13
- package/dist/hooks/svelte/index.js +2 -8
- package/dist/hooks/svelte/useDeepSignal.svelte.d.ts +1 -1
- package/dist/hooks/svelte/useDeepSignal.svelte.d.ts.map +1 -1
- package/dist/hooks/svelte/useDeepSignal.svelte.js +6 -9
- package/dist/hooks/svelte4/index.js +2 -8
- package/dist/hooks/svelte4/useDeepSignal.svelte.d.ts +1 -1
- package/dist/hooks/svelte4/useDeepSignal.svelte.d.ts.map +1 -1
- package/dist/hooks/svelte4/useDeepSignal.svelte.js +14 -17
- package/dist/hooks/vue/index.d.ts +1 -1
- package/dist/hooks/vue/index.d.ts.map +1 -1
- package/dist/hooks/vue/index.js +2 -8
- package/dist/hooks/vue/useDeepSignal.d.ts +1 -1
- package/dist/hooks/vue/useDeepSignal.d.ts.map +1 -1
- package/dist/hooks/vue/useDeepSignal.js +6 -9
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -27
- package/dist/iteratorHelpers.js +2 -6
- package/dist/test/frontend/astro-app/src/components/ReactPanel.d.ts.map +1 -1
- package/dist/test/frontend/astro-app/src/components/ReactPanel.jsx +13 -51
- package/dist/test/frontend/playwright/crossFrameworkHooks.spec.js +24 -26
- package/dist/test/frontend/utils/mockData.js +16 -21
- package/dist/test/frontend/utils/paths.js +2 -6
- package/dist/test/frontend/utils/renderMetrics.js +5 -12
- package/dist/test/frontend/utils/state.d.ts +1 -1
- package/dist/test/frontend/utils/state.js +13 -17
- package/dist/test/lib/deepSignalOptions.test.js +66 -68
- package/dist/test/lib/index.test.js +373 -375
- package/dist/test/lib/misc.test.js +98 -100
- package/dist/test/lib/watch.test.js +355 -357
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -2
- package/dist/watch.d.ts +1 -1
- package/dist/watch.d.ts.map +1 -1
- package/dist/watch.js +6 -9
- package/package.json +2 -1
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// Copyright (c) 2025 Laurin Weger, Par le Peuple, NextGraph.org developers
|
|
3
2
|
// All rights reserved.
|
|
4
3
|
// Licensed under the Apache License, Version 2.0
|
|
@@ -8,69 +7,68 @@
|
|
|
8
7
|
// notice may not be copied, modified, or distributed except
|
|
9
8
|
// according to those terms.
|
|
10
9
|
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
(0, vitest_1.it)("uses custom ID generator for objects without @id", async () => {
|
|
10
|
+
import { describe, it, expect } from "vitest";
|
|
11
|
+
import { deepSignal } from "../../deepSignal.js";
|
|
12
|
+
import { watch } from "../../watch.js";
|
|
13
|
+
describe("deepSignal options", () => {
|
|
14
|
+
describe("custom ID generator", () => {
|
|
15
|
+
it("uses custom ID generator for objects without @id", async () => {
|
|
18
16
|
let counter = 1000;
|
|
19
17
|
const options = {
|
|
20
18
|
propGenerator: () => ({ syntheticId: `custom-${counter++}` }),
|
|
21
19
|
syntheticIdPropertyName: "@id",
|
|
22
20
|
};
|
|
23
|
-
const state =
|
|
21
|
+
const state = deepSignal({ data: {} }, options);
|
|
24
22
|
const patches = [];
|
|
25
|
-
const { stopListening: stop } =
|
|
23
|
+
const { stopListening: stop } = watch(state, ({ patches: batch }) => patches.push(batch));
|
|
26
24
|
state.data.user = { name: "Alice" };
|
|
27
25
|
await Promise.resolve();
|
|
28
26
|
// Check that @id was assigned
|
|
29
|
-
|
|
27
|
+
expect(state.data.user["@id"]).toBe("custom-1000");
|
|
30
28
|
// Check that patch was emitted for @id
|
|
31
29
|
const flat = patches.flat().map((p) => p.path.join("."));
|
|
32
|
-
|
|
30
|
+
expect(flat).toContain("data.user.@id");
|
|
33
31
|
stop();
|
|
34
32
|
});
|
|
35
|
-
|
|
33
|
+
it("respects existing @id on objects", async () => {
|
|
36
34
|
const options = {
|
|
37
35
|
propGenerator: () => ({ syntheticId: "should-not-be-used" }),
|
|
38
36
|
syntheticIdPropertyName: "@id",
|
|
39
37
|
};
|
|
40
|
-
const state =
|
|
38
|
+
const state = deepSignal({ items: [] }, options);
|
|
41
39
|
state.items.push({ "@id": "existing-123", value: 42 });
|
|
42
40
|
// Should use the existing @id
|
|
43
|
-
|
|
41
|
+
expect(state.items[0]["@id"]).toBe("existing-123");
|
|
44
42
|
});
|
|
45
|
-
|
|
43
|
+
it("uses @id property from objects added to Sets", async () => {
|
|
46
44
|
const options = {
|
|
47
45
|
propGenerator: ({ object }) => ({
|
|
48
46
|
syntheticId: object["@id"] || "fallback-id",
|
|
49
47
|
}),
|
|
50
48
|
syntheticIdPropertyName: "@id",
|
|
51
49
|
};
|
|
52
|
-
const state =
|
|
50
|
+
const state = deepSignal({ s: new Set() }, options);
|
|
53
51
|
const patches = [];
|
|
54
|
-
const { stopListening: stop } =
|
|
52
|
+
const { stopListening: stop } = watch(state, ({ patches: batch }) => patches.push(batch));
|
|
55
53
|
const obj = { "@id": "set-entry-1", data: "test" };
|
|
56
54
|
state.s.add(obj);
|
|
57
55
|
await Promise.resolve();
|
|
58
56
|
const flat = patches.flat().map((p) => p.path.join("."));
|
|
59
57
|
// Path should use the @id as synthetic key
|
|
60
|
-
|
|
58
|
+
expect(flat.some((p) => p.startsWith("s.set-entry-1"))).toBe(true);
|
|
61
59
|
stop();
|
|
62
60
|
});
|
|
63
61
|
});
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
describe("syntheticIdPropertyName option", () => {
|
|
63
|
+
it("adds @id to all nested objects when enabled", async () => {
|
|
66
64
|
let counter = 100;
|
|
67
65
|
const options = {
|
|
68
66
|
propGenerator: () => ({ syntheticId: `auto-${counter++}` }),
|
|
69
67
|
syntheticIdPropertyName: "@id",
|
|
70
68
|
};
|
|
71
|
-
const state =
|
|
69
|
+
const state = deepSignal({ root: {} }, options);
|
|
72
70
|
const patches = [];
|
|
73
|
-
const { stopListening: stop } =
|
|
71
|
+
const { stopListening: stop } = watch(state, ({ patches: batch }) => patches.push(batch));
|
|
74
72
|
state.root.level1 = {
|
|
75
73
|
level2: {
|
|
76
74
|
level3: { value: "deep" },
|
|
@@ -78,52 +76,52 @@ const watch_1 = require("../../watch");
|
|
|
78
76
|
};
|
|
79
77
|
await Promise.resolve();
|
|
80
78
|
// Check all levels have @id
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
expect(state.root.level1["@id"]).toBeDefined();
|
|
80
|
+
expect(state.root.level1.level2["@id"]).toBeDefined();
|
|
81
|
+
expect(state.root.level1.level2.level3["@id"]).toBeDefined();
|
|
84
82
|
// Check patches were emitted for all @id fields
|
|
85
83
|
const flat = patches.flat().map((p) => p.path.join("."));
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
expect(flat).toContain("root.level1.@id");
|
|
85
|
+
expect(flat).toContain("root.level1.level2.@id");
|
|
86
|
+
expect(flat).toContain("root.level1.level2.level3.@id");
|
|
89
87
|
stop();
|
|
90
88
|
});
|
|
91
|
-
|
|
92
|
-
const state =
|
|
89
|
+
it("does not add @id when option is false", () => {
|
|
90
|
+
const state = deepSignal({ data: { nested: {} } });
|
|
93
91
|
// Should not have @id
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
expect("@id" in state.data).toBe(false);
|
|
93
|
+
expect("@id" in state.data.nested).toBe(false);
|
|
96
94
|
});
|
|
97
|
-
|
|
95
|
+
it("adds @id to objects in arrays", async () => {
|
|
98
96
|
let counter = 200;
|
|
99
97
|
const options = {
|
|
100
98
|
propGenerator: () => ({ syntheticId: `arr-${counter++}` }),
|
|
101
99
|
syntheticIdPropertyName: "@id",
|
|
102
100
|
};
|
|
103
|
-
const state =
|
|
101
|
+
const state = deepSignal({ items: [] }, options);
|
|
104
102
|
const patches = [];
|
|
105
|
-
const { stopListening: stop } =
|
|
103
|
+
const { stopListening: stop } = watch(state, ({ patches: batch }) => patches.push(batch));
|
|
106
104
|
state.items.push({ name: "Item 1" }, { name: "Item 2" });
|
|
107
105
|
await Promise.resolve();
|
|
108
106
|
// Both items should have @id
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
expect(state.items[0]["@id"]).toBeDefined();
|
|
108
|
+
expect(state.items[1]["@id"]).toBeDefined();
|
|
111
109
|
// Check patches
|
|
112
110
|
const flat = patches.flat().map((p) => p.path.join("."));
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
expect(flat).toContain("items.0.@id");
|
|
112
|
+
expect(flat).toContain("items.1.@id");
|
|
115
113
|
stop();
|
|
116
114
|
});
|
|
117
|
-
|
|
115
|
+
it("adds @id to objects in Sets", async () => {
|
|
118
116
|
const options = {
|
|
119
117
|
propGenerator: () => ({
|
|
120
118
|
syntheticId: `gen-${Math.random().toString(36).substr(2, 9)}`,
|
|
121
119
|
}),
|
|
122
120
|
syntheticIdPropertyName: "@id",
|
|
123
121
|
};
|
|
124
|
-
const state =
|
|
122
|
+
const state = deepSignal({ s: new Set() }, options);
|
|
125
123
|
const patches = [];
|
|
126
|
-
const { stopListening: stop } =
|
|
124
|
+
const { stopListening: stop } = watch(state, ({ patches: batch }) => patches.push(batch));
|
|
127
125
|
const obj1 = { value: 1 };
|
|
128
126
|
const obj2 = { value: 2 };
|
|
129
127
|
state.s.add(obj1);
|
|
@@ -131,66 +129,66 @@ const watch_1 = require("../../watch");
|
|
|
131
129
|
await Promise.resolve();
|
|
132
130
|
// Get proxied objects from Set
|
|
133
131
|
const proxiedObjs = Array.from(state.s);
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
expect(proxiedObjs[0]["@id"]).toBeDefined();
|
|
133
|
+
expect(proxiedObjs[1]["@id"]).toBeDefined();
|
|
136
134
|
// @id should be used as synthetic key in paths
|
|
137
135
|
const flat = patches.flat().map((p) => p.path.join("."));
|
|
138
136
|
const obj1Id = proxiedObjs[0]["@id"];
|
|
139
137
|
const obj2Id = proxiedObjs[1]["@id"];
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
expect(flat.some((p) => p.startsWith(`s.${obj1Id}`))).toBe(true);
|
|
139
|
+
expect(flat.some((p) => p.startsWith(`s.${obj2Id}`))).toBe(true);
|
|
142
140
|
stop();
|
|
143
141
|
});
|
|
144
142
|
});
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
describe("@id property behavior", () => {
|
|
144
|
+
it("makes @id readonly", () => {
|
|
147
145
|
const options = {
|
|
148
146
|
syntheticIdPropertyName: "@id",
|
|
149
147
|
readOnlyProps: ["@id"],
|
|
150
148
|
};
|
|
151
|
-
const state =
|
|
149
|
+
const state = deepSignal({ obj: {} }, options);
|
|
152
150
|
state.obj.data = { value: 1 };
|
|
153
151
|
// Attempting to modify @id should throw
|
|
154
|
-
|
|
152
|
+
expect(() => {
|
|
155
153
|
state.obj.data["@id"] = "new-id";
|
|
156
154
|
}).toThrow("Cannot modify readonly property '@id'");
|
|
157
155
|
});
|
|
158
|
-
|
|
156
|
+
it("makes @id enumerable", () => {
|
|
159
157
|
let counter = 300;
|
|
160
158
|
const options = {
|
|
161
159
|
propGenerator: () => ({ syntheticId: `enum-${counter++}` }),
|
|
162
160
|
syntheticIdPropertyName: "@id",
|
|
163
161
|
};
|
|
164
|
-
const state =
|
|
162
|
+
const state = deepSignal({ obj: {} }, options);
|
|
165
163
|
state.obj.data = { value: 1 };
|
|
166
164
|
// @id should show up in Object.keys()
|
|
167
165
|
const keys = Object.keys(state.obj.data);
|
|
168
|
-
|
|
166
|
+
expect(keys).toContain("@id");
|
|
169
167
|
});
|
|
170
|
-
|
|
168
|
+
it("emits patches for @id even on objects with existing @id", async () => {
|
|
171
169
|
const options = {
|
|
172
170
|
syntheticIdPropertyName: "@id",
|
|
173
171
|
};
|
|
174
|
-
const state =
|
|
172
|
+
const state = deepSignal({ container: {} }, options);
|
|
175
173
|
const patches = [];
|
|
176
|
-
const { stopListening: stop } =
|
|
174
|
+
const { stopListening: stop } = watch(state, ({ patches: batch }) => patches.push(batch));
|
|
177
175
|
// Object already has @id before being added
|
|
178
176
|
const objWithId = { "@id": "pre-existing", data: "test" };
|
|
179
177
|
state.container.item = objWithId;
|
|
180
178
|
await Promise.resolve();
|
|
181
179
|
const flat = patches.flat().map((p) => p.path.join("."));
|
|
182
180
|
// Patch should still be emitted for @id
|
|
183
|
-
|
|
181
|
+
expect(flat).toContain("container.item.@id");
|
|
184
182
|
// Verify the value in the patch
|
|
185
183
|
const idPatch = patches
|
|
186
184
|
.flat()
|
|
187
185
|
.find((p) => p.path.join(".") === "container.item.@id");
|
|
188
|
-
|
|
186
|
+
expect(idPatch.value).toBe("pre-existing");
|
|
189
187
|
stop();
|
|
190
188
|
});
|
|
191
189
|
});
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
describe("options inheritance", () => {
|
|
191
|
+
it("child objects inherit options from root", async () => {
|
|
194
192
|
let idCounter = 5000;
|
|
195
193
|
const options = {
|
|
196
194
|
propGenerator: () => ({
|
|
@@ -198,7 +196,7 @@ const watch_1 = require("../../watch");
|
|
|
198
196
|
}),
|
|
199
197
|
syntheticIdPropertyName: "@id",
|
|
200
198
|
};
|
|
201
|
-
const state =
|
|
199
|
+
const state = deepSignal({ root: {} }, options);
|
|
202
200
|
// Add nested structure
|
|
203
201
|
state.root.child = {
|
|
204
202
|
grandchild: {
|
|
@@ -206,10 +204,10 @@ const watch_1 = require("../../watch");
|
|
|
206
204
|
},
|
|
207
205
|
};
|
|
208
206
|
// All should have IDs generated by the custom generator
|
|
209
|
-
|
|
210
|
-
|
|
207
|
+
expect(state.root.child["@id"]).toMatch(/^inherited-/);
|
|
208
|
+
expect(state.root.child.grandchild["@id"]).toMatch(/^inherited-/);
|
|
211
209
|
});
|
|
212
|
-
|
|
210
|
+
it("objects added to Sets inherit options", async () => {
|
|
213
211
|
let counter = 9000;
|
|
214
212
|
const options = {
|
|
215
213
|
propGenerator: () => ({
|
|
@@ -217,14 +215,14 @@ const watch_1 = require("../../watch");
|
|
|
217
215
|
}),
|
|
218
216
|
syntheticIdPropertyName: "@id",
|
|
219
217
|
};
|
|
220
|
-
const state =
|
|
218
|
+
const state = deepSignal({ s: new Set() }, options);
|
|
221
219
|
const obj = { nested: { value: 1 } };
|
|
222
220
|
state.s.add(obj);
|
|
223
221
|
// Iterate to get proxied object
|
|
224
222
|
const proxied = Array.from(state.s)[0];
|
|
225
223
|
// Object and nested object should have custom IDs
|
|
226
|
-
|
|
227
|
-
|
|
224
|
+
expect(proxied["@id"]).toMatch(/^set-child-/);
|
|
225
|
+
expect(proxied.nested["@id"]).toMatch(/^set-child-/);
|
|
228
226
|
});
|
|
229
227
|
});
|
|
230
228
|
});
|