atom.io 0.27.5 → 0.28.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 (73) hide show
  1. package/data/dist/index.d.ts +31 -29
  2. package/data/dist/index.js +16 -17
  3. package/data/src/join.ts +17 -19
  4. package/dist/{chunk-6ABWLAGY.js → chunk-6WL4RQMQ.js} +314 -249
  5. package/dist/chunk-D52JNVER.js +721 -0
  6. package/dist/chunk-YQ46F5O2.js +95 -0
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +3 -1
  9. package/internal/dist/index.d.ts +72 -36
  10. package/internal/dist/index.js +1 -1
  11. package/internal/src/atom/dispose-atom.ts +2 -9
  12. package/internal/src/families/dispose-from-store.ts +29 -18
  13. package/internal/src/families/find-in-store.ts +17 -7
  14. package/internal/src/get-state/get-from-store.ts +41 -32
  15. package/internal/src/ingest-updates/ingest-creation-disposal.ts +10 -1
  16. package/internal/src/molecule/dispose-molecule.ts +6 -17
  17. package/internal/src/pretty-print.ts +1 -16
  18. package/internal/src/selector/dispose-selector.ts +2 -9
  19. package/internal/src/set-state/set-into-store.ts +17 -19
  20. package/internal/src/store/circular-buffer.ts +34 -0
  21. package/internal/src/store/counterfeit.ts +109 -0
  22. package/internal/src/store/deposit.ts +14 -0
  23. package/internal/src/store/index.ts +1 -0
  24. package/internal/src/store/store.ts +3 -0
  25. package/internal/src/store/withdraw.ts +15 -10
  26. package/internal/src/transaction/build-transaction.ts +1 -0
  27. package/introspection/dist/index.d.ts +84 -4
  28. package/introspection/dist/index.js +1 -392
  29. package/introspection/src/attach-introspection-states.ts +7 -4
  30. package/introspection/src/attach-type-selectors.ts +26 -0
  31. package/introspection/src/differ.ts +167 -0
  32. package/introspection/src/index.ts +2 -0
  33. package/introspection/src/refinery.ts +100 -0
  34. package/json/dist/index.d.ts +31 -30
  35. package/json/dist/index.js +2 -77
  36. package/json/src/entries.ts +6 -0
  37. package/json/src/index.ts +53 -6
  38. package/package.json +18 -9
  39. package/react-devtools/dist/index.d.ts +1 -91
  40. package/react-devtools/dist/index.js +285 -414
  41. package/react-devtools/src/AtomIODevtools.tsx +2 -2
  42. package/react-devtools/src/StateEditor.tsx +20 -12
  43. package/react-devtools/src/StateIndex.tsx +10 -27
  44. package/react-devtools/src/TimelineIndex.tsx +3 -3
  45. package/react-devtools/src/TransactionIndex.tsx +6 -6
  46. package/react-devtools/src/Updates.tsx +2 -3
  47. package/react-devtools/src/index.ts +0 -71
  48. package/react-devtools/src/store.ts +51 -0
  49. package/realtime/dist/index.d.ts +7 -7
  50. package/realtime/dist/index.js +18 -22
  51. package/realtime/src/realtime-continuity.ts +30 -37
  52. package/realtime-client/dist/index.js +24 -10
  53. package/realtime-client/src/pull-atom-family-member.ts +2 -0
  54. package/realtime-client/src/pull-mutable-atom-family-member.ts +2 -0
  55. package/realtime-client/src/pull-selector-family-member.ts +2 -0
  56. package/realtime-client/src/realtime-client-stores/client-main-store.ts +6 -6
  57. package/realtime-client/src/sync-continuity.ts +28 -6
  58. package/realtime-server/dist/index.js +41 -5
  59. package/realtime-server/src/ipc-sockets/child-socket.ts +2 -0
  60. package/realtime-server/src/realtime-continuity-synchronizer.ts +42 -78
  61. package/realtime-testing/dist/index.d.ts +2 -0
  62. package/realtime-testing/dist/index.js +50 -8
  63. package/realtime-testing/src/setup-realtime-test.tsx +61 -9
  64. package/src/logger.ts +1 -0
  65. package/src/silo.ts +7 -3
  66. package/transceivers/set-rtx/src/set-rtx.ts +1 -0
  67. package/web/dist/index.d.ts +9 -0
  68. package/{dist/chunk-H6EDLPKH.js → web/dist/index.js} +5 -4
  69. package/web/package.json +13 -0
  70. package/web/src/index.ts +1 -0
  71. package/web/src/persist-sync.ts +25 -0
  72. package/dist/chunk-AK23DRMD.js +0 -21
  73. package/dist/chunk-IW6WYRS7.js +0 -140
