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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/coValues/account.js +62 -29
  3. package/dist/coValues/account.js.map +1 -1
  4. package/dist/coValues/coList.js +139 -89
  5. package/dist/coValues/coList.js.map +1 -1
  6. package/dist/coValues/coMap.js +135 -151
  7. package/dist/coValues/coMap.js.map +1 -1
  8. package/dist/coValues/coStream.js +131 -57
  9. package/dist/coValues/coStream.js.map +1 -1
  10. package/dist/coValues/extensions/imageDef.js +10 -7
  11. package/dist/coValues/extensions/imageDef.js.map +1 -1
  12. package/dist/coValues/group.js +8 -30
  13. package/dist/coValues/group.js.map +1 -1
  14. package/dist/coValues/interfaces.js +7 -3
  15. package/dist/coValues/interfaces.js.map +1 -1
  16. package/dist/implementation/encoding.js +26 -0
  17. package/dist/implementation/encoding.js.map +1 -0
  18. package/dist/implementation/refs.js +11 -10
  19. package/dist/implementation/refs.js.map +1 -1
  20. package/dist/index.js +4 -3
  21. package/dist/index.js.map +1 -1
  22. package/dist/internal.js +1 -1
  23. package/dist/internal.js.map +1 -1
  24. package/dist/tests/coList.test.js +5 -9
  25. package/dist/tests/coList.test.js.map +1 -1
  26. package/dist/tests/coMap.test.js +87 -37
  27. package/dist/tests/coMap.test.js.map +1 -1
  28. package/dist/tests/coStream.test.js +46 -51
  29. package/dist/tests/coStream.test.js.map +1 -1
  30. package/package.json +2 -2
  31. package/src/coValues/account.ts +90 -60
  32. package/src/coValues/coList.ts +177 -114
  33. package/src/coValues/coMap.ts +191 -240
  34. package/src/coValues/coStream.ts +175 -97
  35. package/src/coValues/extensions/imageDef.ts +7 -12
  36. package/src/coValues/group.ts +24 -71
  37. package/src/coValues/interfaces.ts +10 -9
  38. package/src/implementation/encoding.ts +105 -0
  39. package/src/implementation/refs.ts +21 -20
  40. package/src/index.ts +3 -3
  41. package/src/internal.ts +1 -1
  42. package/src/tests/coList.test.ts +5 -9
  43. package/src/tests/coMap.test.ts +68 -52
  44. package/src/tests/coStream.test.ts +61 -66
  45. package/dist/implementation/schema.js +0 -6
  46. package/dist/implementation/schema.js.map +0 -1
  47. package/src/implementation/schema.ts +0 -69
@@ -3,42 +3,38 @@ import type { Simplify } from "effect/Types";
3
3
  import { Schema } from "@effect/schema";
4
4
  import type {
5
5
  CoValue,
6
- Encoder,
7
- FieldDescriptor,
8
- FieldDescriptorFor,
6
+ Encoding,
7
+ EncodingFor,
9
8
  Group,
10
9
  ID,
11
- RefField,
10
+ RefEncoded,
12
11
  EnsureCoValueNullable,
13
- CoValueClass,
12
+ IsVal,
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,
23
24
  } from "../internal.js";
24
25
 
