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.
Files changed (37) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/.turbo/turbo-lint.log +1 -1
  3. package/.turbo/turbo-test.log +33 -33
  4. package/CHANGELOG.md +177 -165
  5. package/LICENSE.txt +1 -1
  6. package/dist/coValues/account.js +41 -4
  7. package/dist/coValues/account.js.map +1 -1
  8. package/dist/coValues/coList.js +179 -8
  9. package/dist/coValues/coList.js.map +1 -1
  10. package/dist/coValues/coMap.js +141 -5
  11. package/dist/coValues/coMap.js.map +1 -1
  12. package/dist/coValues/coStream.js +49 -1
  13. package/dist/coValues/coStream.js.map +1 -1
  14. package/dist/coValues/group.js +26 -2
  15. package/dist/coValues/group.js.map +1 -1
  16. package/dist/coValues/interfaces.js +32 -48
  17. package/dist/coValues/interfaces.js.map +1 -1
  18. package/dist/implementation/schema.js.map +1 -1
  19. package/dist/implementation/subscriptionScope.js.map +1 -1
  20. package/dist/index.js +1 -0
  21. package/dist/index.js.map +1 -1
  22. package/dist/tests/deepLoading.test.js +2 -2
  23. package/dist/tests/deepLoading.test.js.map +1 -1
  24. package/dist/tests/groupsAndAccounts.test.js +3 -3
  25. package/dist/tests/groupsAndAccounts.test.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/coValues/account.ts +106 -13
  28. package/src/coValues/coList.ts +236 -23
  29. package/src/coValues/coMap.ts +194 -11
  30. package/src/coValues/coStream.ts +128 -11
  31. package/src/coValues/group.ts +78 -5
  32. package/src/coValues/interfaces.ts +141 -211
  33. package/src/implementation/schema.ts +7 -4
  34. package/src/implementation/subscriptionScope.ts +3 -3
  35. package/src/index.ts +7 -0
  36. package/src/tests/deepLoading.test.ts +6 -3
  37. package/src/tests/groupsAndAccounts.test.ts +4 -4
@@ -13,74 +13,29 @@ import {
13
13
  } from "../internal.js";
14
14
  import { fulfillsDepth } from "./deepLoading.js";
15
15
 
16
- export type ClassOf<T> = {
17
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
- new (...args: any[]): T;
19
- };
20
-
21
16
  /** @category Abstract interfaces */
