jazz-tools 0.7.0-alpha.9 → 0.7.3

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 (73) hide show
  1. package/.eslintrc.cjs +3 -10
  2. package/.prettierrc.js +9 -0
  3. package/.turbo/turbo-build.log +14 -15
  4. package/.turbo/turbo-lint.log +4 -0
  5. package/.turbo/turbo-test.log +140 -0
  6. package/CHANGELOG.md +331 -27
  7. package/LICENSE.txt +1 -1
  8. package/README.md +10 -2
  9. package/dist/coValues/account.js +86 -41
  10. package/dist/coValues/account.js.map +1 -1
  11. package/dist/coValues/coList.js +75 -48
  12. package/dist/coValues/coList.js.map +1 -1
  13. package/dist/coValues/coMap.js +167 -44
  14. package/dist/coValues/coMap.js.map +1 -1
  15. package/dist/coValues/coStream.js +192 -35
  16. package/dist/coValues/coStream.js.map +1 -1
  17. package/dist/coValues/deepLoading.js +60 -0
  18. package/dist/coValues/deepLoading.js.map +1 -0
  19. package/dist/coValues/extensions/imageDef.js +10 -7
  20. package/dist/coValues/extensions/imageDef.js.map +1 -1
  21. package/dist/coValues/group.js +73 -13
  22. package/dist/coValues/group.js.map +1 -1
  23. package/dist/coValues/interfaces.js +58 -35
  24. package/dist/coValues/interfaces.js.map +1 -1
  25. package/dist/implementation/devtoolsFormatters.js +114 -0
  26. package/dist/implementation/devtoolsFormatters.js.map +1 -0
  27. package/dist/implementation/refs.js +58 -18
  28. package/dist/implementation/refs.js.map +1 -1
  29. package/dist/implementation/schema.js +58 -0
  30. package/dist/implementation/schema.js.map +1 -0
  31. package/dist/implementation/subscriptionScope.js +19 -1
  32. package/dist/implementation/subscriptionScope.js.map +1 -1
  33. package/dist/implementation/symbols.js +5 -0
  34. package/dist/implementation/symbols.js.map +1 -0
  35. package/dist/index.js +4 -5
  36. package/dist/index.js.map +1 -1
  37. package/dist/internal.js +5 -2
  38. package/dist/internal.js.map +1 -1
  39. package/dist/tests/coList.test.js +51 -48
  40. package/dist/tests/coList.test.js.map +1 -1
  41. package/dist/tests/coMap.test.js +131 -74
  42. package/dist/tests/coMap.test.js.map +1 -1
  43. package/dist/tests/coStream.test.js +56 -41
  44. package/dist/tests/coStream.test.js.map +1 -1
  45. package/dist/tests/deepLoading.test.js +188 -0
  46. package/dist/tests/deepLoading.test.js.map +1 -0
  47. package/dist/tests/groupsAndAccounts.test.js +83 -0
  48. package/dist/tests/groupsAndAccounts.test.js.map +1 -0
  49. package/package.json +17 -9
  50. package/src/coValues/account.ts +186 -128
  51. package/src/coValues/coList.ts +156 -107
  52. package/src/coValues/coMap.ts +272 -148
  53. package/src/coValues/coStream.ts +388 -87
  54. package/src/coValues/deepLoading.ts +229 -0
  55. package/src/coValues/extensions/imageDef.ts +17 -13
  56. package/src/coValues/group.ts +166 -59
  57. package/src/coValues/interfaces.ts +189 -160
  58. package/src/implementation/devtoolsFormatters.ts +110 -0
  59. package/src/implementation/inspect.ts +1 -1
  60. package/src/implementation/refs.ts +80 -28
  61. package/src/implementation/schema.ts +141 -0
  62. package/src/implementation/subscriptionScope.ts +48 -12
  63. package/src/implementation/symbols.ts +11 -0
  64. package/src/index.ts +19 -8
  65. package/src/internal.ts +7 -3
  66. package/src/tests/coList.test.ts +77 -62
  67. package/src/tests/coMap.test.ts +201 -114
  68. package/src/tests/coStream.test.ts +113 -84
  69. package/src/tests/deepLoading.test.ts +304 -0
  70. package/src/tests/groupsAndAccounts.test.ts +91 -0
  71. package/dist/implementation/encoding.js +0 -26
  72. package/dist/implementation/encoding.js.map +0 -1
  73. package/src/implementation/encoding.ts +0 -105