25
- type EnsureValid<
26
- Fields extends { [key: string]: any; [indexSignature]?: any },
27
- > = {
28
- [Key in OwnKeys<Fields> as IfOptionalKey<
29
- Key,
30
- Fields
26
+ type ValidFields<Fields extends { [key: string]: any; [ItemsSym]?: any }> = {
27
+ [Key in keyof Fields & string as IsVal<
28
+ Fields[Key],
29
+ IfOptionalKey<Key, Fields>
31
30
  >]?: EnsureCoValueNullable<Fields[Key], Key>;
32
31
  } & {
33
- [Key in OwnKeys<Fields> as IfRequiredKey<
34
- Key,
35
- Fields
32
+ [Key in keyof Fields & string as IsVal<
33
+ Fields[Key],
34
+ IfRequiredKey<Key, Fields>
36
35
  >]: EnsureCoValueNullable<Fields[Key], Key>;
37
36
  } & {
38
- [Key in indexSignature]?: EnsureCoValueNullable<
39
- Fields[indexSignature],
40
- Key
41
- >;
37
+ [Key in ItemsSym]?: EnsureCoValueNullable<Fields[ItemsSym], Key>;
42
38
  };
43
39
 
44
40
  type IfOptionalKey<Key extends keyof Obj, Obj> = Pick<
@@ -56,10 +52,10 @@ type IfRequiredKey<Key extends keyof Obj, Obj> = Pick<
56
52
 
57
53
  type DefaultFields = {
58
54
  [key: string]: any;
59
- [indexSignature]?: any;
55
+ [ItemsSym]?: any;
60
56
  };
61
57
 
62
- export class CoMap<Fields extends EnsureValid<Fields> = DefaultFields>
58
+ export class CoMap<Fields extends ValidFields<Fields> = DefaultFields>
63
59
  extends CoValueBase
64
60
  implements CoValue<"CoMap", RawCoMap>
65
61
  {
@@ -72,10 +68,12 @@ export class CoMap<Fields extends EnsureValid<Fields> = DefaultFields>
72
68
 
73
69
  static _encoding: any;
74
70
  get _encoding(): {
75
- [Key in OwnKeys<Fields>]: FieldDescriptorFor<Fields[Key]>;
71
+ [Key in OwnKeys<Fields> as IsVal<Fields[Key], Key>]: EncodingFor<
72
+ Fields[Key]
73
+ >;
76
74
  } & {
77
- [indexSignature]: indexSignature extends keyof Fields
78
- ? FieldDescriptorFor<Fields[indexSignature]>
75
+ [ItemsSym]: ItemsSym extends keyof Fields
76
+ ? EncodingFor<Fields[ItemsSym]>
79
77
  : never;
80
78
  } {
81
79
  return (this.constructor as typeof CoMap)._encoding;
@@ -85,7 +83,7 @@ export class CoMap<Fields extends EnsureValid<Fields> = DefaultFields>
85
83
  [Key in OwnKeys<Fields> as NonNullable<Fields[Key]> extends CoValue
86
84
  ? Key
87
85
  : never]: NonNullable<Fields[Key]> extends CoValue
88
- ? ValueRef<NonNullable<Fields[Key]>>
86
+ ? Ref<NonNullable<Fields[Key]>>
89
87
  : never;
90
88
  } {
91
89
  return makeRefs<OwnKeys<Fields>>(
@@ -94,18 +92,18 @@ export class CoMap<Fields extends EnsureValid<Fields> = DefaultFields>
94
92
  Object.keys(this._encoding).filter((key) => {
95
93
  const schema = this._encoding[
96
94
  key as keyof typeof this._encoding
97
- ] as FieldDescriptor;
95
+ ] as Encoding;
98
96
  schema !== "json" && "ref" in schema;
99
97
  }) as OwnKeys<Fields>[],
100
98
  this._loadedAs,
101
- (key) => (this._encoding[key] as RefField<CoValue>).ref()
99
+ (key) => this._encoding[key] as RefEncoded<CoValue>
102
100
  ) as any;
103
101
  }
104
102
 
105
103
  get _edits(): {
106
- [Key in OwnKeys<Fields>]: {
104
+ [Key in OwnKeys<Fields> as IsVal<Fields[Key], Key>]: {
107
105
  value?: Fields[Key];
108
- ref?: Fields[Key] extends CoValue ? ValueRef<Fields[Key]> : never;
106
+ ref?: Fields[Key] extends CoValue ? Ref<Fields[Key]> : never;
109
107
  by?: Account;
110
108
  madeAt: Date;
111
109
  };
@@ -117,7 +115,7 @@ export class CoMap<Fields extends EnsureValid<Fields> = DefaultFields>
117
115
 
118
116
  const descriptor = target._encoding[
119
117
  key as keyof typeof target._encoding
120
- ] as FieldDescriptor;
118
+ ] as Encoding;
121
119
 
122
120
  return {
123
121
  value:
@@ -127,26 +125,24 @@ export class CoMap<Fields extends EnsureValid<Fields> = DefaultFields>
127
125
  ? Schema.decodeSync(descriptor.encoded)(
128
126
  rawEdit.value
129
127
  )
130
- : new ValueRef(
128
+ : new Ref(
131
129
  rawEdit.value as ID<CoValue>,
132
130
  target._loadedAs,
133
- descriptor.ref()
131
+ descriptor
134
132
  ).accessFrom(target),
135
133
  ref:
136
134
  descriptor !== "json" && "ref" in descriptor
137
- ? new ValueRef(
135
+ ? new Ref(
138
136
  rawEdit.value as ID<CoValue>,
139
137
  target._loadedAs,
140
- descriptor.ref()
138
+ descriptor
141
139
  )
142
140
  : undefined,
143
141
  by:
144
142
  rawEdit.by &&
145
- new ValueRef(
146
- rawEdit.by as ID<Account>,
147
- target._loadedAs,
148
- Account
149
- ).accessFrom(target),
143
+ new Ref(rawEdit.by as ID<Account>, target._loadedAs, {
144
+ ref: () => Account,
145
+ }).accessFrom(target),
150
146
  madeAt: rawEdit.at,
151
147
  };
152
148
  },
@@ -157,6 +153,11 @@ export class CoMap<Fields extends EnsureValid<Fields> = DefaultFields>
157
153
  return Account.fromNode(this._raw.core.node);
158
154
  }
159
155
 
156
+ [InitValues]?: {
157
+ init: Simplify<CoMapInit<Fields>>;
158
+ owner: Account | Group;
159
+ };
160
+
160
161
  constructor(_init: undefined, options: { fromRaw: RawCoMap });
161
162
  constructor(
162
163
  init: Simplify<CoMapInit<Fields>>,
@@ -168,35 +169,27 @@ export class CoMap<Fields extends EnsureValid<Fields> = DefaultFields>
168
169
  ) {
169
170
  super();
170
171
 
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
- );
172
+ if (init && "owner" in options) {
173
+ this[InitValues] = { init, owner: options.owner };
174
+ } else if ("fromRaw" in options) {
175
+ Object.defineProperties(this, {
176
+ id: {
177
+ value: options.fromRaw.id as unknown as ID<this>,
178
+ enumerable: false,
179
+ },
180
+ _raw: { value: options.fromRaw, enumerable: false },
181
+ });
182
+ } else {
183
+ throw new Error("Invalid CoMap constructor arguments");
177
184
  }
178
185
 
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();
190
-
191
- if (this._encoding[indexSignature]) {
192
- return new Proxy(this, CoMapProxyHandler<Fields>());
193
- }
186
+ return new Proxy(this, CoMapProxyHandler as ProxyHandler<this>);
194
187
  }
195
188
 
196
189
  toJSON() {
197
190
  const jsonedFields = this._raw.keys().map((key) => {
198
191
  const tKey = key as OwnKeys<Fields>;
199
- const descriptor = this._encoding[tKey] as FieldDescriptor;
192
+ const descriptor = this._encoding[tKey] as Encoding;
200
193
 
201
194
  if (descriptor == "json" || "encode" in descriptor) {
202
195
  return [key, this._raw.get(key)];
@@ -220,144 +213,43 @@ export class CoMap<Fields extends EnsureValid<Fields> = DefaultFields>
220
213
  }
221
214
 
222
215
  rawFromInit<Fields extends object = Record<string, any>>(
223
- options: { owner: Account | Group } | { fromRaw: RawCoMap },
224
- init: Simplify<CoMapInit<Fields>> | undefined
216
+ init: Simplify<CoMapInit<Fields>> | undefined,
217
+ owner: Account | Group
225
218
  ) {
226
- let raw: RawCoMap;
227
-
228
- if ("fromRaw" in options) {
229
- raw = options.fromRaw;
230
- } else {
231
- const rawOwner = options.owner._raw;
219
+ const rawOwner = owner._raw;
232
220
 
233
- const rawInit = {} as {
234
- [key in keyof Fields]: JsonValue | undefined;
235
- };
221
+ const rawInit = {} as {
222
+ [key in keyof Fields]: JsonValue | undefined;
223
+ };
236
224
 
237
- if (init)
238
- for (const key of Object.keys(init) as (keyof Fields)[]) {
239
- const initValue = init[key as keyof typeof init];
225
+ if (init)
226
+ for (const key of Object.keys(init) as (keyof Fields)[]) {
227
+ const initValue = init[key as keyof typeof init];
240
228
 
241
- const descriptor = (this._encoding[
242
- key as keyof typeof this._encoding
243
- ] || this._encoding[indexSignature]) as FieldDescriptor;
229
+ const descriptor = (this._encoding[
230
+ key as keyof typeof this._encoding
231
+ ] || this._encoding[ItemsSym]) as Encoding;
244
232
 
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
- );
233
+ if (descriptor === "json") {
234
+ rawInit[key] = initValue as JsonValue;
235
+ } else if ("ref" in descriptor) {
236
+ if (initValue) {
237
+ rawInit[key] = (initValue as unknown as CoValue).id;
255
238
  }
239
+ } else if ("encoded" in descriptor) {
240
+ rawInit[key] = Schema.encodeSync(descriptor.encoded)(
241
+ initValue as any
242
+ );
256
243
  }
257
-
258
- raw = rawOwner.createMap(rawInit);
259
- }
260
- return raw;
261
- }
262
-
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
- }
274
-
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
- );
300
244
  }
301
- }
302
- }
303
245
 
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
- };
315
- }
316
-
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
- }
332
-
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
- };
246
+ return rawOwner.createMap(rawInit);
355
247
  }
