jazz-tools 0.7.0-alpha.9 → 0.7.3

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