@ng-org/orm 0.1.2-alpha.1 → 0.1.2-alpha.10

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 (89) hide show
  1. package/README.md +151 -230
  2. package/dist/connector/applyPatches.d.ts +19 -49
  3. package/dist/connector/applyPatches.d.ts.map +1 -1
  4. package/dist/connector/applyPatches.js +57 -58
  5. package/dist/connector/discrete/discreteOrmSubscriptionHandler.d.ts +168 -0
  6. package/dist/connector/discrete/discreteOrmSubscriptionHandler.d.ts.map +1 -0
  7. package/dist/connector/discrete/discreteOrmSubscriptionHandler.js +309 -0
  8. package/dist/connector/getObjects.d.ts +11 -0
  9. package/dist/connector/getObjects.d.ts.map +1 -0
  10. package/dist/connector/getObjects.js +26 -0
  11. package/dist/connector/initNg.d.ts +33 -2
  12. package/dist/connector/initNg.d.ts.map +1 -1
  13. package/dist/connector/initNg.js +30 -0
  14. package/dist/connector/insertObject.d.ts +9 -0
  15. package/dist/connector/insertObject.d.ts.map +1 -0
  16. package/dist/connector/insertObject.js +24 -0
  17. package/dist/connector/ormSubscriptionHandler.d.ts +167 -0
  18. package/dist/connector/ormSubscriptionHandler.d.ts.map +1 -0
  19. package/dist/connector/ormSubscriptionHandler.js +400 -0
  20. package/dist/connector/utils.d.ts +17 -0
  21. package/dist/connector/utils.d.ts.map +1 -0
  22. package/dist/connector/utils.js +68 -0
  23. package/dist/frontendAdapters/react/index.d.ts +2 -1
  24. package/dist/frontendAdapters/react/index.d.ts.map +1 -1
  25. package/dist/frontendAdapters/react/index.js +2 -1
  26. package/dist/frontendAdapters/react/useDiscrete.d.ts +104 -0
  27. package/dist/frontendAdapters/react/useDiscrete.d.ts.map +1 -0
  28. package/dist/frontendAdapters/react/useDiscrete.js +151 -0
  29. package/dist/frontendAdapters/react/useShape.d.ts +69 -2
  30. package/dist/frontendAdapters/react/useShape.d.ts.map +1 -1
  31. package/dist/frontendAdapters/react/useShape.js +89 -8
  32. package/dist/frontendAdapters/svelte/index.d.ts +2 -1
  33. package/dist/frontendAdapters/svelte/index.d.ts.map +1 -1
  34. package/dist/frontendAdapters/svelte/index.js +2 -1
  35. package/dist/frontendAdapters/svelte/useDiscrete.svelte.d.ts +94 -0
  36. package/dist/frontendAdapters/svelte/useDiscrete.svelte.d.ts.map +1 -0
  37. package/dist/frontendAdapters/svelte/useDiscrete.svelte.js +137 -0
  38. package/dist/frontendAdapters/svelte/useShape.svelte.d.ts +79 -10
  39. package/dist/frontendAdapters/svelte/useShape.svelte.d.ts.map +1 -1
  40. package/dist/frontendAdapters/svelte/useShape.svelte.js +88 -9
  41. package/dist/frontendAdapters/svelte4/index.d.ts +5 -0
  42. package/dist/frontendAdapters/svelte4/index.d.ts.map +1 -0
  43. package/dist/{connector/createSignalObjectForShape.js → frontendAdapters/svelte4/index.js} +3 -15
  44. package/dist/frontendAdapters/svelte4/useDiscrete.svelte.d.ts +84 -0
  45. package/dist/frontendAdapters/svelte4/useDiscrete.svelte.d.ts.map +1 -0
  46. package/dist/frontendAdapters/svelte4/useDiscrete.svelte.js +126 -0
  47. package/dist/frontendAdapters/svelte4/useShape.svelte.d.ts +77 -0
  48. package/dist/frontendAdapters/svelte4/useShape.svelte.d.ts.map +1 -0
  49. package/dist/frontendAdapters/svelte4/useShape.svelte.js +91 -0
  50. package/dist/frontendAdapters/utils.d.ts +2 -0
  51. package/dist/frontendAdapters/utils.d.ts.map +1 -0
  52. package/dist/frontendAdapters/utils.js +14 -0
  53. package/dist/frontendAdapters/vue/index.d.ts +2 -1
  54. package/dist/frontendAdapters/vue/index.d.ts.map +1 -1
  55. package/dist/frontendAdapters/vue/index.js +2 -1
  56. package/dist/frontendAdapters/vue/useDiscrete.d.ts +99 -0
  57. package/dist/frontendAdapters/vue/useDiscrete.d.ts.map +1 -0
  58. package/dist/frontendAdapters/vue/useDiscrete.js +123 -0
  59. package/dist/frontendAdapters/vue/useShape.d.ts +73 -2
  60. package/dist/frontendAdapters/vue/useShape.d.ts.map +1 -1
  61. package/dist/frontendAdapters/vue/useShape.js +79 -4
  62. package/dist/index.d.ts +13 -6
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +21 -6
  65. package/dist/types.d.ts +63 -11
  66. package/dist/types.d.ts.map +1 -1
  67. package/dist/types.js +13 -1
  68. package/package.json +37 -48
  69. package/dist/connector/applyPatches.test.d.ts +0 -2
  70. package/dist/connector/applyPatches.test.d.ts.map +0 -1
  71. package/dist/connector/applyPatches.test.js +0 -772
  72. package/dist/connector/createSignalObjectForShape.d.ts +0 -14
  73. package/dist/connector/createSignalObjectForShape.d.ts.map +0 -1
  74. package/dist/connector/ormConnectionHandler.d.ts +0 -47
  75. package/dist/connector/ormConnectionHandler.d.ts.map +0 -1
  76. package/dist/connector/ormConnectionHandler.js +0 -240
  77. package/src/connector/applyPatches.test.ts +0 -842
  78. package/src/connector/applyPatches.ts +0 -404
  79. package/src/connector/createSignalObjectForShape.ts +0 -35
  80. package/src/connector/initNg.ts +0 -30
  81. package/src/connector/ormConnectionHandler.ts +0 -300
  82. package/src/frontendAdapters/react/index.ts +0 -2
  83. package/src/frontendAdapters/react/useShape.ts +0 -36
  84. package/src/frontendAdapters/svelte/index.ts +0 -2
  85. package/src/frontendAdapters/svelte/useShape.svelte.ts +0 -46
  86. package/src/frontendAdapters/vue/index.ts +0 -2
  87. package/src/frontendAdapters/vue/useShape.ts +0 -33
  88. package/src/index.ts +0 -14
  89. package/src/types.ts +0 -26
