jazz-tools 0.7.0-alpha.4 → 0.7.0-alpha.42

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) 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 +252 -0
  7. package/README.md +10 -2
  8. package/dist/coValues/account.js +104 -50
  9. package/dist/coValues/account.js.map +1 -1
  10. package/dist/coValues/coList.js +165 -112
  11. package/dist/coValues/coList.js.map +1 -1
  12. package/dist/coValues/coMap.js +243 -163
  13. package/dist/coValues/coMap.js.map +1 -1
  14. package/dist/coValues/coStream.js +256 -73
  15. package/dist/coValues/coStream.js.map +1 -1
  16. package/dist/coValues/deepLoading.js +57 -0
  17. package/dist/coValues/deepLoading.js.map +1 -0
  18. package/dist/coValues/extensions/imageDef.js +14 -8
  19. package/dist/coValues/extensions/imageDef.js.map +1 -1
  20. package/dist/coValues/group.js +49 -38
  21. package/dist/coValues/group.js.map +1 -1
  22. package/dist/coValues/interfaces.js +66 -26
  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 +60 -19
  27. package/dist/implementation/refs.js.map +1 -1
  28. package/dist/implementation/schema.js +44 -1
  29. package/dist/implementation/schema.js.map +1 -1
  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 +4 -5
  35. package/dist/index.js.map +1 -1
  36. package/dist/internal.js +4 -1
  37. package/dist/internal.js.map +1 -1
  38. package/dist/tests/coList.test.js +51 -52
  39. package/dist/tests/coList.test.js.map +1 -1
  40. package/dist/tests/coMap.test.js +196 -75
  41. package/dist/tests/coMap.test.js.map +1 -1
  42. package/dist/tests/coStream.test.js +95 -85
  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 +184 -153
  50. package/src/coValues/coList.ts +220 -173
  51. package/src/coValues/coMap.ts +322 -312
  52. package/src/coValues/coStream.ts +397 -135
  53. package/src/coValues/deepLoading.ts +215 -0
  54. package/src/coValues/extensions/imageDef.ts +16 -17
  55. package/src/coValues/group.ts +95 -111
  56. package/src/coValues/interfaces.ts +217 -115
  57. package/src/implementation/devtoolsFormatters.ts +110 -0
  58. package/src/implementation/inspect.ts +1 -1
  59. package/src/implementation/refs.ts +91 -38
  60. package/src/implementation/schema.ts +87 -46
  61. package/src/implementation/subscriptionScope.ts +44 -12
  62. package/src/implementation/symbols.ts +11 -0
  63. package/src/index.ts +13 -9
  64. package/src/internal.ts +6 -2
  65. package/src/tests/coList.test.ts +67 -66
  66. package/src/tests/coMap.test.ts +226 -123
  67. package/src/tests/coStream.test.ts +141 -131
  68. package/src/tests/deepLoading.test.ts +301 -0
  69. package/src/tests/groupsAndAccounts.test.ts +91 -0
@@ -1,206 +1,237 @@
1
1
  import type { JsonValue, RawCoMap } from "cojson";
2
2
  import type { Simplify } from "effect/Types";
3
- import { Schema } from "@effect/schema";
3
+ import { encodeSync, decodeSync } from "@effect/schema/Schema";
4
4
  import type {
5
5
  CoValue,
6
- Encoder,
7
- FieldDescriptor,
8
- FieldDescriptorFor,
6
+ Schema,
9
7
  Group,
10
8
  ID,
11
- RefField,
12
- EnsureCoValueNullable,
13
- CoValueClass,
9
+ RefEncoded,
10
+ IfCo,
11
+ RefIfCoValue,
12
+ ClassOf,
14
13
  } from "../internal.js";