22
17
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
- export interface CoValueClass<Value extends CoValue = CoValue, Init = any> {
24
- new (init: Init, options: { owner: Account | Group }): Value;
25
-
18
+ export interface CoValueClass<Value extends CoValue = CoValue> {
26
19
  /** @ignore */
27
- fromRaw(raw: Value["_raw"]): Value;
28
-
29
- /** @category Subscription & Loading */
30
- load<V extends Value, Depth>(
31
- this: ClassOf<V>,
32
- id: ID<V>,
33
- as: Account,
34
- depth: Depth & DepthsIn<V>,
35
- ): Promise<DeeplyLoaded<V, Depth> | undefined>;
36
- /** @category Subscription & Loading */
37
- load<V extends Value, Depth>(
38
- this: ClassOf<V>,
39
- existing: V,
40
- depth: Depth & DepthsIn<V>,
41
- ): Promise<DeeplyLoaded<V, Depth> | undefined>;
42
-
43
- /** @category Subscription & Loading */
44
- loadEf<V extends Value, Depth>(
45
- this: ClassOf<V>,
46
- id: ID<V>,
47
- depth: Depth & DepthsIn<V>,
48
- ): Effect.Effect<DeeplyLoaded<V, Depth>, UnavailableError, AccountCtx>;
49
-
50
- /** @category Subscription & Loading */
51
- subscribe<V extends Value, Depth>(
52
- this: ClassOf<V>,
53
- id: ID<V>,
54
- as: Account,
55
- depth: Depth & DepthsIn<V>,
56
- listener: (value: DeeplyLoaded<V, Depth>) => void,
57
- ): () => void;
58
- subscribe<V extends Value, Depth>(
59
- this: ClassOf<V>,
60
- existing: V,
61
- depth: Depth & DepthsIn<V>,
62
- listener: (value: DeeplyLoaded<V, Depth>) => void,
63
- ): () => void;
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ new (...args: any[]): Value;
22
+ }
64
23
 
65
- /** @category Subscription & Loading */
66
- subscribeEf<V extends Value, Depth>(
67
- this: ClassOf<V>,
68
- id: ID<V>,
69
- depth: Depth & DepthsIn<V>,
70
- ): Stream.Stream<DeeplyLoaded<V, Depth>, UnavailableError, AccountCtx>;
24
+ export interface CoValueFromRaw<V extends CoValue> {
25
+ fromRaw(raw: V["_raw"]): V;
71
26
  }
72
27
 
73
28
  /** @category Abstract interfaces */
74
29
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
75
- export interface CoValue<Type extends string = string, Raw = any> {
30
+ export interface CoValue {
76
31
  /** @category Content */
77
32
  readonly id: ID<this>;
78
33
  /** @category Type Helpers */
79
- _type: Type;
34
+ _type: string;
80
35
  /** @category Collaboration */
81
36
  _owner: Account | Group;
82
37
  /** @category Internals */
83
- _raw: Raw;
38
+ _raw: RawCoValue;
84
39
  /** @internal */
85
40
  readonly _loadedAs: Account;
86
41
  /** @category Stringifying & Inspection */
@@ -96,8 +51,10 @@ export function isCoValue(value: any): value is CoValue {
96
51
  return value && value._type !== undefined;
97
52
  }
98
53
 
99
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
- export function isCoValueClass(value: any): value is CoValueClass {
54
+ export function isCoValueClass<V extends CoValue>(
55
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
+ value: any,
57
+ ): value is CoValueClass<V> & CoValueFromRaw<V> {
101
58
  return typeof value === "function" && value.fromRaw !== undefined;
102
59
  }
103
60
 
@@ -111,6 +68,7 @@ export class CoValueBase implements CoValue {
111
68
  id!: ID<this>;
112
69
  _type!: string;
113
70
  _raw!: RawCoValue;
71
+ /** @category Internals */
114
72
  _instanceID!: string;
115
73
 
116
74
  get _owner(): Account | Group {
@@ -142,156 +100,13 @@ export class CoValueBase implements CoValue {
142
100
  }
143
101
 
144
102
  /** @category Internals */
145
- static fromRaw<V extends CoValue>(this: ClassOf<V>, raw: RawCoValue): V {
103
+ static fromRaw<V extends CoValue>(
104
+ this: CoValueClass<V>,
105
+ raw: RawCoValue,
106
+ ): V {
146
107
  return new this({ fromRaw: raw });
147
108
  }
148
109
 
149
- /** @category Subscription & Loading */
150
- static load<V extends CoValue, Depth>(
151
- this: ClassOf<V> & typeof CoValueBase,
152
- id: ID<V>,
153
- as: Account,
154
- depth: Depth & DepthsIn<V>,
155
- ): Promise<DeeplyLoaded<V, Depth> | undefined>;
156
- /** @category Subscription & Loading */
157
- static load<V extends CoValue, Depth>(
158
- this: ClassOf<V> & typeof CoValueBase,
159
- existing: V,
160
- depth: Depth & DepthsIn<V>,
161
- ): Promise<DeeplyLoaded<V, Depth> | undefined>;
162
- static load<V extends CoValue, Depth>(
163
- this: ClassOf<V> & typeof CoValueBase,
164
-
165
- ...args:
166
- | [ID<V>, Account, Depth & DepthsIn<V>]
167
- | [V, Depth & DepthsIn<V>]
168
- ): Promise<DeeplyLoaded<V, Depth> | undefined> {
169
- const { id, as, depth } =
170
- args.length === 3
171
- ? { id: args[0], as: args[1], depth: args[2] }
172
- : { id: args[0].id, as: args[0]._loadedAs, depth: args[1] };
173
- return Effect.runPromise(
174
- this.loadEf(id, depth).pipe(
175
- Effect.mapError(() => undefined),
176
- Effect.merge,
177
- Effect.provideService(AccountCtx, as),
178
- ),
179
- );
180
- }
181
-
182
- /** @category Subscription & Loading */
183
- static loadEf<V extends CoValue, Depth>(
184
- this: ClassOf<V> & typeof CoValueBase,
185
- id: ID<V>,
186
- depth: Depth & DepthsIn<V>,
187
- ): Effect.Effect<DeeplyLoaded<V, Depth>, UnavailableError, AccountCtx> {
188
- return this.subscribeEf(id, depth).pipe(
189
- Stream.runHead,
190
- Effect.andThen(
191
- Effect.mapError((_noSuchElem) => "unavailable" as const),
192
- ),
193
- );
194
- }
195
-
196
- /** @category Subscription & Loading */
197
- static subscribe<V extends CoValue, Depth>(
198
- this: ClassOf<V> & typeof CoValueBase,
199
- id: ID<V>,
200
- as: Account,
201
- depth: Depth & DepthsIn<V>,
202
- listener: (value: DeeplyLoaded<V, Depth>) => void,
203
- ): () => void;
204
- static subscribe<V extends CoValue, Depth>(
205
- this: ClassOf<V> & typeof CoValueBase,
206
- existing: V,
207
- depth: Depth & DepthsIn<V>,
208
- listener: (value: DeeplyLoaded<V, Depth>) => void,
209
- ): () => void;
210
- static subscribe<V extends CoValue, Depth>(
211
- this: ClassOf<V> & typeof CoValueBase,
212
- ...args:
213
- | [
214
- ID<V>,
215
- Account,
216
- Depth & DepthsIn<V>,
217
- (value: DeeplyLoaded<V, Depth>) => void,
218
- ]
219
- | [V, Depth & DepthsIn<V>, (value: DeeplyLoaded<V, Depth>) => void]
220
- ): () => void {
221
- const { id, as, depth, listener } =
222
- args.length === 4
223
- ? {
224
- id: args[0],
225
- as: args[1],
226
- depth: args[2],
227
- listener: args[3],
228
- }
229
- : {
230
- id: args[0].id,
231
- as: args[0]._loadedAs,
232
- depth: args[1],
233
- listener: args[2],
234
- };
235
- void Effect.runPromise(
236
- Effect.provideService(
237
- this.subscribeEf(id, depth).pipe(
238
- Stream.run(
239
- Sink.forEach((update) =>
240
- Effect.sync(() => listener(update)),
241
- ),
242
- ),
243
- ),
244
- AccountCtx,
245
- as,
246
- ),
247
- );
248
-
249
- return function unsubscribe() {};
250
- }
251
-
252
- /** @category Subscription & Loading */
253
- static subscribeEf<V extends CoValue, Depth>(
254
- this: ClassOf<V> & typeof CoValueBase,
255
- id: ID<V>,
256
- depth: Depth & DepthsIn<V>,
257
- ): Stream.Stream<DeeplyLoaded<V, Depth>, UnavailableError, AccountCtx> {
258
- return AccountCtx.pipe(
259
- Effect.andThen((account) =>
260
- new Ref(id, account, {
261
- ref: this as CoValueClass<V>,
262
- optional: false,
263
- }).loadEf(),
264
- ),
265
- Stream.fromEffect,
266
- Stream.flatMap((value: V) =>
267
- Stream.asyncScoped<V, UnavailableError>((emit) =>
268
- Effect.gen(this, function* (_) {
269
- const subscription = new SubscriptionScope(
270
- value,
271
- this,
272
- (update) => void emit.single(update as V),
273
- );
274
-
275
- yield* _(
276
- Effect.addFinalizer(() =>
277
- Effect.sync(() =>
278
- subscription.unsubscribeAll(),
279
- ),
280
- ),
281
- );
282
- }),
283
- ),
284
- ),
285
- Stream.filterMap((update: V) =>
286
- Option.fromNullable(
287
- fulfillsDepth(depth, update)
288
- ? (update as DeeplyLoaded<V, Depth>)
289
- : undefined,
290
- ),
291
- ),
292
- );
293
- }
294
-
295
110
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
296
111
  toJSON(): object | any[] {
297
112
  return {
@@ -305,13 +120,128 @@ export class CoValueBase implements CoValue {
305
120
  return this.toJSON();
306
121
  }
307
122
 
308
- /** @category Type Helpers*/
309
- as<C extends CoValueClass>(otherSchema: C): InstanceType<C> {
310
- const cast = otherSchema.fromRaw(this._raw) as InstanceType<C>;
311
- const subScope = subscriptionsScopes.get(this);
312
- if (subScope) {
313
- subscriptionsScopes.set(cast, subScope);
314
- }
315
- return cast;
123
+ /** @category Type Helpers */
124
+ castAs<Cl extends CoValueClass & CoValueFromRaw<CoValue>>(
125
+ cl: Cl,
126
+ ): InstanceType<Cl> {
127
+ return cl.fromRaw(this._raw) as InstanceType<Cl>;
316
128
  }
317
129
  }
130
+
131
+ export function loadCoValue<V extends CoValue, Depth>(
132
+ cls: CoValueClass<V>,
133
+ id: ID<V>,
134
+ as: Account,
135
+ depth: Depth & DepthsIn<V>,
136
+ ): Promise<DeeplyLoaded<V, Depth> | undefined> {
137
+ return Effect.runPromise(
138
+ loadCoValueEf(cls, id, depth).pipe(
139
+ Effect.mapError(() => undefined),
140
+ Effect.merge,
141
+ Effect.provideService(AccountCtx, as),
142
+ ),
143
+ );
144
+ }
145
+
146
+ export function ensureCoValueLoaded<V extends CoValue, Depth>(
147
+ existing: V,
148
+ depth: Depth & DepthsIn<V>,
149
+ ): Promise<DeeplyLoaded<V, Depth> | undefined> {
150
+ return loadCoValue(
151
+ existing.constructor as CoValueClass<V>,
152
+ existing.id,
153
+ existing._loadedAs,
154
+ depth,
155
+ );
156
+ }
157
+
158
+ export function loadCoValueEf<V extends CoValue, Depth>(
159
+ cls: CoValueClass<V>,
160
+ id: ID<V>,
161
+ depth: Depth & DepthsIn<V>,
162
+ ): Effect.Effect<DeeplyLoaded<V, Depth>, UnavailableError, AccountCtx> {
163
+ return subscribeToCoValueEf(cls, id, depth).pipe(
164
+ Stream.runHead,
165
+ Effect.andThen(
166
+ Effect.mapError((_noSuchElem) => "unavailable" as const),
167
+ ),
168
+ );
169
+ }
170
+
171
+ export function subscribeToCoValue<V extends CoValue, Depth>(
172
+ cls: CoValueClass<V>,
173
+ id: ID<V>,
174
+ as: Account,
175
+ depth: Depth & DepthsIn<V>,
176
+ listener: (value: DeeplyLoaded<V, Depth>) => void,
177
+ ): () => void {
178
+ void Effect.runPromise(
179
+ Effect.provideService(
180
+ subscribeToCoValueEf(cls, id, depth).pipe(
181
+ Stream.run(
182
+ Sink.forEach((update) =>
183
+ Effect.sync(() => listener(update)),
184
+ ),
185
+ ),
186
+ ),
187
+ AccountCtx,
188
+ as,
189
+ ),
190
+ );
191
+
192
+ return function unsubscribe() {};
193
+ }
194
+
195
+ export function subscribeToExistingCoValue<V extends CoValue, Depth>(
196
+ existing: V,
197
+ depth: Depth & DepthsIn<V>,
198
+ listener: (value: DeeplyLoaded<V, Depth>) => void,
199
+ ): () => void {
200
+ return subscribeToCoValue(
201
+ existing.constructor as CoValueClass<V>,
202
+ existing.id,
203
+ existing._loadedAs,
204
+ depth,
205
+ listener,
206
+ );
207
+ }
208
+
209
+ export function subscribeToCoValueEf<V extends CoValue, Depth>(
210
+ cls: CoValueClass<V>,
211
+ id: ID<V>,
212
+ depth: Depth & DepthsIn<V>,
213
+ ): Stream.Stream<DeeplyLoaded<V, Depth>, UnavailableError, AccountCtx> {
214
+ return AccountCtx.pipe(
215
+ Effect.andThen((account) =>
216
+ new Ref(id, account, {
217
+ ref: cls,
218
+ optional: false,
219
+ }).loadEf(),
220
+ ),
221
+ Stream.fromEffect,
222
+ Stream.flatMap((value: V) =>
223
+ Stream.asyncScoped<V, UnavailableError>((emit) =>
224
+ Effect.gen(function* (_) {
225
+ const subscription = new SubscriptionScope(
226
+ value,
227
+ cls as CoValueClass<V> & CoValueFromRaw<V>,
228
+ (update) => void emit.single(update as V),
229
+ );
230
+
231
+ yield* _(
232
+ Effect.addFinalizer(() =>
233
+ Effect.sync(() => subscription.unsubscribeAll()),
234
+ ),
235
+ );
236
+ }),
237
+ ),
238
+ ),
239
+ Stream.filterMap((update: V) =>
240
+ Option.fromNullable(
241
+ fulfillsDepth(depth, update)
242
+ ? (update as DeeplyLoaded<V, Depth>)
243
+ : undefined,
244
+ ),
245
+ ),
246
+ );
247
+ }
@@ -3,6 +3,7 @@ import {
3
3
  type CoValue,
4
4
  type CoValueClass,
5
5
  isCoValueClass,
6
+ CoValueFromRaw,
6
7
  } from "../internal.js";
7
8
  import type { Schema as EffectSchema, TypeId } from "@effect/schema/Schema";
8
9
 
@@ -96,11 +97,13 @@ export function instantiateRefEncoded<V extends CoValue>(
96
97
  schema: RefEncoded<V>,
97
98
  raw: RawCoValue,
98
99
  ): V {
99
- return isCoValueClass(schema.ref)
100
+ return isCoValueClass<V>(schema.ref)
100
101
  ? schema.ref.fromRaw(raw)
101
- : (schema.ref as (raw: RawCoValue) => CoValueClass<V>)(raw).fromRaw(
102
- raw,
103
- );
102
+ : (
103
+ schema.ref as (
104
+ raw: RawCoValue,
105
+ ) => CoValueClass<V> & CoValueFromRaw<V>
106
+ )(raw).fromRaw(raw);
104
107
  }
105
108
 
106
109
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -2,9 +2,9 @@ import type { RawCoValue } from "cojson";
2
2
  import type {
3
3
  Account,
4
4
  CoValue,
5
- CoValueBase,
6
5
  ID,
7
- ClassOf,
6
+ CoValueClass,
7
+ CoValueFromRaw,
8
8
  } from "../internal.js";
9
9
 
10
10
  export const subscriptionsScopes = new WeakMap<
@@ -35,7 +35,7 @@ export class SubscriptionScope<Root extends CoValue> {
35
35
 
36
36
  constructor(
37
37
  root: Root,
38
- rootSchema: ClassOf<Root> & typeof CoValueBase,
38
+ rootSchema: CoValueClass<Root> & CoValueFromRaw<Root>,
39
39
  onUpdate: (newRoot: Root) => void,
40
40
  ) {
41
41
  this.rootEntry = {
package/src/index.ts CHANGED
@@ -25,3 +25,10 @@ export { Account, isControlledAccount } from "./internal.js";
25
25
  export { ImageDefinition } from "./internal.js";
26
26
  export { CoValueBase, type CoValueClass } from "./internal.js";
27
27
  export type { DepthsIn, DeeplyLoaded } from "./internal.js";
28
+
29
+ export {
30
+ loadCoValue,
31
+ loadCoValueEf,
32
+ subscribeToCoValue,
33
+ subscribeToCoValueEf,
34
+ } from "./internal.js";
@@ -180,7 +180,10 @@ class CustomAccount extends Account {
180
180
  profile = co.ref(CustomProfile);
181
181
  root = co.ref(TestMap);
182
182
 
183
- async migrate(creationProps?: { name: string } | undefined) {
183
+ async migrate(
184
+ this: CustomAccount,
185
+ creationProps?: { name: string } | undefined,
186
+ ) {
184
187
  if (creationProps) {
185
188
  this.profile = CustomProfile.create(
186
189
  {
@@ -195,7 +198,7 @@ class CustomAccount extends Account {
195
198
  );
196
199
  }
197
200
 
198
- const thisLoaded = await CustomAccount.load(this, {
201
+ const thisLoaded = await this.ensureLoaded({
199
202
  profile: { stream: [] },
200
203
  root: { list: [] },
201
204
  });
@@ -219,7 +222,7 @@ test("Deep loading within account", async () => {
219
222
  crypto: Crypto,
220
223
  });
221
224
 
222
- const meLoaded = await CustomAccount.load(me, {
225
+ const meLoaded = await me.ensureLoaded({
223
226
  profile: { stream: [] },
224
227
  root: { list: [] },
225
228
  });
@@ -13,7 +13,7 @@ describe("Custom accounts and groups", async () => {
13
13
  profile = co.ref(CustomProfile);
14
14
  root = co.ref(CoMap);
15
15
 
16
- migrate(creationProps?: { name: string }) {
16
+ migrate(this: CustomAccount, creationProps?: { name: string }) {
17
17
  if (creationProps) {
18
18
  const profileGroup = Group.create({ owner: this });
19
19
  profileGroup.addMember("everyone", "reader");
@@ -56,7 +56,7 @@ describe("Custom accounts and groups", async () => {
56
56
  expect(group.nMembers).toBe(2);
57
57
 
58
58
  await new Promise<void>((resolve) => {
59
- CustomGroup.subscribe(group, {}, (update) => {
59
+ group.subscribe({}, (update) => {
60
60
  const meAsMember = update.members.find((member) => {
61
61
  return member.id === me.id && member.account?.profile;
62
62
  });
@@ -77,7 +77,7 @@ describe("Custom accounts and groups", async () => {
77
77
  const map = MyMap.create({ name: "test" }, { owner: group });
78
78
 
79
79
  const meAsCastMember = map._owner
80
- .as(CustomGroup)
80
+ .castAs(CustomGroup)
81
81
  .members.find((member) => member.id === me.id);
82
82
  expect(meAsCastMember?.account?.profile?.name).toBe(
83
83
  "Hermes Puggington",
@@ -86,6 +86,6 @@ describe("Custom accounts and groups", async () => {
86
86
 
87
87
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
88
88
  expect((map._owner as any).nMembers).toBeUndefined();
89
- expect(map._owner.as(CustomGroup).nMembers).toBe(2);
89
+ expect(map._owner.castAs(CustomGroup).nMembers).toBe(2);
90
90
  });
91
91
  });