@@ -0,0 +1,151 @@
1
+ // Copyright (c) 2026 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 { useEffect, useMemo, useRef } from "react";
11
+ import { DiscreteOrmSubscription } from "../../connector/discrete/discreteOrmSubscriptionHandler.js";
12
+ import { useDeepSignal } from "@ng-org/alien-deepsignals/react";
13
+ const EMPTY_OBJECT = {};
14
+ /**
15
+ * Hook to subscribe to an existing discrete (JSON) CRDT document.
16
+ * You can modify the returned object like any other JSON object. Changes are immediately
17
+ * reflected in the CRDT document.
18
+ *
19
+ * Establishes a 2-way binding: Modifications to the object are immediately committed;
20
+ * changes coming from the engine (or other components) cause an immediate rerender.
21
+ *
22
+ * In comparison to {@link reactUseShape}, discrete CRDTs are untyped.
23
+ * You can put any JSON data inside and need to validate the schema yourself.
24
+ *
25
+ * @param documentId The NURI of the CRDT document.
26
+ * @returns An object that contains as `doc` the reactive DeepSignal object or undefined if `documentId` is undefined.
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * // We assume you have created a CRDT document already, as below.
31
+ * // const documentId = await ng.doc_create(
32
+ * // session_id,
33
+ * // crdt, // "Automerge" | "YMap" | "YArray". YArray is for root arrays, the other two have objects at root.
34
+ * // crdt === "Automerge" ? "data:json" : crdt === "YMap ? "data:map" : "data:array",
35
+ * // "store",
36
+ * // undefined
37
+ * // );
38
+ *
39
+ * function Expenses({documentId}: {documentId: string}) {
40
+ * const { doc } = useDiscrete(documentId);
41
+ *
42
+ * // If the CRDT document is still empty, we need to initialize it.
43
+ * if (doc && !doc.expenses) {
44
+ * doc.expenses = [];
45
+ * }
46
+ * const expenses = doc?.expenses;
47
+ *
48
+ * const createExpense = useCallback(() => {
49
+ * // Note that we use *expense["@id"]* as a key in the expense list.
50
+ * // Every object added to a CRDT array gets a stable `@id` property assigned
51
+ * // which you can use for referencing objects in arrays even as
52
+ * // objects are removed or added from the array.
53
+ * // The `@id` is a NURI with the schema `<documentId>:d:<object-specific id>`.
54
+ * // Since the `@id` is generated in the engine, the object is
55
+ * // *preliminarily given a mock id* which will be replaced immediately.
56
+ * expenses.push({
57
+ * title: "New expense",
58
+ * date: new Date().toISOString(),
59
+ * });
60
+ * },
61
+ * [expenses]
62
+ * );
63
+ *
64
+ * // Still loading?
65
+ * if (!doc) return <div>Loading...</div>;
66
+ *
67
+ * return (
68
+ * <div>
69
+ * <button
70
+ * onClick={() => createExpense()}
71
+ * >
72
+ * + Add expense
73
+ * </button>
74
+ * <div>
75
+ * {expenses.length === 0 ? (
76
+ * <p>
77
+ * No expenses yet.
78
+ * </p>
79
+ * ) : (
80
+ * expenses.map((expense) => (
81
+ * <ExpenseCard
82
+ * key={expense["@id"]}
83
+ * expense={expense}
84
+ * />
85
+ * ))
86
+ * )}
87
+ * </div>
88
+ * </div>
89
+ * );
90
+ * }
91
+ * ```
92
+ *
93
+ * ---
94
+ * In the ExpenseCard component:
95
+ * ```tsx
96
+ * function ExpenseCard({expense}: {expense: Expense}) {
97
+ * return (
98
+ * <input
99
+ * value={expense.title}
100
+ * onChange={(e) => {
101
+ * expense.title = e.target.value; // Changes trigger rerender.
102
+ * }}
103
+ * />
104
+ * <div>
105
+ * <p>Date</p>
106
+ * <p>{expense.doc}
107
+ * </div
108
+ * );
109
+ * }
110
+ * ```
111
+ */
112
+ export function useDiscrete(documentId) {
113
+ const prevDocumentId = useRef(undefined);
114
+ const prevOrmSubscription = useRef(undefined);
115
+ const ormConnection = useMemo(() => {
116
+ // Close previous connection if documentId changed.
117
+ if (prevOrmSubscription.current &&
118
+ prevDocumentId.current !== documentId) {
119
+ prevOrmSubscription.current.close();
120
+ prevOrmSubscription.current = undefined;
121
+ }
122
+ // If no documentId, return undefined.
123
+ if (!documentId) {
124
+ prevDocumentId.current = undefined;
125
+ return undefined;
126
+ }
127
+ // Create new connection only if needed.
128
+ if (!prevOrmSubscription.current ||
129
+ prevDocumentId.current !== documentId) {
130
+ prevOrmSubscription.current =
131
+ DiscreteOrmSubscription.getOrCreate(documentId);
132
+ prevDocumentId.current = documentId;
133
+ }
134
+ return prevOrmSubscription.current;
135
+ }, [documentId]);
136
+ useEffect(() => {
137
+ return () => {
138
+ prevOrmSubscription.current?.close();
139
+ };
140
+ }, []);
141
+ // useDeepSignal requires an object, so pass empty object when no connection.
142
+ const signalSource = ormConnection?.signalObject ?? EMPTY_OBJECT;
143
+ const deepSignalValue = useDeepSignal(signalSource, {
144
+ replaceProxiesInBranchOnChange: true,
145
+ });
146
+ // Only return doc if we have a valid connection with a signal object.
147
+ const docOrUndefined = ormConnection?.signalObject
148
+ ? deepSignalValue
149
+ : undefined;
150
+ return { doc: docOrUndefined };
151
+ }
@@ -1,6 +1,73 @@
1
1
  import type { BaseType } from "@ng-org/shex-orm";
