jazz-tools 0.7.1 → 0.7.6
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/.turbo/turbo-build.log +2 -2
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +33 -33
- package/CHANGELOG.md +177 -165
- package/LICENSE.txt +1 -1
- package/dist/coValues/account.js +41 -4
- package/dist/coValues/account.js.map +1 -1
- package/dist/coValues/coList.js +179 -8
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/coMap.js +141 -5
- package/dist/coValues/coMap.js.map +1 -1
- package/dist/coValues/coStream.js +49 -1
- package/dist/coValues/coStream.js.map +1 -1
- package/dist/coValues/group.js +26 -2
- package/dist/coValues/group.js.map +1 -1
- package/dist/coValues/interfaces.js +32 -48
- package/dist/coValues/interfaces.js.map +1 -1
- package/dist/implementation/schema.js.map +1 -1
- package/dist/implementation/subscriptionScope.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/tests/deepLoading.test.js +2 -2
- package/dist/tests/deepLoading.test.js.map +1 -1
- package/dist/tests/groupsAndAccounts.test.js +3 -3
- package/dist/tests/groupsAndAccounts.test.js.map +1 -1
- package/package.json +1 -1
- package/src/coValues/account.ts +106 -13
- package/src/coValues/coList.ts +236 -23
- package/src/coValues/coMap.ts +194 -11
- package/src/coValues/coStream.ts +128 -11
- package/src/coValues/group.ts +78 -5
- package/src/coValues/interfaces.ts +141 -211
- package/src/implementation/schema.ts +7 -4
- package/src/implementation/subscriptionScope.ts +3 -3
- package/src/index.ts +7 -0
- package/src/tests/deepLoading.test.ts +6 -3
- package/src/tests/groupsAndAccounts.test.ts +4 -4
package/src/coValues/coList.ts
CHANGED
@@ -6,31 +6,69 @@ import type {
|
|
6
6
|
SchemaFor,
|
7
7
|
ID,
|
8
8
|
RefEncoded,
|
9
|
-
ClassOf,
|
10
9
|
UnCo,
|
11
10
|
CoValueClass,
|
11
|
+
DepthsIn,
|
12
|
+
DeeplyLoaded,
|
13
|
+
UnavailableError,
|
14
|
+
AccountCtx,
|
15
|
+
CoValueFromRaw,
|
12
16
|
} from "../internal.js";
|
13
17
|
import {
|
14
18
|
Account,
|
15
|
-
CoValueBase,
|
16
19
|
Group,
|
17
20
|
InitValues,
|
18
21
|
ItemsSym,
|
19
22
|
Ref,
|
20
23
|
SchemaInit,
|
21
24
|
co,
|
25
|
+
ensureCoValueLoaded,
|
22
26
|
inspect,
|
23
27
|
isRefEncoded,
|
28
|
+
loadCoValue,
|
29
|
+
loadCoValueEf,
|
24
30
|
makeRefs,
|
31
|
+
subscribeToCoValue,
|
32
|
+
subscribeToCoValueEf,
|
33
|
+
subscribeToExistingCoValue,
|
25
34
|
} from "../internal.js";
|
26
35
|
import { encodeSync, decodeSync } from "@effect/schema/Schema";
|
27
|
-
|
28
|
-
|
36
|
+
import { Effect, Stream } from "effect";
|
37
|
+
|
38
|
+
/**
|
39
|
+
* CoLists are collaborative versions of plain arrays.
|
40
|
+
*
|
41
|
+
* * @categoryDescription Content
|
42
|
+
* You can access items on a `CoList` as if they were normal items on a plain array, using `[]` notation, etc.
|
43
|
+
*
|
44
|
+
* Since `CoList` is a subclass of `Array`, you can use all the normal array methods like `push`, `pop`, `splice`, etc.
|
45
|
+
*
|
46
|
+
* ```ts
|
47
|
+
* colorList[0];
|
48
|
+
* colorList[3] = "yellow";
|
49
|
+
* colorList.push("Kawazaki Green");
|
50
|
+
* colorList.splice(1, 1);
|
51
|
+
* ```
|
52
|
+
*
|
53
|
+
* @category CoValues
|
54
|
+
*/
|
29
55
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
30
|
-
export class CoList<Item = any>
|
31
|
-
|
32
|
-
|
33
|
-
|
56
|
+
export class CoList<Item = any> extends Array<Item> implements CoValue {
|
57
|
+
/**
|
58
|
+
* Declare a `CoList` by subclassing `CoList.Of(...)` and passing the item schema using `co`.
|
59
|
+
*
|
60
|
+
* @example
|
61
|
+
* ```ts
|
62
|
+
* class ColorList extends CoList.Of(
|
63
|
+
* co.string
|
64
|
+
* ) {}
|
65
|
+
* class AnimalList extends CoList.Of(
|
66
|
+
* co.ref(Animal)
|
67
|
+
* ) {}
|
68
|
+
* ```
|
69
|
+
*
|
70
|
+
* @category Declaration
|
71
|
+
*/
|
34
72
|
static Of<Item>(item: Item): typeof CoList<Item> {
|
35
73
|
// TODO: cache superclass for item class
|
36
74
|
return class CoListOf extends CoList<Item> {
|
@@ -38,36 +76,62 @@ export class CoList<Item = any>
|
|
38
76
|
};
|
39
77
|
}
|
40
78
|
|
41
|
-
/**
|
79
|
+
/**
|
80
|
+
* @ignore
|
81
|
+
* @deprecated Use UPPERCASE `CoList.Of` instead! */
|
42
82
|
static of(..._args: never): never {
|
43
83
|
throw new Error("Can't use Array.of with CoLists");
|
44
84
|
}
|
45
85
|
|
86
|
+
/**
|
87
|
+
* The ID of this `CoList`
|
88
|
+
* @category Content */
|
46
89
|
id!: ID<this>;
|
90
|
+
/** @category Type Helpers */
|
47
91
|
_type!: "CoList";
|
48
92
|
static {
|
49
93
|
this.prototype._type = "CoList";
|
50
94
|
}
|
95
|
+
/** @category Internals */
|
51
96
|
_raw!: RawCoList;
|
97
|
+
/** @category Internals */
|
52
98
|
_instanceID!: string;
|
53
99
|
|
54
100
|
/** @internal This is only a marker type and doesn't exist at runtime */
|
55
101
|
[ItemsSym]!: Item;
|
102
|
+
/** @internal */
|
56
103
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
57
104
|
static _schema: any;
|
105
|
+
/** @internal */
|
58
106
|
get _schema(): {
|
59
107
|
[ItemsSym]: SchemaFor<Item>;
|
60
108
|
} {
|
61
109
|
return (this.constructor as typeof CoList)._schema;
|
62
110
|
}
|
63
111
|
|
112
|
+
/** @category Collaboration */
|
64
113
|
get _owner(): Account | Group {
|
65
114
|
return this._raw.group instanceof RawAccount
|
66
115
|
? Account.fromRaw(this._raw.group)
|
67
116
|
: Group.fromRaw(this._raw.group);
|
68
117
|
}
|
69
118
|
|
70
|
-
/**
|
119
|
+
/**
|
120
|
+
* If a `CoList`'s items are a `co.ref(...)`, you can use `coList._refs[i]` to access
|
121
|
+
* the `Ref` instead of the potentially loaded/null value.
|
122
|
+
*
|
123
|
+
* This allows you to always get the ID or load the value manually.
|
124
|
+
*
|
125
|
+
* @example
|
126
|
+
* ```ts
|
127
|
+
* animals._refs[0].id; // => ID<Animal>
|
128
|
+
* animals._refs[0].value;
|
129
|
+
* // => Animal | null
|
130
|
+
* const animal = await animals._refs[0].load();
|
131
|
+
* ```
|
132
|
+
*
|
133
|
+
* @category Content
|
134
|
+
**/
|
71
135
|
get _refs(): {
|
72
136
|
[idx: number]: Exclude<Item, null> extends CoValue
|
73
137
|
? Ref<UnCo<Exclude<Item, null>>>
|
@@ -145,17 +209,35 @@ export class CoList<Item = any>
|
|
145
209
|
return new Proxy(this, CoListProxyHandler as ProxyHandler<this>);
|
146
210
|
}
|
147
211
|
|
212
|
+
/**
|
213
|
+
* Create a new CoList with the given initial values and owner.
|
214
|
+
*
|
215
|
+
* The owner (a Group or Account) determines access rights to the CoMap.
|
216
|
+
*
|
217
|
+
* The CoList will immediately be persisted and synced to connected peers.
|
218
|
+
*
|
219
|
+
* @example
|
220
|
+
* ```ts
|
221
|
+
* const colours = ColorList.create(
|
222
|
+
* ["red", "green", "blue"],
|
223
|
+
* { owner: me }
|
224
|
+
* );
|
225
|
+
* const animals = AnimalList.create(
|
226
|
+
* [cat, dog, fish],
|
227
|
+
* { owner: me }
|
228
|
+
* );
|
229
|
+
* ```
|
230
|
+
*
|
231
|
+
* @category Creation
|
232
|
+
**/
|
148
233
|
static create<L extends CoList>(
|
149
|
-
this:
|
234
|
+
this: CoValueClass<L>,
|
150
235
|
items: UnCo<L[number]>[],
|
151
236
|
options: { owner: Account | Group },
|
152
237
|
) {
|
153
238
|
return new this({ init: items, owner: options.owner });
|
154
239
|
}
|
155
240
|
|
156
|
-
push(...items: Item[]): number;
|
157
|
-
/** @private For exact type compatibility with Array superclass */
|
158
|
-
push(...items: Item[]): number;
|
159
241
|
push(...items: Item[]): number {
|
160
242
|
for (const item of toRawItems(
|
161
243
|
items as Item[],
|
@@ -167,9 +249,6 @@ export class CoList<Item = any>
|
|
167
249
|
return this._raw.entries().length;
|
168
250
|
}
|
169
251
|
|
170
|
-
unshift(...items: Item[]): number;
|
171
|
-
/** @private For exact type compatibility with Array superclass */
|
172
|
-
unshift(...items: Item[]): number;
|
173
252
|
unshift(...items: Item[]): number {
|
174
253
|
for (const item of toRawItems(
|
175
254
|
items as Item[],
|
@@ -240,18 +319,15 @@ export class CoList<Item = any>
|
|
240
319
|
return this.toJSON();
|
241
320
|
}
|
242
321
|
|
322
|
+
/** @category Internals */
|
243
323
|
static fromRaw<V extends CoList>(
|
244
|
-
this:
|
324
|
+
this: CoValueClass<V> & typeof CoList,
|
245
325
|
raw: RawCoList,
|
246
326
|
) {
|
247
327
|
return new this({ fromRaw: raw });
|
248
328
|
}
|
249
329
|
|
250
|
-
|
251
|
-
static loadEf = CoValueBase.loadEf as CoValueClass["loadEf"];
|
252
|
-
static subscribe = CoValueBase.subscribe as CoValueClass["subscribe"];
|
253
|
-
static subscribeEf = CoValueBase.subscribeEf as CoValueClass["subscribeEf"];
|
254
|
-
|
330
|
+
/** @internal */
|
255
331
|
static schema<V extends CoList>(
|
256
332
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
257
333
|
this: { new (...args: any): V } & typeof CoList,
|
@@ -260,6 +336,143 @@ export class CoList<Item = any>
|
|
260
336
|
this._schema ||= {};
|
261
337
|
Object.assign(this._schema, def);
|
262
338
|
}
|
339
|
+
|
340
|
+
/**
|
341
|
+
* Load a `CoList` with a given ID, as a given account.
|
342
|
+
*
|
343
|
+
* `depth` specifies if item CoValue references should be loaded as well before resolving.
|
344
|
+
* The `DeeplyLoaded` return type guarantees that corresponding referenced CoValues are loaded to the specified depth.
|
345
|
+
*
|
346
|
+
* You can pass `[]` or for shallowly loading only this CoList, or `[itemDepth]` for recursively loading referenced CoValues.
|
347
|
+
*
|
348
|
+
* Check out the `load` methods on `CoMap`/`CoList`/`CoStream`/`Group`/`Account` to see which depth structures are valid to nest.
|
349
|
+
*
|
350
|
+
* @example
|
351
|
+
* ```ts
|
352
|
+
* const animalsWithVets =
|
353
|
+
* await ListOfAnimals.load(
|
354
|
+
* "co_zdsMhHtfG6VNKt7RqPUPvUtN2Ax",
|
355
|
+
* me,
|
356
|
+
* [{ vet: {} }]
|
357
|
+
* );
|
358
|
+
* ```
|
359
|
+
*
|
360
|
+
* @category Subscription & Loading
|
361
|
+
*/
|
362
|
+
static load<L extends CoList, Depth>(
|
363
|
+
this: CoValueClass<L>,
|
364
|
+
id: ID<L>,
|
365
|
+
as: Account,
|
366
|
+
depth: Depth & DepthsIn<L>,
|
367
|
+
): Promise<DeeplyLoaded<L, Depth> | undefined> {
|
368
|
+
return loadCoValue(this, id, as, depth);
|
369
|
+
}
|
370
|
+
|
371
|
+
/**
|
372
|
+
* Effectful version of `CoList.load()`.
|
373
|
+
*
|
374
|
+
* Needs to be run inside an `AccountCtx` context.
|
375
|
+
*
|
376
|
+
* @category Subscription & Loading
|
377
|
+
*/
|
378
|
+
static loadEf<L extends CoList, Depth>(
|
379
|
+
this: CoValueClass<L>,
|
380
|
+
id: ID<L>,
|
381
|
+
depth: Depth & DepthsIn<L>,
|
382
|
+
): Effect.Effect<DeeplyLoaded<L, Depth>, UnavailableError, AccountCtx> {
|
383
|
+
return loadCoValueEf<L, Depth>(this, id, depth);
|
384
|
+
}
|
385
|
+
|
386
|
+
/**
|
387
|
+
* Load and subscribe to a `CoList` with a given ID, as a given account.
|
388
|
+
*
|
389
|
+
* Automatically also subscribes to updates to all referenced/nested CoValues as soon as they are accessed in the listener.
|
390
|
+
*
|
391
|
+
* `depth` specifies if item CoValue references should be loaded as well before calling `listener` for the first time.
|
392
|
+
* The `DeeplyLoaded` return type guarantees that corresponding referenced CoValues are loaded to the specified depth.
|
393
|
+
*
|
394
|
+
* You can pass `[]` or for shallowly loading only this CoList, or `[itemDepth]` for recursively loading referenced CoValues.
|
395
|
+
*
|
396
|
+
* Check out the `load` methods on `CoMap`/`CoList`/`CoStream`/`Group`/`Account` to see which depth structures are valid to nest.
|
397
|
+
*
|
398
|
+
* Returns an unsubscribe function that you should call when you no longer need updates.
|
399
|
+
*
|
400
|
+
* Also see the `useCoState` hook to reactively subscribe to a CoValue in a React component.
|
401
|
+
*
|
402
|
+
* @example
|
403
|
+
* ```ts
|
404
|
+
* const unsub = ListOfAnimals.subscribe(
|
405
|
+
* "co_zdsMhHtfG6VNKt7RqPUPvUtN2Ax",
|
406
|
+
* me,
|
407
|
+
* { vet: {} },
|
408
|
+
* (animalsWithVets) => console.log(animalsWithVets)
|
409
|
+
* );
|
410
|
+
* ```
|
411
|
+
*
|
412
|
+
* @category Subscription & Loading
|
413
|
+
*/
|
414
|
+
static subscribe<L extends CoList, Depth>(
|
415
|
+
this: CoValueClass<L>,
|
416
|
+
id: ID<L>,
|
417
|
+
as: Account,
|
418
|
+
depth: Depth & DepthsIn<L>,
|
419
|
+
listener: (value: DeeplyLoaded<L, Depth>) => void,
|
420
|
+
): () => void {
|
421
|
+
return subscribeToCoValue<L, Depth>(this, id, as, depth, listener);
|
422
|
+
}
|
423
|
+
|
424
|
+
/**
|
425
|
+
* Effectful version of `CoList.subscribe()` that returns a stream of updates.
|
426
|
+
*
|
427
|
+
* Needs to be run inside an `AccountCtx` context.
|
428
|
+
*
|
429
|
+
* @category Subscription & Loading
|
430
|
+
*/
|
431
|
+
static subscribeEf<L extends CoList, Depth>(
|
432
|
+
this: CoValueClass<L>,
|
433
|
+
id: ID<L>,
|
434
|
+
depth: Depth & DepthsIn<L>,
|
435
|
+
): Stream.Stream<DeeplyLoaded<L, Depth>, UnavailableError, AccountCtx> {
|
436
|
+
return subscribeToCoValueEf<L, Depth>(this, id, depth);
|
437
|
+
}
|
438
|
+
|
439
|
+
/**
|
440
|
+
* Given an already loaded `CoList`, ensure that items are loaded to the specified depth.
|
441
|
+
*
|
442
|
+
* Works like `CoList.load()`, but you don't need to pass the ID or the account to load as again.
|
443
|
+
*
|
444
|
+
* @category Subscription & Loading
|
445
|
+
*/
|
446
|
+
ensureLoaded<L extends CoList, Depth>(
|
447
|
+
this: L,
|
448
|
+
depth: Depth & DepthsIn<L>,
|
449
|
+
): Promise<DeeplyLoaded<L, Depth> | undefined> {
|
450
|
+
return ensureCoValueLoaded(this, depth);
|
451
|
+
}
|
452
|
+
|
453
|
+
/**
|
454
|
+
* Given an already loaded `CoList`, subscribe to updates to the `CoList` and ensure that items are loaded to the specified depth.
|
455
|
+
*
|
456
|
+
* Works like `CoList.subscribe()`, but you don't need to pass the ID or the account to load as again.
|
457
|
+
*
|
458
|
+
* Returns an unsubscribe function that you should call when you no longer need updates.
|
459
|
+
*
|
460
|
+
* @category Subscription & Loading
|
461
|
+
**/
|
462
|
+
subscribe<L extends CoList, Depth>(
|
463
|
+
this: L,
|
464
|
+
depth: Depth & DepthsIn<L>,
|
465
|
+
listener: (value: DeeplyLoaded<L, Depth>) => void,
|
466
|
+
): () => void {
|
467
|
+
return subscribeToExistingCoValue(this, depth, listener);
|
468
|
+
}
|
469
|
+
|
470
|
+
/** @category Type Helpers */
|
471
|
+
castAs<Cl extends CoValueClass & CoValueFromRaw<CoValue>>(
|
472
|
+
cl: Cl,
|
473
|
+
): InstanceType<Cl> {
|
474
|
+
return cl.fromRaw(this._raw) as InstanceType<Cl>;
|
475
|
+
}
|
263
476
|
}
|
264
477
|
|
265
478
|
function toRawItems<Item>(items: Item[], itemDescriptor: Schema) {
|
package/src/coValues/coMap.ts
CHANGED
@@ -9,7 +9,11 @@ import type {
|
|
9
9
|
RefEncoded,
|
10
10
|
IfCo,
|
11
11
|
RefIfCoValue,
|
12
|
-
|
12
|
+
DepthsIn,
|
13
|
+
DeeplyLoaded,
|
14
|
+
UnavailableError,
|
15
|
+
AccountCtx,
|
16
|
+
CoValueClass,
|
13
17
|
} from "../internal.js";
|
14
18
|
import {
|
15
19
|
Account,
|
@@ -22,7 +26,14 @@ import {
|
|
22
26
|
ItemsSym,
|
23
27
|
InitValues,
|
24
28
|
isRefEncoded,
|
29
|
+
loadCoValue,
|
30
|
+
loadCoValueEf,
|
31
|
+
subscribeToCoValue,
|
32
|
+
subscribeToCoValueEf,
|
33
|
+
ensureCoValueLoaded,
|
34
|
+
subscribeToExistingCoValue,
|
25
35
|
} from "../internal.js";
|
36
|
+
import { Effect, Stream } from "effect";
|
26
37
|
|
27
38
|
type CoMapEdit<V> = {
|
28
39
|
value?: V;
|
@@ -42,6 +53,8 @@ type InitValuesFor<C extends CoMap> = {
|
|
42
53
|
* @categoryDescription Declaration
|
43
54
|
* Declare your own CoMap schemas by subclassing `CoMap` and assigning field schemas with `co`.
|
44
55
|
*
|
56
|
+
* Optional `co.ref(...)` fields must be marked with `{ optional: true }`.
|
57
|
+
*
|
45
58
|
* ```ts
|
46
59
|
* import { co, CoMap } from "jazz-tools";
|
47
60
|
*
|
@@ -49,6 +62,7 @@ type InitValuesFor<C extends CoMap> = {
|
|
49
62
|
* name = co.string;
|
50
63
|
* age = co.number;
|
51
64
|
* pet = co.ref(Animal);
|
65
|
+
* car = co.ref(Car, { optional: true });
|
52
66
|
* }
|
53
67
|
* ```
|
54
68
|
*
|
@@ -66,7 +80,7 @@ type InitValuesFor<C extends CoMap> = {
|
|
66
80
|
*
|
67
81
|
* @category CoValues
|
68
82
|
* */
|
69
|
-
export class CoMap extends CoValueBase implements CoValue
|
83
|
+
export class CoMap extends CoValueBase implements CoValue {
|
70
84
|
/**
|
71
85
|
* The ID of this `CoMap`
|
72
86
|
* @category Content */
|
@@ -92,17 +106,19 @@ export class CoMap extends CoValueBase implements CoValue<"CoMap", RawCoMap> {
|
|
92
106
|
/**
|
93
107
|
* If property `prop` is a `co.ref(...)`, you can use `coMaps._refs.prop` to access
|
94
108
|
* the `Ref` instead of the potentially loaded/null value.
|
109
|
+
*
|
95
110
|
* This allows you to always get the ID or load the value manually.
|
96
111
|
*
|
97
112
|
* @example
|
98
113
|
* ```ts
|
99
114
|
* person._refs.pet.id; // => ID<Animal>
|
100
115
|
* person._refs.pet.value;
|
101
|
-
* // => Animal |
|
116
|
+
* // => Animal | null
|
102
117
|
* const pet = await person._refs.pet.load();
|
103
118
|
* ```
|
104
119
|
*
|
105
|
-
* @category Content
|
120
|
+
* @category Content
|
121
|
+
**/
|
106
122
|
get _refs(): {
|
107
123
|
[Key in CoKeys<this>]: IfCo<this[Key], RefIfCoValue<this[Key]>>;
|
108
124
|
} {
|
@@ -161,10 +177,14 @@ export class CoMap extends CoValueBase implements CoValue<"CoMap", RawCoMap> {
|
|
161
177
|
: undefined,
|
162
178
|
by:
|
163
179
|
rawEdit.by &&
|
164
|
-
new Ref
|
165
|
-
|
166
|
-
|
167
|
-
|
180
|
+
new Ref<Account>(
|
181
|
+
rawEdit.by as ID<Account>,
|
182
|
+
target._loadedAs,
|
183
|
+
{
|
184
|
+
ref: Account,
|
185
|
+
optional: false,
|
186
|
+
},
|
187
|
+
).accessFrom(
|
168
188
|
target,
|
169
189
|
"_edits." + key.toString() + ".by",
|
170
190
|
),
|
@@ -212,9 +232,26 @@ export class CoMap extends CoValueBase implements CoValue<"CoMap", RawCoMap> {
|
|
212
232
|
return new Proxy(this, CoMapProxyHandler as ProxyHandler<this>);
|
213
233
|
}
|
214
234
|
|
215
|
-
/**
|
235
|
+
/**
|
236
|
+
* Create a new CoMap with the given initial values and owner.
|
237
|
+
*
|
238
|
+
* The owner (a Group or Account) determines access rights to the CoMap.
|
239
|
+
*
|
240
|
+
* The CoMap will immediately be persisted and synced to connected peers.
|
241
|
+
*
|
242
|
+
* @example
|
243
|
+
* ```ts
|
244
|
+
* const person = Person.create({
|
245
|
+
* name: "Alice",
|
246
|
+
* age: 42,
|
247
|
+
* pet: cat,
|
248
|
+
* }, { owner: friendGroup });
|
249
|
+
* ```
|
250
|
+
*
|
251
|
+
* @category Creation
|
252
|
+
**/
|
216
253
|
static create<M extends CoMap>(
|
217
|
-
this:
|
254
|
+
this: CoValueClass<M>,
|
218
255
|
init: Simplify<CoMapInit<M>>,
|
219
256
|
options: { owner: Account | Group },
|
220
257
|
) {
|
@@ -286,7 +323,24 @@ export class CoMap extends CoValueBase implements CoValue<"CoMap", RawCoMap> {
|
|
286
323
|
return rawOwner.createMap(rawInit);
|
287
324
|
}
|
288
325
|
|
289
|
-
/**
|
326
|
+
/**
|
327
|
+
* Declare a Record-like CoMap schema, by extending `CoMap.Record(...)` and passing the value schema using `co`. Keys are always `string`.
|
328
|
+
*
|
329
|
+
* @example
|
330
|
+
* ```ts
|
331
|
+
* import { co, CoMap } from "jazz-tools";
|
332
|
+
*
|
333
|
+
* class ColorToFruitMap extends CoMap.Record(
|
334
|
+
* co.ref(Fruit)
|
335
|
+
* ) {}
|
336
|
+
*
|
337
|
+
* // assume we have map: ColorToFruitMap
|
338
|
+
* // and strawberry: Fruit
|
339
|
+
* map["red"] = strawberry;
|
340
|
+
* ```
|
341
|
+
*
|
342
|
+
* @category Declaration
|
343
|
+
*/
|
290
344
|
static Record<Value>(value: IfCo<Value, Value>) {
|
291
345
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
292
346
|
class RecordLikeCoMap extends CoMap {
|
@@ -297,6 +351,135 @@ export class CoMap extends CoValueBase implements CoValue<"CoMap", RawCoMap> {
|
|
297
351
|
|
298
352
|
return RecordLikeCoMap;
|
299
353
|
}
|
354
|
+
|
355
|
+
/**
|
356
|
+
* Load a `CoMap` with a given ID, as a given account.
|
357
|
+
*
|
358
|
+
* `depth` specifies which (if any) fields that reference other CoValues to load as well before resolving.
|
359
|
+
* The `DeeplyLoaded` return type guarantees that corresponding referenced CoValues are loaded to the specified depth.
|
360
|
+
*
|
361
|
+
* You can pass `[]` or `{}` for shallowly loading only this CoMap, or `{ fieldA: depthA, fieldB: depthB }` for recursively loading referenced CoValues.
|
362
|
+
*
|
363
|
+
* Check out the `load` methods on `CoMap`/`CoList`/`CoStream`/`Group`/`Account` to see which depth structures are valid to nest.
|
364
|
+
*
|
365
|
+
* @example
|
366
|
+
* ```ts
|
367
|
+
* const person = await Person.load(
|
368
|
+
* "co_zdsMhHtfG6VNKt7RqPUPvUtN2Ax",
|
369
|
+
* me,
|
370
|
+
* { pet: {} }
|
371
|
+
* );
|
372
|
+
* ```
|
373
|
+
*
|
374
|
+
* @category Subscription & Loading
|
375
|
+
*/
|
376
|
+
static load<M extends CoMap, Depth>(
|
377
|
+
this: CoValueClass<M>,
|
378
|
+
id: ID<M>,
|
379
|
+
as: Account,
|
380
|
+
depth: Depth & DepthsIn<M>,
|
381
|
+
): Promise<DeeplyLoaded<M, Depth> | undefined> {
|
382
|
+
return loadCoValue(this, id, as, depth);
|
383
|
+
}
|
384
|
+
|
385
|
+
/**
|
386
|
+
* Effectful version of `CoMap.load()`.
|
387
|
+
*
|
388
|
+
* Needs to be run inside an `AccountCtx` context.
|
389
|
+
*
|
390
|
+
* @category Subscription & Loading
|
391
|
+
*/
|
392
|
+
static loadEf<M extends CoMap, Depth>(
|
393
|
+
this: CoValueClass<M>,
|
394
|
+
id: ID<M>,
|
395
|
+
depth: Depth & DepthsIn<M>,
|
396
|
+
): Effect.Effect<DeeplyLoaded<M, Depth>, UnavailableError, AccountCtx> {
|
397
|
+
return loadCoValueEf<M, Depth>(this, id, depth);
|
398
|
+
}
|
399
|
+
|
400
|
+
/**
|
401
|
+
* Load and subscribe to a `CoMap` with a given ID, as a given account.
|
402
|
+
*
|
403
|
+
* Automatically also subscribes to updates to all referenced/nested CoValues as soon as they are accessed in the listener.
|
404
|
+
*
|
405
|
+
* `depth` specifies which (if any) fields that reference other CoValues to load as well before calling `listener` for the first time.
|
406
|
+
* The `DeeplyLoaded` return type guarantees that corresponding referenced CoValues are loaded to the specified depth.
|
407
|
+
*
|
408
|
+
* You can pass `[]` or `{}` for shallowly loading only this CoMap, or `{ fieldA: depthA, fieldB: depthB }` for recursively loading referenced CoValues.
|
409
|
+
*
|
410
|
+
* Check out the `load` methods on `CoMap`/`CoList`/`CoStream`/`Group`/`Account` to see which depth structures are valid to nest.
|
411
|
+
*
|
412
|
+
* Returns an unsubscribe function that you should call when you no longer need updates.
|
413
|
+
*
|
414
|
+
* Also see the `useCoState` hook to reactively subscribe to a CoValue in a React component.
|
415
|
+
*
|
416
|
+
* @example
|
417
|
+
* ```ts
|
418
|
+
* const unsub = Person.subscribe(
|
419
|
+
* "co_zdsMhHtfG6VNKt7RqPUPvUtN2Ax",
|
420
|
+
* me,
|
421
|
+
* { pet: {} },
|
422
|
+
* (person) => console.log(person)
|
423
|
+
* );
|
424
|
+
* ```
|
425
|
+
*
|
426
|
+
* @category Subscription & Loading
|
427
|
+
*/
|
428
|
+
static subscribe<M extends CoMap, Depth>(
|
429
|
+
this: CoValueClass<M>,
|
430
|
+
id: ID<M>,
|
431
|
+
as: Account,
|
432
|
+
depth: Depth & DepthsIn<M>,
|
433
|
+
listener: (value: DeeplyLoaded<M, Depth>) => void,
|
434
|
+
): () => void {
|
435
|
+
return subscribeToCoValue<M, Depth>(this, id, as, depth, listener);
|
436
|
+
}
|
437
|
+
|
438
|
+
/**
|
439
|
+
* Effectful version of `CoMap.subscribe()` that returns a stream of updates.
|
440
|
+
*
|
441
|
+
* Needs to be run inside an `AccountCtx` context.
|
442
|
+
*
|
443
|
+
* @category Subscription & Loading
|
444
|
+
*/
|
445
|
+
static subscribeEf<M extends CoMap, Depth>(
|
446
|
+
this: CoValueClass<M>,
|
447
|
+
id: ID<M>,
|
448
|
+
depth: Depth & DepthsIn<M>,
|
449
|
+
): Stream.Stream<DeeplyLoaded<M, Depth>, UnavailableError, AccountCtx> {
|
450
|
+
return subscribeToCoValueEf<M, Depth>(this, id, depth);
|
451
|
+
}
|
452
|
+
|
453
|
+
/**
|
454
|
+
* Given an already loaded `CoMap`, ensure that the specified fields are loaded to the specified depth.
|
455
|
+
*
|
456
|
+
* Works like `CoMap.load()`, but you don't need to pass the ID or the account to load as again.
|
457
|
+
*
|
458
|
+
* @category Subscription & Loading
|
459
|
+
*/
|
460
|
+
ensureLoaded<M extends CoMap, Depth>(
|
461
|
+
this: M,
|
462
|
+
depth: Depth & DepthsIn<M>,
|
463
|
+
): Promise<DeeplyLoaded<M, Depth> | undefined> {
|
464
|
+
return ensureCoValueLoaded(this, depth);
|
465
|
+
}
|
466
|
+
|
467
|
+
/**
|
468
|
+
* Given an already loaded `CoMap`, subscribe to updates to the `CoMap` and ensure that the specified fields are loaded to the specified depth.
|
469
|
+
*
|
470
|
+
* Works like `CoMap.subscribe()`, but you don't need to pass the ID or the account to load as again.
|
471
|
+
*
|
472
|
+
* Returns an unsubscribe function that you should call when you no longer need updates.
|
473
|
+
*
|
474
|
+
* @category Subscription & Loading
|
475
|
+
**/
|
476
|
+
subscribe<M extends CoMap, Depth>(
|
477
|
+
this: M,
|
478
|
+
depth: Depth & DepthsIn<M>,
|
479
|
+
listener: (value: DeeplyLoaded<M, Depth>) => void,
|
480
|
+
): () => void {
|
481
|
+
return subscribeToExistingCoValue(this, depth, listener);
|
482
|
+
}
|
300
483
|
}
|
301
484
|
|
302
485
|
export type CoKeys<Map extends object> = Exclude<
|