356
248
  }
357
249
 
358
250
  export type OwnKeys<Fields extends object> = Exclude<
359
251
  keyof Fields & string,
360
- keyof CoMap<Record<string, never>> | `_${string}`
252
+ keyof CoMap<Record<string, never>>
361
253
  >;
362
254
 
363
255
  export type CoMapInit<Fields extends object> = {
@@ -365,21 +257,44 @@ export type CoMapInit<Fields extends object> = {
365
257
  ? never
366
258
  : null extends Fields[Key]
367
259
  ? never
368
- : Key]: Fields[Key];
369
- } & { [Key in OwnKeys<Fields>]?: Fields[Key] };
260
+ : IsVal<Fields[Key], Key>]: Fields[Key];
261
+ } & { [Key in OwnKeys<Fields> as IsVal<Fields[Key], Key>]?: Fields[Key] };
262
+
263
+ function tryInit(map: CoMap) {
264
+ if (
265
+ map[InitValues] &&
266
+ (map._encoding[ItemsSym] ||
267
+ Object.keys(map[InitValues].init).every(
268
+ (key) => map._encoding[key]
269
+ ))
270
+ ) {
271
+ const raw = map.rawFromInit(
272
+ map[InitValues].init,
273
+ map[InitValues].owner
274
+ );
275
+ Object.defineProperties(map, {
276
+ id: {
277
+ value: raw.id,
278
+ enumerable: false,
279
+ },
280
+ _raw: { value: raw, enumerable: false },
281
+ });
282
+ delete map[InitValues];
283
+ }
284
+ }
370
285
 
