@ng-org/orm 0.1.2-alpha.1

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 (54) hide show
  1. package/README.md +347 -0
  2. package/dist/connector/applyPatches.d.ts +106 -0
  3. package/dist/connector/applyPatches.d.ts.map +1 -0
  4. package/dist/connector/applyPatches.js +317 -0
  5. package/dist/connector/applyPatches.test.d.ts +2 -0
  6. package/dist/connector/applyPatches.test.d.ts.map +1 -0
  7. package/dist/connector/applyPatches.test.js +772 -0
  8. package/dist/connector/createSignalObjectForShape.d.ts +14 -0
  9. package/dist/connector/createSignalObjectForShape.d.ts.map +1 -0
  10. package/dist/connector/createSignalObjectForShape.js +24 -0
  11. package/dist/connector/initNg.d.ts +14 -0
  12. package/dist/connector/initNg.d.ts.map +1 -0
  13. package/dist/connector/initNg.js +16 -0
  14. package/dist/connector/ormConnectionHandler.d.ts +47 -0
  15. package/dist/connector/ormConnectionHandler.d.ts.map +1 -0
  16. package/dist/connector/ormConnectionHandler.js +240 -0
  17. package/dist/frontendAdapters/react/index.d.ts +3 -0
  18. package/dist/frontendAdapters/react/index.d.ts.map +1 -0
  19. package/dist/frontendAdapters/react/index.js +2 -0
  20. package/dist/frontendAdapters/react/useShape.d.ts +6 -0
  21. package/dist/frontendAdapters/react/useShape.d.ts.map +1 -0
  22. package/dist/frontendAdapters/react/useShape.js +24 -0
  23. package/dist/frontendAdapters/svelte/index.d.ts +3 -0
  24. package/dist/frontendAdapters/svelte/index.d.ts.map +1 -0
  25. package/dist/frontendAdapters/svelte/index.js +2 -0
  26. package/dist/frontendAdapters/svelte/useShape.svelte.d.ts +14 -0
  27. package/dist/frontendAdapters/svelte/useShape.svelte.d.ts.map +1 -0
  28. package/dist/frontendAdapters/svelte/useShape.svelte.js +22 -0
  29. package/dist/frontendAdapters/vue/index.d.ts +3 -0
  30. package/dist/frontendAdapters/vue/index.d.ts.map +1 -0
  31. package/dist/frontendAdapters/vue/index.js +2 -0
  32. package/dist/frontendAdapters/vue/useShape.d.ts +5 -0
  33. package/dist/frontendAdapters/vue/useShape.d.ts.map +1 -0
  34. package/dist/frontendAdapters/vue/useShape.js +22 -0
  35. package/dist/index.d.ts +8 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +7 -0
  38. package/dist/types.d.ts +13 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/dist/types.js +10 -0
  41. package/package.json +90 -0
  42. package/src/connector/applyPatches.test.ts +842 -0
  43. package/src/connector/applyPatches.ts +404 -0
  44. package/src/connector/createSignalObjectForShape.ts +35 -0
  45. package/src/connector/initNg.ts +30 -0
  46. package/src/connector/ormConnectionHandler.ts +300 -0
  47. package/src/frontendAdapters/react/index.ts +2 -0
  48. package/src/frontendAdapters/react/useShape.ts +36 -0
  49. package/src/frontendAdapters/svelte/index.ts +2 -0
  50. package/src/frontendAdapters/svelte/useShape.svelte.ts +46 -0
  51. package/src/frontendAdapters/vue/index.ts +2 -0
  52. package/src/frontendAdapters/vue/useShape.ts +33 -0
  53. package/src/index.ts +14 -0
  54. package/src/types.ts +26 -0
