atom.io 0.40.7 → 0.40.8

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/dist/data/index.js +1 -2
  2. package/dist/data/index.js.map +1 -1
  3. package/dist/employ-socket-D6wgByWh.js.map +1 -1
  4. package/dist/eslint-plugin/index.js.map +1 -1
  5. package/dist/{has-role-hv4-hJMw.js → has-role-CMlaUlaf.js} +30 -46
  6. package/dist/has-role-CMlaUlaf.js.map +1 -0
  7. package/dist/internal/index.d.ts.map +1 -1
  8. package/dist/internal/index.js +103 -174
  9. package/dist/internal/index.js.map +1 -1
  10. package/dist/introspection/index.d.ts.map +1 -1
  11. package/dist/introspection/index.js +13 -32
  12. package/dist/introspection/index.js.map +1 -1
  13. package/dist/is-fn-DY1wZ-md.js.map +1 -1
  14. package/dist/json/index.d.ts.map +1 -1
  15. package/dist/json/index.js.map +1 -1
  16. package/dist/main/index.d.ts.map +1 -1
  17. package/dist/main/index.js +1 -2
  18. package/dist/main/index.js.map +1 -1
  19. package/dist/mutex-store-CSvxY9i3.js.map +1 -1
  20. package/dist/react/index.d.ts.map +1 -1
  21. package/dist/react/index.js.map +1 -1
  22. package/dist/react-devtools/index.d.ts.map +1 -1
  23. package/dist/react-devtools/index.js +2 -4
  24. package/dist/react-devtools/index.js.map +1 -1
  25. package/dist/realtime/index.d.ts.map +1 -1
  26. package/dist/realtime/index.js +2 -3
  27. package/dist/realtime/index.js.map +1 -1
  28. package/dist/realtime-client/index.d.ts +1 -4
  29. package/dist/realtime-client/index.d.ts.map +1 -1
  30. package/dist/realtime-client/index.js +5 -20
  31. package/dist/realtime-client/index.js.map +1 -1
  32. package/dist/realtime-react/index.d.ts +2 -5
  33. package/dist/realtime-react/index.d.ts.map +1 -1
  34. package/dist/realtime-react/index.js +4 -15
  35. package/dist/realtime-react/index.js.map +1 -1
  36. package/dist/realtime-server/index.d.ts +36 -44
  37. package/dist/realtime-server/index.d.ts.map +1 -1
  38. package/dist/realtime-server/index.js +125 -171
  39. package/dist/realtime-server/index.js.map +1 -1
  40. package/dist/realtime-testing/index.d.ts +4 -2
  41. package/dist/realtime-testing/index.d.ts.map +1 -1
  42. package/dist/realtime-testing/index.js +13 -7
  43. package/dist/realtime-testing/index.js.map +1 -1
  44. package/dist/{shared-room-store-COGGKqes.js → shared-room-store-BfW3nWif.js} +2 -3
  45. package/dist/{shared-room-store-COGGKqes.js.map → shared-room-store-BfW3nWif.js.map} +1 -1
  46. package/dist/shared-room-store-D2o4ZLjC.d.ts.map +1 -1
  47. package/dist/transceivers/set-rtx/index.d.ts.map +1 -1
  48. package/dist/transceivers/set-rtx/index.js +4 -8
  49. package/dist/transceivers/set-rtx/index.js.map +1 -1
  50. package/dist/web/index.js.map +1 -1
  51. package/package.json +11 -11
  52. package/src/internal/mutable/tracker.ts +66 -51
  53. package/src/internal/subscribe/subscribe-to-state.ts +9 -0
  54. package/src/realtime-client/index.ts +0 -1
  55. package/src/realtime-react/index.ts +0 -1
  56. package/src/realtime-server/continuity/continuity-store.ts +1 -26
  57. package/src/realtime-server/continuity/provide-continuity.ts +50 -0
  58. package/src/realtime-server/continuity/{subscribe-to-continuity-actions.ts → provide-outcomes.ts} +14 -12
  59. package/src/realtime-server/continuity/{subscribe-to-continuity-perpectives.ts → provide-perspectives.ts} +10 -8
  60. package/src/realtime-server/continuity/{prepare-to-send-initial-payload.ts → provide-startup-payloads.ts} +6 -4
  61. package/src/realtime-server/continuity/receive-action-requests.ts +68 -0
  62. package/src/realtime-server/continuity/{prepare-to-track-client-acknowledgement.ts → track-acknowledgements.ts} +15 -8
  63. package/src/realtime-server/index.ts +1 -2
  64. package/src/realtime-server/ipc-sockets/custom-socket.ts +3 -3
  65. package/src/realtime-server/ipc-sockets/parent-socket.ts +19 -13
  66. package/src/realtime-server/server-config.ts +0 -1
  67. package/src/realtime-testing/setup-realtime-test.tsx +20 -14
  68. package/dist/has-role-hv4-hJMw.js.map +0 -1
  69. package/src/realtime-client/server-action.ts +0 -23
  70. package/src/realtime-react/use-server-action.ts +0 -19
  71. package/src/realtime-server/continuity/prepare-to-serve-transaction-request.ts +0 -59
  72. package/src/realtime-server/continuity/prepare-to-sync-realtime-continuity.ts +0 -145
  73. package/src/realtime-server/realtime-action-receiver.ts +0 -40
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/transceivers/set-rtx/set-rtx.ts"],"sourcesContent":["import type { Lineage, Transceiver, TransceiverMode } from \"atom.io/internal\"\nimport { Subject } from \"atom.io/internal\"\nimport type { Json, primitive } from \"atom.io/json\"\nimport { stringifyJson } from \"atom.io/json\"\n\nexport type SetUpdateType = `add` | `clear` | `del` | `tx`\nexport type SetUpdate = `${SetUpdateType}:${string}`\nexport type NumberedSetUpdate = `${number}=${SetUpdate}`\n\nexport interface SetRTXView<P extends primitive> extends ReadonlySet<P> {\n\treadonly cache: ReadonlyArray<NumberedSetUpdate | null>\n\treadonly cacheLimit: number\n\treadonly cacheIdx: number\n\treadonly cacheUpdateNumber: number\n}\n\nexport interface SetRTXJson<P extends primitive> extends Json.Object {\n\tmembers: P[]\n\tcache: (NumberedSetUpdate | null)[]\n\tcacheLimit: number\n\tcacheIdx: number\n\tcacheUpdateNumber: number\n}\nexport class SetRTX<P extends primitive>\n\textends Set<P>\n\timplements\n\t\tTransceiver<SetRTXView<P>, NumberedSetUpdate, SetRTXJson<P>>,\n\t\tLineage\n{\n\tpublic mode: TransceiverMode = `record`\n\tpublic readonly subject: Subject<SetUpdate> = new Subject<SetUpdate>()\n\tpublic cacheLimit = 0\n\tpublic cache: (NumberedSetUpdate | null)[] = []\n\tpublic cacheIdx = -1\n\tpublic cacheUpdateNumber = -1\n\n\tpublic constructor(values?: Iterable<P>, cacheLimit = 0) {\n\t\tsuper(values)\n\t\tif (values instanceof SetRTX) {\n\t\t\tthis.parent = values\n\t\t\tthis.cacheUpdateNumber = values.cacheUpdateNumber\n\t\t}\n\t\tif (cacheLimit) {\n\t\t\tthis.cacheLimit = cacheLimit\n\t\t\tthis.cache = new Array(cacheLimit)\n\t\t\tthis.subscribe(`auto cache`, (update) => {\n\t\t\t\tthis.cacheIdx++\n\t\t\t\tthis.cacheIdx %= this.cacheLimit\n\t\t\t\tthis.cache[this.cacheIdx] = update\n\t\t\t})\n\t\t}\n\t}\n\n\tpublic readonly READONLY_VIEW: SetRTXView<P> = this\n\n\tpublic toJSON(): SetRTXJson<P> {\n\t\treturn {\n\t\t\tmembers: [...this],\n\t\t\tcache: this.cache,\n\t\t\tcacheLimit: this.cacheLimit,\n\t\t\tcacheIdx: this.cacheIdx,\n\t\t\tcacheUpdateNumber: this.cacheUpdateNumber,\n\t\t}\n\t}\n\n\tpublic static fromJSON<P extends primitive>(json: SetRTXJson<P>): SetRTX<P> {\n\t\tconst set = new SetRTX<P>(json.members, json.cacheLimit)\n\t\tset.cache = json.cache\n\t\tset.cacheIdx = json.cacheIdx\n\t\tset.cacheUpdateNumber = json.cacheUpdateNumber\n\t\treturn set\n\t}\n\n\tpublic add(value: P): this {\n\t\tconst result = super.add(value)\n\t\tif (this.mode === `record`) {\n\t\t\tthis.cacheUpdateNumber++\n\t\t\tthis.emit(`add:${stringifyJson<P>(value)}`)\n\t\t}\n\t\treturn result\n\t}\n\n\tpublic clear(): void {\n\t\tconst capturedContents = this.mode === `record` ? [...this] : null\n\t\tsuper.clear()\n\t\tif (capturedContents) {\n\t\t\tthis.cacheUpdateNumber++\n\t\t\tthis.emit(`clear:${JSON.stringify(capturedContents)}`)\n\t\t}\n\t}\n\n\tpublic delete(value: P): boolean {\n\t\tconst result = super.delete(value)\n\t\tif (this.mode === `record`) {\n\t\t\tthis.cacheUpdateNumber++\n\t\t\tthis.emit(`del:${stringifyJson<P>(value)}`)\n\t\t}\n\t\treturn result\n\t}\n\n\tpublic readonly parent: SetRTX<P> | null = null\n\tpublic child: SetRTX<P> | null = null\n\tpublic transactionUpdates: SetUpdate[] | null = null\n\tpublic transaction(run: (child: SetRTX<P>) => boolean): void {\n\t\tthis.mode = `transaction`\n\t\tthis.transactionUpdates = []\n\t\tthis.child = new SetRTX(this)\n\t\tconst unsubscribe = this.child._subscribe(`transaction`, (update) => {\n\t\t\tthis.transactionUpdates?.push(update)\n\t\t})\n\t\ttry {\n\t\t\tconst shouldCommit = run(this.child)\n\t\t\tif (shouldCommit) {\n\t\t\t\tfor (const update of this.transactionUpdates) {\n\t\t\t\t\tthis.doStep(update)\n\t\t\t\t}\n\t\t\t\tthis.cacheUpdateNumber++\n\t\t\t\tthis.emit(`tx:${this.transactionUpdates.join(`;`)}`)\n\t\t\t}\n\t\t} catch (thrown) {\n\t\t\t/* eslint-disable-next-line no-console */\n\t\t\tconsole.warn(\n\t\t\t\t`Did not apply transaction to SetRTX; this error was thrown:`,\n\t\t\t\tthrown,\n\t\t\t)\n\t\t\tthrow thrown\n\t\t} finally {\n\t\t\tunsubscribe()\n\t\t\tthis.child = null\n\t\t\tthis.transactionUpdates = null\n\t\t\tthis.mode = `record`\n\t\t}\n\t}\n\n\tprotected _subscribe(\n\t\tkey: string,\n\t\tfn: (update: SetUpdate) => void,\n\t): () => void {\n\t\treturn this.subject.subscribe(key, fn)\n\t}\n\tpublic subscribe(\n\t\tkey: string,\n\t\tfn: (update: NumberedSetUpdate) => void,\n\t): () => void {\n\t\treturn this.subject.subscribe(key, (update) => {\n\t\t\tfn(`${this.cacheUpdateNumber}=${update}`)\n\t\t})\n\t}\n\n\tpublic emit(update: SetUpdate): void {\n\t\tthis.subject.next(update)\n\t}\n\n\tprivate doStep(update: SetUpdate): void {\n\t\tconst typeValueBreak = update.indexOf(`:`)\n\t\tconst type = update.substring(0, typeValueBreak) as SetUpdateType\n\t\tconst value = update.substring(typeValueBreak + 1)\n\t\tswitch (type) {\n\t\t\tcase `add`:\n\t\t\t\tthis.add(JSON.parse(value))\n\t\t\t\tbreak\n\t\t\tcase `clear`:\n\t\t\t\tthis.clear()\n\t\t\t\tbreak\n\t\t\tcase `del`:\n\t\t\t\tthis.delete(JSON.parse(value))\n\t\t\t\tbreak\n\t\t\tcase `tx`:\n\t\t\t\tfor (const subUpdate of value.split(`;`)) {\n\t\t\t\t\tthis.doStep(subUpdate as SetUpdate)\n\t\t\t\t}\n\t\t}\n\t}\n\n\tpublic getUpdateNumber(update: NumberedSetUpdate): number {\n\t\tconst breakpoint = update.indexOf(`=`)\n\t\treturn Number(update.substring(0, breakpoint))\n\t}\n\n\tpublic do(update: NumberedSetUpdate): number | `OUT_OF_RANGE` | null {\n\t\tconst breakpoint = update.indexOf(`=`)\n\t\tconst updateNumber = Number(update.substring(0, breakpoint))\n\t\tconst eventOffset = updateNumber - this.cacheUpdateNumber\n\t\tconst isFuture = eventOffset > 0\n\t\tif (isFuture) {\n\t\t\tif (eventOffset === 1) {\n\t\t\t\tthis.mode = `playback`\n\t\t\t\tconst innerUpdate = update.substring(breakpoint + 1) as SetUpdate\n\t\t\t\tthis.doStep(innerUpdate)\n\t\t\t\tthis.mode = `record`\n\t\t\t\tthis.cacheUpdateNumber = updateNumber\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.cacheUpdateNumber + 1\n\t\t}\n\t\tif (Math.abs(eventOffset) < this.cacheLimit) {\n\t\t\tconst eventIdx = this.cacheIdx + eventOffset\n\t\t\tconst cachedUpdate = this.cache[eventIdx]\n\t\t\tif (cachedUpdate === update) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tthis.mode = `playback`\n\t\t\tlet done = false\n\t\t\twhile (!done) {\n\t\t\t\tthis.cacheIdx %= this.cacheLimit\n\t\t\t\tconst u = this.cache[this.cacheIdx]\n\t\t\t\tthis.cacheIdx--\n\t\t\t\tif (!u) {\n\t\t\t\t\treturn `OUT_OF_RANGE`\n\t\t\t\t}\n\t\t\t\tthis.undo(u)\n\t\t\t\tdone = this.cacheIdx === eventIdx - 1\n\t\t\t}\n\t\t\tconst innerUpdate = update.substring(breakpoint + 1) as SetUpdate\n\t\t\tthis.doStep(innerUpdate)\n\t\t\tthis.mode = `record`\n\t\t\tthis.cacheUpdateNumber = updateNumber\n\t\t\treturn null\n\t\t}\n\t\treturn `OUT_OF_RANGE`\n\t}\n\n\tpublic undoStep(update: SetUpdate): void {\n\t\tconst breakpoint = update.indexOf(`:`)\n\t\tconst type = update.substring(0, breakpoint) as SetUpdateType\n\t\tconst value = update.substring(breakpoint + 1)\n\t\tswitch (type) {\n\t\t\tcase `add`:\n\t\t\t\tthis.delete(JSON.parse(value))\n\t\t\t\tbreak\n\t\t\tcase `del`:\n\t\t\t\tthis.add(JSON.parse(value))\n\t\t\t\tbreak\n\t\t\tcase `clear`: {\n\t\t\t\tconst values = JSON.parse(value) as P[]\n\t\t\t\tfor (const v of values) this.add(v)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase `tx`: {\n\t\t\t\tconst updates = value.split(`;`) as SetUpdate[]\n\t\t\t\tfor (let i = updates.length - 1; i >= 0; i--) {\n\t\t\t\t\tthis.undoStep(updates[i])\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic undo(update: NumberedSetUpdate): number | null {\n\t\tconst breakpoint = update.indexOf(`=`)\n\t\tconst updateNumber = Number(update.substring(0, breakpoint))\n\t\tif (updateNumber === this.cacheUpdateNumber) {\n\t\t\tthis.mode = `playback`\n\t\t\tconst innerUpdate = update.substring(breakpoint + 1) as SetUpdate\n\t\t\tthis.undoStep(innerUpdate)\n\t\t\tthis.mode = `record`\n\t\t\tthis.cacheUpdateNumber--\n\t\t\treturn null\n\t\t}\n\t\treturn this.cacheUpdateNumber\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/transceivers/set-rtx/set-rtx.ts"],"sourcesContent":["import type { Lineage, Transceiver, TransceiverMode } from \"atom.io/internal\"\nimport { Subject } from \"atom.io/internal\"\nimport type { Json, primitive } from \"atom.io/json\"\nimport { stringifyJson } from \"atom.io/json\"\n\nexport type SetUpdateType = `add` | `clear` | `del` | `tx`\nexport type SetUpdate = `${SetUpdateType}:${string}`\nexport type NumberedSetUpdate = `${number}=${SetUpdate}`\n\nexport interface SetRTXView<P extends primitive> extends ReadonlySet<P> {\n\treadonly cache: ReadonlyArray<NumberedSetUpdate | null>\n\treadonly cacheLimit: number\n\treadonly cacheIdx: number\n\treadonly cacheUpdateNumber: number\n}\n\nexport interface SetRTXJson<P extends primitive> extends Json.Object {\n\tmembers: P[]\n\tcache: (NumberedSetUpdate | null)[]\n\tcacheLimit: number\n\tcacheIdx: number\n\tcacheUpdateNumber: number\n}\nexport class SetRTX<P extends primitive>\n\textends Set<P>\n\timplements\n\t\tTransceiver<SetRTXView<P>, NumberedSetUpdate, SetRTXJson<P>>,\n\t\tLineage\n{\n\tpublic mode: TransceiverMode = `record`\n\tpublic readonly subject: Subject<SetUpdate> = new Subject<SetUpdate>()\n\tpublic cacheLimit = 0\n\tpublic cache: (NumberedSetUpdate | null)[] = []\n\tpublic cacheIdx = -1\n\tpublic cacheUpdateNumber = -1\n\n\tpublic constructor(values?: Iterable<P>, cacheLimit = 0) {\n\t\tsuper(values)\n\t\tif (values instanceof SetRTX) {\n\t\t\tthis.parent = values\n\t\t\tthis.cacheUpdateNumber = values.cacheUpdateNumber\n\t\t}\n\t\tif (cacheLimit) {\n\t\t\tthis.cacheLimit = cacheLimit\n\t\t\tthis.cache = new Array(cacheLimit)\n\t\t\tthis.subscribe(`auto cache`, (update) => {\n\t\t\t\tthis.cacheIdx++\n\t\t\t\tthis.cacheIdx %= this.cacheLimit\n\t\t\t\tthis.cache[this.cacheIdx] = update\n\t\t\t})\n\t\t}\n\t}\n\n\tpublic readonly READONLY_VIEW: SetRTXView<P> = this\n\n\tpublic toJSON(): SetRTXJson<P> {\n\t\treturn {\n\t\t\tmembers: [...this],\n\t\t\tcache: this.cache,\n\t\t\tcacheLimit: this.cacheLimit,\n\t\t\tcacheIdx: this.cacheIdx,\n\t\t\tcacheUpdateNumber: this.cacheUpdateNumber,\n\t\t}\n\t}\n\n\tpublic static fromJSON<P extends primitive>(json: SetRTXJson<P>): SetRTX<P> {\n\t\tconst set = new SetRTX<P>(json.members, json.cacheLimit)\n\t\tset.cache = json.cache\n\t\tset.cacheIdx = json.cacheIdx\n\t\tset.cacheUpdateNumber = json.cacheUpdateNumber\n\t\treturn set\n\t}\n\n\tpublic add(value: P): this {\n\t\tconst result = super.add(value)\n\t\tif (this.mode === `record`) {\n\t\t\tthis.cacheUpdateNumber++\n\t\t\tthis.emit(`add:${stringifyJson<P>(value)}`)\n\t\t}\n\t\treturn result\n\t}\n\n\tpublic clear(): void {\n\t\tconst capturedContents = this.mode === `record` ? [...this] : null\n\t\tsuper.clear()\n\t\tif (capturedContents) {\n\t\t\tthis.cacheUpdateNumber++\n\t\t\tthis.emit(`clear:${JSON.stringify(capturedContents)}`)\n\t\t}\n\t}\n\n\tpublic delete(value: P): boolean {\n\t\tconst result = super.delete(value)\n\t\tif (this.mode === `record`) {\n\t\t\tthis.cacheUpdateNumber++\n\t\t\tthis.emit(`del:${stringifyJson<P>(value)}`)\n\t\t}\n\t\treturn result\n\t}\n\n\tpublic readonly parent: SetRTX<P> | null = null\n\tpublic child: SetRTX<P> | null = null\n\tpublic transactionUpdates: SetUpdate[] | null = null\n\tpublic transaction(run: (child: SetRTX<P>) => boolean): void {\n\t\tthis.mode = `transaction`\n\t\tthis.transactionUpdates = []\n\t\tthis.child = new SetRTX(this)\n\t\tconst unsubscribe = this.child._subscribe(`transaction`, (update) => {\n\t\t\tthis.transactionUpdates?.push(update)\n\t\t})\n\t\ttry {\n\t\t\tconst shouldCommit = run(this.child)\n\t\t\tif (shouldCommit) {\n\t\t\t\tfor (const update of this.transactionUpdates) {\n\t\t\t\t\tthis.doStep(update)\n\t\t\t\t}\n\t\t\t\tthis.cacheUpdateNumber++\n\t\t\t\tthis.emit(`tx:${this.transactionUpdates.join(`;`)}`)\n\t\t\t}\n\t\t} catch (thrown) {\n\t\t\t/* eslint-disable-next-line no-console */\n\t\t\tconsole.warn(\n\t\t\t\t`Did not apply transaction to SetRTX; this error was thrown:`,\n\t\t\t\tthrown,\n\t\t\t)\n\t\t\tthrow thrown\n\t\t} finally {\n\t\t\tunsubscribe()\n\t\t\tthis.child = null\n\t\t\tthis.transactionUpdates = null\n\t\t\tthis.mode = `record`\n\t\t}\n\t}\n\n\tprotected _subscribe(\n\t\tkey: string,\n\t\tfn: (update: SetUpdate) => void,\n\t): () => void {\n\t\treturn this.subject.subscribe(key, fn)\n\t}\n\tpublic subscribe(\n\t\tkey: string,\n\t\tfn: (update: NumberedSetUpdate) => void,\n\t): () => void {\n\t\treturn this.subject.subscribe(key, (update) => {\n\t\t\tfn(`${this.cacheUpdateNumber}=${update}`)\n\t\t})\n\t}\n\n\tpublic emit(update: SetUpdate): void {\n\t\tthis.subject.next(update)\n\t}\n\n\tprivate doStep(update: SetUpdate): void {\n\t\tconst typeValueBreak = update.indexOf(`:`)\n\t\tconst type = update.substring(0, typeValueBreak) as SetUpdateType\n\t\tconst value = update.substring(typeValueBreak + 1)\n\t\tswitch (type) {\n\t\t\tcase `add`:\n\t\t\t\tthis.add(JSON.parse(value))\n\t\t\t\tbreak\n\t\t\tcase `clear`:\n\t\t\t\tthis.clear()\n\t\t\t\tbreak\n\t\t\tcase `del`:\n\t\t\t\tthis.delete(JSON.parse(value))\n\t\t\t\tbreak\n\t\t\tcase `tx`:\n\t\t\t\tfor (const subUpdate of value.split(`;`)) {\n\t\t\t\t\tthis.doStep(subUpdate as SetUpdate)\n\t\t\t\t}\n\t\t}\n\t}\n\n\tpublic getUpdateNumber(update: NumberedSetUpdate): number {\n\t\tconst breakpoint = update.indexOf(`=`)\n\t\treturn Number(update.substring(0, breakpoint))\n\t}\n\n\tpublic do(update: NumberedSetUpdate): number | `OUT_OF_RANGE` | null {\n\t\tconst breakpoint = update.indexOf(`=`)\n\t\tconst updateNumber = Number(update.substring(0, breakpoint))\n\t\tconst eventOffset = updateNumber - this.cacheUpdateNumber\n\t\tconst isFuture = eventOffset > 0\n\t\tif (isFuture) {\n\t\t\tif (eventOffset === 1) {\n\t\t\t\tthis.mode = `playback`\n\t\t\t\tconst innerUpdate = update.substring(breakpoint + 1) as SetUpdate\n\t\t\t\tthis.doStep(innerUpdate)\n\t\t\t\tthis.mode = `record`\n\t\t\t\tthis.cacheUpdateNumber = updateNumber\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.cacheUpdateNumber + 1\n\t\t}\n\t\tif (Math.abs(eventOffset) < this.cacheLimit) {\n\t\t\tconst eventIdx = this.cacheIdx + eventOffset\n\t\t\tconst cachedUpdate = this.cache[eventIdx]\n\t\t\tif (cachedUpdate === update) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tthis.mode = `playback`\n\t\t\tlet done = false\n\t\t\twhile (!done) {\n\t\t\t\tthis.cacheIdx %= this.cacheLimit\n\t\t\t\tconst u = this.cache[this.cacheIdx]\n\t\t\t\tthis.cacheIdx--\n\t\t\t\tif (!u) {\n\t\t\t\t\treturn `OUT_OF_RANGE`\n\t\t\t\t}\n\t\t\t\tthis.undo(u)\n\t\t\t\tdone = this.cacheIdx === eventIdx - 1\n\t\t\t}\n\t\t\tconst innerUpdate = update.substring(breakpoint + 1) as SetUpdate\n\t\t\tthis.doStep(innerUpdate)\n\t\t\tthis.mode = `record`\n\t\t\tthis.cacheUpdateNumber = updateNumber\n\t\t\treturn null\n\t\t}\n\t\treturn `OUT_OF_RANGE`\n\t}\n\n\tpublic undoStep(update: SetUpdate): void {\n\t\tconst breakpoint = update.indexOf(`:`)\n\t\tconst type = update.substring(0, breakpoint) as SetUpdateType\n\t\tconst value = update.substring(breakpoint + 1)\n\t\tswitch (type) {\n\t\t\tcase `add`:\n\t\t\t\tthis.delete(JSON.parse(value))\n\t\t\t\tbreak\n\t\t\tcase `del`:\n\t\t\t\tthis.add(JSON.parse(value))\n\t\t\t\tbreak\n\t\t\tcase `clear`: {\n\t\t\t\tconst values = JSON.parse(value) as P[]\n\t\t\t\tfor (const v of values) this.add(v)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase `tx`: {\n\t\t\t\tconst updates = value.split(`;`) as SetUpdate[]\n\t\t\t\tfor (let i = updates.length - 1; i >= 0; i--) {\n\t\t\t\t\tthis.undoStep(updates[i])\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic undo(update: NumberedSetUpdate): number | null {\n\t\tconst breakpoint = update.indexOf(`=`)\n\t\tconst updateNumber = Number(update.substring(0, breakpoint))\n\t\tif (updateNumber === this.cacheUpdateNumber) {\n\t\t\tthis.mode = `playback`\n\t\t\tconst innerUpdate = update.substring(breakpoint + 1) as SetUpdate\n\t\t\tthis.undoStep(innerUpdate)\n\t\t\tthis.mode = `record`\n\t\t\tthis.cacheUpdateNumber--\n\t\t\treturn null\n\t\t}\n\t\treturn this.cacheUpdateNumber\n\t}\n}\n"],"mappings":";;;;AAuBA,IAAa,SAAb,MAAa,eACJ,IAIT;CACC,AAAO,OAAwB;CAC/B,AAAgB,UAA8B,IAAI,SAAoB;CACtE,AAAO,aAAa;CACpB,AAAO,QAAsC,EAAE;CAC/C,AAAO,WAAW;CAClB,AAAO,oBAAoB;CAE3B,AAAO,YAAY,QAAsB,aAAa,GAAG;AACxD,QAAM,OAAO;AACb,MAAI,kBAAkB,QAAQ;AAC7B,QAAK,SAAS;AACd,QAAK,oBAAoB,OAAO;;AAEjC,MAAI,YAAY;AACf,QAAK,aAAa;AAClB,QAAK,QAAQ,IAAI,MAAM,WAAW;AAClC,QAAK,UAAU,eAAe,WAAW;AACxC,SAAK;AACL,SAAK,YAAY,KAAK;AACtB,SAAK,MAAM,KAAK,YAAY;KAC3B;;;CAIJ,AAAgB,gBAA+B;CAE/C,AAAO,SAAwB;AAC9B,SAAO;GACN,SAAS,CAAC,GAAG,KAAK;GAClB,OAAO,KAAK;GACZ,YAAY,KAAK;GACjB,UAAU,KAAK;GACf,mBAAmB,KAAK;GACxB;;CAGF,OAAc,SAA8B,MAAgC;EAC3E,MAAM,MAAM,IAAI,OAAU,KAAK,SAAS,KAAK,WAAW;AACxD,MAAI,QAAQ,KAAK;AACjB,MAAI,WAAW,KAAK;AACpB,MAAI,oBAAoB,KAAK;AAC7B,SAAO;;CAGR,AAAO,IAAI,OAAgB;EAC1B,MAAM,SAAS,MAAM,IAAI,MAAM;AAC/B,MAAI,KAAK,SAAS,UAAU;AAC3B,QAAK;AACL,QAAK,KAAK,OAAO,cAAiB,MAAM,GAAG;;AAE5C,SAAO;;CAGR,AAAO,QAAc;EACpB,MAAM,mBAAmB,KAAK,SAAS,WAAW,CAAC,GAAG,KAAK,GAAG;AAC9D,QAAM,OAAO;AACb,MAAI,kBAAkB;AACrB,QAAK;AACL,QAAK,KAAK,SAAS,KAAK,UAAU,iBAAiB,GAAG;;;CAIxD,AAAO,OAAO,OAAmB;EAChC,MAAM,SAAS,MAAM,OAAO,MAAM;AAClC,MAAI,KAAK,SAAS,UAAU;AAC3B,QAAK;AACL,QAAK,KAAK,OAAO,cAAiB,MAAM,GAAG;;AAE5C,SAAO;;CAGR,AAAgB,SAA2B;CAC3C,AAAO,QAA0B;CACjC,AAAO,qBAAyC;CAChD,AAAO,YAAY,KAA0C;AAC5D,OAAK,OAAO;AACZ,OAAK,qBAAqB,EAAE;AAC5B,OAAK,QAAQ,IAAI,OAAO,KAAK;EAC7B,MAAM,cAAc,KAAK,MAAM,WAAW,gBAAgB,WAAW;AACpE,QAAK,oBAAoB,KAAK,OAAO;IACpC;AACF,MAAI;AAEH,OADqB,IAAI,KAAK,MAAM,EAClB;AACjB,SAAK,MAAM,UAAU,KAAK,mBACzB,MAAK,OAAO,OAAO;AAEpB,SAAK;AACL,SAAK,KAAK,MAAM,KAAK,mBAAmB,KAAK,IAAI,GAAG;;WAE7C,QAAQ;AAEhB,WAAQ,KACP,+DACA,OACA;AACD,SAAM;YACG;AACT,gBAAa;AACb,QAAK,QAAQ;AACb,QAAK,qBAAqB;AAC1B,QAAK,OAAO;;;CAId,AAAU,WACT,KACA,IACa;AACb,SAAO,KAAK,QAAQ,UAAU,KAAK,GAAG;;CAEvC,AAAO,UACN,KACA,IACa;AACb,SAAO,KAAK,QAAQ,UAAU,MAAM,WAAW;AAC9C,MAAG,GAAG,KAAK,kBAAkB,GAAG,SAAS;IACxC;;CAGH,AAAO,KAAK,QAAyB;AACpC,OAAK,QAAQ,KAAK,OAAO;;CAG1B,AAAQ,OAAO,QAAyB;EACvC,MAAM,iBAAiB,OAAO,QAAQ,IAAI;EAC1C,MAAM,OAAO,OAAO,UAAU,GAAG,eAAe;EAChD,MAAM,QAAQ,OAAO,UAAU,iBAAiB,EAAE;AAClD,UAAQ,MAAR;GACC,KAAK;AACJ,SAAK,IAAI,KAAK,MAAM,MAAM,CAAC;AAC3B;GACD,KAAK;AACJ,SAAK,OAAO;AACZ;GACD,KAAK;AACJ,SAAK,OAAO,KAAK,MAAM,MAAM,CAAC;AAC9B;GACD,KAAK,KACJ,MAAK,MAAM,aAAa,MAAM,MAAM,IAAI,CACvC,MAAK,OAAO,UAAuB;;;CAKvC,AAAO,gBAAgB,QAAmC;EACzD,MAAM,aAAa,OAAO,QAAQ,IAAI;AACtC,SAAO,OAAO,OAAO,UAAU,GAAG,WAAW,CAAC;;CAG/C,AAAO,GAAG,QAA2D;EACpE,MAAM,aAAa,OAAO,QAAQ,IAAI;EACtC,MAAM,eAAe,OAAO,OAAO,UAAU,GAAG,WAAW,CAAC;EAC5D,MAAM,cAAc,eAAe,KAAK;AAExC,MADiB,cAAc,GACjB;AACb,OAAI,gBAAgB,GAAG;AACtB,SAAK,OAAO;IACZ,MAAM,cAAc,OAAO,UAAU,aAAa,EAAE;AACpD,SAAK,OAAO,YAAY;AACxB,SAAK,OAAO;AACZ,SAAK,oBAAoB;AACzB,WAAO;;AAER,UAAO,KAAK,oBAAoB;;AAEjC,MAAI,KAAK,IAAI,YAAY,GAAG,KAAK,YAAY;GAC5C,MAAM,WAAW,KAAK,WAAW;AAEjC,OADqB,KAAK,MAAM,cACX,OACpB,QAAO;AAER,QAAK,OAAO;GACZ,IAAI,OAAO;AACX,UAAO,CAAC,MAAM;AACb,SAAK,YAAY,KAAK;IACtB,MAAM,IAAI,KAAK,MAAM,KAAK;AAC1B,SAAK;AACL,QAAI,CAAC,EACJ,QAAO;AAER,SAAK,KAAK,EAAE;AACZ,WAAO,KAAK,aAAa,WAAW;;GAErC,MAAM,cAAc,OAAO,UAAU,aAAa,EAAE;AACpD,QAAK,OAAO,YAAY;AACxB,QAAK,OAAO;AACZ,QAAK,oBAAoB;AACzB,UAAO;;AAER,SAAO;;CAGR,AAAO,SAAS,QAAyB;EACxC,MAAM,aAAa,OAAO,QAAQ,IAAI;EACtC,MAAM,OAAO,OAAO,UAAU,GAAG,WAAW;EAC5C,MAAM,QAAQ,OAAO,UAAU,aAAa,EAAE;AAC9C,UAAQ,MAAR;GACC,KAAK;AACJ,SAAK,OAAO,KAAK,MAAM,MAAM,CAAC;AAC9B;GACD,KAAK;AACJ,SAAK,IAAI,KAAK,MAAM,MAAM,CAAC;AAC3B;GACD,KAAK,SAAS;IACb,MAAM,SAAS,KAAK,MAAM,MAAM;AAChC,SAAK,MAAM,KAAK,OAAQ,MAAK,IAAI,EAAE;AACnC;;GAED,KAAK,MAAM;IACV,MAAM,UAAU,MAAM,MAAM,IAAI;AAChC,SAAK,IAAI,IAAI,QAAQ,SAAS,GAAG,KAAK,GAAG,IACxC,MAAK,SAAS,QAAQ,GAAG;;;;CAM7B,AAAO,KAAK,QAA0C;EACrD,MAAM,aAAa,OAAO,QAAQ,IAAI;AAEtC,MADqB,OAAO,OAAO,UAAU,GAAG,WAAW,CAAC,KACvC,KAAK,mBAAmB;AAC5C,QAAK,OAAO;GACZ,MAAM,cAAc,OAAO,UAAU,aAAa,EAAE;AACpD,QAAK,SAAS,YAAY;AAC1B,QAAK,OAAO;AACZ,QAAK;AACL,UAAO;;AAER,SAAO,KAAK"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/web/storage-sync.ts"],"sourcesContent":["import type { AtomEffect, ViewOf } from \"atom.io\"\n\nexport type StringInterface<T> = {\n\tstringify: (t: ViewOf<T>) => string\n\tparse: (s: string) => T\n}\n\nexport const storageSync =\n\t<T>(\n\t\tstorage: Storage | undefined,\n\t\t{ stringify, parse }: StringInterface<T>,\n\t\tkey: string,\n\t): AtomEffect<T> =>\n\t({ setSelf, onSet }) => {\n\t\tif (!storage) {\n\t\t\treturn\n\t\t}\n\t\tconst savedValue = storage.getItem(key)\n\t\tif (savedValue != null) setSelf(parse(savedValue))\n\n\t\tonSet(({ newValue }) => {\n\t\t\tif (newValue == null) {\n\t\t\t\tstorage.removeItem(key)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tstorage.setItem(key, stringify(newValue))\n\t\t})\n\t}\n"],"mappings":";AAOA,MAAa,eAEX,SACA,EAAE,WAAW,SACb,SAEA,EAAE,SAAS,YAAY;AACvB,KAAI,CAAC,QACJ;CAED,MAAM,aAAa,QAAQ,QAAQ;AACnC,KAAI,cAAc,KAAM,SAAQ,MAAM;AAEtC,QAAO,EAAE,eAAe;AACvB,MAAI,YAAY,MAAM;AACrB,WAAQ,WAAW;AACnB;;AAED,UAAQ,QAAQ,KAAK,UAAU"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/web/storage-sync.ts"],"sourcesContent":["import type { AtomEffect, ViewOf } from \"atom.io\"\n\nexport type StringInterface<T> = {\n\tstringify: (t: ViewOf<T>) => string\n\tparse: (s: string) => T\n}\n\nexport const storageSync =\n\t<T>(\n\t\tstorage: Storage | undefined,\n\t\t{ stringify, parse }: StringInterface<T>,\n\t\tkey: string,\n\t): AtomEffect<T> =>\n\t({ setSelf, onSet }) => {\n\t\tif (!storage) {\n\t\t\treturn\n\t\t}\n\t\tconst savedValue = storage.getItem(key)\n\t\tif (savedValue != null) setSelf(parse(savedValue))\n\n\t\tonSet(({ newValue }) => {\n\t\t\tif (newValue == null) {\n\t\t\t\tstorage.removeItem(key)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tstorage.setItem(key, stringify(newValue))\n\t\t})\n\t}\n"],"mappings":";AAOA,MAAa,eAEX,SACA,EAAE,WAAW,SACb,SAEA,EAAE,SAAS,YAAY;AACvB,KAAI,CAAC,QACJ;CAED,MAAM,aAAa,QAAQ,QAAQ,IAAI;AACvC,KAAI,cAAc,KAAM,SAAQ,MAAM,WAAW,CAAC;AAElD,QAAO,EAAE,eAAe;AACvB,MAAI,YAAY,MAAM;AACrB,WAAQ,WAAW,IAAI;AACvB;;AAED,UAAQ,QAAQ,KAAK,UAAU,SAAS,CAAC;GACxC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atom.io",
3
- "version": "0.40.7",
3
+ "version": "0.40.8",
4
4
  "description": "Composable and testable reactive data library.",