371
286
  // 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 {
287
+ const CoMapProxyHandler: ProxyHandler<CoMap> = {
288
+ get(target, key, receiver) {
289
+ if (key === "_encoding") {
290
+ return Reflect.get(target, key);
291
+ } else if (key in target) {
292
+ return Reflect.get(target, key, receiver);
293
+ } else {
294
+ const descriptor = (target._encoding[
295
+ key as keyof CoMap["_encoding"]
296
+ ] || target._encoding[ItemsSym]) as Encoding;
297
+ if (descriptor && typeof key === "string") {
383
298
  const raw = target._raw.get(key);
384
299
 
385
300
  if (descriptor === "json") {
@@ -391,57 +306,93 @@ function CoMapProxyHandler<Fields extends EnsureValid<Fields>>(): ProxyHandler<
391
306
  } else if ("ref" in descriptor) {
392
307
  return raw === undefined
393
308
  ? undefined
394
- : new ValueRef(
309
+ : new Ref(
395
310
  raw as unknown as ID<CoValue>,
396
311
  target._loadedAs,
397
- descriptor.ref()
398
- ).accessFrom(target);
312
+ descriptor
313
+ ).accessFrom(receiver);
399
314
  }
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
315
  } 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;
316
+ return undefined;
423
317
  }
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
- }
318
+ }
319
+ },
320
+ set(target, key, value, receiver) {
321
+ if (
322
+ (typeof key === "string" || ItemsSym) &&
323
+ typeof value === "object" &&
324
+ SchemaInit in value
325
+ ) {
326
+ (target.constructor as typeof CoMap)._encoding ||= {};
327
+ (target.constructor as typeof CoMap)._encoding[key] =
328
+ value[SchemaInit];
329
+ tryInit(target);
330
+ return true;
331
+ }
332
+
333
+ const descriptor = (target._encoding[key as keyof CoMap["_encoding"]] ||
334
+ target._encoding[ItemsSym]) as Encoding;
335
+ if (descriptor && typeof key === "string") {
336
+ if (descriptor === "json") {
337
+ target._raw.set(key, value);
338
+ } else if ("encoded" in descriptor) {
339
+ target._raw.set(
340
+ key,
341
+ Schema.encodeSync(descriptor.encoded)(value)
342
+ );
343
+ } else if ("ref" in descriptor) {
344
+ target._raw.set(key, value.id);
345
+ subscriptionsScopes.get(target)?.onRefAccessedOrSet(value.id);
431
346
  }
347
+ return true;
348
+ } else {
349
+ return Reflect.set(target, key, value, receiver);
350
+ }
351
+ },
352
+ defineProperty(target, key, attributes) {
353
+ if (
354
+ "value" in attributes &&
355
+ typeof attributes.value === "object" &&
356
+ SchemaInit in attributes.value
357
+ ) {
358
+ (target.constructor as typeof CoMap)._encoding ||= {};
359
+ (target.constructor as typeof CoMap)._encoding[key as string] =
360
+ attributes.value[SchemaInit];
361
+ tryInit(target);
362
+ return true;
363
+ } else {
364
+ return Reflect.defineProperty(target, key, attributes);
365
+ }
366
+ },
367
+ ownKeys(target) {
368
+ const keys = Reflect.ownKeys(target).filter((k) => k !== ItemsSym);
369
+ for (const key of Reflect.ownKeys(target._encoding)) {
370
+ if (key !== ItemsSym && !keys.includes(key)) {
371
+ keys.push(key);
372
+ }
373
+ }
374
+ for (const key of target._raw.keys()) {
375
+ if (!keys.includes(key)) {
376
+ keys.push(key);
377
+ }
378
+ }
432
379
 
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) {
380
+ return keys;
381
+ },
382
+ getOwnPropertyDescriptor(target, key) {
383
+ if (key in target) {
384
+ return Reflect.getOwnPropertyDescriptor(target, key);
385
+ } else {
386
+ const descriptor = (target._encoding[
387
+ key as keyof CoMap["_encoding"]
388
+ ] || target._encoding[ItemsSym]) as Encoding;
389
+ if (descriptor || key in target._raw.ops) {
439
390
  return {
440
391
  enumerable: true,
441
392
  configurable: true,
442
393
  writable: true,
443
394
  };
444
395
  }
445
- },
446
- };
447
- }
396
+ }
397
+ },
398
+ };