@@ -0,0 +1,317 @@
1
+ // Copyright (c) 2025 Laurin Weger, Par le Peuple, NextGraph.org developers
2
+ // All rights reserved.
3
+ // Licensed under the Apache License, Version 2.0
4
+ // <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
5
+ // or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
6
+ // at your option. All files in the project carrying such
7
+ // notice may not be copied, modified, or distributed except
8
+ // according to those terms.
9
+ // SPDX-License-Identifier: Apache-2.0 OR MIT
10
+ import { batch } from "@ng-org/alien-deepsignals";
11
+ function isPrimitive(v) {
12
+ return (typeof v === "string" || typeof v === "number" || typeof v === "boolean");
13
+ }
14
+ /**
15
+ * Parse a combined identifier of the form "graph|subject".
16
+ * If there is no pipe, returns { graph: undefined, id: input }.
17
+ */
18
+ function parseGraphId(input) {
19
+ if (typeof input !== "string")
20
+ return { id: String(input) };
21
+ const idx = input.indexOf("|");
22
+ if (idx === -1)
23
+ return { id: input };
24
+ const graph = input.slice(0, idx);
25
+ const id = input.slice(idx + 1);
26
+ return { graph, id };
27
+ }
28
+ /**
29
+ * Find an object in a Set by its @id property.
30
+ * Returns the object if found, otherwise undefined.
31
+ */
32
+ function findInSetBySegment(set, seg) {
33
+ // TODO: We could optimize that by leveraging key @id to object mapping in sets of deepSignals.
34
+ const { graph, id } = parseGraphId(seg);
35
+ for (const item of set) {
36
+ if (typeof item !== "object" || item === null)
37
+ continue;
38
+ // If graph was provided, require both to match
39
+ if (graph && item["@graph"] === graph && item["@id"] === id)
40
+ return item;
41
+ // Match by @id only when no graph part is provided
42
+ if (!graph && item["@id"] === id)
43
+ return item;
44
+ }
45
+ return undefined;
46
+ }
47
+ /**
48
+ * Apply a diff to an object.
49
+ *
50
+ * The syntax is inspired by RFC 6902 but it is not compatible.
51
+ *
52
+ * It supports Sets for multi-valued properties:
53
+ * - Primitive values are added as Sets (Set<string | number | boolean>)
54
+ * - Multi-valued objects are stored in Sets, accessed by their @id property
55
+ * - Single objects are plain objects with an @id property
56
+ *
57
+ * Path traversal:
58
+ * - When traversing through a Set, the path segment is treated as an @id to find the object
59
+ * - When traversing through a plain object, the path segment is a property name
60
+ *
61
+ * @example operations
62
+ * ```jsonc
63
+ * // === SINGLE OBJECT ===
64
+ * // Creating a single object (has @id at same level)
65
+ * { "op": "add", "path": "/urn:example:person1/address", "valType": "object" }
66
+ * { "op": "add", "path": "/urn:example:person1/address/@id", "value": "urn:test:address1" }
67
+ * // Adding primitives to single object
68
+ * { "op": "add", "path": "/urn:example:person1/address/street", "value": "1st street" }
69
+ * { "op": "add", "path": "/urn:example:person1/address/country", "value": "Greece" }
70
+ * // Remove a primitive from object
71
+ * { "op": "remove", "path": "/urn:example:person1/address/street" }
72
+ * // Remove the entire object
73
+ * { "op": "remove", "path": "/urn:example:person1/address" }
74
+ *
75
+ * // === MULTI-VALUED OBJECTS (Set) ===
76
+ * // Creating a multi-object container (NO @id at this level -> creates Set)
77
+ * { "op": "add", "path": "/urn:example:person1/children", "valType": "object" }
78
+ * // Adding an object to the Set (path includes object's @id)
79
+ * { "op": "add", "path": "/urn:example:person1/children/urn:example:child1", "valType": "object" }
80
+ * { "op": "add", "path": "/urn:example:person1/children/urn:example:child1/@id", "value": "urn:example:child1" }
81
+ * // Adding properties to object in Set
82
+ * { "op": "add", "path": "/urn:example:person1/children/urn:example:child1/name", "value": "Alice" }
83
+ * // Remove an object from Set
84
+ * { "op": "remove", "path": "/urn:example:person1/children/urn:example:child1" }
85
+ * // Remove all objects (the Set itself)
86
+ * { "op": "remove", "path": "/urn:example:person1/children" }
87
+ *
88
+ * // === PRIMITIVE SETS ===
89
+ * // Add primitive types to Sets
90
+ * { "op": "add", "valType": "set", "path": "/urn:example:person1/tags", "value": [1,2,3] }
91
+ * // Remove primitive types from a Set
92
+ * { "op": "remove", "valType": "set", "path": "/urn:example:person1/tags", "value": [1,2] }
93
+ * ```
94
+ *
95
+ * @param currentState The object before the patch
96
+ * @param patches An array of patches to apply to the object.
97
+ * @param ensurePathExists If true, create nested objects along the path if the path does not exist.
98
+ *
99
+ * @note When creating new objects, this function pre-scans upcoming patches to find @id and @graph
100
+ * values that will be assigned to the object. This prevents the signal library's propGenerator
101
+ * from being triggered before these identity fields are set, which would cause it to generate
102
+ * random IDs unnecessarily.
103
+ */
104
+ export function applyPatches(currentState, patches, ensurePathExists = false) {
105
+ for (let patchIndex = 0; patchIndex < patches.length; patchIndex++) {
106
+ const patch = patches[patchIndex];
107
+ if (!patch.path.startsWith("/"))
108
+ continue;
109
+ const pathParts = patch.path
110
+ .slice(1)
111
+ .split("/")
112
+ .filter(Boolean)
113
+ .map(decodePathSegment);
114
+ if (pathParts.length === 0) {
115
+ console.warn("[applyDiff] No path specified for patch", patch);
116
+ continue;
117
+ }
118
+ const lastKey = pathParts[pathParts.length - 1];
119
+ let parentVal = currentState;
120
+ let parentMissing = false;
121
+ // Traverse only intermediate segments (to leaf object at path)
122
+ for (let i = 0; i < pathParts.length - 1; i++) {
123
+ const seg = pathParts[i];
124
+ // Handle Sets: if parentVal is a Set, find object by path segment.
125
+ if (parentVal instanceof Set) {
126
+ const foundObj = findInSetBySegment(parentVal, seg);
127
+ if (foundObj) {
128
+ parentVal = foundObj;
129
+ }
130
+ else if (ensurePathExists) {
131
+ // Create new object in the set.
132
+ const newObj = {};
133
+ parentVal.add(newObj);
134
+ parentVal = newObj;
135
+ }
136
+ else {
137
+ parentMissing = true;
138
+ break;
139
+ }
140
+ continue;
141
+ }
142
+ // Handle regular objects
143
+ if (parentVal != null &&
144
+ typeof parentVal === "object" &&
145
+ Object.prototype.hasOwnProperty.call(parentVal, seg)) {
146
+ parentVal = parentVal[seg];
147
+ continue;
148
+ }
149
+ if (ensurePathExists) {
150
+ if (parentVal != null && typeof parentVal === "object") {
151
+ // Check if we need to create an object or a set:
152
+ if (pathParts[i + 1]?.includes("|")) {
153
+ // The next path segment is an IRI, that means the new element must be a set of objects. Create a set.
154
+ parentVal[seg] = new Set();
155
+ }
156
+ else {
157
+ // Create a new object
158
+ parentVal[seg] = {};
159
+ }
160
+ parentVal = parentVal[seg];
161
+ }
162
+ else {
163
+ parentMissing = true;
164
+ break;
165
+ }
166
+ }
167
+ else {
168
+ parentMissing = true;
169
+ break;
170
+ }
171
+ }
172
+ if (parentMissing) {
173
+ console.warn(`[applyDiff] Skipping patch due to missing parent path segment(s): ${patch.path}`);
174
+ continue;
175
+ }
176
+ // parentVal now should be an object or Set into which we apply lastKey
177
+ if (parentVal == null ||
178
+ (typeof parentVal !== "object" && !(parentVal instanceof Set))) {
179
+ console.warn(`[applyDiff] Skipping patch because parent is not an object or Set: ${patch.path}`);
180
+ continue;
181
+ }
182
+ const key = lastKey;
183
+ // Special handling when parent is a Set
184
+ if (parentVal instanceof Set) {
185
+ // The key represents the identifier of an object within the Set
186
+ const targetObj = findInSetBySegment(parentVal, key);
187
+ // Handle object creation in a Set
188
+ if (patch.op === "add" && patch.valType === "object") {
189
+ if (!targetObj) {
190
+ // Determine if this will be a single object or nested Set
191
+ const hasId = patches[patchIndex + 2]?.path.endsWith("@id");
192
+ const newLeaf = hasId ? {} : new Set();
193
+ // Pre-assign identity so subsequent patches can find this object
194
+ if (hasId) {
195
+ const { graph, id } = parseGraphId(key);
196
+ newLeaf["@id"] = id;
197
+ const graphPatch = patches[patchIndex + 1];
198
+ if (graphPatch?.path.endsWith("@graph")) {
199
+ newLeaf["@graph"] = graphPatch.value ?? graph;
200
+ }
201
+ else if (graph) {
202
+ newLeaf["@graph"] = graph;
203
+ }
204
+ }
205
+ parentVal.add(newLeaf);
206
+ }
207
+ continue;
208
+ }
209
+ // Handle remove from Set
210
+ if (patch.op === "remove" && patch.valType !== "set") {
211
+ if (targetObj) {
212
+ parentVal.delete(targetObj);
213
+ }
214
+ continue;
215
+ }
216
+ // All other operations require the target object to exist
217
+ if (!targetObj) {
218
+ console.warn(`[applyDiff] Target object with @id=${key} not found in Set for path: ${patch.path}`);
219
+ continue;
220
+ }
221
+ // This shouldn't happen - we handle all intermediate segments in the traversal loop
222
+ console.warn(`[applyDiff] Unexpected: reached end of path with Set as parent: ${patch.path}`);
223
+ continue;
224
+ }
225
+ // Regular object handling (parentVal is a plain object, not a Set)
226
+ // Handle primitive set additions
227
+ if (patch.op === "add" && patch.valType === "set") {
228
+ const existing = parentVal[key];
229
+ const raw = patch.value;
230
+ if (raw == null)
231
+ continue;
232
+ // Normalize to array of primitives
233
+ const toAdd = Array.isArray(raw)
234
+ ? raw.filter(isPrimitive)
235
+ : isPrimitive(raw)
236
+ ? [raw]
237
+ : [];
238
+ if (!toAdd.length)
239
+ continue;
240
+ // Ensure we have a Set, create or add to existing
241
+ if (existing instanceof Set) {
242
+ for (const v of toAdd)
243
+ existing.add(v);
244
+ }
245
+ else {
246
+ // Create new Set (replaces any incompatible existing value)
247
+ parentVal[key] = new Set(toAdd);
248
+ }
249
+ continue;
250
+ }
251
+ // Handle primitive set removals
252
+ if (patch.op === "remove" && patch.valType === "set") {
253
+ const existing = parentVal[key];
254
+ const raw = patch.value;
255
+ if (raw == null)
256
+ continue;
257
+ const toRemove = Array.isArray(raw)
258
+ ? raw
259
+ : [raw];
260
+ if (existing instanceof Set) {
261
+ for (const v of toRemove)
262
+ existing.delete(v);
263
+ }
264
+ continue;
265
+ }
266
+ // Add object (if it does not exist yet).
267
+ // Distinguish between single objects and multi-object containers:
268
+ // - If an @id patch follows for this path, it's a single object -> create {}
269
+ // - If no @id patch follows, it's a container for multi-valued objects -> create set.
270
+ if (patch.op === "add" && patch.valType === "object") {
271
+ const leafVal = parentVal[key];
272
+ const hasId = patches.at(patchIndex + 2)?.path.endsWith("@id");
273
+ // If the leafVal does not exist and it should be a set, create.
274
+ if (!hasId && !leafVal) {
275
+ parentVal[key] = new Set();
276
+ }
277
+ else if (!(typeof leafVal === "object")) {
278
+ // If the leave does not exist yet (as object), create it.
279
+ const newLeaf = {};
280
+ const graphPatch = patches.at(patchIndex + 1);
281
+ if (graphPatch?.path.endsWith("@graph")) {
282
+ newLeaf["@graph"] = graphPatch.value;
283
+ }
284
+ const idPatch = patches.at(patchIndex + 2);
285
+ if (idPatch?.path.endsWith("@id")) {
286
+ newLeaf["@id"] = idPatch.value;
287
+ }
288
+ parentVal[key] = newLeaf;
289
+ }
290
+ continue;
291
+ }
292
+ // Literal add
293
+ if (patch.op === "add" &&
294
+ !(patch.path.endsWith("@id") || patch.path.endsWith("@graph"))) {
295
+ parentVal[key] = patch.value;
296
+ continue;
297
+ }
298
+ // Generic remove (property or value)
299
+ if (patch.op === "remove") {
300
+ if (Object.prototype.hasOwnProperty.call(parentVal, key)) {
301
+ delete parentVal[key];
302
+ }
303
+ continue;
304
+ }
305
+ }
306
+ }
307
+ /**
308
+ * See documentation for applyDiff
309
+ */
310
+ export function applyPatchesToDeepSignal(currentState, patch) {
311
+ batch(() => {
312
+ applyPatches(currentState, patch, true);
313
+ });
314
+ }
315
+ function decodePathSegment(segment) {
316
+ return segment.replace("~1", "/").replace("~0", "~");
317
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=applyPatches.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"applyPatches.test.d.ts","sourceRoot":"","sources":["../../src/connector/applyPatches.test.ts"],"names":[],"mappings":""}