5
5
  "homepage": "https://atom.io.fyi",
6
6
  "sideEffects": false,
@@ -61,9 +61,9 @@
61
61
  },
62
62
  "devDependencies": {
63
63
  "@eslint/core": "0.15.2",
64
- "@storybook/addon-docs": "9.1.4",
65
- "@storybook/addon-onboarding": "9.1.4",
66
- "@storybook/react-vite": "9.1.4",
64
+ "@storybook/addon-docs": "9.1.5",
65
+ "@storybook/addon-onboarding": "9.1.5",
66
+ "@storybook/react-vite": "9.1.5",
67
67
  "@testing-library/react": "16.3.0",
68
68
  "@types/bun": "npm:bun-types@1.2.21",
69
69
  "@types/eslint": "9.6.1",
@@ -72,9 +72,9 @@
72
72
  "@types/npmlog": "7.0.0",
73
73
  "@types/react": "19.1.12",
74
74
  "@types/tmp": "0.2.6",
75
- "@typescript-eslint/parser": "8.42.0",
76
- "@typescript-eslint/rule-tester": "8.42.0",
77
- "@typescript-eslint/utils": "8.42.0",
75
+ "@typescript-eslint/parser": "8.43.0",
76
+ "@typescript-eslint/rule-tester": "8.43.0",
77
+ "@typescript-eslint/utils": "8.43.0",
78
78
  "@vitest/coverage-v8": "3.2.4",
79
79
  "@vitest/ui": "3.2.4",
80
80
  "concurrently": "9.2.1",
@@ -94,15 +94,15 @@
94
94
  "recoverage": "0.1.11",
95
95
  "socket.io": "4.8.1",
96
96
  "socket.io-client": "4.8.1",
97
- "storybook": "9.1.4",
97
+ "storybook": "9.1.5",
98
98
  "tmp": "0.2.5",
99
- "tsdown": "0.14.2",
99
+ "tsdown": "0.15.0",
100
100
  "typescript": "5.9.2",
101
- "vite": "7.1.4",
101
+ "vite": "7.1.5",
102
102
  "vite-tsconfig-paths": "5.1.4",
103
103
  "vitest": "3.2.4",
104
104
  "zod": "3.25.76",
105
- "break-check": "0.6.15"
105
+ "break-check": "0.6.16"
106
106
  },