2
2
  import type { ShapeType } from "@ng-org/shex-orm";
3
- import type { Scope } from "../../types.ts";
4
- declare const useShape: <T extends BaseType>(shape: ShapeType<T>, scope?: Scope) => import("@ng-org/alien-deepsignals").DeepSignalSet<T>;
3
+ import { type Scope } from "../../types.ts";
4
+ import { DeepSignalSet } from "@ng-org/alien-deepsignals";
5
+ /**
6
+ * Hook to subscribe to RDF data in the graph database using a shape, see {@link ShapeType}.
7
+ *
8
+ * Returns a {@link DeepSignalSet} of objects matching the shape and that are within the scope.
9
+ * Establishes a 2-way binding: Modifications to the object are immediately committed,
10
+ * changes coming from the engine (or other components) cause an immediate rerender.
11
+ *
12
+ * @param shape The {@link ShapeType} the objects should have (generated by the shex-orm tool).
13
+ * @param scope The {@link Scope} as graph string or scope object with graphs and subjects.
14
+ * @returns A {@link DeepSignalSet} with the orm objects or an empty set, if still loading.\
15
+ * If the scope is explicitly set to `undefined`, an empty set is returned which errors
16
+ * if you try to make modifications on it.
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * function Expenses() {
21
+ * const expenses: DeepSignal<Set<Expense>> = useShape(ExpenseShapeType, {graphs: ["<graph NURI>"]});
22
+ *
23
+ * const createExpense = useCallback(
24
+ * () => {
25
+ * expenses.add({
26
+ * "@graph": `<graph NURI>`,
27
+ * "@type": "http://example.org/Expense",
28
+ * "@id": "", // Assigns ID automatically, if set to "".
29
+ * title: "New expense",
30
+ * dateOfPurchase: obj.dateOfPurchase ?? new Date().toISOString(),
31
+ * });
32
+ * },
33
+ * [expenses]
34
+ * );
35
+ *
36
+ * const expensesSorted = [...expenses].sort((a, b) =>
37
+ * a.dateOfPurchase.localeCompare(b.dateOfPurchase)
38
+ * );
39
+ *
40
+ * // Note that if you use `@id` (the subject IRI) as key, you need to ensure that it is unique within your scope.
41
+ * // If it is not (i.e. there are two graphs with the same subject), use the combination of `@graph` and `@id`.
42
+ *
43
+ * return (
44
+ * <div>
45
+ * <button
46
+ * onClick={() => createExpense({})}
47
+ * >
48
+ * + Add expense
49
+ * </button>
50
+ * <div>
51
+ * {expensesSorted.length === 0 ? (
52
+ * <p>
53
+ * No expenses yet.
54
+ * </p>
55
+ * ) : (
56
+ * expensesSorted.map((expense) => (
57
+ * // You can modify the expense's properties in the ExpenseCard component
58
+ * // which will instantly trigger a rerender.
59
+ * <ExpenseCard
60
+ * key={expense["@id"]}
61
+ * expense={expense}
62
+ * />
63
+ * ))
64
+ * )}
65
+ * </div>
66
+ * </div>
67
+ * );
68
+ * }
69
+ * ```
70
+ */
71
+ declare const useShape: <T extends BaseType>(shape: ShapeType<T>, scope: Scope | string | undefined) => DeepSignalSet<T>;
5
72
  export default useShape;
