jazz-tools 0.7.0-alpha.9 → 0.7.1

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