@@ -2,6 +2,7 @@ import { LocalNode } from "cojson";
2
2
  import type {
3
3
  AgentSecret,
4
4
  CoID,
5
+ CryptoProvider,
5
6
  InviteSecret,
6
7
  Peer,
7
8
  RawAccount,
@@ -10,121 +11,111 @@ import type {
10
11
  RawControlledAccount,
11
12
  SessionID,
12
13
  } from "cojson";
13
- import { Context } from "effect";
14
+ import { Context, Effect, Stream } from "effect";
14
15
  import type {
15
16
  CoMap,
16
17
  CoValue,
17
18
  CoValueClass,
18
- Encoding,
19
- Group,
19
+ Schema,
20
20
  ID,
21
21
  RefEncoded,
22
- SubclassedConstructor,
22
+ RefIfCoValue,
23
+ DeeplyLoaded,
24
+ DepthsIn,
23
25
  UnavailableError,
24
26
  } from "../internal.js";
25
27
  import {
28
+ Group,
26
29
  CoValueBase,
30
+ MembersSym,
27
31
  Profile,
28
32
  Ref,
29
33
  SchemaInit,
30
34
  inspect,
31
35
  subscriptionsScopes,
36
+ loadCoValue,
37
+ loadCoValueEf,
38
+ subscribeToCoValue,
39
+ subscribeToCoValueEf,
40
+ ensureCoValueLoaded,
41
+ subscribeToExistingCoValue,
32
42
  } from "../internal.js";
33
- import type { Stream } from "effect/Stream";
34
43
 
35
- export class Account<
36
- Def extends { profile: Profile | null; root: CoMap | null } = {
37
- profile: Profile | null;
38
- root: CoMap | null;
39
- },
40
- >
41
- extends CoValueBase
42
- implements CoValue<"Account", RawAccount | RawControlledAccount>
43
- {
44
- id!: ID<this>;
45
- _type!: "Account";
46
- _raw!: RawAccount | RawControlledAccount;
47
-
48
- static _encoding: any;
49
- get _encoding(): {
50
- profile: Encoding;
51
- root: Encoding;
44
+ /** @category Identity & Permissions */
45
+ export class Account extends CoValueBase implements CoValue {
46
+ declare id: ID<this>;
47
+ declare _type: "Account";
48
+ declare _raw: RawAccount | RawControlledAccount;
49
+
50
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
51
+ static _schema: any;
52
+ get _schema(): {
53
+ profile: Schema;
54
+ root: Schema;
52
55
  } {
53
- return (this.constructor as typeof Account)._encoding;
56
+ return (this.constructor as typeof Account)._schema;
54
57
  }
55
58
  static {
56
- this._encoding = {
57
- profile: { ref: () => Profile },
58
- root: { json: true },
59
+ this._schema = {
60
+ profile: {
61
+ ref: () => Profile,
62
+ optional: false,
63
+ } satisfies RefEncoded<Profile>,
64
+ root: "json" satisfies Schema,
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
66
  } as any;
60
67
  }
61
68
 
62
69
  get _owner(): Account {
63
70
  return this as Account;
64
71
  }
65
- get _loadedAs(): Account & Me {
66
- return this.isMe
67
- ? (this as Account & Me)
68
- : Account.fromNode(this._raw.core.node);
72
+ get _loadedAs(): Account {
73
+ return this.isMe ? this : Account.fromNode(this._raw.core.node);
69
74
  }
70
75
 
71
- profile!: NonNullable<Def["profile"]> | null;
72
- root!: NonNullable<Def["root"]> | null;
76
+ declare profile: Profile | null;
77
+ declare root: CoMap | null;
73
78
 
74
- get _refs(): {
75
- profile: NonNullable<Def["profile"]> extends Profile
76
- ? Ref<NonNullable<Def["profile"]>> | null
77
- : null;
78
- root: NonNullable<Def["root"]> extends CoMap
79
- ? Ref<NonNullable<Def["root"]>> | null
80
- : null;
81
- } {
79
+ get _refs() {
82
80
  const profileID = this._raw.get("profile") as unknown as
83
- | ID<NonNullable<Def["profile"]>>
81
+ | ID<NonNullable<this["profile"]>>
84
82
  | undefined;
85
83
  const rootID = this._raw.get("root") as unknown as
86
- | ID<NonNullable<Def["root"]>>
84
+ | ID<NonNullable<this["root"]>>
87
85
  | undefined;
86
+
88
87
  return {
89
88
  profile:
90
89
  profileID &&
91
90
  (new Ref(
92
91
  profileID,
93
92
  this._loadedAs,
94
- this._encoding.profile as RefEncoded<
95
- NonNullable<Def["profile"]> & CoValue
96
- >
97
- ) as any),
93
+ this._schema.profile as RefEncoded<
94
+ NonNullable<this["profile"]> & CoValue
95
+ >,
96
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
97
+ ) as any as RefIfCoValue<this["profile"]>),
98
98
  root:
99
99
  rootID &&
100
100
  (new Ref(
101
101
  rootID,
102
102
  this._loadedAs,
103
- this._encoding.root as RefEncoded<
104
- NonNullable<Def["root"]> & CoValue
105
- >
106
- ) as any),
103
+ this._schema.root as RefEncoded<
104
+ NonNullable<this["root"]> & CoValue
105
+ >,
106
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
107
+ ) as any as RefIfCoValue<this["root"]>),
107
108
  };
108
109
  }
109
110
 
110
111
  isMe: boolean;
111
112
  sessionID: SessionID | undefined;
112
113
 
113
- constructor(init: undefined, options: { owner: Group | Account });
114
- constructor(
115
- init: undefined,
116
- options: { fromRaw: RawAccount | RawControlledAccount }
117
- );
118
- constructor(
119
- init: undefined,
120
- options:
121
- | { fromRaw: RawAccount | RawControlledAccount }
122
- | { owner: Group | Account }
123
- ) {
114
+ constructor(options: { fromRaw: RawAccount | RawControlledAccount }) {
124
115
  super();
125
116
  if (!("fromRaw" in options)) {
126
117
  throw new Error(
127
- "Can only construct account from raw or with .create()"
118
+ "Can only construct account from raw or with .create()",
128
119
  );
129
120
  }
130
121
  this.isMe = options.fromRaw.id == options.fromRaw.core.node.account.id;
@@ -135,16 +126,16 @@ export class Account<
135
126
  enumerable: false,
136
127
  },
137
128
  _raw: { value: options.fromRaw, enumerable: false },
129
+ _type: { value: "Account", enumerable: false },
138
130
  });
139
131
 
140
132
  if (this.isMe) {
141
- (this as Account & Me).sessionID =
142
- options.fromRaw.core.node.currentSessionID;
133
+ this.sessionID = options.fromRaw.core.node.currentSessionID;
143
134
  }
144
135
 
145
136
  return new Proxy(
146
137
  this,
147
- AccountAndGroupProxyHandler as ProxyHandler<this>
138
+ AccountAndGroupProxyHandler as ProxyHandler<this>,
148
139
  );
149
140
  }
150
141
 
@@ -154,88 +145,85 @@ export class Account<
154
145
  }