6
73
  //# sourceMappingURL=useShape.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useShape.d.ts","sourceRoot":"","sources":["../../../src/frontendAdapters/react/useShape.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGlD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE5C,QAAA,MAAM,QAAQ,GAAI,CAAC,SAAS,QAAQ,EAChC,OAAO,SAAS,CAAC,CAAC,CAAC,EACnB,QAAO,KAAU,yDAcpB,CAAC;AAEF,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"useShape.d.ts","sourceRoot":"","sources":["../../../src/frontendAdapters/react/useShape.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAkB,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE5D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAG1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,QAAA,MAAM,QAAQ,GAAI,CAAC,SAAS,QAAQ,EAChC,OAAO,SAAS,CAAC,CAAC,CAAC,EACnB,OAAO,KAAK,GAAG,MAAM,GAAG,SAAS,KAgCjB,aAAa,CAAC,CAAC,CAClC,CAAC;AAEF,eAAe,QAAQ,CAAC"}
@@ -8,17 +8,98 @@
8
8
  // according to those terms.
9
9
  // SPDX-License-Identifier: Apache-2.0 OR MIT
10
10
  import { useDeepSignal } from "@ng-org/alien-deepsignals/react";
11
- import { useEffect, useRef } from "react";
12
- import { createSignalObjectForShape } from "../../connector/createSignalObjectForShape.js";
13
- const useShape = (shape, scope = "") => {
14
- const handleRef = useRef(createSignalObjectForShape(shape, scope));
15
- const handle = handleRef.current;
16
- const state = useDeepSignal(handle.signalObject);
11
+ import { useEffect, useMemo, useRef } from "react";
12
+ import { normalizeScope } from "../../types.js";
13
+ import { OrmSubscription } from "../../connector/ormSubscriptionHandler.js";
14
+ import { readOnlySet } from "../utils.js";
15
+ /**
16
+ * Hook to subscribe to RDF data in the graph database using a shape, see {@link ShapeType}.
17
+ *
18
+ * Returns a {@link DeepSignalSet} of objects matching the shape and that are within the scope.
19
+ * Establishes a 2-way binding: Modifications to the object are immediately committed,
20
+ * changes coming from the engine (or other components) cause an immediate rerender.
21
+ *
22
+ * @param shape The {@link ShapeType} the objects should have (generated by the shex-orm tool).
23
+ * @param scope The {@link Scope} as graph string or scope object with graphs and subjects.
24
+ * @returns A {@link DeepSignalSet} with the orm objects or an empty set, if still loading.\
25
+ * If the scope is explicitly set to `undefined`, an empty set is returned which errors
26
+ * if you try to make modifications on it.
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * function Expenses() {
31
+ * const expenses: DeepSignal<Set<Expense>> = useShape(ExpenseShapeType, {graphs: ["<graph NURI>"]});
32
+ *
33
+ * const createExpense = useCallback(
34
+ * () => {
35
+ * expenses.add({
36
+ * "@graph": `<graph NURI>`,
37
+ * "@type": "http://example.org/Expense",
38
+ * "@id": "", // Assigns ID automatically, if set to "".
39
+ * title: "New expense",
40
+ * dateOfPurchase: obj.dateOfPurchase ?? new Date().toISOString(),
41
+ * });
42
+ * },
43
+ * [expenses]
44
+ * );
45
+ *
46
+ * const expensesSorted = [...expenses].sort((a, b) =>
47
+ * a.dateOfPurchase.localeCompare(b.dateOfPurchase)
48
+ * );
49
+ *
50
+ * // Note that if you use `@id` (the subject IRI) as key, you need to ensure that it is unique within your scope.
51
+ * // If it is not (i.e. there are two graphs with the same subject), use the combination of `@graph` and `@id`.
52
+ *
53
+ * return (
54
+ * <div>
55
+ * <button
56
+ * onClick={() => createExpense({})}
57
+ * >
58
+ * + Add expense
59
+ * </button>
60
+ * <div>
61
+ * {expensesSorted.length === 0 ? (
62
+ * <p>
63
+ * No expenses yet.
64
+ * </p>
65
+ * ) : (
66
+ * expensesSorted.map((expense) => (
67
+ * // You can modify the expense's properties in the ExpenseCard component
68
+ * // which will instantly trigger a rerender.
69
+ * <ExpenseCard
70
+ * key={expense["@id"]}
71
+ * expense={expense}
72
+ * />
73
+ * ))
74
+ * )}
75
+ * </div>
76
+ * </div>
77
+ * );
78
+ * }
79
+ * ```
80
+ */
81
+ const useShape = (shape, scope) => {
82
+ const parsedScope = !scope ? undefined : normalizeScope(scope);
83
+ const prevOrmSubscription = useRef(undefined);
84
+ const ormSubscription = useMemo(() => {
85
+ if (parsedScope === undefined)
86
+ return undefined;
87
+ if (prevOrmSubscription.current)
88
+ prevOrmSubscription.current.close();
89
+ const newOrmSubscription = OrmSubscription.getOrCreate(shape, parsedScope);
90
+ prevOrmSubscription.current = newOrmSubscription;
91
+ return newOrmSubscription;
92
+ }, [shape, scope, parsedScope?.graphs, parsedScope?.subjects]);
17
93
  useEffect(() => {
94
+ if (!ormSubscription)
95
+ return;
18
96
  return () => {
19
- handleRef.current.stop();
97
+ ormSubscription.close();
20
98
  };
21
- }, [shape, scope]);
99
+ }, [ormSubscription]);
100
+ const state = useDeepSignal(ormSubscription?.signalObject ?? readOnlySet, {
101
+ replaceProxiesInBranchOnChange: true,
102
+ });
22
103
  return state;