15
14
  import {
16
15
  Account,
17
16
  CoValueBase,
18
- ValueRef,
17
+ Ref,
18
+ SchemaInit,
19
19
  inspect,
20
20
  makeRefs,
21
21
  subscriptionsScopes,
22
- indexSignature,
22
+ ItemsSym,
23
+ InitValues,
24
+ isRefEncoded,
23
25
  } from "../internal.js";
24
26
 
25
- type EnsureValid<
26
- Fields extends { [key: string]: any; [indexSignature]?: any },
27
- > = {
28
- [Key in OwnKeys<Fields> as IfOptionalKey<
29
- Key,
30
- Fields
31
- >]?: EnsureCoValueNullable<Fields[Key], Key>;
32
- } & {
33
- [Key in OwnKeys<Fields> as IfRequiredKey<
34
- Key,
35
- Fields
36
- >]: EnsureCoValueNullable<Fields[Key], Key>;
37
- } & {
38
- [Key in indexSignature]?: EnsureCoValueNullable<
39
- Fields[indexSignature],
40
- Key
41
- >;
27
+ type CoMapEdit<V> = {
28
+ value?: V;
29
+ ref?: RefIfCoValue<V>;
30
+ by?: Account;
31
+ madeAt: Date;
42
32
  };
43
33
 
44
- type IfOptionalKey<Key extends keyof Obj, Obj> = Pick<
45
- Partial<Obj>,
46
- Key
47
- > extends Pick<Obj, Key>
48
- ? Key
49
- : never;
50
- type IfRequiredKey<Key extends keyof Obj, Obj> = Pick<
51
- Partial<Obj>,
52
- Key
53
- > extends Pick<Obj, Key>
54
- ? never
55
- : Key;
56
-
57
- type DefaultFields = {
58
- [key: string]: any;
59
- [indexSignature]?: any;
34
+ type InitValuesFor<C extends CoMap> = {
35
+ init: Simplify<CoMapInit<C>>;
36
+ owner: Account | Group;
60
37
  };
61
38
 
62
- export class CoMap<Fields extends EnsureValid<Fields> = DefaultFields>
63
- extends CoValueBase
64
- implements CoValue<"CoMap", RawCoMap>
65
- {
66
- id!: ID<this>;
67
- _type!: "CoMap";
39
+ /**
40
+ * CoMaps are collaborative versions of plain objects, mapping string-like keys to values.
41
+ *
42
+ * @categoryDescription Declaration
43
+ * Declare your own CoMap schemas by subclassing `CoMap` and assigning field schemas with `co`.
44
+ *
45
+ * ```ts
46
+ * import { co, CoMap } from "jazz-tools";
47
+ *
48
+ * class Person extends CoMap {
49
+ * name = co.string;
50
+ * age = co.number;
51
+ * pet = co.ref(Animal);
52
+ * }
53
+ * ```
54
+ *
55
+ * @categoryDescription Content
56
+ * You can access properties you declare on a `CoMap` (using `co`) as if they were normal properties on a plain object, using dot notation, `Object.keys()`, etc.
57
+ *
58
+ * ```ts
59
+ * person.name;
60
+ * person["age"];
61
+ * person.age = 42;
62
+ * person.pet?.name;
63
+ * Object.keys(person);
64
+ * // => ["name", "age", "pet"]
65
+ * ```
66
+ *
67
+ * @category CoValues
68
+ * */
69
+ export class CoMap extends CoValueBase implements CoValue<"CoMap", RawCoMap> {
70
+ /**
71
+ * The ID of this `CoMap`
72
+ * @category Content */
73
+ declare id: ID<this>;
74
+ /** @category Type Helpers */
75
+ declare _type: "CoMap";
68
76
  static {
69
77
  this.prototype._type = "CoMap";
70
78
  }
71
- _raw!: RawCoMap;
72
-
73
- static _encoding: any;
74
- get _encoding(): {
75
- [Key in OwnKeys<Fields>]: FieldDescriptorFor<Fields[Key]>;
76
- } & {
77
- [indexSignature]: indexSignature extends keyof Fields
78
- ? FieldDescriptorFor<Fields[indexSignature]>
79
- : never;
80
- } {
81
- return (this.constructor as typeof CoMap)._encoding;
79
+ /** @category Internals */
80
+ declare _raw: RawCoMap;
81
+
82
+ /** @internal */
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ static _schema: any;
85
+ /** @internal */
86
+ get _schema() {
87
+ return (this.constructor as typeof CoMap)._schema as {
88
+ [key: string]: Schema;
89
+ } & { [ItemsSym]?: Schema };
82
90
  }
83
91
 
92
+ /**
93
+ * If property `prop` is a `co.ref(...)`, you can use `coMaps._refs.prop` to access
94
+ * the `Ref` instead of the potentially loaded/null value.
95
+ * This allows you to always get the ID or load the value manually.
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * person._refs.pet.id; // => ID<Animal>
100
+ * person._refs.pet.value;
101
+ * // => Animal | undefined
102
+ * const pet = await person._refs.pet.load();
103
+ * ```
104
+ *
105
+ * @category Content */
84
106
  get _refs(): {
85
- [Key in OwnKeys<Fields> as NonNullable<Fields[Key]> extends CoValue
86
- ? Key
87
- : never]: NonNullable<Fields[Key]> extends CoValue
88
- ? ValueRef<NonNullable<Fields[Key]>>
89
- : never;
107
+ [Key in CoKeys<this>]: IfCo<this[Key], RefIfCoValue<this[Key]>>;
90
108
  } {
91
- return makeRefs<OwnKeys<Fields>>(
109
+ return makeRefs<CoKeys<this>>(
92
110
  (key) => this._raw.get(key as string) as unknown as ID<CoValue>,
93
- () =>
94
- Object.keys(this._encoding).filter((key) => {
95
- const schema = this._encoding[
96
- key as keyof typeof this._encoding
97
- ] as FieldDescriptor;
98
- schema !== "json" && "ref" in schema;
99
- }) as OwnKeys<Fields>[],
111
+ () => {
112
+ const keys = this._raw.keys().filter((key) => {
113
+ const schema =
114
+ this._schema[key as keyof typeof this._schema] ||
115
+ (this._schema[ItemsSym] as Schema | undefined);
116
+ return schema && schema !== "json" && isRefEncoded(schema);
117
+ }) as CoKeys<this>[];
118
+
119
+ return keys;
120
+ },
100
121
  this._loadedAs,
101
- (key) => (this._encoding[key] as RefField<CoValue>).ref()
122
+ (key) =>
123
+ (this._schema[key] ||
124
+ this._schema[ItemsSym]) as RefEncoded<CoValue>,
125
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
102
126
  ) as any;
103
127
  }
104
128
 
105
- get _edits(): {
106
- [Key in OwnKeys<Fields>]: {
107
- value?: Fields[Key];
108
- ref?: Fields[Key] extends CoValue ? ValueRef<Fields[Key]> : never;
109
- by?: Account;
110
- madeAt: Date;
111
- };
112
- } {
129
+ /** @category Collaboration */
130
+ get _edits() {
113
131
  return new Proxy(this, {
114
132
  get(target, key) {
115
133
  const rawEdit = target._raw.lastEditAt(key as string);
116
134
  if (!rawEdit) return undefined;
117
135
 
118
- const descriptor = target._encoding[
119
- key as keyof typeof target._encoding
120
- ] as FieldDescriptor;
136
+ const descriptor = target._schema[
137
+ key as keyof typeof target._schema
138
+ ] as Schema;
121
139
 
122
140
  return {
123
141
  value:
124
142
  descriptor === "json"
125
143
  ? rawEdit.value
126
144
  : "encoded" in descriptor
127
- ? Schema.decodeSync(descriptor.encoded)(
128
- rawEdit.value
129
- )
130
- : new ValueRef(
145
+ ? decodeSync(descriptor.encoded)(rawEdit.value)
146
+ : new Ref(
131
147
  rawEdit.value as ID<CoValue>,
132
148
  target._loadedAs,
133
- descriptor.ref()
134
- ).accessFrom(target),
149
+ descriptor,
150
+ ).accessFrom(
151
+ target,
152
+ "_edits." + key.toString() + ".value",
153
+ ),
135
154
  ref:
136
- descriptor !== "json" && "ref" in descriptor
137
- ? new ValueRef(
155
+ descriptor !== "json" && isRefEncoded(descriptor)
156
+ ? new Ref(
138
157
  rawEdit.value as ID<CoValue>,
139
158
  target._loadedAs,
140
- descriptor.ref()
159
+ descriptor,
141
160
  )
142
161
  : undefined,
143
162
  by:
144
163
  rawEdit.by &&
145
- new ValueRef(
164
+ new Ref(
146
165
  rawEdit.by as ID<Account>,
147
166
  target._loadedAs,
148
- Account
149
- ).accessFrom(target),
167
+ Account,
168
+ ).accessFrom(
169
+ target,
170
+ "_edits." + key.toString() + ".by",
171
+ ),
150
172
  madeAt: rawEdit.at,
151
173
  };
152
174
  },
153
- }) as any;
175
+ }) as {
176
+ [Key in CoKeys<this>]: IfCo<this[Key], CoMapEdit<this[Key]>>;
177
+ };
154
178
  }
155
179
 
180
+ /** @internal */
156
181
  get _loadedAs() {
157
182
  return Account.fromNode(this._raw.core.node);
158
183
  }
159
184
 
160
- constructor(_init: undefined, options: { fromRaw: RawCoMap });
161
- constructor(
162
- init: Simplify<CoMapInit<Fields>>,
163
- options: { owner: Account | Group }
164
- );
185
+ /** @internal */
186
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
187
+ [InitValues]?: any;
188
+
189
+ /** @internal */
165
190
  constructor(
166
- init: Simplify<CoMapInit<Fields>> | undefined,
167
- options: { owner: Account | Group } | { fromRaw: RawCoMap }
191
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
192
+ options: { fromRaw: RawCoMap } | { init: any; owner: Account | Group },
168
193
  ) {
169
194
  super();
170
195
 
171
- if (!this._encoding) {
172
- throw new Error(
173
- "No schema found in " +
174
- this.constructor.name +
175
- " - ensure that you have a `static { this.define({...}) }` block in the class definition."
176
- );
196
+ if ("owner" in options) {
197
+ this[InitValues] = {
198
+ init: options.init,
199
+ owner: options.owner,
200
+ } as InitValuesFor<this>;
201
+ } else if ("fromRaw" in options) {
202
+ Object.defineProperties(this, {
203
+ id: {
204
+ value: options.fromRaw.id as unknown as ID<this>,
205
+ enumerable: false,
206
+ },
207
+ _raw: { value: options.fromRaw, enumerable: false },
208
+ });
209
+ } else {
210
+ throw new Error("Invalid CoMap constructor arguments");
177
211
  }
178
212
 
179
- const raw: RawCoMap = this.rawFromInit<Fields>(options, init);
180
-
181
- Object.defineProperties(this, {
182
- id: {
183
- value: raw.id,
184
- enumerable: false,
185
- },
186
- _raw: { value: raw, enumerable: false },
187
- });
188
-
189
- this.definePropertiesFromSchema();
213
+ return new Proxy(this, CoMapProxyHandler as ProxyHandler<this>);
214
+ }
190
215
 
191
- if (this._encoding[indexSignature]) {
192
- return new Proxy(this, CoMapProxyHandler<Fields>());
193
- }
216
+ /** @category Creation */
217
+ static create<M extends CoMap>(
218
+ this: ClassOf<M>,
219
+ init: Simplify<CoMapInit<M>>,
220
+ options: { owner: Account | Group },
221
+ ) {
222
+ return new this({ init, owner: options.owner });
194
223
  }
195
224
 
196
225
  toJSON() {
197
226
  const jsonedFields = this._raw.keys().map((key) => {
198
- const tKey = key as OwnKeys<Fields>;
199
- const descriptor = this._encoding[tKey] as FieldDescriptor;
227
+ const tKey = key as CoKeys<this>;
228
+ const descriptor = (this._schema[tKey] ||
229
+ this._schema[ItemsSym]) as Schema;
200
230
 
201
231
  if (descriptor == "json" || "encode" in descriptor) {
202
232
  return [key, this._raw.get(key)];
203
- } else if ("ref" in descriptor) {
233
+ } else if (isRefEncoded(descriptor)) {
234
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
204
235
  const jsonedRef = (this as any)[tKey]?.toJSON();
205
236
  return [key, jsonedRef];
206
237
  } else {
@@ -219,167 +250,102 @@ export class CoMap<Fields extends EnsureValid<Fields> = DefaultFields>
219
250
  return this.toJSON();
220
251
  }
221
252
 
253
+ /** @internal */
254
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
222
255
  rawFromInit<Fields extends object = Record<string, any>>(
223
- options: { owner: Account | Group } | { fromRaw: RawCoMap },
224
- init: Simplify<CoMapInit<Fields>> | undefined
256
+ init: Simplify<CoMapInit<Fields>> | undefined,
257
+ owner: Account | Group,
225
258
  ) {
226
- let raw: RawCoMap;
259
+ const rawOwner = owner._raw;
227
260
 
228
- if ("fromRaw" in options) {
229
- raw = options.fromRaw;
230
- } else {
231
- const rawOwner = options.owner._raw;
232
-
233
- const rawInit = {} as {
234
- [key in keyof Fields]: JsonValue | undefined;
235
- };
236
-
237
- if (init)
238
- for (const key of Object.keys(init) as (keyof Fields)[]) {
239
- const initValue = init[key as keyof typeof init];
240
-
241
- const descriptor = (this._encoding[
242
- key as keyof typeof this._encoding
243
- ] || this._encoding[indexSignature]) as FieldDescriptor;
244
-
245
- if (descriptor === "json") {
246
- rawInit[key] = initValue as JsonValue;
247
- } else if ("ref" in descriptor) {
248
- if (initValue) {
249
- rawInit[key] = (initValue as unknown as CoValue).id;
250
- }
251
- } else if ("encoded" in descriptor) {
252
- rawInit[key] = Schema.encodeSync(descriptor.encoded)(
253
- initValue as any
254
- );
255
- }
256
- }
261
+ const rawInit = {} as {
262
+ [key in keyof Fields]: JsonValue | undefined;
263
+ };
257
264
 
258
- raw = rawOwner.createMap(rawInit);
259
- }
260
- return raw;
261
- }
265
+ if (init)
266
+ for (const key of Object.keys(init) as (keyof Fields)[]) {
267
+ const initValue = init[key as keyof typeof init];
262
268
 
263
- static encoding<V extends CoMap>(
264
- this: { new (...args: any): V } & typeof CoMap,
265
- fields: Simplify<{
266
- [Key in keyof V["_encoding"] as V["_encoding"][Key] extends never
267
- ? never
268
- : Key]: Simplify<V["_encoding"][Key]>;
269
- }>
270
- ) {
271
- this._encoding ||= {};
272
- Object.assign(this._encoding, fields);
273
- }
269
+ const descriptor = (this._schema[
270
+ key as keyof typeof this._schema
271
+ ] || this._schema[ItemsSym]) as Schema;
274
272
 
275
- private definePropertiesFromSchema() {
276
- for (const [key, fieldSchema] of Object.entries(this._encoding)) {
277
- if (key === "indexSignature") continue;
278
- const descriptor = fieldSchema as FieldDescriptor;
279
- if (descriptor === "json") {
280
- Object.defineProperty(
281
- this,
282
- key,
283
- this.primitivePropDef(key as string)
284
- );
285
- } else if ("encoded" in descriptor) {
286
- Object.defineProperty(
287
- this,
288
- key,
289
- this.encodedPropDef(key as string, descriptor.encoded)
290
- );
291
- } else if ("ref" in descriptor) {
292
- Object.defineProperty(
293
- this,
294
- key,
295
- this.refPropDef(
296
- key as string,
297
- (descriptor as RefField<CoValue>).ref
298
- )
299
- );
273
+ if (descriptor === "json") {
274
+ rawInit[key] = initValue as JsonValue;
275
+ } else if (isRefEncoded(descriptor)) {
276
+ if (initValue) {
277
+ rawInit[key] = (initValue as unknown as CoValue).id;
278
+ }
279
+ } else if ("encoded" in descriptor) {
280
+ rawInit[key] = encodeSync(descriptor.encoded)(
281
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
282
+ initValue as any,
283
+ );
284
+ }
300
285
  }
301
- }
302
- }
303
286
 
304
- private primitivePropDef(key: string): PropertyDescriptor {
305
- return {
306
- get: () => {
307
- return this._raw.get(key);
308
- },
309
- set(this: CoMap, value: JsonValue) {
310
- this._raw.set(key, value);
311
- },
312
- enumerable: true,
313
- configurable: true,
314
- };
287
+ return rawOwner.createMap(rawInit);
315
288
  }
316
289
 
317
- private encodedPropDef(key: string, arg: Encoder<any>): PropertyDescriptor {
318
- return {
319
- get: () => {
320
- const raw = this._raw.get(key);
321
- return raw === undefined
322
- ? undefined
323
- : Schema.decodeSync(arg)(raw);
324
- },
325
- set(this: CoMap, value: unknown) {
326
- this._raw.set(key, Schema.encodeSync(arg)(value));
327
- },
328
- enumerable: true,
329
- configurable: true,
330
- };
331
- }
290
+ /** @category Declaration */
291
+ static Record<Value>(value: IfCo<Value, Value>) {
292
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
293
+ class RecordLikeCoMap extends CoMap {
294
+ [ItemsSym] = value;
295
+ }
296
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
297
+ interface RecordLikeCoMap extends Record<string, Value> {}
332
298
 
333
- private refPropDef(
334
- key: string,
335
- ref: () => CoValueClass<CoValue>
336
- ): PropertyDescriptor {
337
- return {
338
- get: () => {
339
- const rawID = this._raw.get(key);
340
- return rawID === undefined
341
- ? undefined
342
- : new ValueRef(
343
- rawID as unknown as ID<CoValue>,
344
- this._loadedAs,
345
- ref()
346
- ).accessFrom(this);
347
- },
348
- set: (value: CoValue) => {
349
- this._raw.set(key, value.id);
350
- subscriptionsScopes.get(this)?.onRefAccessedOrSet(value.id);
351
- },
352
- enumerable: true,
353
- configurable: true,
354
- };
299
+ return RecordLikeCoMap;
355
300
  }
356
301
  }
357
302
 
358
- export type OwnKeys<Fields extends object> = Exclude<
359
- keyof Fields & string,
360
- keyof CoMap<Record<string, never>> | `_${string}`
303
+ export type CoKeys<Map extends object> = Exclude<
304
+ keyof Map & string,
305
+ keyof CoMap
361
306
  >;
362
307
 
363
- export type CoMapInit<Fields extends object> = {
364
- [Key in OwnKeys<Fields> as undefined extends Fields[Key]
308
+ export type CoMapInit<Map extends object> = {
309
+ [Key in CoKeys<Map> as undefined extends Map[Key]
365
310
  ? never
366
- : null extends Fields[Key]
367
- ? never
368
- : Key]: Fields[Key];
369
- } & { [Key in OwnKeys<Fields>]?: Fields[Key] };
311
+ : IfCo<Map[Key], Key>]: Map[Key];
312
+ } & { [Key in CoKeys<Map> as IfCo<Map[Key], Key>]?: Map[Key] };
313
+
314
+ function tryInit(map: CoMap) {
315
+ if (
316
+ map[InitValues] &&
317
+ (map._schema[ItemsSym] ||
318
+ Object.keys(map[InitValues].init).every(
319
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
320
+ (key) => (map._schema as any)[key],
321
+ ))
322
+ ) {
323
+ const raw = map.rawFromInit(
324
+ map[InitValues].init,
325
+ map[InitValues].owner,
326
+ );
327
+ Object.defineProperties(map, {
328
+ id: {
329
+ value: raw.id,
330
+ enumerable: false,
331
+ },
332
+ _raw: { value: raw, enumerable: false },
333
+ });
334
+ delete map[InitValues];
335
+ }
336
+ }
370
337
 
371
338
  // TODO: cache handlers per descriptor for performance?
372
- function CoMapProxyHandler<Fields extends EnsureValid<Fields>>(): ProxyHandler<
373
- CoMap<Fields>
374
- > {
375
- return {
376
- get(target, key, receiver) {
377
- const descriptor = target._encoding[
378
- indexSignature
379
- ] as FieldDescriptor;
380
- if (key in target || typeof key === "symbol") {
381
- return Reflect.get(target, key, receiver);
382
- } else {
339
+ const CoMapProxyHandler: ProxyHandler<CoMap> = {
340
+ get(target, key, receiver) {
341
+ if (key === "_schema") {
342
+ return Reflect.get(target, key);
343
+ } else if (key in target) {
344
+ return Reflect.get(target, key, receiver);
345
+ } else {
346
+ const descriptor = (target._schema[key as keyof CoMap["_schema"]] ||
347
+ target._schema[ItemsSym]) as Schema;
348
+ if (descriptor && typeof key === "string") {
383
349
  const raw = target._raw.get(key);
384
350
 
385
351
  if (descriptor === "json") {
@@ -387,61 +353,105 @@ function CoMapProxyHandler<Fields extends EnsureValid<Fields>>(): ProxyHandler<
387
353
  } else if ("encoded" in descriptor) {
388
354
  return raw === undefined
389
355
  ? undefined
390
- : Schema.decodeSync(descriptor.encoded)(raw);
391
- } else if ("ref" in descriptor) {
356
+ : decodeSync(descriptor.encoded)(raw);
357
+ } else if (isRefEncoded(descriptor)) {
392
358
  return raw === undefined
393
359
  ? undefined
394
- : new ValueRef(
360
+ : new Ref(
395
361
  raw as unknown as ID<CoValue>,
396
362
  target._loadedAs,
397
- descriptor.ref()
398
- ).accessFrom(target);
363
+ descriptor,
364
+ ).accessFrom(receiver, key);
399
365
  }
400
- }
401
- },
402
- set(target, key, value, receiver) {
403
- const descriptor = target._encoding[
404
- indexSignature
405
- ] as FieldDescriptor;
406
- if (key in target || typeof key === "symbol") {
407
- return Reflect.set(target, key, value, receiver);
408
366
  } else {
409
- if (descriptor === "json") {
410
- target._raw.set(key, value);
411
- } else if ("encoded" in descriptor) {
412
- target._raw.set(
413
- key,
414
- Schema.encodeSync(descriptor.encoded)(value)
415
- );
416
- } else if ("ref" in descriptor) {
417
- target._raw.set(key, value.id);
418
- subscriptionsScopes
419
- .get(target)
420
- ?.onRefAccessedOrSet(value.id);
421
- }
422
- return true;
367
+ return undefined;
423
368
  }
424
- },
425
- ownKeys(target) {
426
- const keys = Reflect.ownKeys(target);
427
- for (const key of target._raw.keys()) {
428
- if (!keys.includes(key)) {
429
- keys.push(key);
430
- }
369
+ }
370
+ },
371
+ set(target, key, value, receiver) {
372
+ if (
373
+ (typeof key === "string" || ItemsSym) &&
374
+ typeof value === "object" &&
375
+ SchemaInit in value
376
+ ) {
377
+ (target.constructor as typeof CoMap)._schema ||= {};
378
+ (target.constructor as typeof CoMap)._schema[key] =
379
+ value[SchemaInit];
380
+ tryInit(target);
381
+ return true;
382
+ }
383
+
384
+ const descriptor = (target._schema[key as keyof CoMap["_schema"]] ||
385
+ target._schema[ItemsSym]) as Schema;
386
+ if (descriptor && typeof key === "string") {
387
+ if (descriptor === "json") {
388
+ target._raw.set(key, value);
389
+ } else if ("encoded" in descriptor) {
390
+ target._raw.set(key, encodeSync(descriptor.encoded)(value));
391
+ } else if (isRefEncoded(descriptor)) {
392
+ target._raw.set(key, value.id);
393
+ subscriptionsScopes
394
+ .get(target)
395
+ ?.onRefAccessedOrSet(target.id, value.id);
431
396
  }
397
+ return true;
398
+ } else {
399
+ return Reflect.set(target, key, value, receiver);
400
+ }
401
+ },
402
+ defineProperty(target, key, attributes) {
403
+ if (
404
+ "value" in attributes &&
405
+ typeof attributes.value === "object" &&
406
+ SchemaInit in attributes.value
407
+ ) {
408
+ (target.constructor as typeof CoMap)._schema ||= {};
409
+ (target.constructor as typeof CoMap)._schema[key as string] =
410
+ attributes.value[SchemaInit];
411
+ tryInit(target);
412
+ return true;
413
+ } else {
414
+ return Reflect.defineProperty(target, key, attributes);
415
+ }
416
+ },
417
+ ownKeys(target) {
418
+ const keys = Reflect.ownKeys(target).filter((k) => k !== ItemsSym);
419
+ // for (const key of Reflect.ownKeys(target._schema)) {
420
+ // if (key !== ItemsSym && !keys.includes(key)) {
421
+ // keys.push(key);
422
+ // }
423
+ // }
424
+ for (const key of target._raw.keys()) {
425
+ if (!keys.includes(key)) {
426
+ keys.push(key);
427
+ }
428
+ }
432
429
 
433
- return keys;
434
- },
435
- getOwnPropertyDescriptor(target, key) {
436
- if (key in target) {
437
- return Reflect.getOwnPropertyDescriptor(target, key);
438
- } else if (key in target._raw.ops) {
430
+ return keys;
431
+ },
432
+ getOwnPropertyDescriptor(target, key) {
433
+ if (key in target) {
434
+ return Reflect.getOwnPropertyDescriptor(target, key);
435
+ } else {
436
+ const descriptor = (target._schema[key as keyof CoMap["_schema"]] ||
437
+ target._schema[ItemsSym]) as Schema;
438
+ if (descriptor || key in target._raw.ops) {
439
439
  return {
440
440
  enumerable: true,
441
441
  configurable: true,
442
442
  writable: true,
443
443
  };
444
444
  }
445
- },
446
- };
447
- }
445
+ }
446
+ },
447
+ deleteProperty(target, key) {
448
+ const descriptor = (target._schema[key as keyof CoMap["_schema"]] ||
449
+ target._schema[ItemsSym]) as Schema;
450
+ if (typeof key === "string" && descriptor) {
451
+ target._raw.delete(key);
452
+ return true;
453
+ } else {
454
+ return Reflect.deleteProperty(target, key);
455
+ }
456
+ },
457
+ };