@@ -1,393 +1,2 @@
1
+ export { Auditor, Differ, Refinery, attachIntrospectionStates, diffArray, diffBoolean, diffNumber, diffObject, diffString, discoverType, jsonRefinery, jsonTreeRefinery, prettyJson, primitiveRefinery } from '../../dist/chunk-D52JNVER.js';
1
2
  import '../../dist/chunk-XWL6SNVU.js';
2
- import * as Internal from 'atom.io/internal';
3
- import { createRegularAtom, deposit, createStandaloneSelector, IMPLICIT, createRegularAtomFamily, Subject, createSelectorFamily } from 'atom.io/internal';
4
- import { getState } from 'atom.io';
5
-
6
- var attachAtomIndex = (store = IMPLICIT.STORE) => {
7
- const atomTokenIndexState__INTERNAL = createRegularAtom(
8
- store,
9
- {
10
- key: `\u{1F441}\u200D\u{1F5E8} Atom Token Index (Internal)`,
11
- default: () => {
12
- const base = /* @__PURE__ */ new Map();
13
- for (const [key, val] of store.atoms) {
14
- if (!key.includes(`\u{1F441}\u200D\u{1F5E8}`)) {
15
- const token = deposit(val);
16
- if (val.family) {
17
- let familyNode = base.get(val.family.key);
18
- if (!familyNode || !(`familyMembers` in familyNode)) {
19
- familyNode = {
20
- key: val.family.key,
21
- familyMembers: /* @__PURE__ */ new Map()
22
- };
23
- base.set(val.family.key, familyNode);
24
- }
25
- familyNode.familyMembers.set(val.family.subKey, token);
26
- } else {
27
- base.set(key, token);
28
- }
29
- }
30
- }
31
- return base;
32
- },
33
- effects: [
34
- ({ setSelf }) => {
35
- store.on.atomCreation.subscribe(`introspection`, (atomToken) => {
36
- if (atomToken.key.includes(`\u{1F441}\u200D\u{1F5E8}`)) {
37
- return;
38
- }
39
- setSelf((self) => {
40
- if (atomToken.family) {
41
- const { key: familyKey, subKey } = atomToken.family;
42
- let familyNode = self.get(familyKey);
43
- if (familyNode === void 0 || !(`familyMembers` in familyNode)) {
44
- familyNode = {
45
- key: familyKey,
46
- familyMembers: /* @__PURE__ */ new Map()
47
- };
48
- self.set(familyKey, familyNode);
49
- }
50
- familyNode.familyMembers.set(subKey, atomToken);
51
- } else {
52
- self.set(atomToken.key, atomToken);
53
- }
54
- return self;
55
- });
56
- });
57
- store.on.atomDisposal.subscribe(`introspection`, (atomToken) => {
58
- setSelf((self) => {
59
- if (atomToken.family) {
60
- const { key: familyKey, subKey } = atomToken.family;
61
- const familyNode = self.get(familyKey);
62
- if (familyNode && `familyMembers` in familyNode) {
63
- familyNode.familyMembers.delete(subKey);
64
- if (familyNode.familyMembers.size === 0) {
65
- self.delete(familyKey);
66
- }
67
- }
68
- } else {
69
- self.delete(atomToken.key);
70
- }
71
- return self;
72
- });
73
- });
74
- }
75
- ]
76
- },
77
- void 0
78
- );
79
- return createStandaloneSelector(store, {
80
- key: `\u{1F441}\u200D\u{1F5E8} Atom Token Index`,
81
- get: ({ get }) => get(atomTokenIndexState__INTERNAL)
82
- });
83
- };
84
- var attachSelectorIndex = (store = IMPLICIT.STORE) => {
85
- const readonlySelectorTokenIndexState__INTERNAL = createRegularAtom(
86
- store,
87
- {
88
- key: `\u{1F441}\u200D\u{1F5E8} Selector Token Index (Internal)`,
89
- default: () => {
90
- const base = /* @__PURE__ */ new Map();
91
- for (const map of [store.readonlySelectors, store.selectors]) {
92
- for (const [key, val] of map) {
93
- if (!key.includes(`\u{1F441}\u200D\u{1F5E8}`)) {
94
- const token = deposit(val);
95
- if (val.family) {
96
- let familyNode = base.get(val.family.key);
97
- if (!familyNode || !(`familyMembers` in familyNode)) {
98
- familyNode = {
99
- key: val.family.key,
100
- familyMembers: /* @__PURE__ */ new Map()
101
- };
102
- base.set(val.family.key, familyNode);
103
- }
104
- familyNode.familyMembers.set(val.family.subKey, token);
105
- } else {
106
- base.set(key, token);
107
- }
108
- }
109
- }
110
- }
111
- return base;
112
- },
113
- effects: [
114
- ({ setSelf }) => {
115
- store.on.selectorCreation.subscribe(
116
- `introspection`,
117
- (selectorToken) => {
118
- if (selectorToken.key.includes(`\u{1F441}\u200D\u{1F5E8}`)) {
119
- return;
120
- }
121
- setSelf((self) => {
122
- if (selectorToken.family) {
123
- const { key: familyKey, subKey } = selectorToken.family;
124
- let familyNode = self.get(familyKey);
125
- if (familyNode === void 0 || !(`familyMembers` in familyNode)) {
126
- familyNode = {
127
- key: familyKey,
128
- familyMembers: /* @__PURE__ */ new Map()
129
- };
130
- self.set(familyKey, familyNode);
131
- }
132
- familyNode.familyMembers.set(subKey, selectorToken);
133
- } else {
134
- self.set(selectorToken.key, selectorToken);
135
- }
136
- return self;
137
- });
138
- }
139
- );
140
- store.on.selectorDisposal.subscribe(
141
- `introspection`,
142
- (selectorToken) => {
143
- setSelf((self) => {
144
- if (selectorToken.family) {
145
- const { key: familyKey, subKey } = selectorToken.family;
146
- const familyNode = self.get(familyKey);
147
- if (familyNode && `familyMembers` in familyNode) {
148
- familyNode.familyMembers.delete(subKey);
149
- if (familyNode.familyMembers.size === 0) {
150
- self.delete(familyKey);
151
- }
152
- }
153
- } else {
154
- self.delete(selectorToken.key);
155
- }
156
- return self;
157
- });
158
- }
159
- );
160
- }
161
- ]
162
- },
163
- void 0
164
- );
165
- return createStandaloneSelector(IMPLICIT.STORE, {
166
- key: `\u{1F441}\u200D\u{1F5E8} Selector Token Index`,
167
- get: ({ get }) => get(readonlySelectorTokenIndexState__INTERNAL)
168
- });
169
- };
170
- var attachTimelineFamily = (store = IMPLICIT.STORE) => {
171
- const findTimelineLogState__INTERNAL = createRegularAtomFamily(store, {
172
- key: `\u{1F441}\u200D\u{1F5E8} Timeline Update Log (Internal)`,
173
- default: (key) => store.timelines.get(key) ?? {
174
- type: `timeline`,
175
- key: ``,
176
- at: 0,
177
- timeTraveling: null,
178
- history: [],
179
- selectorTime: null,
180
- transactionKey: null,
181
- install: () => {
182
- },
183
- subject: new Subject(),
184
- subscriptions: /* @__PURE__ */ new Map()
185
- },
186
- effects: (key) => [
187
- ({ setSelf }) => {
188
- const tl = store.timelines.get(key);
189
- tl?.subject.subscribe(`introspection`, (_) => {
190
- setSelf({ ...tl });
191
- });
192
- }
193
- ]
194
- });
195
- const findTimelineLogState = createSelectorFamily(
196
- store,
197
- {
198
- key: `\u{1F441}\u200D\u{1F5E8} Timeline Update Log`,
199
- get: (key) => ({ get }) => get(findTimelineLogState__INTERNAL, key)
200
- }
201
- );
202
- return findTimelineLogState;
203
- };
204
- var attachTimelineIndex = (store = IMPLICIT.STORE) => {
205
- const timelineTokenIndexState__INTERNAL = createRegularAtom(
206
- store,
207
- {
208
- key: `\u{1F441}\u200D\u{1F5E8} Timeline Token Index (Internal)`,
209
- default: () => [...store.timelines].map(([key]) => {
210
- return { key, type: `timeline` };
211
- }),
212
- effects: [
213
- ({ setSelf }) => {
214
- store.on.timelineCreation.subscribe(
215
- `introspection`,
216
- (timelineToken) => {
217
- setSelf((state) => [...state, timelineToken]);
218
- }
219
- );
220
- }
221
- ]
222
- },
223
- void 0
224
- );
225
- const timelineTokenIndex = createStandaloneSelector(store, {
226
- key: `\u{1F441}\u200D\u{1F5E8} Timeline Token Index`,
227
- get: ({ get }) => get(timelineTokenIndexState__INTERNAL)
228
- });
229
- return timelineTokenIndex;
230
- };
231
- var attachTransactionIndex = (store = IMPLICIT.STORE) => {
232
- const transactionTokenIndexState__INTERNAL = createRegularAtom(
233
- store,
234
- {
235
- key: `\u{1F441}\u200D\u{1F5E8} Transaction Token Index (Internal)`,
236
- default: () => [...store.transactions].map(([key]) => {
237
- return { key, type: `transaction` };
238
- }),
239
- effects: [
240
- ({ setSelf }) => {
241
- store.on.transactionCreation.subscribe(
242
- `introspection`,
243
- (transactionToken) => {
244
- setSelf((state) => [...state, transactionToken]);
245
- }
246
- );
247
- }
248
- ]
249
- },
250
- void 0
251
- );
252
- const transactionTokenIndex = createStandaloneSelector(store, {
253
- key: `\u{1F441}\u200D\u{1F5E8} Transaction Token Index`,
254
- get: ({ get }) => get(transactionTokenIndexState__INTERNAL)
255
- });
256
- return transactionTokenIndex;
257
- };
258
- var attachTransactionLogs = (store = IMPLICIT.STORE) => {
259
- const transactionUpdateLogAtoms = createRegularAtomFamily(store, {
260
- key: `\u{1F441}\u200D\u{1F5E8} Transaction Update Log (Internal)`,
261
- default: () => [],
262
- effects: (key) => [
263
- ({ setSelf }) => {
264
- const tx = store.transactions.get(key);
265
- tx?.subject.subscribe(`introspection`, (transactionUpdate) => {
266
- if (transactionUpdate.key === key) {
267
- setSelf((state) => [...state, transactionUpdate]);
268
- }
269
- });
270
- }
271
- ]
272
- });
273
- const findTransactionUpdateLogState = createSelectorFamily(store, {
274
- key: `\u{1F441}\u200D\u{1F5E8} Transaction Update Log`,
275
- get: (key) => ({ get }) => get(transactionUpdateLogAtoms, key)
276
- });
277
- return findTransactionUpdateLogState;
278
- };
279
-
280
- // introspection/src/attach-introspection-states.ts
281
- var attachIntrospectionStates = (store = Internal.IMPLICIT.STORE) => {
282
- return {
283
- atomIndex: attachAtomIndex(store),
284
- selectorIndex: attachSelectorIndex(store),
285
- transactionIndex: attachTransactionIndex(store),
286
- findTransactionLogState: attachTransactionLogs(store),
287
- timelineIndex: attachTimelineIndex(store),
288
- findTimelineState: attachTimelineFamily(store)
289
- };
290
- };
291
- var Auditor = class _Auditor {
292
- /**
293
- * @param {Store} store - The store to audit.
294
- */
295
- constructor(store = Internal.IMPLICIT.STORE) {
296
- this.store = store;
297
- this.atomIndex = attachAtomIndex(this.store);
298
- this.selectorIndex = attachSelectorIndex(this.store);
299
- this.unsubscribeFromAtomCreation = this.store.on.atomCreation.subscribe(
300
- `auditor-${this.auditorCreatedAt}`,
301
- ({ key }) => {
302
- this.statesCreatedAt.set(key, performance.now() - this.auditorCreatedAt);
303
- }
304
- );
305
- this.unsubscribeFromAtomDisposal = this.store.on.atomDisposal.subscribe(
306
- `auditor-${this.auditorCreatedAt}`,
307
- ({ key }) => {
308
- this.statesCreatedAt.delete(key);
309
- }
310
- );
311
- this.unsubscribeFromSelectorCreation = this.store.on.selectorCreation.subscribe(
312
- `auditor-${this.auditorCreatedAt}`,
313
- ({ key }) => {
314
- this.statesCreatedAt.set(
315
- key,
316
- performance.now() - this.auditorCreatedAt
317
- );
318
- }
319
- );
320
- this.unsubscribeFromSelectorDisposal = this.store.on.selectorDisposal.subscribe(
321
- `auditor-${this.auditorCreatedAt}`,
322
- ({ key }) => {
323
- this.statesCreatedAt.delete(key);
324
- }
325
- );
326
- }
327
- auditorCreatedAt = performance.now();
328
- statesCreatedAt = /* @__PURE__ */ new Map();
329
- atomIndex;
330
- selectorIndex;
331
- disposed = false;
332
- unsubscribeFromAtomCreation;
333
- unsubscribeFromAtomDisposal;
334
- unsubscribeFromSelectorCreation;
335
- unsubscribeFromSelectorDisposal;
336
- static DEFAULT_LIST_RESOURCES_PARAM = {
337
- atomFamilies: true,
338
- selectorFamilies: true
339
- };
340
- /**
341
- * Lists all resources in the store, along with their creation time.
342
- *
343
- * @param {ListResourcesParam} [param] - Optional parameters for filtering the list of resources.
344
- * @returns {readonly [ReadableToken<unknown>, number]}[] - An array of tuples, where each tuple contains a state token belonging to a family in the store and that state's creation time.
345
- */
346
- listResources(param = _Auditor.DEFAULT_LIST_RESOURCES_PARAM) {
347
- if (this.disposed) {
348
- throw new Error(`This Auditor has been disposed`);
349
- }
350
- const atoms = getState(this.atomIndex);
351
- const selectors = getState(this.selectorIndex);
352
- const atomFamilyNodes = [...atoms.values()].filter(
353
- (node) => `familyMembers` in node
354
- );
355
- const selectorFamilyNodes = [...selectors.values()].filter(
356
- (node) => `familyMembers` in node
357
- );
358
- const currentTime = performance.now();
359
- const resources = [];
360
- if (param.atomFamilies) {
361
- for (const familyNode of atomFamilyNodes) {
362
- const tokens = familyNode.familyMembers.values();
363
- for (const token of tokens) {
364
- const storedTime = this.statesCreatedAt.get(token.key);
365
- const creationTime = storedTime ?? this.auditorCreatedAt;
366
- const age = currentTime - creationTime;
367
- resources.push([token, age]);
368
- }
369
- }
370
- }
371
- if (param.selectorFamilies) {
372
- for (const familyNode of selectorFamilyNodes) {
373
- const tokens = familyNode.familyMembers.values();
374
- for (const token of tokens) {
375
- const storedTime = this.statesCreatedAt.get(token.key);
376
- const creationTime = storedTime ?? this.auditorCreatedAt;
377
- const age = currentTime - creationTime;
378
- resources.push([token, age]);
379
- }
380
- }
381
- }
382
- return resources;
383
- }
384
- [Symbol.dispose]() {
385
- this.unsubscribeFromAtomCreation();
386
- this.unsubscribeFromAtomDisposal();
387
- this.unsubscribeFromSelectorCreation();
388
- this.unsubscribeFromSelectorDisposal();
389
- this.disposed = true;
390
- }
391
- };
392
-
393
- export { Auditor, attachIntrospectionStates };
@@ -15,6 +15,7 @@ import { attachTimelineFamily } from "./attach-timeline-family"
15
15
  import { attachTimelineIndex } from "./attach-timeline-index"