23
104
  };
24
105
  export default useShape;
@@ -1,3 +1,4 @@
1
1
  import useShape from "./useShape.svelte.ts";
2
- export { useShape };
2
+ import { useDiscrete } from "./useDiscrete.svelte.ts";
3
+ export { useShape, useDiscrete };
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/frontendAdapters/svelte/index.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/frontendAdapters/svelte/index.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC"}
@@ -1,2 +1,3 @@
1
1
  import useShape from "./useShape.svelte.js";
2
- export { useShape };
2
+ import { useDiscrete } from "./useDiscrete.svelte.js";
3
+ export { useShape, useDiscrete };
@@ -0,0 +1,94 @@
1
+ import { DiscreteRoot } from "../../types.ts";
2
+ import { DeepSignal } from "@ng-org/alien-deepsignals";
3
+ /**
4
+ * Svelte 5 hook to subscribe to existing discrete (JSON) CRDT documents.
5
+ * You can modify the returned object like any other JSON object. Changes are immediately
6
+ * reflected in the CRDT.
7
+ *
8
+ * Establishes a 2-way binding: Modifications to the object are immediately committed,
9
+ * changes coming from the engine (or other components) cause an immediate rerender.
10
+ *
11
+ * In comparison to {@link svelteUseShape}, discrete CRDTs are untyped.
12
+ * You can put any JSON data inside and need to validate the schema yourself.
13
+ *
14
+ * @param documentIdOrPromise The NURI of the CRDT document or a promise to that.
15
+ * @returns The reactive JSON object of the CRDT document.
16
+ *
17
+ *@example
18
+ * ```svelte
19
+ * <script lang="ts">
20
+ * // We assume you have created a CRDT document already, as below.
21
+ * // const documentId = await ng.doc_create(
22
+ * // session_id,
23
+ * // crdt, // "Automerge" | "YMap" | "YArray"
24
+ * // crdt === "Automerge" ? "data:json" : crdt === "YMap ? "data:map" : "data:array",
25
+ * // "store",
26
+ * // undefined,
27
+ * // );
28
+ *
29
+ * const { doc } = useDiscrete(documentIdPromise);
30
+ *
31
+ * $effect(() => {
32
+ * // If the CRDT document is still empty, we need to initialize it.
33
+ * if (doc && !doc.expenses) {
34
+ * doc.expenses = [];
35
+ * }
36
+ * });
37
+ *
38
+ * const createExpense = () => {
39
+ * // Note that we use *expense["@id"]* as a key in the expense list.
40
+ * // Every object added to a CRDT array gets a stable `@id` property assigned
41
+ * // which you can use for referencing objects in arrays even as
42
+ * // preceding objects are removed or added from the array.
43
+ * // The `@id` is an NURI with the schema `<documentId>:d:<object-specific id>`.
44
+ * // Since the `@id` is generated in the engine, the object is
45
+ * // *preliminarily given a mock id* which will be replaced immediately.
46
+ * expenses.push({
47
+ * title: "New expense",
48
+ * date: new Date().toISOString(),
49
+ * });
50
+ * };
51
+ *
52
+ *
53
+ * </script>
54
+ *
55
+ * <section>
56
+ * <div>
57
+ * <button on:click={() => createExpense({})}/>
58
+ *
59
+ * {#if !doc}
60
+ * Loading...
61
+ * {:else if doc.expenses.length === 0}
62
+ * <p>
63
+ * Nothing tracked yet - log your first purchase to kick things off.
64
+ * </p>
65
+ * {:else}
66
+ * {#each doc.expenses as expense, index (expense['@id']) }
67
+ * <ExpenseCard
68
+ * expense={expense}
69
+ * />
70
+ * {/each}
71
+ * {/if}
72
+ * </div>
73
+ * </section>
74
+ * ```
75
+ *
76
+ * ---
77
+ * In the ExpenseCard component:
78
+ * ```svelte
79
+ * let {
80
+ * expense = $bindable(),
81
+ * }: { expense: Expense; } = $props();
82
+ * </script>
83
+ *
84
+ * <div>
85
+ * <input
86
+ * bind:value={expense.title}
87
+ * />
88
+ * </div>
89
+ * ```
90
+ */
91
+ export declare function useDiscrete<T extends DiscreteRoot = DiscreteRoot>(documentIdOrPromise: string | Promise<string> | undefined): {
92
+ doc: DeepSignal<T | undefined>;
93
+ };
94
+ //# sourceMappingURL=useDiscrete.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDiscrete.svelte.d.ts","sourceRoot":"","sources":["../../../src/frontendAdapters/svelte/useDiscrete.svelte.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuFG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAC7D,mBAAmB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,SAAS,GAC1D;IACC,GAAG,EAAE,UAAU,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;CAClC,CAqCA"}
@@ -0,0 +1,137 @@
1
+ // Copyright (c) 2026 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 { onDestroy } from "svelte";
11
+ import { useDeepSignal } from "@ng-org/alien-deepsignals/svelte";
12
+ import { DiscreteOrmSubscription } from "../../connector/discrete/discreteOrmSubscriptionHandler.js";
13
+ /**
14
+ * Svelte 5 hook to subscribe to existing discrete (JSON) CRDT documents.
15
+ * You can modify the returned object like any other JSON object. Changes are immediately
16
+ * reflected in the CRDT.
17
+ *
18
+ * Establishes a 2-way binding: Modifications to the object are immediately committed,
19
+ * changes coming from the engine (or other components) cause an immediate rerender.
20
+ *
21
+ * In comparison to {@link svelteUseShape}, discrete CRDTs are untyped.
22
+ * You can put any JSON data inside and need to validate the schema yourself.
23
+ *
24
+ * @param documentIdOrPromise The NURI of the CRDT document or a promise to that.
25
+ * @returns The reactive JSON object of the CRDT document.
26
+ *
27
+ *@example
28
+ * ```svelte
29
+ * <script lang="ts">
30
+ * // We assume you have created a CRDT document already, as below.
31
+ * // const documentId = await ng.doc_create(
32
+ * // session_id,
33
+ * // crdt, // "Automerge" | "YMap" | "YArray"
34
+ * // crdt === "Automerge" ? "data:json" : crdt === "YMap ? "data:map" : "data:array",
35
+ * // "store",
36
+ * // undefined,
37
+ * // );
38
+ *
39
+ * const { doc } = useDiscrete(documentIdPromise);
40
+ *
41
+ * $effect(() => {
42
+ * // If the CRDT document is still empty, we need to initialize it.
43
+ * if (doc && !doc.expenses) {
44
+ * doc.expenses = [];
45
+ * }
46
+ * });
47
+ *
48
+ * const createExpense = () => {
49
+ * // Note that we use *expense["@id"]* as a key in the expense list.
50
+ * // Every object added to a CRDT array gets a stable `@id` property assigned
51
+ * // which you can use for referencing objects in arrays even as
52
+ * // preceding objects are removed or added from the array.
53
+ * // The `@id` is an NURI with the schema `<documentId>:d:<object-specific id>`.
54
+ * // Since the `@id` is generated in the engine, the object is
55
+ * // *preliminarily given a mock id* which will be replaced immediately.
56
+ * expenses.push({
57
+ * title: "New expense",
58
+ * date: new Date().toISOString(),
59
+ * });
60
+ * };
61
+ *
62
+ *
63
+ * </script>
64
+ *
65
+ * <section>
66
+ * <div>
67
+ * <button on:click={() => createExpense({})}/>
68
+ *
69
+ * {#if !doc}
70
+ * Loading...
71
+ * {:else if doc.expenses.length === 0}
72
+ * <p>
73
+ * Nothing tracked yet - log your first purchase to kick things off.
74
+ * </p>
75
+ * {:else}
76
+ * {#each doc.expenses as expense, index (expense['@id']) }
77
+ * <ExpenseCard
78
+ * expense={expense}
79
+ * />
80
+ * {/each}
81
+ * {/if}
82
+ * </div>
83
+ * </section>
84
+ * ```
85
+ *
86
+ * ---
87
+ * In the ExpenseCard component:
88
+ * ```svelte
89
+ * let {
90
+ * expense = $bindable(),
91
+ * }: { expense: Expense; } = $props();
92
+ * </script>
93
+ *
94
+ * <div>
95
+ * <input
96
+ * bind:value={expense.title}
97
+ * />
98
+ * </div>
99
+ * ```
100
+ */
101
+ export function useDiscrete(documentIdOrPromise) {
102
+ let connection;
103
+ let isDestroyed = false;
104
+ let doc = $state.raw(undefined);
105
+ const init = (docId) => {
106
+ if (isDestroyed)
107
+ return;
108
+ connection = DiscreteOrmSubscription.getOrCreate(docId);
109
+ connection.readyPromise.then(() => {
110
+ if (isDestroyed) {
111
+ connection?.close();
112
+ return;
113
+ }
114
+ doc = useDeepSignal(connection.signalObject);
115
+ });
116
+ };
117
+ if (typeof documentIdOrPromise === "string") {
118
+ init(documentIdOrPromise);
119
+ }
120
+ else if (documentIdOrPromise === undefined) {
121
+ // There is nothing to initialize.
122
+ }
123
+ else {
124
+ documentIdOrPromise.then(init);
125
+ }
126
+ onDestroy(() => {
127
+ isDestroyed = true;
128
+ if (connection) {
129
+ connection.close();
130
+ }
131
+ });
132
+ return {
133
+ get doc() {
134
+ return doc;
135
+ },
136
+ };
137
+ }