107
107
  "main": "./dist/main/index.js",
108
108
  "files": [
@@ -1,9 +1,15 @@
1
- import type { FamilyMetadata, MutableAtomToken, RegularAtomToken } from "atom.io"
1
+ import type {
2
+ FamilyMetadata,
3
+ MutableAtomToken,
4
+ RegularAtomToken,
5
+ StateUpdate,
6
+ } from "atom.io"
2
7
 
3
8
  import { createRegularAtom } from "../atom"
4
9
  import { getFromStore } from "../get-state"
5
10
  import { newest } from "../lineage"
6
11
  import { setIntoStore } from "../set-state"
12
+ import { JOIN_OP, operateOnStore } from "../set-state/operate-on-store"
7
13
  import type { Store } from "../store"
8
14
  import { subscribeToState, subscribeToTimeline } from "../subscribe"
9
15
  import { isChildStore } from "../transaction/is-root-store"
@@ -61,9 +67,9 @@ export class Tracker<T extends Transceiver<any, any, any>> {
61
67
  const storeStatus = isChildStore(target)
62
68
  ? target.transactionMeta.update.token.key
63
69
  : `main`
64
- const subscriptionKey = `tracker:${storeName}:${storeStatus}:${stateKey}`
70
+ const subscriptionKey = `tracker-from-core:${storeName}:${storeStatus}:${stateKey}`
65
71
  const trackerCapturesOutboundSignal = (update: SignalFrom<T>) => {
66
- setIntoStore(target, latestSignalState, update)
72
+ operateOnStore(JOIN_OP, target, latestSignalState, update)
67
73
  }
68
74
  const originalInnerValue = getFromStore(target, mutableState)
69
75
  this.unsubscribeFromInnerValue = originalInnerValue.subscribe(
@@ -91,60 +97,69 @@ export class Tracker<T extends Transceiver<any, any, any>> {
91
97
  latestSignalState: RegularAtomToken<SignalFrom<T> | null>,
92
98
  target: Store,
93
99
  ): void {
94
- const subscriptionKey = `tracker:${target.config.name}:${
95
- isChildStore(target) ? target.transactionMeta.update.token.key : `main`
96
- }:${mutableState.key}`
100
+ const stateKey = mutableState.key
101
+ const storeName = target.config.name
102
+ const storeStatus = isChildStore(target)
103
+ ? target.transactionMeta.update.token.key
104
+ : `main`
105
+ const subscriptionKey = `tracker-to-core:${storeName}:${storeStatus}:${stateKey}`
97
106
  subscribeToState(
98
107
  target,
99
108
  latestSignalState,
100
109
  subscriptionKey,
101
- function trackerCapturesInboundSignal({ newValue, oldValue }) {
102
- const timelineId = target.timelineTopics.getRelatedKey(
103
- latestSignalState.key,
104
- )
105
-
106
- if (timelineId && target.timelines.get(timelineId)?.timeTraveling) {
107
- const unsubscribe = subscribeToTimeline(
108
- target,
109
- { key: timelineId, type: `timeline` },
110
- subscriptionKey,
111
- function trackerWaitsForTimeTravelToFinish(update) {
112
- unsubscribe()
113
- setIntoStore(target, mutableState, (transceiver) => {
114
- if (update === `redo` && newValue) {
115
- transceiver.do(newValue)
116
- } else if (update === `undo` && oldValue) {
117
- transceiver.undo(oldValue)
118
- }
119
- return transceiver
120
- })
121
- },
110
+ Object.assign(
111
+ function trackerCapturesInboundSignal({
112
+ newValue,
113
+ oldValue,
114
+ }: StateUpdate<SignalFrom<T> | null>) {
115
+ const timelineId = target.timelineTopics.getRelatedKey(
116
+ latestSignalState.key,
122
117
  )
123
- return
124
- }
125
118
 
126
- const mutable = getFromStore(target, mutableState)
127
- const updateNumber = mutable.getUpdateNumber(newValue)
128
- const eventOffset = updateNumber - mutable.cacheUpdateNumber
129
- if (newValue && eventOffset === 1) {
130
- setIntoStore(
131
- target,
132
- mutableState,
133
- (transceiver) => (transceiver.do(newValue), transceiver),
134
- )
135
- } else {
136
- const expected = mutable.cacheUpdateNumber + 1
137
- target.logger.info(
138
- `❌`,
139
- `mutable_atom`,
140
- mutableState.key,
141
- `could not be updated. Expected update number`,
142
- expected,
143
- `but got`,
144
- updateNumber,
145
- )
146
- }
147
- },
119
+ if (timelineId && target.timelines.get(timelineId)?.timeTraveling) {
120
+ const unsubscribe = subscribeToTimeline(
121
+ target,
122
+ { key: timelineId, type: `timeline` },
123
+ subscriptionKey,
124
+ function trackerWaitsForTimeTravelToFinish(update) {
125
+ unsubscribe()
126
+ setIntoStore(target, mutableState, (transceiver) => {
127
+ if (update === `redo` && newValue) {
128
+ transceiver.do(newValue)
129
+ } else if (update === `undo` && oldValue) {
130
+ transceiver.undo(oldValue)
131
+ }
132
+ return transceiver
133
+ })
134
+ },
135
+ )
136
+ return
137
+ }
138
+
139
+ const mutable = getFromStore(target, mutableState)
140
+ const updateNumber = mutable.getUpdateNumber(newValue)
141
+ const eventOffset = updateNumber - mutable.cacheUpdateNumber
142
+ if (newValue && eventOffset === 1) {
143
+ setIntoStore(
144
+ target,
145
+ mutableState,
146
+ (transceiver) => (transceiver.do(newValue), transceiver),
147
+ )
148
+ } else {
149
+ const expected = mutable.cacheUpdateNumber + 1
150
+ target.logger.info(
151
+ `❌`,
152
+ `mutable_atom`,
153
+ mutableState.key,
154
+ `could not be updated. Expected update number`,
155
+ expected,
156
+ `but got`,
157
+ updateNumber,
158
+ )
159
+ }
160
+ },
161
+ { inboundTracker: true },
162
+ ),
148
163
  )
149
164
  }
150
165
 
@@ -1,5 +1,6 @@
1
1
  import type { ReadableToken, StateUpdate, UpdateHandler } from "atom.io"
2
2
 
3
+ import { hasRole } from "../atom"
3
4
  import { readOrComputeValue } from "../get-state"
4
5
  import { reduceReference } from "../get-state/reduce-reference"
5
6
  import { traceRootSelectorAtoms } from "../selector"
@@ -15,6 +16,14 @@ export function subscribeToState<T, E>(
15
16
  ): () => void {
16
17
  function safelyHandleUpdate(update: StateUpdate<any>): void {
17
18
  if (store.operation.open) {
19
+ if (
20
+ state?.type === `atom` &&
21
+ hasRole(state, `tracker:signal`) &&
22
+ `*` + store.operation.token.key === token.key &&
23
+ `inboundTracker` in handleUpdate
24
+ ) {
25
+ return
26
+ }
18
27
  const unsubscribe = store.on.operationClose.subscribe(
19
28
  `state subscription ${key}`,
20
29
  () => {
@@ -7,5 +7,4 @@ export * from "./pull-selector"
7
7
  export * from "./pull-selector-family-member"
8
8
  export * from "./push-state"
9
9
  export * from "./realtime-client-stores"
10
- export * from "./server-action"
11
10
  export * from "./sync-continuity"
@@ -7,6 +7,5 @@ export * from "./use-pull-selector"
7
7
  export * from "./use-pull-selector-family-member"
8
8
  export * from "./use-push"
9
9
  export * from "./use-realtime-service"
10
- export * from "./use-server-action"
11
10
  export * from "./use-single-effect"
12
11
  export * from "./use-sync-continuity"
@@ -7,14 +7,6 @@ import { atomFamily } from "atom.io"
7
7
 
8
8
  import type { UserKey } from "../realtime-server-stores/server-user-store"
9
9
 
10
- // export const completeUpdateAtoms = atomFamily<
11
- // TransactionUpdate<any> | null,
12
- // string
13
- // >({
14
- // key: `completeUpdate`,
15
- // default: null,
16
- // })
17
-
18
10
  export function redactTransactionUpdateContent(
19
11
  visibleStateKeys: string[],
20
12
  updates: TransactionSubEvent[],
@@ -67,29 +59,12 @@ export const redactorAtoms: RegularAtomFamilyToken<
67
59
  key: `redactor`,
68
60
  default: { occlude: (updates) => updates },
69
61
  })
70
- // export const redactedUpdateSelectors = selectorFamily<
71
- // TransactionUpdate<any> | null,
72
- // [transactionKey: string, updateId: string]
73
- // >({
74
- // key: `redactedUpdate`,
75
- // get:
76
- // ([transactionKey, updateId]) =>
77
- // ({ get, find }) => {
78
- // const update = get(find(completeUpdateAtoms, updateId))
79
- // const { filter } = get(find(transactionRedactorAtoms, transactionKey))
80
-
81
- // if (update && filter) {
82
- // return { ...update, updates: filter(update.updates) }
83
- // }
84
- // return null
85
- // },
86
- // })
87
62
 
88
63
  export type ContinuitySyncTransactionUpdate = Pick<
89
64
  TransactionOutcomeEvent<any>,
90
65
  `epoch` | `id` | `output` | `subEvents` | `token`
91
66
  >
92
- export const userUnacknowledgedQueues: RegularAtomFamilyToken<
67
+ export const unacknowledgedUpdatesAtoms: RegularAtomFamilyToken<
93
68
  ContinuitySyncTransactionUpdate[],
94
69
  UserKey
95
70
  > = atomFamily<ContinuitySyncTransactionUpdate[], UserKey>({
@@ -0,0 +1,50 @@
1
+ import { getFromStore, IMPLICIT } from "atom.io/internal"
2
+ import type { Json } from "atom.io/json"
3
+ import type { ContinuityToken } from "atom.io/realtime"
4
+
5
+ import type { ServerConfig, UserKey } from ".."
6
+ import { unacknowledgedUpdatesAtoms } from "./continuity-store"
7
+ import { provideOutcomes } from "./provide-outcomes"
8
+ import { providePerspectives } from "./provide-perspectives"
9
+ import { provideStartupPayloads } from "./provide-startup-payloads"
10
+ import { receiveActionRequests } from "./receive-action-requests"
11
+ import { trackAcknowledgements } from "./track-acknowledgements"
12
+
13
+ export type ProvideContinuity = (
14
+ continuity: ContinuityToken,
15
+ userKey: UserKey,
16
+ ) => () => void
17
+ export function prepareToProvideContinuity({
18
+ socket,
19
+ store = IMPLICIT.STORE,
20
+ }: ServerConfig): ProvideContinuity {
21
+ return function syncRealtimeContinuity(continuity, userKey) {
22
+ const continuityKey = continuity.key
23
+
24
+ const unacknowledgedUpdates = getFromStore(
25
+ store,
26
+ unacknowledgedUpdatesAtoms,
27
+ userKey,
28
+ )
29
+ for (const unacknowledgedUpdate of unacknowledgedUpdates) {
30
+ socket.emit(
31
+ `tx-new:${continuityKey}`,
32
+ unacknowledgedUpdate as Json.Serializable,
33
+ )
34
+ }
35
+
36
+ const subscriptions = new Set<() => void>()
37
+ const clearSubscriptions = () => {
38
+ for (const unsubscribe of subscriptions) unsubscribe()
39
+ subscriptions.clear()
40
+ }
41
+
42
+ subscriptions.add(providePerspectives(store, socket, continuity, userKey))
43
+ subscriptions.add(provideOutcomes(store, socket, continuity, userKey))
44
+ subscriptions.add(provideStartupPayloads(store, socket, continuity, userKey))
45
+ subscriptions.add(receiveActionRequests(store, socket, continuity, userKey))
46
+ subscriptions.add(trackAcknowledgements(store, socket, continuity, userKey))
47
+
48
+ return clearSubscriptions
49
+ }
50
+ }
@@ -12,24 +12,24 @@ import type { ContinuityToken } from "atom.io/realtime"
12
12
  import type { Socket, UserKey } from ".."
13
13
  import {
14
14
  redactTransactionUpdateContent,
15
- userUnacknowledgedQueues,
15
+ unacknowledgedUpdatesAtoms,
16
16
  } from "./continuity-store"
17
17
 
18
- export function subscribeToContinuityActions(
18
+ export function provideOutcomes(
19
19
  store: Store,
20
+ socket: Socket,
20
21
  continuity: ContinuityToken,
21
22
  userKey: UserKey,
22
- socket: Socket | null,
23
- ): (() => void)[] {
23
+ ): () => void {
24
24
  const continuityKey = continuity.key
25
- const unsubscribeFunctions: (() => void)[] = []
25
+ const unsubscribeFunctions = new Set<() => void>()
26
26
 
27
27
  for (const transaction of continuity.actions) {
28
28
  const unsubscribeFromTransaction = subscribeToTransaction(
29
29
  store,
30
30
  transaction,
31
31
  `sync-continuity:${continuityKey}:${userKey}`,
32
- (update) => {
32
+ (outcomes) => {
33
33
  try {
34
34
  const visibleKeys = continuity.globals
35
35
  .map((atom) => {
@@ -59,13 +59,13 @@ export function subscribeToContinuityActions(
59
59
  )
60
60
  const redactedUpdates = redactTransactionUpdateContent(
61
61
  visibleKeys,
62
- update.subEvents,
62
+ outcomes.subEvents,
63
63
  )
64
64
  const redactedUpdate = {
65
- ...update,
65
+ ...outcomes,
66
66
  updates: redactedUpdates,
67
67
  }
68
- setIntoStore(store, userUnacknowledgedQueues, userKey, (updates) => {
68
+ setIntoStore(store, unacknowledgedUpdatesAtoms, userKey, (updates) => {
69
69
  if (redactedUpdate) {
70
70
  updates.push(redactedUpdate)
71
71
  updates.sort((a, b) => a.epoch - b.epoch)
@@ -81,7 +81,7 @@ export function subscribeToContinuityActions(
81
81
  return updates
82
82
  })
83
83
 
84
- socket?.emit(
84
+ socket.emit(
85
85
  `tx-new:${continuityKey}`,
86
86
  redactedUpdate as Json.Serializable,
87
87
  )
@@ -98,7 +98,9 @@ export function subscribeToContinuityActions(
98
98
  }
99
99
  },
100
100
  )
101
- unsubscribeFunctions.push(unsubscribeFromTransaction)
101
+ unsubscribeFunctions.add(unsubscribeFromTransaction)
102
+ }
103
+ return () => {
104
+ for (const unsubscribe of unsubscribeFunctions) unsubscribe()
102
105
  }
103
- return unsubscribeFunctions
104
106
  }
@@ -10,14 +10,14 @@ import type { ContinuityToken } from "atom.io/realtime"
10
10
  import type { Socket } from ".."
11
11
  import type { UserKey } from "../realtime-server-stores"
12
12
 
13
- export function subscribeToContinuityPerspectives(
13
+ export function providePerspectives(
14
14
  store: Store,
15
+ socket: Socket,
15
16
  continuity: ContinuityToken,
16
17
  userKey: UserKey,
17
- socket: Socket | null,
18
- ): (() => void)[] {
18
+ ): () => void {
19
19
  const continuityKey = continuity.key
20
- const unsubFns: (() => void)[] = []
20
+ const unsubFns = new Set<() => void>()
21
21
  for (const perspective of continuity.perspectives) {
22
22
  const { viewAtoms } = perspective
23
23
  const userViewState = findInStore(store, viewAtoms, userKey)
@@ -47,14 +47,16 @@ export function subscribeToContinuityPerspectives(
47
47
  { oldKeys, newKeys, revealed, concealed },
48
48
  )
49
49
  if (revealed.length > 0) {
50
- socket?.emit(`reveal:${continuityKey}`, revealed)
50
+ socket.emit(`reveal:${continuityKey}`, revealed)
51
51
  }
52
52
  if (concealed && concealed.length > 0) {
53
- socket?.emit(`conceal:${continuityKey}`, concealed)
53
+ socket.emit(`conceal:${continuityKey}`, concealed)
54
54
  }
55
55
  },
56
56
  )
57
- unsubFns.push(unsubscribeFromUserView)
57
+ unsubFns.add(unsubscribeFromUserView)
58
+ }
59
+ return () => {
60
+ for (const unsubscribe of unsubFns) unsubscribe()
58
61
  }
59
- return unsubFns
60
62
  }
@@ -9,15 +9,16 @@ import type { Json } from "atom.io/json"
9
9
  import type { ContinuityToken } from "atom.io/realtime"
10
10
 
11
11
  import type { Socket, UserKey } from ".."
12
+ import { employSocket } from "../employ-socket"
12
13
 
13
- export function prepareToSendInitialPayload(
14
+ export function provideStartupPayloads(
14
15
  store: Store,
16
+ socket: Socket,
15
17
  continuity: ContinuityToken,
16
18
  userKey: UserKey,
17
- socket: Socket | null,
18
19
  ): () => void {
19
20
  const continuityKey = continuity.key
20
- return function sendInitialPayload(): void {
21
+ function sendInitialPayload(): void {
21
22
  const initialPayload: Json.Serializable[] = []
22
23
  for (const atom of continuity.globals) {
23
24
  const resourceToken =
@@ -49,6 +50,7 @@ export function prepareToSendInitialPayload(
49
50
  ? (store.transactionMeta.epoch.get(continuityKey) ?? null)
50
51
  : null
51
52
 
52
- socket?.emit(`continuity-init:${continuityKey}`, epoch, initialPayload)
53
+ socket.emit(`continuity-init:${continuityKey}`, epoch, initialPayload)
53
54
  }
55
+ return employSocket(socket, `get:${continuityKey}`, sendInitialPayload)
54
56
  }
@@ -0,0 +1,68 @@
1
+ import type { TransactionOutcomeEvent, TransactionToken } from "atom.io"
2
+ import type { Store } from "atom.io/internal"
3
+ import { actUponStore } from "atom.io/internal"
4
+ import type { Json, JsonIO } from "atom.io/json"
5
+ import type { ContinuityToken } from "atom.io/realtime"
6
+
7
+ import { employSocket } from "../employ-socket"
8
+ import type { Socket } from "../socket-interface"
9
+
10
+ export function receiveActionRequests(
11
+ store: Store,
12
+ socket: Socket,
13
+ continuity: ContinuityToken,
14
+ userKey: string,
15
+ ): () => void {
16
+ const continuityKey = continuity.key
17
+
18
+ return employSocket(
19
+ socket,
20
+ `tx-run:${continuityKey}`,
21
+ function serveTransactionRequest(
22
+ txOutcome: Json.Serializable &
23
+ Pick<
24
+ TransactionOutcomeEvent<TransactionToken<JsonIO>>,
25
+ `id` | `params` | `token`
26
+ >,
27
+ ) {
28
+ store.logger.info(`🛎️`, `continuity`, continuityKey, `received`, txOutcome)
29
+ const transactionKey = txOutcome.token.key
30
+ const updateId = txOutcome.id
31
+ const performanceKey = `tx-run:${transactionKey}:${updateId}`
32
+ const performanceKeyStart = `${performanceKey}:start`
33
+ const performanceKeyEnd = `${performanceKey}:end`
34
+ performance.mark(performanceKeyStart)
35
+ try {
36
+ actUponStore(
37
+ store,
38
+ { type: `transaction`, key: transactionKey },
39
+ updateId,
40
+ )(...txOutcome.params)
41
+ } catch (thrown) {
42
+ if (thrown instanceof Error) {
43
+ store.logger.error(
44
+ `❌`,
45
+ `continuity`,
46
+ continuityKey,
47
+ `failed to run transaction ${transactionKey} from ${userKey} with update ${updateId}`,
48
+ thrown.message,
49
+ )
50
+ }
51
+ }
52
+ performance.mark(performanceKeyEnd)
53
+ const metric = performance.measure(
54
+ performanceKey,
55
+ performanceKeyStart,
56
+ performanceKeyEnd,
57
+ )
58
+ store.logger.info(
59
+ `🚀`,
60
+ `transaction`,
61
+ transactionKey,
62
+ updateId,
63
+ userKey,
64
+ metric.duration,
65
+ )
66
+ },
67
+ )
68
+ }
@@ -1,19 +1,25 @@
1
1
  import type { Store } from "atom.io/internal"
2
- import { setIntoStore } from "atom.io/internal"
2
+ import { getFromStore, setIntoStore } from "atom.io/internal"
3
3
  import type { ContinuityToken } from "atom.io/realtime"
4
4
 
5
+ import { employSocket } from "../employ-socket"
5
6
  import type { UserKey } from "../realtime-server-stores"
6
- import type { ContinuitySyncTransactionUpdate } from "./continuity-store"
7
- import { userUnacknowledgedQueues } from "./continuity-store"
7
+ import type { Socket } from "../socket-interface"
8
+ import { unacknowledgedUpdatesAtoms } from "./continuity-store"
8
9
 
9
- export function prepareToTrackClientAcknowledgement(
10
+ export function trackAcknowledgements(
10
11
  store: Store,
12
+ socket: Socket,
11
13
  continuity: ContinuityToken,
12
14
  userKey: UserKey,
13
- userUnacknowledgedUpdates: ContinuitySyncTransactionUpdate[],
14
- ): (epoch: number) => void {
15
+ ): () => void {
15
16
  const continuityKey = continuity.key
16
- return function trackClientAcknowledgement(epoch) {
17
+ const userUnacknowledgedUpdates = getFromStore(
18
+ store,
19
+ unacknowledgedUpdatesAtoms,
20
+ userKey,
21
+ )
22
+ function trackClientAcknowledgement(epoch: number): void {
17
23
  store.logger.info(
18
24
  `👍`,
19
25
  `continuity`,
@@ -22,7 +28,7 @@ export function prepareToTrackClientAcknowledgement(
22
28
  )
23
29
  const isUnacknowledged = userUnacknowledgedUpdates[0]?.epoch === epoch
24
30
  if (isUnacknowledged) {
25
- setIntoStore(store, userUnacknowledgedQueues, userKey, (updates) => {
31
+ setIntoStore(store, unacknowledgedUpdatesAtoms, userKey, (updates) => {
26
32
  updates.shift()
27
33
  store.logger.info(
28
34
  `👍`,
@@ -36,4 +42,5 @@ export function prepareToTrackClientAcknowledgement(
36
42
  })
37
43
  }
38
44
  }
45
+ return employSocket(socket, `ack:${continuityKey}`, trackClientAcknowledgement)
39
46
  }