@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.
Files changed (53) hide show
  1. package/README.md +74 -92
  2. package/dist/connector/applyPatches.d.ts +13 -4
  3. package/dist/connector/applyPatches.d.ts.map +1 -1
  4. package/dist/connector/applyPatches.js +9 -5
  5. package/dist/connector/discrete/discreteOrmSubscriptionHandler.d.ts +156 -0
  6. package/dist/connector/discrete/discreteOrmSubscriptionHandler.d.ts.map +1 -0
  7. package/dist/connector/discrete/{discreteOrmConnectionHandler.js → discreteOrmSubscriptionHandler.js} +130 -19
  8. package/dist/connector/getObjects.js +2 -2
  9. package/dist/connector/initNg.d.ts +35 -0
  10. package/dist/connector/initNg.d.ts.map +1 -1
  11. package/dist/connector/initNg.js +35 -0
  12. package/dist/connector/insertObject.js +2 -2
  13. package/dist/connector/ormSubscriptionHandler.d.ts +166 -0
  14. package/dist/connector/ormSubscriptionHandler.d.ts.map +1 -0
  15. package/dist/connector/{ormConnectionHandler.js → ormSubscriptionHandler.js} +157 -32
  16. package/dist/frontendAdapters/react/useDiscrete.d.ts +89 -69
  17. package/dist/frontendAdapters/react/useDiscrete.d.ts.map +1 -1
  18. package/dist/frontendAdapters/react/useDiscrete.js +103 -81
  19. package/dist/frontendAdapters/react/useShape.d.ts +55 -55
  20. package/dist/frontendAdapters/react/useShape.d.ts.map +1 -1
  21. package/dist/frontendAdapters/react/useShape.js +71 -73
  22. package/dist/frontendAdapters/svelte/useDiscrete.svelte.d.ts +80 -71
  23. package/dist/frontendAdapters/svelte/useDiscrete.svelte.d.ts.map +1 -1
  24. package/dist/frontendAdapters/svelte/useDiscrete.svelte.js +102 -91
  25. package/dist/frontendAdapters/svelte/useShape.svelte.d.ts +70 -64
  26. package/dist/frontendAdapters/svelte/useShape.svelte.d.ts.map +1 -1
  27. package/dist/frontendAdapters/svelte/useShape.svelte.js +73 -62
  28. package/dist/frontendAdapters/svelte4/index.d.ts +5 -0
  29. package/dist/frontendAdapters/svelte4/index.d.ts.map +1 -0
  30. package/dist/frontendAdapters/svelte4/index.js +12 -0
  31. package/dist/frontendAdapters/svelte4/useDiscrete.svelte.d.ts +85 -0
  32. package/dist/frontendAdapters/svelte4/useDiscrete.svelte.d.ts.map +1 -0
  33. package/dist/frontendAdapters/svelte4/useDiscrete.svelte.js +124 -0
  34. package/dist/frontendAdapters/svelte4/useShape.svelte.d.ts +76 -0
  35. package/dist/frontendAdapters/svelte4/useShape.svelte.d.ts.map +1 -0
  36. package/dist/frontendAdapters/svelte4/useShape.svelte.js +84 -0
  37. package/dist/frontendAdapters/vue/useDiscrete.d.ts +87 -80
  38. package/dist/frontendAdapters/vue/useDiscrete.d.ts.map +1 -1
  39. package/dist/frontendAdapters/vue/useDiscrete.js +96 -84
  40. package/dist/frontendAdapters/vue/useShape.d.ts +57 -63
  41. package/dist/frontendAdapters/vue/useShape.d.ts.map +1 -1
  42. package/dist/frontendAdapters/vue/useShape.js +59 -64
  43. package/dist/index.d.ts +6 -3
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +14 -3
  46. package/dist/types.d.ts +17 -7
  47. package/dist/types.d.ts.map +1 -1
  48. package/dist/types.js +11 -2
  49. package/package.json +7 -3
  50. package/dist/connector/discrete/discreteOrmConnectionHandler.d.ts +0 -45
  51. package/dist/connector/discrete/discreteOrmConnectionHandler.d.ts.map +0 -1
  52. package/dist/connector/ormConnectionHandler.d.ts +0 -48
  53. 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
- export class OrmConnection {
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
- closeOrmConnection;
36
- /** Promise that resolves once initial data has been applied. */
67
+ closeOrmSubscription;
68
+ /** Function to call once initial data has been applied. */
37
69
  resolveReady;
38
- // FinalizationRegistry to clean up connections when signal objects are GC'd.
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 = OrmConnection.idToEntry;
84
+ window.ormSignalConnections = OrmSubscription.idToEntry;
53
85
  // @ts-expect-error
54
- window.OrmConnection = OrmConnection;
86
+ window.OrmSubscription = OrmSubscription;
55
87
  this.shapeType = shapeType;
56
- this.scope = scope;
88
+ const normalizedScope = normalizeScope(scope);
89
+ this.scope = normalizedScope;
57
90
  this.refCount = 1;
58
- this.closeOrmConnection = () => { };
91
+ this.closeOrmSubscription = () => { };
59
92
  this.suspendDeepWatcher = false;
60
- this.identifier = `${shapeType.shape}|${canonicalScope(scope)}`;
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
- OrmConnection.cleanupSignalRegistry?.register(this.signalObject, this.identifier, this.signalObject);
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
- // Initialize per-entry readiness promise that resolves in setUpConnection
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.closeOrmConnection = await ng.orm_start_graph(scope.graphs ?? ["did:ng:i"], scope.subjects ?? [], shapeType, session.session_id, this.onBackendMessage);
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
- * Get or create a connection which contains the ORM and lifecycle methods.
86
- * @param shapeType
87
- * @param scope
88
- * @param ng
89
- * @returns
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 = OrmConnection.idToEntry.get(identifier);
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 OrmConnection(shapeType, scope);
105
- OrmConnection.idToEntry.set(identifier, newConnection);
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
- OrmConnection.idToEntry.delete(this.identifier);
115
- OrmConnection.cleanupSignalRegistry?.unregister(this.signalObject);
116
- this.closeOrmConnection();
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 backend", message);
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 backend.
357
+ // Nothing to send to the engine.
233
358
  }
234
359
  else {
235
- // Send patches to backend.
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 backend.
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 documents.
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 backend (or other components) cause an immediate rerender.
9
+ * changes coming from the engine (or other components) cause an immediate rerender.
10
10
  *
11
- * In comparison to {@link useShape}, discrete CRDTs are untyped.
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 crdt document.
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
- *@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(documentIdPromise);
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
- expenses.add({
39
- title: "New expense",
40
- dateOfPurchase: obj.dateOfPurchase ?? new Date().toISOString(),
41
- });
42
- },
43
- [expenses]
44
- );
45
-
46
- // Loaded already?
47
- if (!expenses) return <div>Loading...</div>;
48
-
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 from the array. The ID is an IRI with the schema `<documentId>:d:<object-specific id>`.
53
- // Since the `@id` is generated in the backend, the object is preliminarily
54
- // given a mock id which will be replaced immediately
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
- }
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
- data: DeepSignal<DiscreteArray | DiscreteObject> | undefined;
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6EG;AAEH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS;;EAmDzD"}
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"}