16
16
  import { attachTransactionIndex } from "./attach-transaction-index"
17
17
  import { attachTransactionLogs } from "./attach-transaction-logs"
18
+ import { attachTypeSelectors } from "./attach-type-selectors"
18
19
 
19
20
  export const attachIntrospectionStates = (
20
21
  store: Internal.Store = Internal.IMPLICIT.STORE,
@@ -22,19 +23,21 @@ export const attachIntrospectionStates = (
22
23
  atomIndex: ReadonlySelectorToken<AtomTokenIndex>
23
24
  selectorIndex: ReadonlySelectorToken<SelectorTokenIndex>
24
25
  transactionIndex: ReadonlySelectorToken<TransactionToken<Func>[]>
25
- findTransactionLogState: ReadonlySelectorFamilyToken<
26
+ transactionLogSelectors: ReadonlySelectorFamilyToken<
26
27
  TransactionUpdate<Func>[],
27
28
  string
28
29
  >
29
30
  timelineIndex: ReadonlySelectorToken<TimelineToken<any>[]>
30
- findTimelineState: ReadonlySelectorFamilyToken<Timeline<any>, string>
31
+ timelineSelectors: ReadonlySelectorFamilyToken<Timeline<any>, string>
32
+ typeSelectors: ReadonlySelectorFamilyToken<string, string>
31
33
  } => {
32
34
  return {
33
35
  atomIndex: attachAtomIndex(store),
34
36
  selectorIndex: attachSelectorIndex(store),
35
37
  transactionIndex: attachTransactionIndex(store),
36
- findTransactionLogState: attachTransactionLogs(store),
38
+ transactionLogSelectors: attachTransactionLogs(store),
37
39
  timelineIndex: attachTimelineIndex(store),
38
- findTimelineState: attachTimelineFamily(store),
40
+ timelineSelectors: attachTimelineFamily(store),
41
+ typeSelectors: attachTypeSelectors(store),
39
42
  }
40
43
  }
@@ -0,0 +1,26 @@
1
+ import type { ReadonlySelectorFamilyToken } from "atom.io"
2
+ import type { Store } from "atom.io/internal"
3
+ import { createReadonlySelectorFamily, IMPLICIT } from "atom.io/internal"
4
+
5
+ import { discoverType } from "./refinery"
6
+
7
+ export const attachTypeSelectors = (
8
+ store: Store = IMPLICIT.STORE,
9
+ ): ReadonlySelectorFamilyToken<string, string> => {
10
+ const typeSelectors = createReadonlySelectorFamily<string, string>(store, {
11
+ key: `👁‍🗨 State Type`,
12
+ get:
13
+ (token) =>
14
+ ({ get }) => {
15
+ let state: unknown
16
+ try {
17
+ state = get(token as any)
18
+ } catch (error) {
19
+ return `error`
20
+ }
21
+ const typeOfState = discoverType(state)
22
+ return typeOfState
23
+ },
24
+ })
25
+ return typeSelectors
26
+ }
@@ -0,0 +1,167 @@
1
+ import { sprawl } from "anvl/object"
2
+ import type { Json } from "atom.io/json"
3
+
4
+ import type { Refinery, Supported } from "./refinery"
5
+ import { discoverType, jsonTreeRefinery, primitiveRefinery } from "./refinery"
6
+
7
+ export function diffNumber(a: number, b: number): Delta {
8
+ const sign = a < b ? `+` : `-`
9
+ return {
10
+ summary: `${sign}${Math.abs(a - b)} (${a} → ${b})`,
11
+ }
12
+ }
13
+
14
+ export function diffString(a: string, b: string): Delta {
15
+ const sign = a.length < b.length ? `+` : `-`
16
+ return {
17
+ summary: `${sign}${Math.abs(a.length - b.length)} ("${a}" → "${b}")`,
18
+ }
19
+ }
20
+
21
+ export function diffBoolean(a: boolean, b: boolean): Delta {
22
+ return {
23
+ summary: `${a} → ${b}`,
24
+ }
25
+ }
26
+
27
+ export function diffObject(
28
+ a: Json.Tree.Object,
29
+ b: Json.Tree.Object,
30
+ recurse: Diff<unknown>,
31
+ ): Delta {
32
+ let summary = ``
33
+ const added: Delta[`added`] = []
34
+ const removed: Delta[`removed`] = []
35
+ const changed: Delta[`changed`] = []
36
+
37
+ sprawl(a, (path, nodeA) => {
38
+ let key: string
39
+ for (key of path) {
40
+ const nodeB = b[key]
41
+ if (nodeB === undefined) {
42
+ removed.push([key, JSON.stringify(nodeA)])
43
+ } else {
44
+ const delta = recurse(nodeA, nodeB)
45
+ if (delta.summary !== `No Change`) {
46
+ changed.push([key, delta])
47
+ }
48
+ }
49
+ }
50
+ })
51
+
52
+ sprawl(b, (path, nodeB) => {
53
+ let key: string
54
+ for (key of path) {
55
+ const nodeA = a[key]
56
+ if (nodeA === undefined) {
57
+ added.push([key, JSON.stringify(nodeB)])
58
+ }
59
+ }
60
+ })
61
+
62
+ summary = `~${changed.length} +${added.length} -${removed.length}`
63
+
64
+ return {
65
+ summary,
66
+ added,
67
+ removed,
68
+ changed,
69
+ }
70
+ }
71
+
72
+ export function diffArray(
73
+ a: Json.Tree.Array,
74
+ b: Json.Tree.Array,
75
+ recurse: Diff<unknown>,
76
+ ): Delta {
77
+ return diffObject(a as any, b as any, recurse)
78
+ }
79
+
80
+ type Delta = {
81
+ summary: string
82
+ added?: [path: string, addedStringifiedValue: string][]
83
+ removed?: [path: string, removedStringifiedValue: string][]
84
+ changed?: [path: string, delta: Delta][]
85
+ }
86
+
87
+ type Diff<T> = (a: T, b: T) => Delta
88
+ type DiffTree<T> = (a: T, b: T, recurse: Differ<any, any>[`diff`]) => Delta
89
+
90
+ export class Differ<
91
+ Leaf extends Record<string, any>,
92
+ Tree extends Record<string, any>,
93
+ > {
94
+ public leafRefinery: Refinery<Leaf>
95
+ public treeRefinery: Refinery<Tree>
96
+ public leafDiffers: { [KL in keyof Leaf]: Diff<Supported<Leaf[KL]>> }
97
+ public treeDiffers: { [KT in keyof Tree]: DiffTree<Supported<Tree[KT]>> }
98
+
99
+ public constructor(
100
+ leafRefinery: Refinery<Leaf>,
101
+ treeRefinery: Refinery<Tree>,
102
+ diffFunctions: {
103
+ [KT in keyof Tree]: DiffTree<Supported<Tree[KT]>>
104
+ } & { [KL in keyof Leaf]: Diff<Supported<Leaf[KL]>> },
105
+ ) {
106
+ this.leafRefinery = leafRefinery
107
+ this.treeRefinery = treeRefinery
108
+ this.leafDiffers = {} as any
109
+ this.treeDiffers = {} as any
110
+ for (const key of Object.keys(leafRefinery.supported)) {
111
+ const diffFunction = diffFunctions[key]
112
+ this.leafDiffers[key as keyof Leaf] = diffFunction
113
+ }
114
+ for (const key of Object.keys(treeRefinery.supported)) {
115
+ const diffFunction = diffFunctions[key]
116
+ this.treeDiffers[key as keyof Tree] = diffFunction
117
+ }
118
+ }
119
+
120
+ public diff(a: unknown, b: unknown): Delta {
121
+ if (a === b) {
122
+ return { summary: `No Change` }
123
+ }
124
+
125
+ const aRefined = this.leafRefinery.refine(a) ?? this.treeRefinery.refine(a)
126
+ const bRefined = this.leafRefinery.refine(b) ?? this.treeRefinery.refine(b)
127
+
128
+ if (aRefined !== null && bRefined !== null) {
129
+ if (aRefined.type === bRefined.type) {
130
+ if (aRefined.type in this.leafDiffers) {
131
+ const delta = this.leafDiffers[aRefined.type](
132
+ aRefined.data,
133
+ bRefined.data,
134
+ )
135
+ return delta
136
+ }
137
+ if (aRefined.type in this.treeDiffers) {
138
+ const delta = this.treeDiffers[aRefined.type](
139
+ aRefined.data,
140
+ bRefined.data,
141
+ (x, y) => this.diff(x, y),
142
+ )
143
+ return delta
144
+ }
145
+ }
146
+ }
147
+ const typeA = discoverType(a)
148
+ const typeB = discoverType(b)
149
+ if (typeA === typeB) {
150
+ return {
151
+ summary: `${typeA} → ${typeB}`,
152
+ }
153
+ }
154
+ return {
155
+ summary: `Type change: ${typeA} → ${typeB}`,
156
+ }
157
+ }
158
+ }
159
+
160
+ export const prettyJson = new Differ(primitiveRefinery, jsonTreeRefinery, {
161
+ number: diffNumber,
162
+ string: diffString,
163
+ boolean: diffBoolean,
164
+ null: () => ({ summary: `No Change` }),
165
+ object: diffObject,
166
+ array: diffArray,
167
+ })
@@ -2,6 +2,8 @@ import type { ReadableToken } from "atom.io"
2
2
 
3
3
  export * from "./attach-introspection-states"
4
4
  export * from "./auditor"
5
+ export * from "./differ"
6
+ export * from "./refinery"
5
7
 
6
8
  export type FamilyNode<Token extends ReadableToken<unknown>> = {
7
9
  key: string