@ng-org/orm 0.1.2-alpha.7 → 0.1.2-alpha.9
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/README.md +74 -92
- package/dist/connector/applyPatches.d.ts +13 -4
- package/dist/connector/applyPatches.d.ts.map +1 -1
- package/dist/connector/applyPatches.js +9 -5
- package/dist/connector/discrete/discreteOrmSubscriptionHandler.d.ts +156 -0
- package/dist/connector/discrete/discreteOrmSubscriptionHandler.d.ts.map +1 -0
- package/dist/connector/discrete/{discreteOrmConnectionHandler.js → discreteOrmSubscriptionHandler.js} +130 -19
- package/dist/connector/getObjects.js +2 -2
- package/dist/connector/initNg.d.ts +35 -0
- package/dist/connector/initNg.d.ts.map +1 -1
- package/dist/connector/initNg.js +35 -0
- package/dist/connector/insertObject.js +2 -2
- package/dist/connector/ormSubscriptionHandler.d.ts +166 -0
- package/dist/connector/ormSubscriptionHandler.d.ts.map +1 -0
- package/dist/connector/{ormConnectionHandler.js → ormSubscriptionHandler.js} +157 -32
- package/dist/frontendAdapters/react/useDiscrete.d.ts +89 -69
- package/dist/frontendAdapters/react/useDiscrete.d.ts.map +1 -1
- package/dist/frontendAdapters/react/useDiscrete.js +103 -81
- package/dist/frontendAdapters/react/useShape.d.ts +55 -55
- package/dist/frontendAdapters/react/useShape.d.ts.map +1 -1
- package/dist/frontendAdapters/react/useShape.js +71 -73
- package/dist/frontendAdapters/svelte/useDiscrete.svelte.d.ts +80 -71
- package/dist/frontendAdapters/svelte/useDiscrete.svelte.d.ts.map +1 -1
- package/dist/frontendAdapters/svelte/useDiscrete.svelte.js +102 -91
- package/dist/frontendAdapters/svelte/useShape.svelte.d.ts +70 -64
- package/dist/frontendAdapters/svelte/useShape.svelte.d.ts.map +1 -1
- package/dist/frontendAdapters/svelte/useShape.svelte.js +73 -62
- package/dist/frontendAdapters/svelte4/index.d.ts +5 -0
- package/dist/frontendAdapters/svelte4/index.d.ts.map +1 -0
- package/dist/frontendAdapters/svelte4/index.js +12 -0
- package/dist/frontendAdapters/svelte4/useDiscrete.svelte.d.ts +85 -0
- package/dist/frontendAdapters/svelte4/useDiscrete.svelte.d.ts.map +1 -0
- package/dist/frontendAdapters/svelte4/useDiscrete.svelte.js +124 -0
- package/dist/frontendAdapters/svelte4/useShape.svelte.d.ts +76 -0
- package/dist/frontendAdapters/svelte4/useShape.svelte.d.ts.map +1 -0
- package/dist/frontendAdapters/svelte4/useShape.svelte.js +84 -0
- package/dist/frontendAdapters/vue/useDiscrete.d.ts +87 -80
- package/dist/frontendAdapters/vue/useDiscrete.d.ts.map +1 -1
- package/dist/frontendAdapters/vue/useDiscrete.js +96 -84
- package/dist/frontendAdapters/vue/useShape.d.ts +57 -63
- package/dist/frontendAdapters/vue/useShape.d.ts.map +1 -1
- package/dist/frontendAdapters/vue/useShape.js +59 -64
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -3
- package/dist/types.d.ts +17 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +11 -2
- package/package.json +7 -3
- package/dist/connector/discrete/discreteOrmConnectionHandler.d.ts +0 -45
- package/dist/connector/discrete/discreteOrmConnectionHandler.d.ts.map +0 -1
- package/dist/connector/ormConnectionHandler.d.ts +0 -48
- package/dist/connector/ormConnectionHandler.d.ts.map +0 -1
|
@@ -7,35 +7,67 @@
|
|
|
7
7
|
// notice may not be copied, modified, or distributed except
|
|
8
8
|
// according to those terms.
|
|
9
9
|
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
10
|
+
import { normalizeScope } from "../types.js";
|
|
10
11
|
import { applyPatchesToDeepSignal } from "./applyPatches.js";
|
|
11
12
|
import { ngSession } from "./initNg.js";
|
|
12
13
|
import { deepSignal, watch as watchDeepSignal, batch, } from "@ng-org/alien-deepsignals";
|
|
13
14
|
import { deepPatchesToWasm } from "./utils.js";
|
|
15
|
+
/**
|
|
16
|
+
* Delay in ms to wait before closing subscription.\
|
|
17
|
+
* Useful when a hook unsubscribes and resubscribes in a short time interval
|
|
18
|
+
* so that no new subscriptions need to be set up.
|
|
19
|
+
*/
|
|
14
20
|
const WAIT_BEFORE_CLOSE = 500;
|
|
15
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Class for managing RDF-based ORM subscriptions with the engine.
|
|
23
|
+
*
|
|
24
|
+
* You have two options on how to interact with the ORM:
|
|
25
|
+
* - Use a hook for your favorite framework under `@ng-org/orm/react|vue|svelte`
|
|
26
|
+
* - Call {@link OrmSubscription.getOrCreate} to create a subscription manually
|
|
27
|
+
*
|
|
28
|
+
* For more information about RDF-based ORM subscriptions,
|
|
29
|
+
* see the README and follow the tutorial.
|
|
30
|
+
*/
|
|
31
|
+
export class OrmSubscription {
|
|
32
|
+
/** Global store of all subscriptions. We use that for pooling. */
|
|
16
33
|
static idToEntry = new Map();
|
|
17
|
-
/**
|
|
18
|
-
* Delay in ms to wait before closing connection.\
|
|
19
|
-
* Useful when a hook unsubscribes and resubscribes in a short time interval
|
|
20
|
-
* so that no new connections need to be set up.
|
|
21
|
-
*/
|
|
34
|
+
/** The shape type that is subscribed to. */
|
|
22
35
|
shapeType;
|
|
36
|
+
/** The {@link Scope} of the subscription. */
|
|
23
37
|
scope;
|
|
38
|
+
/**
|
|
39
|
+
* The signalObject containing all data matching the shape and scope
|
|
40
|
+
* (once subscription is established).
|
|
41
|
+
* The object is of type {@link DeepSignalSet} which
|
|
42
|
+
* to the outside behaves like a regular set but has a couple of
|
|
43
|
+
* additional features:
|
|
44
|
+
* - Modifications are immediately propagated back to the database.
|
|
45
|
+
* - Database changes are immediately reflected in the object.
|
|
46
|
+
* - `.getBy(graphIri, subjectIri)` utility for quicker access to objects in set.
|
|
47
|
+
* - `.first()` utility to get the first element added to the set.
|
|
48
|
+
* - the iterator utilities, e.g. `.map()`, `.filter()`, ...
|
|
49
|
+
* - Watch for object changes using {@link watchDeepSignal}.
|
|
50
|
+
*/
|
|
24
51
|
signalObject;
|
|
25
52
|
stopSignalListening;
|
|
53
|
+
/** The subscription id kept as an identifier for communicating with the verifier. */
|
|
26
54
|
subscriptionId;
|
|
55
|
+
/** The number of OrmSubscriptions with the same shape and scope (for pooling). */
|
|
27
56
|
refCount;
|
|
28
57
|
/** Identifier as a combination of shape type and scope. Prevents duplications. */
|
|
29
58
|
identifier;
|
|
59
|
+
/** When true, modifications from the signalObject are not processed. */
|
|
30
60
|
suspendDeepWatcher;
|
|
61
|
+
/** True, if a transaction is running. */
|
|
31
62
|
inTransaction = false;
|
|
32
63
|
/** Aggregation of patches to be sent when in transaction. */
|
|
33
64
|
pendingPatches;
|
|
65
|
+
/** **Await to ensure that the subscription is established and the data arrived.** */
|
|
34
66
|
readyPromise;
|
|
35
|
-
|
|
36
|
-
/**
|
|
67
|
+
closeOrmSubscription;
|
|
68
|
+
/** Function to call once initial data has been applied. */
|
|
37
69
|
resolveReady;
|
|
38
|
-
// FinalizationRegistry to clean up
|
|
70
|
+
// FinalizationRegistry to clean up subscriptions when signal objects are GC'd.
|
|
39
71
|
static cleanupSignalRegistry = typeof FinalizationRegistry === "function"
|
|
40
72
|
? new FinalizationRegistry((connectionId) => {
|
|
41
73
|
console.log("finalization called for", connectionId);
|
|
@@ -49,32 +81,33 @@ export class OrmConnection {
|
|
|
49
81
|
: null;
|
|
50
82
|
constructor(shapeType, scope) {
|
|
51
83
|
// @ts-expect-error
|
|
52
|
-
window.ormSignalConnections =
|
|
84
|
+
window.ormSignalConnections = OrmSubscription.idToEntry;
|
|
53
85
|
// @ts-expect-error
|
|
54
|
-
window.
|
|
86
|
+
window.OrmSubscription = OrmSubscription;
|
|
55
87
|
this.shapeType = shapeType;
|
|
56
|
-
|
|
88
|
+
const normalizedScope = normalizeScope(scope);
|
|
89
|
+
this.scope = normalizedScope;
|
|
57
90
|
this.refCount = 1;
|
|
58
|
-
this.
|
|
91
|
+
this.closeOrmSubscription = () => { };
|
|
59
92
|
this.suspendDeepWatcher = false;
|
|
60
|
-
this.identifier = `${shapeType.shape}|${canonicalScope(
|
|
93
|
+
this.identifier = `${shapeType.shape}|${canonicalScope(normalizedScope)}`;
|
|
61
94
|
this.signalObject = deepSignal(new Set(), {
|
|
62
95
|
propGenerator: this.signalObjectPropGenerator,
|
|
63
96
|
// Don't set syntheticIdPropertyName - let propGenerator handle all ID logic
|
|
64
97
|
readOnlyProps: ["@id", "@graph"],
|
|
65
98
|
});
|
|
66
99
|
// Schedule cleanup of the connection when the signal object is GC'd.
|
|
67
|
-
|
|
100
|
+
OrmSubscription.cleanupSignalRegistry?.register(this.signalObject, this.identifier, this.signalObject);
|
|
68
101
|
// Add listener to deep signal object to report changes back to wasm land.
|
|
69
102
|
const { stopListening } = watchDeepSignal(this.signalObject, this.onSignalObjectUpdate);
|
|
70
103
|
this.stopSignalListening = stopListening;
|
|
71
|
-
//
|
|
104
|
+
// Set promise to be resolved when data arrived from engine.
|
|
72
105
|
this.readyPromise = new Promise((resolve) => {
|
|
73
106
|
this.resolveReady = resolve;
|
|
74
107
|
});
|
|
75
108
|
ngSession.then(async ({ ng, session }) => {
|
|
76
109
|
try {
|
|
77
|
-
this.
|
|
110
|
+
this.closeOrmSubscription = await ng.orm_start_graph(normalizedScope.graphs, normalizedScope.subjects, shapeType, session.session_id, this.onBackendMessage);
|
|
78
111
|
}
|
|
79
112
|
catch (e) {
|
|
80
113
|
console.error(e);
|
|
@@ -82,11 +115,79 @@ export class OrmConnection {
|
|
|
82
115
|
});
|
|
83
116
|
}
|
|
84
117
|
/**
|
|
85
|
-
*
|
|
86
|
-
* @
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* @
|
|
118
|
+
* Returns an OrmSubscription which subscribes to the given
|
|
119
|
+
* {@link ShapeType} and {@link Scope} in a 2-way binding.
|
|
120
|
+
*
|
|
121
|
+
* You **find the data** and objects matching the shape and scope
|
|
122
|
+
* in the **`signalObject`** once {@link readyPromise} resolves. This is a {@link DeepSignalSet} which
|
|
123
|
+
* to the outside behaves like a regular set but has a couple of
|
|
124
|
+
* additional features:
|
|
125
|
+
* - Modifications are propagated back to the database.
|
|
126
|
+
* Note that multiple immediate modifications in the same task,
|
|
127
|
+
* e.g. `obj[0] = "foo"; obj[1] = "bar"` are batched together
|
|
128
|
+
* and sent in a subsequent microtask.
|
|
129
|
+
* - Database changes are immediately reflected in the object.
|
|
130
|
+
* - `.getBy(graphIri, subjectIri)` utility for quicker access to objects in set.
|
|
131
|
+
* - `.first()` utility to get the first element added to the set.
|
|
132
|
+
* - the iterator utilities, e.g. `.map()`, `.filter()`, ...
|
|
133
|
+
* - Watch for object changes using {@link watchDeepSignal}.
|
|
134
|
+
*
|
|
135
|
+
* You can use **transactions**, to prevent excessive calls to the database
|
|
136
|
+
* with {@link beginTransaction} and {@link commitTransaction}.
|
|
137
|
+
*
|
|
138
|
+
* In many cases, you are advised to use a hook for your
|
|
139
|
+
* favorite framework under `@ng-org/orm/react|vue|svelte`
|
|
140
|
+
* instead of calling `getOrCreate` directly.
|
|
141
|
+
*
|
|
142
|
+
* Call `{@link close}, to close the subscription.
|
|
143
|
+
*
|
|
144
|
+
* Note: If another call to `getOrCreate` was previously made
|
|
145
|
+
* and `close` was not called on it (or only shortly after),
|
|
146
|
+
* it will return the same OrmSubscription.
|
|
147
|
+
*
|
|
148
|
+
* @param shapeType The {@link ShapeType}
|
|
149
|
+
* @param scope The {@link Scope}. If no scope is given, the whole store is considered.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* // We assume you have created a graph document already, as below.
|
|
154
|
+
* // const documentId = await ng.doc_create(
|
|
155
|
+
* // session_id,
|
|
156
|
+
* // "Graph",
|
|
157
|
+
* // "data:graph",
|
|
158
|
+
* // "store",
|
|
159
|
+
* // undefined
|
|
160
|
+
* // );
|
|
161
|
+
* const subscription = OrmSubscription.getOrCreate(ExpenseShapeType, {graphs: [graphIri]});
|
|
162
|
+
* // Wait for data.
|
|
163
|
+
* await subscription.readyPromise;
|
|
164
|
+
*
|
|
165
|
+
* const expense = subscription.signalObject.first()
|
|
166
|
+
* expense.name = "updated name";
|
|
167
|
+
* expense.description = "updated description";
|
|
168
|
+
*
|
|
169
|
+
* // Await promise to run the below code in a new task.
|
|
170
|
+
* // That will push the changes to the database.
|
|
171
|
+
* await Promise.resolve();
|
|
172
|
+
*
|
|
173
|
+
* // Here, the expense modifications have been have been committed
|
|
174
|
+
* // (unless you had previously called subscription.beginTransaction()).
|
|
175
|
+
* // The data is available in subscriptions running on a different device too.
|
|
176
|
+
*
|
|
177
|
+
* subscription.close();
|
|
178
|
+
* // If you create a new subscription with the same document within a couple of 100ms,
|
|
179
|
+
* // The subscription hasn't been closed and the old one is returned so that the data
|
|
180
|
+
* // is available instantly. This is especially useful in the context of frontend frameworks.
|
|
181
|
+
* const subscription2 = OrmSubscription.getOrCreate(ExpenseShapeType, {graphs: [graphIri]});
|
|
182
|
+
*
|
|
183
|
+
* subscription2.signalObject.add({
|
|
184
|
+
* "@graph": graphIri,
|
|
185
|
+
* "@id": "", // Leave empty to auto-assign one.
|
|
186
|
+
* name": "A new expense",
|
|
187
|
+
* description: "A new description"
|
|
188
|
+
* });
|
|
189
|
+
*
|
|
190
|
+
* subscription2.close()
|
|
90
191
|
*/
|
|
91
192
|
static getOrCreate = (shapeType, scope) => {
|
|
92
193
|
const scopeKey = canonicalScope(scope);
|
|
@@ -95,28 +196,39 @@ export class OrmConnection {
|
|
|
95
196
|
// If we already have an object for this shape+scope,
|
|
96
197
|
// return it and just increase the reference count.
|
|
97
198
|
// Otherwise, create new one.
|
|
98
|
-
const existingConnection =
|
|
199
|
+
const existingConnection = OrmSubscription.idToEntry.get(identifier);
|
|
99
200
|
if (existingConnection) {
|
|
100
201
|
existingConnection.refCount += 1;
|
|
101
202
|
return existingConnection;
|
|
102
203
|
}
|
|
103
204
|
else {
|
|
104
|
-
const newConnection = new
|
|
105
|
-
|
|
205
|
+
const newConnection = new OrmSubscription(shapeType, scope);
|
|
206
|
+
OrmSubscription.idToEntry.set(identifier, newConnection);
|
|
106
207
|
return newConnection;
|
|
107
208
|
}
|
|
108
209
|
};
|
|
210
|
+
/**
|
|
211
|
+
* Stop the subscription.
|
|
212
|
+
*
|
|
213
|
+
* **If there is more than one subscription with the same shape type and scope
|
|
214
|
+
* the orm subscription will persist.**
|
|
215
|
+
*
|
|
216
|
+
* Additionally, the closing of the subscription is delayed by a couple hundred milliseconds
|
|
217
|
+
* so that when frontend frameworks unmount and soon mound a component again with the same
|
|
218
|
+
* shape type and scope, no new orm subscription has be set up with the engine.
|
|
219
|
+
*/
|
|
109
220
|
close = () => {
|
|
110
221
|
setTimeout(() => {
|
|
111
222
|
if (this.refCount > 0)
|
|
112
223
|
this.refCount--;
|
|
113
224
|
if (this.refCount === 0) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
this.
|
|
225
|
+
OrmSubscription.idToEntry.delete(this.identifier);
|
|
226
|
+
OrmSubscription.cleanupSignalRegistry?.unregister(this.signalObject);
|
|
227
|
+
this.closeOrmSubscription();
|
|
117
228
|
}
|
|
118
229
|
}, WAIT_BEFORE_CLOSE);
|
|
119
230
|
};
|
|
231
|
+
/** Handle updates (patches) coming from signal object modifications. */
|
|
120
232
|
onSignalObjectUpdate = async ({ patches }) => {
|
|
121
233
|
if (this.suspendDeepWatcher || !patches.length)
|
|
122
234
|
return;
|
|
@@ -131,6 +243,7 @@ export class OrmConnection {
|
|
|
131
243
|
await this.readyPromise;
|
|
132
244
|
ng.graph_orm_update(this.subscriptionId, ormPatches, session.session_id);
|
|
133
245
|
};
|
|
246
|
+
/** Handle messages coming from the engine (initial data or patches). */
|
|
134
247
|
onBackendMessage = (message) => {
|
|
135
248
|
const data = message?.V0;
|
|
136
249
|
if (data?.GraphOrmInitial) {
|
|
@@ -140,7 +253,7 @@ export class OrmConnection {
|
|
|
140
253
|
this.onBackendUpdate(data.GraphOrmUpdate);
|
|
141
254
|
}
|
|
142
255
|
else {
|
|
143
|
-
console.warn("Received unknown ORM message from
|
|
256
|
+
console.warn("Received unknown ORM message from engine", message);
|
|
144
257
|
}
|
|
145
258
|
};
|
|
146
259
|
handleInitialResponse = ([initialData, subscriptionId]) => {
|
|
@@ -162,6 +275,7 @@ export class OrmConnection {
|
|
|
162
275
|
this.resolveReady();
|
|
163
276
|
});
|
|
164
277
|
};
|
|
278
|
+
/** Handle incoming patches from the engine */
|
|
165
279
|
onBackendUpdate = (patches) => {
|
|
166
280
|
this.suspendDeepWatcher = true;
|
|
167
281
|
applyPatchesToDeepSignal(this.signalObject, patches, "set");
|
|
@@ -212,6 +326,13 @@ export class OrmConnection {
|
|
|
212
326
|
syntheticId: graphIri + "|" + subjectIri,
|
|
213
327
|
};
|
|
214
328
|
};
|
|
329
|
+
/**
|
|
330
|
+
* Begins a transaction that batches changes to be committed to the database.
|
|
331
|
+
* This is useful for performance reasons.
|
|
332
|
+
*
|
|
333
|
+
* Note that this does not disable reactivity of the `signalObject`.
|
|
334
|
+
* Modifications keep being rendered.
|
|
335
|
+
*/
|
|
215
336
|
beginTransaction = () => {
|
|
216
337
|
this.inTransaction = true;
|
|
217
338
|
this.pendingPatches = [];
|
|
@@ -221,6 +342,10 @@ export class OrmConnection {
|
|
|
221
342
|
const { stopListening } = watchDeepSignal(this.signalObject, this.onSignalObjectUpdate, { triggerInstantly: true });
|
|
222
343
|
this.stopSignalListening = stopListening;
|
|
223
344
|
};
|
|
345
|
+
/**
|
|
346
|
+
* Commits a transactions sending all modifications made during the transaction
|
|
347
|
+
* (started with `beginTransaction`) to the database.
|
|
348
|
+
*/
|
|
224
349
|
commitTransaction = async () => {
|
|
225
350
|
if (!this.inTransaction) {
|
|
226
351
|
throw new Error("No transaction is open. Call `beginTransaction` first.");
|
|
@@ -229,16 +354,16 @@ export class OrmConnection {
|
|
|
229
354
|
await this.readyPromise;
|
|
230
355
|
this.inTransaction = false;
|
|
231
356
|
if (this.pendingPatches?.length == 0) {
|
|
232
|
-
// Nothing to send to the
|
|
357
|
+
// Nothing to send to the engine.
|
|
233
358
|
}
|
|
234
359
|
else {
|
|
235
|
-
// Send patches to
|
|
360
|
+
// Send patches to engine.
|
|
236
361
|
await ng.graph_orm_update(this.subscriptionId, this.pendingPatches, session.session_id);
|
|
237
362
|
}
|
|
238
363
|
this.pendingPatches = undefined;
|
|
239
364
|
// Go back to the regular object modification listening where we want batching
|
|
240
365
|
// scheduled in a microtask only triggered after the main task.
|
|
241
|
-
// This way we prevent excessive calls to the
|
|
366
|
+
// This way we prevent excessive calls to the engine.
|
|
242
367
|
this.stopSignalListening();
|
|
243
368
|
const { stopListening } = watchDeepSignal(this.signalObject, this.onSignalObjectUpdate);
|
|
244
369
|
this.stopSignalListening = stopListening;
|
|
@@ -1,84 +1,104 @@
|
|
|
1
1
|
import { DeepSignal } from "@ng-org/alien-deepsignals";
|
|
2
2
|
import { DiscreteArray, DiscreteObject } from "../../types.ts";
|
|
3
3
|
/**
|
|
4
|
-
* Hook to subscribe to discrete (JSON) CRDT
|
|
4
|
+
* Hook to subscribe to an existing discrete (JSON) CRDT document.
|
|
5
5
|
* You can modify the returned object like any other JSON object. Changes are immediately
|
|
6
|
-
* reflected in the CRDT.
|
|
6
|
+
* reflected in the CRDT document.
|
|
7
7
|
*
|
|
8
8
|
* Establishes a 2-way binding: Modifications to the object are immediately committed,
|
|
9
|
-
* changes coming from the
|
|
9
|
+
* changes coming from the engine (or other components) cause an immediate rerender.
|
|
10
10
|
*
|
|
11
|
-
* In comparison to {@link
|
|
11
|
+
* In comparison to {@link reactUseShape}, discrete CRDTs are untyped.
|
|
12
12
|
* You can put any JSON data inside and need to validate the schema yourself.
|
|
13
13
|
*
|
|
14
|
-
* @param documentId The IRI of the
|
|
14
|
+
* @param documentId The IRI of the CRDT document.
|
|
15
15
|
* @returns An object that contains as `data` the reactive DeepSignal object or undefined if `documentId` is undefined.
|
|
16
16
|
*
|
|
17
|
-
|
|
18
|
-
```tsx
|
|
19
|
-
// We assume you have created a CRDT document already, as below.
|
|
20
|
-
// const documentId = await ng.doc_create(
|
|
21
|
-
// session_id,
|
|
22
|
-
// crdt, // "Automerge" | "YMap" | "YArray". YArray is for root arrays, the other two have objects at root.
|
|
23
|
-
// crdt === "Automerge" ? "data:json" : crdt === "YMap ? "data:map" : "data:array",
|
|
24
|
-
// "store",
|
|
25
|
-
// undefined
|
|
26
|
-
// );
|
|
27
|
-
|
|
28
|
-
function Expenses({documentId}: {documentId: string}) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* // We assume you have created a CRDT document already, as below.
|
|
20
|
+
* // const documentId = await ng.doc_create(
|
|
21
|
+
* // session_id,
|
|
22
|
+
* // crdt, // "Automerge" | "YMap" | "YArray". YArray is for root arrays, the other two have objects at root.
|
|
23
|
+
* // crdt === "Automerge" ? "data:json" : crdt === "YMap ? "data:map" : "data:array",
|
|
24
|
+
* // "store",
|
|
25
|
+
* // undefined
|
|
26
|
+
* // );
|
|
27
|
+
*
|
|
28
|
+
* function Expenses({documentId}: {documentId: string}) {
|
|
29
|
+
* const { data } = useDiscrete(documentId);
|
|
30
|
+
*
|
|
31
|
+
* // If the CRDT document is still empty, we need to initialize it.
|
|
32
|
+
* if (data && !data.expenses) {
|
|
33
|
+
* data.expenses = [];
|
|
34
|
+
* }
|
|
35
|
+
* const expenses = data?.expenses;
|
|
36
|
+
*
|
|
37
|
+
* const createExpense = useCallback(() => {
|
|
38
|
+
* // Note that we use *expense["@id"]* as a key in the expense list.
|
|
39
|
+
* // Every object added to a CRDT array gets a stable `@id` property assigned
|
|
40
|
+
* // which you can use for referencing objects in arrays even as
|
|
41
|
+
* // objects are removed or added from the array.
|
|
42
|
+
* // The `@id` is an IRI with the schema `<documentId>:d:<object-specific id>`.
|
|
43
|
+
* // Since the `@id` is generated in the engine, the object is
|
|
44
|
+
* // *preliminarily given a mock id* which will be replaced immediately.
|
|
45
|
+
* expenses.push({
|
|
46
|
+
* title: "New expense",
|
|
47
|
+
* date: new Date().toISOString(),
|
|
48
|
+
* });
|
|
49
|
+
* },
|
|
50
|
+
* [expenses]
|
|
51
|
+
* );
|
|
52
|
+
*
|
|
53
|
+
* // Still loading (data undefined)?
|
|
54
|
+
* if (!data) return <div>Loading...</div>;
|
|
55
|
+
*
|
|
56
|
+
* return (
|
|
57
|
+
* <div>
|
|
58
|
+
* <button
|
|
59
|
+
* onClick={() => createExpense()}
|
|
60
|
+
* >
|
|
61
|
+
* + Add expense
|
|
62
|
+
* </button>
|
|
63
|
+
* <div>
|
|
64
|
+
* {expenses.length === 0 ? (
|
|
65
|
+
* <p>
|
|
66
|
+
* No expenses yet.
|
|
67
|
+
* </p>
|
|
68
|
+
* ) : (
|
|
69
|
+
* expenses.map((expense) => (
|
|
70
|
+
* <ExpenseCard
|
|
71
|
+
* key={expense["@id"]}
|
|
72
|
+
* expense={expense}
|
|
73
|
+
* />
|
|
74
|
+
* ))
|
|
75
|
+
* )}
|
|
76
|
+
* </div>
|
|
77
|
+
* </div>
|
|
78
|
+
* );
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* ---
|
|
83
|
+
* In the ExpenseCard component:
|
|
84
|
+
* ```tsx
|
|
85
|
+
* function ExpenseCard({expense}: {expense: Expense}) {
|
|
86
|
+
* return (
|
|
87
|
+
* <input
|
|
88
|
+
* value={expense.title}
|
|
89
|
+
* onChange={(e) => {
|
|
90
|
+
* expense.title = e.target.value; // Changes trigger rerender.
|
|
91
|
+
* }}
|
|
92
|
+
* />
|
|
93
|
+
* <div>
|
|
94
|
+
* <p>Date</p>
|
|
95
|
+
* <p>{expense.data}
|
|
96
|
+
* </div
|
|
97
|
+
* );
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
80
100
|
*/
|
|
81
101
|
export declare function useDiscrete(documentId: string | undefined): {
|
|
82
|
-
|
|
102
|
+
doc: DeepSignal<DiscreteArray | DiscreteObject> | undefined;
|
|
83
103
|
};
|
|
84
104
|
//# sourceMappingURL=useDiscrete.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDiscrete.d.ts","sourceRoot":"","sources":["../../../src/frontendAdapters/react/useDiscrete.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAI/D
|
|
1
|
+
{"version":3,"file":"useDiscrete.d.ts","sourceRoot":"","sources":["../../../src/frontendAdapters/react/useDiscrete.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAI/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiGG;AAEH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS;;EAqDzD"}
|