jazz-tools 0.7.1 → 0.7.6

Sign up to get free protection for your applications and to get access to all the features.
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
  });