155
146
  }
156
147
 
157
- acceptInvite:
158
- | (<V extends CoValue>(
159
- valueID: ID<V>,
160
- inviteSecret: InviteSecret,
161
- coValueClass: CoValueClass<V>
162
- ) => Promise<V | undefined>)
163
- | undefined = (async <V extends CoValue>(
148
+ async acceptInvite<V extends CoValue>(
164
149
  valueID: ID<V>,
165
150
  inviteSecret: InviteSecret,
166
- coValueClass: CoValueClass<V>
167
- ) => {
151
+ coValueClass: CoValueClass<V>,
152
+ ) {
168
153
  if (!this.isMe) {
169
154
  throw new Error("Only a controlled account can accept invites");
170
155
  }
171
156
 
172
157
  await (this._raw as RawControlledAccount).acceptInvite(
173
158
  valueID as unknown as CoID<RawCoValue>,
174
- inviteSecret
159
+ inviteSecret,
175
160
  );
176
161
 
177
- return coValueClass.load(valueID, {
178
- as: this as Account & Me,
179
- });
180
- }) as any;
162
+ return loadCoValue(coValueClass, valueID, this as Account, []);
163
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
164
+ }
181
165
 
182
166
  static async create<A extends Account>(
183
- this: SubclassedConstructor<A> & typeof Account,
167
+ this: CoValueClass<A> & typeof Account,
184
168
  options: {
185
- name: string;
169
+ creationProps: { name: string };
186
170
  initialAgentSecret?: AgentSecret;
187
171
  peersToLoadFrom?: Peer[];
188
- }
189
- ): Promise<A & Me> {
172
+ crypto: CryptoProvider;
173
+ },
174
+ ): Promise<A> {
190
175
  const { node } = await LocalNode.withNewlyCreatedAccount({
191
176
  ...options,
192
- migration: async (rawAccount) => {
193
- const account = new this(undefined, {
177
+ migration: async (rawAccount, _node, creationProps) => {
178
+ const account = new this({
194
179
  fromRaw: rawAccount,
195
- }) as A & Me;
180
+ }) as A;
196
181
 
197
- await account.migrate?.();
182
+ await account.migrate?.(creationProps);
198
183
  },
199
184
  });
200
185
 
201
- return this.fromNode(node) as A & Me;
186
+ return this.fromNode(node) as A;
202
187
  }
203
188
 
204
189
  static async become<A extends Account>(
205
- this: SubclassedConstructor<A> & typeof Account,
190
+ this: CoValueClass<A> & typeof Account,
206
191
  options: {
207
192
  accountID: ID<Account>;
208
193
  accountSecret: AgentSecret;
209
- sessionID: SessionID;
194
+ sessionID?: SessionID;
210
195
  peersToLoadFrom: Peer[];
211
- }
212
- ): Promise<A & Me> {
196
+ crypto: CryptoProvider;
197
+ },
198
+ ): Promise<A> {
213
199
  const node = await LocalNode.withLoadedAccount({
214
200
  accountID: options.accountID as unknown as CoID<RawAccount>,
215
201
  accountSecret: options.accountSecret,
216
202
  sessionID: options.sessionID,
217
203
  peersToLoadFrom: options.peersToLoadFrom,
218
- migration: async (rawAccount) => {
219
- const account = new this(undefined, {
204
+ crypto: options.crypto,
205
+ migration: async (rawAccount, _node, creationProps) => {
206
+ const account = new this({
220
207
  fromRaw: rawAccount,
221
- }) as A & Me;
208
+ }) as A;
222
209
 
223
- await account.migrate?.();
210
+ await account.migrate?.(creationProps);
224
211
  },
225
212
  });
226
213
 
227
- return this.fromNode(node) as A & Me;
214
+ return this.fromNode(node) as A;
228
215
  }
229
216
 
230
217
  static fromNode<A extends Account>(
231
- this: SubclassedConstructor<A>,
232
- node: LocalNode
233
- ): A & Me {
234
- return new this(undefined, {
218
+ this: CoValueClass<A>,
219
+ node: LocalNode,
220
+ ): A {
221
+ return new this({
235
222
  fromRaw: node.account as RawControlledAccount,
236
- }) as A & Me;
223
+ }) as A;
237
224
  }
238
225
 
226
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
239
227
  toJSON(): object | any[] {
240
228
  return {
241
229
  id: this.id,
@@ -247,45 +235,122 @@ export class Account<
247
235
  return this.toJSON();
248
236
  }
249
237
 
250
- migrate: (() => void | Promise<void>) | undefined;
238
+ migrate(
239
+ this: Account,
240
+ creationProps?: { name: string },
241
+ ): void | Promise<void> {
242
+ if (creationProps) {
243
+ const profileGroup = Group.create({ owner: this });
244
+ profileGroup.addMember("everyone", "reader");
245
+ this.profile = Profile.create(
246
+ { name: creationProps.name },
247
+ { owner: profileGroup },
248
+ );
249
+ }
250
+ }
251
+
252
+ /** @category Subscription & Loading */
253
+ static load<A extends Account, Depth>(
254
+ this: CoValueClass<A>,
255
+ id: ID<A>,
256
+ as: Account,
257
+ depth: Depth & DepthsIn<A>,
258
+ ): Promise<DeeplyLoaded<A, Depth> | undefined> {
259
+ return loadCoValue(this, id, as, depth);
260
+ }
261
+
262
+ /** @category Subscription & Loading */
263
+ static loadEf<A extends Account, Depth>(
264
+ this: CoValueClass<A>,
265
+ id: ID<A>,
266
+ depth: Depth & DepthsIn<A>,
267
+ ): Effect.Effect<DeeplyLoaded<A, Depth>, UnavailableError, AccountCtx> {
268
+ return loadCoValueEf<A, Depth>(this, id, depth);
269
+ }
270
+
271
+ /** @category Subscription & Loading */
272
+ static subscribe<A extends Account, Depth>(
273
+ this: CoValueClass<A>,
274
+ id: ID<A>,
275
+ as: Account,
276
+ depth: Depth & DepthsIn<A>,
277
+ listener: (value: DeeplyLoaded<A, Depth>) => void,
278
+ ): () => void {
279
+ return subscribeToCoValue<A, Depth>(this, id, as, depth, listener);
280
+ }
281
+
282
+ /** @category Subscription & Loading */
283
+ static subscribeEf<A extends Account, Depth>(
284
+ this: CoValueClass<A>,
285
+ id: ID<A>,
286
+ depth: Depth & DepthsIn<A>,
287
+ ): Stream.Stream<DeeplyLoaded<A, Depth>, UnavailableError, AccountCtx> {
288
+ return subscribeToCoValueEf<A, Depth>(this, id, depth);
289
+ }
290
+
291
+ /** @category Subscription & Loading */
292
+ ensureLoaded<A extends Account, Depth>(
293
+ this: A,
294
+ depth: Depth & DepthsIn<A>,
295
+ ): Promise<DeeplyLoaded<A, Depth> | undefined> {
296
+ return ensureCoValueLoaded(this, depth);
297
+ }
298
+
299
+ /** @category Subscription & Loading */
300
+ subscribe<A extends Account, Depth>(
301
+ this: A,
302
+ depth: Depth & DepthsIn<A>,
303
+ listener: (value: DeeplyLoaded<A, Depth>) => void,
304
+ ): () => void {
305
+ return subscribeToExistingCoValue(this, depth, listener);
306
+ }
251
307
  }
252
308
 
253
309
  export const AccountAndGroupProxyHandler: ProxyHandler<Account | Group> = {
254
310
  get(target, key, receiver) {
255
311
  if (key === "profile") {
256
312
  const ref = target._refs.profile;
257
- return ref ? ref.accessFrom(receiver) : (undefined as any);
313
+ return ref
314
+ ? ref.accessFrom(receiver, "profile")
315
+ : // eslint-disable-next-line @typescript-eslint/no-explicit-any
316
+ (undefined as any);
258
317
  } else if (key === "root") {
259
318
  const ref = target._refs.root;
260
- return ref ? ref.accessFrom(receiver) : (undefined as any);
319
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
320
+ return ref ? ref.accessFrom(receiver, "root") : (undefined as any);
261
321
  } else {
262
322
  return Reflect.get(target, key, receiver);
263
323
  }
264
324
  },
265
325
  set(target, key, value, receiver) {
266
326
  if (
267
- (key === "profile" || key === "root") &&
327
+ (key === "profile" || key === "root" || key === MembersSym) &&
268
328
  typeof value === "object" &&
269
329
  SchemaInit in value
270
330
  ) {
271
- (target.constructor as typeof CoMap)._encoding ||= {};
272
- (target.constructor as typeof CoMap)._encoding[key] =
331
+ (target.constructor as typeof CoMap)._schema ||= {};
332
+ (target.constructor as typeof CoMap)._schema[key] =
273
333
  value[SchemaInit];
274
334
  return true;
275
335
  } else if (key === "profile") {
276
336
  if (value) {
277
337
  target._raw.set(
278
338
  "profile",
279
- value.id as unknown as CoID<RawCoMap>
339
+ value.id as unknown as CoID<RawCoMap>,
340
+ "trusting",
280
341
  );
281
342
  }
282
- subscriptionsScopes.get(receiver)?.onRefAccessedOrSet(value.id);
343
+ subscriptionsScopes
344
+ .get(receiver)
345
+ ?.onRefAccessedOrSet(target.id, value.id);
283
346
  return true;
284
347
  } else if (key === "root") {
285
348
  if (value) {
286
349
  target._raw.set("root", value.id as unknown as CoID<RawCoMap>);
287
350
  }
288
- subscriptionsScopes.get(receiver)?.onRefAccessedOrSet(value.id);
351
+ subscriptionsScopes
352
+ .get(receiver)
353
+ ?.onRefAccessedOrSet(target.id, value.id);
289
354
  return true;
290
355
  } else {
291
356
  return Reflect.set(target, key, value, receiver);
@@ -293,12 +358,12 @@ export const AccountAndGroupProxyHandler: ProxyHandler<Account | Group> = {
293
358
  },
294
359
  defineProperty(target, key, descriptor) {
295
360
  if (
296
- (key === "profile" || key === "root") &&
361
+ (key === "profile" || key === "root" || key === MembersSym) &&
297
362
  typeof descriptor.value === "object" &&
298
363
  SchemaInit in descriptor.value
299
364
  ) {
300
- (target.constructor as typeof CoMap)._encoding ||= {};
301
- (target.constructor as typeof CoMap)._encoding[key] =
365
+ (target.constructor as typeof CoMap)._schema ||= {};
366
+ (target.constructor as typeof CoMap)._schema[key] =
302
367
  descriptor.value[SchemaInit];
303
368
  return true;
304
369
  } else {
@@ -307,21 +372,14 @@ export const AccountAndGroupProxyHandler: ProxyHandler<Account | Group> = {
307
372
  },
308
373
  };
309
374
 
310
- export interface Me {
311
- id: ID<any>;
375
+ /** @category Identity & Permissions */
376
+ export class AccountCtx extends Context.Tag("Account")<AccountCtx, Account>() {}
377
+
378
+ /** @category Identity & Permissions */
379
+ export function isControlledAccount(account: Account): account is Account & {
312
380
  isMe: true;
313
- _raw: RawControlledAccount;
314
381
  sessionID: SessionID;
315
- subscribe(listener: (update: this & Me) => void): () => void;
316
- subscribeEf(): Stream<this & Me, UnavailableError, never>;
317
- acceptInvite: (...args: any[]) => any;
318
- }
319
-
320
- export class AccountCtx extends Context.Tag("Account")<
321
- AccountCtx,
322
- Account & Me
323
- >() {}
324
-
325
- export function isControlledAccount(account: Account): account is Account & Me {
382
+ _raw: RawControlledAccount;
383
+ } {
326
384
  return account.isMe;
327
385
  }