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
@@ -1,7 +1,7 @@
1
- import { Effect, Sink, Stream } from "effect";
1
+ import { Effect, Option, Sink, Stream } from "effect";
2
2
  import type { CojsonInternalTypes, RawCoValue } from "cojson";
3
3
  import { RawAccount } from "cojson";
4
- import type { Me, UnavailableError } from "../internal.js";
4
+ import type { DeeplyLoaded, DepthsIn, UnavailableError } from "../internal.js";
5
5
  import {
6
6
  Account,
7
7
  AccountCtx,
@@ -9,209 +9,238 @@ import {
9
9
  SubscriptionScope,
10
10
  Ref,
11
11
  inspect,
12
+ subscriptionsScopes,
12
13
  } from "../internal.js";
14
+ import { fulfillsDepth } from "./deepLoading.js";
13
15
 
14
- export type SubclassedConstructor<T> = {
15
- new (...args: any[]): T;
16
- };
17
-
18
- export interface CoValueClass<Value extends CoValue = CoValue, Init = any> {
19
- /** @category Construction and loading */
20
- new (init: Init, options: { owner: Account | Group }): Value;
21
-
16
+ /** @category Abstract interfaces */
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ export interface CoValueClass<Value extends CoValue = CoValue> {
22
19
  /** @ignore */
23
- fromRaw(raw: Value["_raw"]): Value;
24
-
25
- /** @category Construction and loading */
26
- load<V extends Value>(
27
- this: SubclassedConstructor<V>,
28
- id: ID<V>,
29
- options: {
30
- as: Account & Me;
31
- onProgress?: (progress: number) => void;
32
- }
33
- ): Promise<V | undefined>;
34
-
35
- /** @category Construction and loading */
36
- loadEf<V extends Value>(
37
- this: SubclassedConstructor<V>,
38
- id: ID<V>
39
- ): Effect.Effect<V, UnavailableError, AccountCtx>;
40
-
41
- /** @category Subscription */
42
- subscribe<V extends Value, Acc extends Account>(
43
- this: SubclassedConstructor<V>,
44
- id: ID<V>,
45
- options: { as: Acc & Me },
46
- onUpdate: (value: V) => void
47
- ): () => void;
48
-
49
- /** @category Subscription */
50
- subscribeEf<V extends Value>(
51
- this: SubclassedConstructor<V>,
52
- id: ID<V>
53
- ): Stream.Stream<V, UnavailableError, AccountCtx>;
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ new (...args: any[]): Value;
54
22
  }
55
23
 
56
- /** @category Schemas & CoValues - Abstract interfaces */
57
- export interface CoValue<Type extends string = string, Raw = any> {
58
- /** @category Value identity */
24
+ export interface CoValueFromRaw<V extends CoValue> {
25
+ fromRaw(raw: V["_raw"]): V;
26
+ }
27
+
28
+ /** @category Abstract interfaces */
29
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
+ export interface CoValue {
31
+ /** @category Content */
59
32
  readonly id: ID<this>;
60
- /** @category Value identity */
61
- _type: Type;
33
+ /** @category Type Helpers */
34
+ _type: string;
62
35
  /** @category Collaboration */
63
36
  _owner: Account | Group;
64
- /** @category Subscription */
65
- subscribe(listener: (update: this) => void): () => void;
66
- /** @category Subscription */
67
- subscribeEf(): Stream.Stream<this, UnavailableError, never>;
68
- /** @category Internals */
69
- _raw: Raw;
70
37
  /** @category Internals */
71
- readonly _loadedAs: Account & Me;
72
- /** @category Stringifying & inspection */
38
+ _raw: RawCoValue;
39
+ /** @internal */
40
+ readonly _loadedAs: Account;
41
+ /** @category Stringifying & Inspection */
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
43
  toJSON(): any[] | object;
74
- /** @category Stringifying & inspection */
44
+ /** @category Stringifying & Inspection */
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
75
46
  [inspect](): any;
76
47
  }
77
48
 
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
50
  export function isCoValue(value: any): value is CoValue {
79
51
  return value && value._type !== undefined;
80
52
  }
81
53
 
82
- /** @category Schemas & CoValues - Abstract interfaces */
83
- export type ID<T> = CojsonInternalTypes.RawCoID & {
84
- readonly __type: T;
85
- };
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> {
58
+ return typeof value === "function" && value.fromRaw !== undefined;
59
+ }
86
60
 
61
+ /** @category CoValues */
62
+ export type ID<T> = CojsonInternalTypes.RawCoID & IDMarker<T>;
63
+
64
+ type IDMarker<out T> = { __type(_: never): T };
65
+
66
+ /** @internal */
87
67
  export class CoValueBase implements CoValue {
88
68
  id!: ID<this>;
89
69
  _type!: string;
90
70
  _raw!: RawCoValue;
71
+ _instanceID!: string;
91
72
 
92
73
  get _owner(): Account | Group {
93
- return this._raw.group instanceof RawAccount
94
- ? Account.fromRaw(this._raw.group)
95
- : Group.fromRaw(this._raw.group);
74
+ const owner =
75
+ this._raw.group instanceof RawAccount
76
+ ? Account.fromRaw(this._raw.group)
77
+ : Group.fromRaw(this._raw.group);
78
+
79
+ const subScope = subscriptionsScopes.get(this);
80
+ if (subScope) {
81
+ subScope.onRefAccessedOrSet(this.id, owner.id);
82
+ subscriptionsScopes.set(owner, subScope);
83
+ }
84
+
85
+ return owner;
96
86
  }
97
87
 
88
+ /** @private */
98
89
  get _loadedAs() {
99
90
  return Account.fromNode(this._raw.core.node);
100
91
  }
101
92
 
102
- constructor(..._args: any) {}
93
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
94
+ constructor(..._args: any) {
95
+ Object.defineProperty(this, "_instanceID", {
96
+ value: `instance-${Math.random().toString(36).slice(2)}`,
97
+ enumerable: false,
98
+ });
99
+ }
103
100
 
101
+ /** @category Internals */
104
102
  static fromRaw<V extends CoValue>(
105
- this: SubclassedConstructor<V>,
106
- raw: RawCoValue
103
+ this: CoValueClass<V>,
104
+ raw: RawCoValue,
107
105
  ): V {
108
- return new this(undefined, { fromRaw: raw });
106
+ return new this({ fromRaw: raw });
109
107
  }
110
108
 
111
- static loadEf<V extends CoValue>(
112
- this: SubclassedConstructor<V> & typeof CoValueBase,
113
- id: ID<V>
114
- ): Effect.Effect<V, UnavailableError, AccountCtx> {
115
- return Effect.gen(this, function* (_) {
116
- const account = yield* _(AccountCtx);
117
- return yield* _(
118
- new Ref(id as ID<V>, account, {
119
- ref: () => this as CoValueClass<V>,
120
- }).loadEf()
121
- );
122
- });
109
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
110
+ toJSON(): object | any[] {
111
+ return {
112
+ id: this.id,
113
+ type: this._type,
114
+ error: "unknown CoValue class",
115
+ };
123
116
  }
124
117
 
125
- static load<V extends CoValue>(
126
- this: SubclassedConstructor<V> & typeof CoValueBase,
127
- id: ID<V>,
128
- options: {
129
- as: Account & Me;
130
- onProgress?: (progress: number) => void;
131
- }
132
- ): Promise<V | undefined> {
133
- return new Ref(id as ID<V>, options.as, {
134
- ref: () => this as CoValueClass<V>,
135
- }).load(options?.onProgress && { onProgress: options.onProgress });
118
+ [inspect]() {
119
+ return this.toJSON();
136
120
  }
137
121
 
138
- static subscribe<V extends CoValue, Acc extends Account>(
139
- this: SubclassedConstructor<V> & typeof CoValueBase,
140
- id: ID<V>,
141
- options: { as: Acc & Me },
142
- onUpdate: (value: V) => void
143
- ): () => void {
144
- void Effect.runPromise(
145
- Effect.provideService(
146
- this.subscribeEf(id).pipe(
147
- Stream.run(
148
- Sink.forEach((update) =>
149
- Effect.sync(() => onUpdate(update))
150
- )
151
- )
152
- ),
153
- AccountCtx,
154
- options.as as Account & Me
155
- )
156
- );
157
-
158
- return function unsubscribe() {};
122
+ /** @category Type Helpers */
123
+ castAs<Cl extends CoValueClass & CoValueFromRaw<CoValue>>(
124
+ cl: Cl,
125
+ ): InstanceType<Cl> {
126
+ return cl.fromRaw(this._raw) as InstanceType<Cl>;
159
127
  }
128
+ }
160
129
 
161
- static subscribeEf<V extends CoValue>(
162
- this: SubclassedConstructor<V> & typeof CoValueBase,
163
- id: ID<V>
164
- ): Stream.Stream<V, UnavailableError, AccountCtx> {
165
- return Stream.fromEffect(this.loadEf(id)).pipe(
166
- Stream.flatMap((value) =>
167
- Stream.asyncScoped<V, UnavailableError>((emit) =>
168
- Effect.gen(this, function* (_) {
169
- const subscription = new SubscriptionScope(
170
- value,
171
- this,
172
- (update) => {
173
- void emit.single(update as V);
174
- }
175
- );
176
-
177
- yield* _(
178
- Effect.addFinalizer(() =>
179
- Effect.sync(() => subscription.unsubscribeAll())
180
- )
181
- );
182
- })
183
- )
184
- )
185
- );
186
- }
130
+ export function loadCoValue<V extends CoValue, Depth>(
131
+ cls: CoValueClass<V>,
132
+ id: ID<V>,
133
+ as: Account,
134
+ depth: Depth & DepthsIn<V>,
135
+ ): Promise<DeeplyLoaded<V, Depth> | undefined> {
136
+ return Effect.runPromise(
137
+ loadCoValueEf(cls, id, depth).pipe(
138
+ Effect.mapError(() => undefined),
139
+ Effect.merge,
140
+ Effect.provideService(AccountCtx, as),
141
+ ),
142
+ );
143
+ }
187
144
 
188
- subscribe(listener: (update: this) => void): () => void {
189
- return (this.constructor as unknown as typeof CoValueBase).subscribe(
190
- this.id as unknown as ID<CoValue>,
191
- { as: this._loadedAs },
192
- listener as unknown as (update: CoValue) => void
193
- );
194
- }
145
+ export function ensureCoValueLoaded<V extends CoValue, Depth>(
146
+ existing: V,
147
+ depth: Depth & DepthsIn<V>,
148
+ ): Promise<DeeplyLoaded<V, Depth> | undefined> {
149
+ return loadCoValue(
150
+ existing.constructor as CoValueClass<V>,
151
+ existing.id,
152
+ existing._loadedAs,
153
+ depth,
154
+ );
155
+ }
195
156
 
196
- subscribeEf(): Stream.Stream<this, UnavailableError, never> {
197
- return Stream.provideService(
198
- (this.constructor as unknown as typeof CoValueBase).subscribeEf(
199
- this.id as unknown as ID<CoValue>
157
+ export function loadCoValueEf<V extends CoValue, Depth>(
158
+ cls: CoValueClass<V>,
159
+ id: ID<V>,
160
+ depth: Depth & DepthsIn<V>,
161
+ ): Effect.Effect<DeeplyLoaded<V, Depth>, UnavailableError, AccountCtx> {
162
+ return subscribeToCoValueEf(cls, id, depth).pipe(
163
+ Stream.runHead,
164
+ Effect.andThen(
165
+ Effect.mapError((_noSuchElem) => "unavailable" as const),
166
+ ),
167
+ );
168
+ }
169
+
170
+ export function subscribeToCoValue<V extends CoValue, Depth>(
171
+ cls: CoValueClass<V>,
172
+ id: ID<V>,
173
+ as: Account,
174
+ depth: Depth & DepthsIn<V>,
175
+ listener: (value: DeeplyLoaded<V, Depth>) => void,
176
+ ): () => void {
177
+ void Effect.runPromise(
178
+ Effect.provideService(
179
+ subscribeToCoValueEf(cls, id, depth).pipe(
180
+ Stream.run(
181
+ Sink.forEach((update) =>
182
+ Effect.sync(() => listener(update)),
183
+ ),
184
+ ),
200
185
  ),
201
186
  AccountCtx,
202
- this._loadedAs
203
- ) as unknown as Stream.Stream<this, UnavailableError, never>;
204
- }
187
+ as,
188
+ ),
189
+ );
205
190
 
206
- toJSON(): object | any[] {
207
- return {
208
- id: this.id,
209
- type: this._type,
210
- error: "unknown CoValue class",
211
- };
212
- }
191
+ return function unsubscribe() {};
192
+ }
213
193
 
214
- [inspect]() {
215
- return this.toJSON();
216
- }
194
+ export function subscribeToExistingCoValue<V extends CoValue, Depth>(
195
+ existing: V,
196
+ depth: Depth & DepthsIn<V>,
197
+ listener: (value: DeeplyLoaded<V, Depth>) => void,
198
+ ): () => void {
199
+ return subscribeToCoValue(
200
+ existing.constructor as CoValueClass<V>,
201
+ existing.id,
202
+ existing._loadedAs,
203
+ depth,
204
+ listener,
205
+ );
206
+ }
207
+
208
+ export function subscribeToCoValueEf<V extends CoValue, Depth>(
209
+ cls: CoValueClass<V>,
210
+ id: ID<V>,
211
+ depth: Depth & DepthsIn<V>,
212
+ ): Stream.Stream<DeeplyLoaded<V, Depth>, UnavailableError, AccountCtx> {
213
+ return AccountCtx.pipe(
214
+ Effect.andThen((account) =>
215
+ new Ref(id, account, {
216
+ ref: cls,
217
+ optional: false,
218
+ }).loadEf(),
219
+ ),
220
+ Stream.fromEffect,
221
+ Stream.flatMap((value: V) =>
222
+ Stream.asyncScoped<V, UnavailableError>((emit) =>
223
+ Effect.gen(function* (_) {
224
+ const subscription = new SubscriptionScope(
225
+ value,
226
+ cls as CoValueClass<V> & CoValueFromRaw<V>,
227
+ (update) => void emit.single(update as V),
228
+ );
229
+
230
+ yield* _(
231
+ Effect.addFinalizer(() =>
232
+ Effect.sync(() => subscription.unsubscribeAll()),
233
+ ),
234
+ );
235
+ }),
236
+ ),
237
+ ),
238
+ Stream.filterMap((update: V) =>
239
+ Option.fromNullable(
240
+ fulfillsDepth(depth, update)
241
+ ? (update as DeeplyLoaded<V, Depth>)
242
+ : undefined,
243
+ ),
244
+ ),
245
+ );
217
246
  }
@@ -0,0 +1,110 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { ItemsSym } from "./symbols.js";
3
+
4
+ (globalThis as any).devtoolsFormatters = [
5
+ {
6
+ header: (object: any) => {
7
+ if (object._type === "CoMap") {
8
+ return ["div", {}, ["span", {}, object.constructor.name]];
9
+ } else if (object._type === "CoList") {
10
+ return [
11
+ "div",
12
+ {},
13
+ [
14
+ "span",
15
+ {},
16
+ object.constructor.name + "(" + object.length + ") ",
17
+ ],
18
+ ];
19
+ } else if (object._type === "Account") {
20
+ return [
21
+ "div",
22
+ {},
23
+ [
24
+ "span",
25
+ {},
26
+ object.constructor.name +
27
+ "(" +
28
+ object._refs.profile.value?.name +
29
+ (object.isMe ? " ME" : "") +
30
+ ")",
31
+ ],
32
+ ];
33
+ } else {
34
+ return null;
35
+ }
36
+ },
37
+ hasBody: function () {
38
+ return true;
39
+ },
40
+ body: function (object: any) {
41
+ if (object._type === "CoMap" || object._type === "Account") {
42
+ return [
43
+ "div",
44
+ { style: "margin-left: 15px" },
45
+ ["div", "id: ", ["object", { object: object.id }]],
46
+ ...Object.entries(object).map(([k, v]) => [
47
+ "div",
48
+ { style: "white-space: nowrap;" },
49
+ [
50
+ "span",
51
+ { style: "font-weight: bold; opacity: 0.6" },
52
+ k,
53
+ ": ",
54
+ ],
55
+ ["object", { object: v }],
56
+ ...(typeof object._schema[k] === "function"
57
+ ? v === null
58
+ ? [
59
+ [
60
+ "span",
61
+ { style: "opacity: 0.5" },
62
+ ` (pending ${object._schema[k].name} `,
63
+ [
64
+ "object",
65
+ { object: object._refs[k] },
66
+ ],
67
+ ")",
68
+ ],
69
+ ]
70
+ : []
71
+ : []),
72
+ ]),
73
+ ];
74
+ } else if (object._type === "CoList") {
75
+ return [
76
+ "div",
77
+ { style: "margin-left: 15px" },
78
+ ["div", "id: ", ["object", { object: object.id }]],
79
+ ...(object as any[]).map((v, i) => [
80
+ "div",
81
+ { style: "white-space: nowrap;" },
82
+ [
83
+ "span",
84
+ { style: "font-weight: bold; opacity: 0.6" },
85
+ i,
86
+ ": ",
87
+ ],
88
+ ["object", { object: v }],
89
+ ...(typeof object._schema[ItemsSym] === "function"
90
+ ? v === null
91
+ ? [
92
+ [
93
+ "span",
94
+ { style: "opacity: 0.5" },
95
+ ` (pending ${object._schema[ItemsSym].name} `,
96
+ [
97
+ "object",
98
+ { object: object._refs[i] },
99
+ ],
100
+ ")",
101
+ ],
102
+ ]
103
+ : []
104
+ : []),
105
+ ]),
106
+ ];
107
+ }
108
+ },
109
+ },
110
+ ];
@@ -1,2 +1,2 @@
1
1
  export const inspect = Symbol.for("nodejs.util.inspect.custom");
2
- export type inspect = typeof inspect;
2
+ export type inspect = typeof inspect;