jazz-tools 0.7.0-alpha.9 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -4,35 +4,44 @@ import type {
4
4
  Account,
5
5
  CoValue,
6
6
  ID,
7
- Me,
8
7
  RefEncoded,
8
+ UnCo,
9
9
  UnavailableError,
10
10
  } from "../internal.js";
11
- import { subscriptionsScopes } from "../internal.js";
11
+ import {
12
+ instantiateRefEncoded,
13
+ isRefEncoded,
14
+ subscriptionsScopes,
15
+ } from "../internal.js";
16
+
17
+ const refCache = new WeakMap<RawCoValue, CoValue>();
12
18
 
13
- export class Ref<V extends CoValue> {
14
- private cachedValue: V | undefined;
19
+ const TRACE_ACCESSES = false;
15
20
 
21
+ export class Ref<out V extends CoValue> {
16
22
  constructor(
17
23
  readonly id: ID<V>,
18
- readonly controlledAccount: Account & Me,
19
- readonly encoding: RefEncoded<V>
24
+ readonly controlledAccount: Account,
25
+ readonly schema: RefEncoded<V>,
20
26
  ) {
21
- if (!("ref" in encoding)) {
22
- throw new Error("Ref must be constructed with a ref encoding");
27
+ if (!isRefEncoded(schema)) {
28
+ throw new Error("Ref must be constructed with a ref schema");
23
29
  }
24
30
  }
25
31
 
26
32
  get value() {
27
- if (this.cachedValue) return this.cachedValue;
28
- // TODO: cache it for object identity!!!
29
33
  const raw = this.controlledAccount._raw.core.node.getLoaded(
30
- this.id as unknown as CoID<RawCoValue>
34
+ this.id as unknown as CoID<RawCoValue>,
31
35
  );
32
36
  if (raw) {
33
- const value = this.encoding.ref(raw).fromRaw(raw);
34
- this.cachedValue = value;
35
- return value;
37
+ let value = refCache.get(raw);
38
+ if (value) {
39
+ // console.log("Using cached value for " + this.id);
40
+ } else {
41
+ value = instantiateRefEncoded(this.schema, raw);
42
+ refCache.set(raw, value);
43
+ }
44
+ return value as V;
36
45
  } else {
37
46
  return null;
38
47
  }
@@ -59,16 +68,12 @@ export class Ref<V extends CoValue> {
59
68
  }): Promise<V | "unavailable"> {
60
69
  const raw = await this.controlledAccount._raw.core.node.load(
61
70
  this.id as unknown as CoID<RawCoValue>,
62
- options?.onProgress
71
+ options?.onProgress,
63
72
  );
64
73
  if (raw === "unavailable") {
65
74
  return "unavailable";
66
75
  } else {
67
- return new Ref(
68
- this.id,
69
- this.controlledAccount,
70
- this.encoding
71
- ).value!;
76
+ return new Ref(this.id, this.controlledAccount, this.schema).value!;
72
77
  }
73
78
  }
74
79
 
@@ -83,24 +88,55 @@ export class Ref<V extends CoValue> {
83
88
  }
84
89
  }
85
90
 
86
- accessFrom(fromScopeValue: CoValue): V | null {
91
+ accessFrom(
92
+ fromScopeValue: CoValue,
93
+ key: string | number | symbol,
94
+ ): V | null {
87
95
  const subScope = subscriptionsScopes.get(fromScopeValue);
88
96
 
89
- subScope?.onRefAccessedOrSet(this.id);
97
+ subScope?.onRefAccessedOrSet(fromScopeValue.id, this.id);
98
+ TRACE_ACCESSES &&
99
+ console.log(
100
+ subScope?.scopeID,
101
+ "accessing",
102
+ fromScopeValue,
103
+ key,
104
+ this.id,
105
+ );
90
106
 
91
107
  if (this.value && subScope) {
92
108
  subscriptionsScopes.set(this.value, subScope);
93
109
  }
94
110
 
95
- return this.value;
111
+ if (subScope) {
112
+ const cached = subScope.cachedValues[this.id];
113
+ if (cached) {
114
+ TRACE_ACCESSES && console.log("cached", cached);
115
+ return cached as V;
116
+ } else if (this.value !== null) {
117
+ const freshValueInstance = instantiateRefEncoded(
118
+ this.schema,
119
+ this.value?._raw,
120
+ );
121
+ TRACE_ACCESSES &&
122
+ console.log("freshValueInstance", freshValueInstance);
123
+ subScope.cachedValues[this.id] = freshValueInstance;
124
+ subscriptionsScopes.set(freshValueInstance, subScope);
125
+ return freshValueInstance as V;
126
+ } else {
127
+ return null;
128
+ }
129
+ } else {
130
+ return this.value;
131
+ }
96
132
  }
97
133
  }
98
134
 
99
135
  export function makeRefs<Keys extends string | number>(
100
136
  getIdForKey: (key: Keys) => ID<CoValue> | undefined,
101
137
  getKeysWithIds: () => Keys[],
102
- controlledAccount: Account & Me,
103
- refEncodingForKey: (key: Keys) => RefEncoded<CoValue>
138
+ controlledAccount: Account,
139
+ refSchemaForKey: (key: Keys) => RefEncoded<CoValue>,
104
140
  ): { [K in Keys]: Ref<CoValue> } & {
105
141
  [Symbol.iterator]: () => IterableIterator<Ref<CoValue>>;
106
142
  length: number;
@@ -110,14 +146,14 @@ export function makeRefs<Keys extends string | number>(
110
146
  length: number;
111
147
  };
112
148
  return new Proxy(refs, {
113
- get(target, key) {
149
+ get(_target, key) {
114
150
  if (key === Symbol.iterator) {
115
151
  return function* () {
116
152
  for (const key of getKeysWithIds()) {
117
153
  yield new Ref(
118
154
  getIdForKey(key)!,
119
155
  controlledAccount,
120
- refEncodingForKey(key)
156
+ refSchemaForKey(key),
121
157
  );
122
158
  }
123
159
  };
@@ -131,11 +167,27 @@ export function makeRefs<Keys extends string | number>(
131
167
  return new Ref(
132
168
  id as ID<CoValue>,
133
169
  controlledAccount,
134
- refEncodingForKey(key as Keys)
170
+ refSchemaForKey(key as Keys),
135
171
  );
136
172
  },
137
173
  ownKeys() {
138
174
  return getKeysWithIds().map((key) => key.toString());
139
175
  },
176
+ getOwnPropertyDescriptor(target, key) {
177
+ const id = getIdForKey(key as Keys);
178
+ if (id) {
179
+ return {
180
+ enumerable: true,
181
+ configurable: true,
182
+ writable: true,
183
+ };
184
+ } else {
185
+ return Reflect.getOwnPropertyDescriptor(target, key);
186
+ }
187
+ },
140
188
  });
141
189
  }
190
+
191
+ export type RefIfCoValue<V> = NonNullable<V> extends CoValue
192
+ ? Ref<UnCo<NonNullable<V>>>
193
+ : never;
@@ -0,0 +1,138 @@
1
+ import type { JsonValue, RawCoValue } from "cojson";
2
+ import {
3
+ type CoValue,
4
+ type CoValueClass,
5
+ isCoValueClass,
6
+ } from "../internal.js";
7
+ import type { Schema as EffectSchema, TypeId } from "@effect/schema/Schema";
8
+
9
+ export type CoMarker = { readonly __co: unique symbol };
10
+ /** @category Schema definition */
11
+ export type co<T> = T | (T & CoMarker);
12
+ export type IfCo<C, R> = C extends infer _A | infer B
13
+ ? B extends CoMarker
14
+ ? R
15
+ : never
16
+ : never;
17
+ export type UnCo<T> = T extends co<infer A> ? A : T;
18
+
19
+ /** @category Schema definition */
20
+ export const co = {
21
+ string: {
22
+ [SchemaInit]: "json" satisfies Schema,
23
+ } as unknown as co<string>,
24
+ number: {
25
+ [SchemaInit]: "json" satisfies Schema,
26
+ } as unknown as co<number>,
27
+ boolean: {
28
+ [SchemaInit]: "json" satisfies Schema,
29
+ } as unknown as co<boolean>,
30
+ null: {
31
+ [SchemaInit]: "json" satisfies Schema,
32
+ } as unknown as co<null>,
33
+ literal<T extends (string | number | boolean)[]>(
34
+ ..._lit: T
35
+ ): co<T[number]> {
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ return { [SchemaInit]: "json" satisfies Schema } as any;
38
+ },
39
+ json<T extends JsonValue>(): co<T> {
40
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
+ return { [SchemaInit]: "json" satisfies Schema } as any;
42
+ },
43
+ encoded<T>(arg: Encoder<T>): co<T> {
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
+ return { [SchemaInit]: { encoded: arg } satisfies Schema } as any;
46
+ },
47
+ ref,
48
+ items: ItemsSym as ItemsSym,
49
+ members: MembersSym as MembersSym,
50
+ };
51
+
52
+ function ref<C extends CoValueClass>(
53
+ arg: C | ((_raw: InstanceType<C>["_raw"]) => C),
54
+ ): co<InstanceType<C> | null>;
55
+ function ref<C extends CoValueClass>(
56
+ arg: C | ((_raw: InstanceType<C>["_raw"]) => C),
57
+ options: { optional: true },
58
+ ): co<InstanceType<C> | null | undefined>;
59
+ function ref<
60
+ C extends CoValueClass,
61
+ Options extends { optional?: boolean } | undefined,
62
+ >(
63
+ arg: C | ((_raw: InstanceType<C>["_raw"]) => C),
64
+ options?: Options,
65
+ ): Options extends { optional: true }
66
+ ? co<InstanceType<C> | null | undefined>
67
+ : co<InstanceType<C> | null> {
68
+ return {
69
+ [SchemaInit]: {
70
+ ref: arg,
71
+ optional: options?.optional || false,
72
+ } satisfies Schema,
73
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
+ } as any;
75
+ }
76
+
77
+ export type JsonEncoded = "json";
78
+ export type EncodedAs<V> = { encoded: Encoder<V> };
79
+ export type RefEncoded<V extends CoValue> = {
80
+ ref: CoValueClass<V> | ((raw: RawCoValue) => CoValueClass<V>);
81
+ optional: boolean;
82
+ };
83
+
84
+ export function isRefEncoded<V extends CoValue>(
85
+ schema: Schema,
86
+ ): schema is RefEncoded<V> {
87
+ return (
88
+ typeof schema === "object" &&
89
+ "ref" in schema &&
90
+ "optional" in schema &&
91
+ typeof schema.ref === "function"
92
+ );
93
+ }
94
+
95
+ export function instantiateRefEncoded<V extends CoValue>(
96
+ schema: RefEncoded<V>,
97
+ raw: RawCoValue,
98
+ ): V {
99
+ return isCoValueClass(schema.ref)
100
+ ? schema.ref.fromRaw(raw)
101
+ : (schema.ref as (raw: RawCoValue) => CoValueClass<V>)(raw).fromRaw(
102
+ raw,
103
+ );
104
+ }
105
+
106
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
107
+ export type Schema = JsonEncoded | RefEncoded<CoValue> | EncodedAs<any>;
108
+
109
+ export type SchemaFor<Field> = NonNullable<Field> extends CoValue
110
+ ? RefEncoded<NonNullable<Field>>
111
+ : NonNullable<Field> extends JsonValue
112
+ ? JsonEncoded
113
+ : EncodedAs<NonNullable<Field>>;
114
+
115
+ export type EffectSchemaWithInputAndOutput<A, I = A> = EffectSchema<
116
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
117
+ any,
118
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
+ any,
120
+ never
121
+ > & {
122
+ [TypeId]: {
123
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
+ _A: (_: any) => A;
125
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
+ _I: (_: any) => I;
127
+ };
128
+ };
129
+
130
+ export type Encoder<V> = EffectSchemaWithInputAndOutput<V, JsonValue>;
131
+
132
+ import { Date } from "@effect/schema/Schema";
133
+ import { SchemaInit, ItemsSym, MembersSym } from "./symbols.js";
134
+
135
+ /** @category Schema definition */
136
+ export const Encoders = {
137
+ Date,
138
+ };
@@ -1,16 +1,23 @@
1
1
  import type { RawCoValue } from "cojson";
2
- import type { Account, CoValue, CoValueBase, ID, Me, SubclassedConstructor } from "../internal.js";
2
+ import type {
3
+ Account,
4
+ CoValue,
5
+ CoValueBase,
6
+ ID,
7
+ ClassOf,
8
+ } from "../internal.js";
3
9
 
4
10
  export const subscriptionsScopes = new WeakMap<
5
11
  CoValue,
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
13
  SubscriptionScope<any>
7
14
  >();
8
15
 
9
- export class SubscriptionScope<
10
- Root extends CoValue
11
- > {
16
+ const TRACE_INVALIDATIONS = false;
17
+
18
+ export class SubscriptionScope<Root extends CoValue> {
12
19
  scopeID: string = `scope-${Math.random().toString(36).slice(2)}`;
13
- subscriber: Account & Me;
20
+ subscriber: Account;
14
21
  entries = new Map<
15
22
  ID<CoValue>,
16
23
  | { state: "loading"; immediatelyUnsub?: boolean }
@@ -23,11 +30,13 @@ export class SubscriptionScope<
23
30
  };
24
31
  onUpdate: (newRoot: Root) => void;
25
32
  scheduledUpdate: boolean = false;
33
+ cachedValues: { [id: ID<CoValue>]: CoValue } = {};
34
+ parents: { [id: ID<CoValue>]: Set<ID<CoValue>> } = {};
26
35
 
27
36
  constructor(
28
37
  root: Root,
29
- rootSchema: SubclassedConstructor<Root> & typeof CoValueBase,
30
- onUpdate: (newRoot: Root) => void
38
+ rootSchema: ClassOf<Root> & typeof CoValueBase,
39
+ onUpdate: (newRoot: Root) => void,
31
40
  ) {
32
41
  this.rootEntry = {
33
42
  state: "loaded" as const,
@@ -43,13 +52,11 @@ export class SubscriptionScope<
43
52
  this.rootEntry.rawUnsub = root._raw.core.subscribe(
44
53
  (rawUpdate: RawCoValue | undefined) => {
45
54
  if (!rawUpdate) return;
46
- this.rootEntry.value = rootSchema.fromRaw(
47
- rawUpdate
48
- ) as Root;
55
+ this.rootEntry.value = rootSchema.fromRaw(rawUpdate) as Root;
49
56
  // console.log("root update", this.rootEntry.value.toJSON());
50
57
  subscriptionsScopes.set(this.rootEntry.value, this);
51
58
  this.scheduleUpdate();
52
- }
59
+ },
53
60
  );
54
61
  }
55
62
 
@@ -63,12 +70,19 @@ export class SubscriptionScope<
63
70
  }
64
71
  }
65
72
 
66
- onRefAccessedOrSet(accessedOrSetId: ID<CoValue> | undefined) {
73
+ onRefAccessedOrSet(
74
+ fromId: ID<CoValue>,
75
+ accessedOrSetId: ID<CoValue> | undefined,
76
+ ) {
67
77
  // console.log("onRefAccessedOrSet", this.scopeID, accessedOrSetId);
68
78
  if (!accessedOrSetId) {
69
79
  return;
70
80
  }
71
81
 
82
+ this.parents[accessedOrSetId] =
83
+ this.parents[accessedOrSetId] || new Set();
84
+ this.parents[accessedOrSetId]!.add(fromId);
85
+
72
86
  if (!this.entries.has(accessedOrSetId)) {
73
87
  const loadingEntry = {
74
88
  state: "loading",
@@ -94,6 +108,7 @@ export class SubscriptionScope<
94
108
  const rawUnsub = core.subscribe((rawUpdate) => {
95
109
  // console.log("ref update", this.scopeID, accessedOrSetId, JSON.stringify(rawUpdate))
96
110
  if (!rawUpdate) return;
111
+ this.invalidate(accessedOrSetId);
97
112
  this.scheduleUpdate();
98
113
  });
99
114
 
@@ -103,6 +118,27 @@ export class SubscriptionScope<
103
118
  }
104
119
  }
105
120
 
121
+ invalidate(
122
+ id: ID<CoValue>,
123
+ fromChild?: ID<CoValue>,
124
+ seen: Set<ID<CoValue>> = new Set(),
125
+ ) {
126
+ if (seen.has(id)) return;
127
+ TRACE_INVALIDATIONS &&
128
+ console.log(
129
+ "invalidating",
130
+ fromChild,
131
+ "->",
132
+ id,
133
+ this.cachedValues[id],
134
+ );
135
+ delete this.cachedValues[id];
136
+ seen.add(id);
137
+ for (const parent of this.parents[id] || []) {
138
+ this.invalidate(parent, id, seen);
139
+ }
140
+ }
141
+
106
142
  unsubscribeAll() {
107
143
  for (const entry of this.entries.values()) {
108
144
  if (entry.state === "loaded") {
@@ -0,0 +1,11 @@
1
+ export const SchemaInit = Symbol.for("SchemaInit");
2
+ export type SchemaInit = typeof SchemaInit;
3
+
4
+ export const InitValues = Symbol.for("InitValues");
5
+ export type InitValues = typeof InitValues;
6
+
7
+ export const ItemsSym = Symbol.for("items");
8
+ export type ItemsSym = typeof ItemsSym;
9
+
10
+ export const MembersSym = Symbol.for("members");
11
+ export type MembersSym = typeof MembersSym;
package/src/index.ts CHANGED
@@ -1,23 +1,27 @@
1
1
  export {
2
- /** @category Internal types */
3
- cojsonReady as jazzReady,
2
+ cojsonInternals,
3
+ MAX_RECOMMENDED_TX_SIZE,
4
+ WasmCrypto,
5
+ PureJSCrypto,
6
+ } from "cojson";
7
+ export type {
4
8
  InviteSecret,
5
9
  Peer,
6
10
  SessionID,
7
11
  AgentID,
8
12
  SyncMessage,
9
- cojsonInternals,
10
- MAX_RECOMMENDED_TX_SIZE,
13
+ CryptoProvider,
11
14
  } from "cojson";
12
15
 
13
- export { ID, CoValue } from "./internal.js";
16
+ export type { ID, CoValue } from "./internal.js";
14
17
 
15
- export { Encoders, val } from "./internal.js";
18
+ export { Encoders, co } from "./internal.js";
16
19
 
17
20
  export { CoMap } from "./internal.js";
18
21
  export { CoList } from "./internal.js";
19
22
  export { CoStream, BinaryCoStream } from "./internal.js";
20
23
  export { Group, Profile } from "./internal.js";
21
- export { Account, Me } from "./internal.js";
24
+ export { Account, isControlledAccount } from "./internal.js";
22
25
  export { ImageDefinition } from "./internal.js";
23
- export { CoValueBase, CoValueClass } from "./internal.js";
26
+ export { CoValueBase, type CoValueClass } from "./internal.js";
27
+ export type { DepthsIn, DeeplyLoaded } from "./internal.js";
package/src/internal.ts CHANGED
@@ -1,4 +1,5 @@
1
- export * from './implementation/inspect.js';
1
+ export * from "./implementation/symbols.js";
2
+ export * from "./implementation/inspect.js";
2
3
  export * from "./coValues/interfaces.js";
3
4
 
4
5
  export * from "./coValues/coMap.js";
@@ -9,7 +10,10 @@ export * from "./coValues/group.js";
9
10
 
10
11
  export * from "./implementation/errors.js";
11
12
  export * from "./implementation/refs.js";
12
- export * from "./implementation/encoding.js";
13
+ export * from "./implementation/schema.js";
13
14
  export * from "./implementation/subscriptionScope.js";
15
+ export * from "./coValues/deepLoading.js";
14
16
 
15
- export * from "./coValues/extensions/imageDef.js";
17
+ export * from "./coValues/extensions/imageDef.js";
18
+
19
+ import "./implementation/devtoolsFormatters.js";