atom.io 0.17.0 → 0.18.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.
- package/data/dist/index.cjs +62 -40
- package/data/dist/index.cjs.map +1 -1
- package/data/dist/index.d.ts +8 -2
- package/data/dist/index.js +64 -42
- package/data/dist/index.js.map +1 -1
- package/data/src/dict.ts +8 -4
- package/data/src/join.ts +74 -33
- package/data/src/struct-family.ts +18 -17
- package/dist/chunk-IZHOMSXA.js +331 -0
- package/dist/chunk-IZHOMSXA.js.map +1 -0
- package/dist/chunk-JDUNWJFB.js +18 -0
- package/dist/chunk-JDUNWJFB.js.map +1 -0
- package/dist/index.cjs +4 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +66 -51
- package/dist/index.js +5 -11
- package/dist/index.js.map +1 -1
- package/internal/dist/index.cjs +187 -58
- package/internal/dist/index.cjs.map +1 -1
- package/internal/dist/index.d.ts +95 -71
- package/internal/dist/index.js +179 -53
- package/internal/dist/index.js.map +1 -1
- package/internal/src/arbitrary.ts +3 -0
- package/internal/src/atom/delete-atom.ts +7 -6
- package/internal/src/caching.ts +6 -4
- package/internal/src/families/find-in-store.ts +16 -0
- package/internal/src/get-environment-data.ts +4 -7
- package/internal/src/index.ts +6 -5
- package/internal/src/ingest-updates/ingest-atom-update.ts +6 -2
- package/internal/src/ingest-updates/ingest-transaction-update.ts +0 -1
- package/internal/src/selector/create-standalone-selector.ts +0 -2
- package/internal/src/set-state/copy-mutable-if-needed.ts +5 -0
- package/internal/src/set-state/emit-update.ts +25 -11
- package/internal/src/set-state/set-atom.ts +15 -18
- package/internal/src/store/store.ts +14 -2
- package/internal/src/store/withdraw.ts +72 -2
- package/internal/src/subscribe/subscribe-to-timeline.ts +2 -2
- package/internal/src/subscribe/subscribe-to-transaction.ts +2 -2
- package/internal/src/timeline/create-timeline.ts +12 -1
- package/internal/src/transaction/act-upon-store.ts +19 -0
- package/internal/src/transaction/apply-transaction.ts +6 -1
- package/internal/src/transaction/assign-transaction-to-continuity.ts +18 -0
- package/internal/src/transaction/build-transaction.ts +7 -6
- package/internal/src/transaction/create-transaction.ts +1 -1
- package/internal/src/transaction/get-epoch-number.ts +40 -0
- package/internal/src/transaction/index.ts +10 -1
- package/internal/src/transaction/set-epoch-number.ts +30 -0
- package/introspection/dist/index.cjs.map +1 -1
- package/introspection/dist/index.d.ts +3 -3
- package/introspection/dist/index.js.map +1 -1
- package/introspection/src/attach-introspection-states.ts +6 -2
- package/introspection/src/attach-timeline-family.ts +5 -2
- package/introspection/src/attach-transaction-logs.ts +2 -2
- package/json/dist/index.d.ts +3 -1
- package/json/src/index.ts +4 -0
- package/package.json +241 -230
- package/react/dist/index.cjs.map +1 -1
- package/react/dist/index.d.ts +1 -1
- package/react/dist/index.js.map +1 -1
- package/react/src/use-json.ts +1 -1
- package/react-devtools/dist/index.cjs +131 -134
- package/react-devtools/dist/index.cjs.map +1 -1
- package/react-devtools/dist/index.css +2 -2
- package/react-devtools/dist/index.css.map +1 -1
- package/react-devtools/dist/index.d.ts +3 -3
- package/react-devtools/dist/index.js +91 -108
- package/react-devtools/dist/index.js.map +1 -1
- package/react-devtools/src/StateEditor.tsx +4 -4
- package/react-devtools/src/StateIndex.tsx +1 -4
- package/react-devtools/src/TimelineIndex.tsx +3 -3
- package/react-devtools/src/TransactionIndex.tsx +9 -8
- package/react-devtools/src/index.ts +2 -2
- package/realtime/dist/index.cjs +120 -0
- package/realtime/dist/index.cjs.map +1 -0
- package/realtime/dist/index.d.ts +146 -0
- package/realtime/dist/index.js +111 -0
- package/realtime/dist/index.js.map +1 -0
- package/realtime/package.json +16 -0
- package/realtime/src/index.ts +2 -0
- package/realtime/src/realtime-continuity.ts +162 -0
- package/realtime/src/shared-room-store.ts +48 -0
- package/realtime-client/dist/index.cjs +424 -170
- package/realtime-client/dist/index.cjs.map +1 -1
- package/realtime-client/dist/index.d.ts +15 -11
- package/realtime-client/dist/index.js +96 -177
- package/realtime-client/dist/index.js.map +1 -1
- package/realtime-client/src/index.ts +8 -7
- package/realtime-client/src/{pull-family-member.ts → pull-atom-family-member.ts} +2 -2
- package/realtime-client/src/{pull-state.ts → pull-atom.ts} +2 -2
- package/realtime-client/src/{pull-mutable-family-member.ts → pull-mutable-atom-family-member.ts} +6 -6
- package/realtime-client/src/{pull-mutable.ts → pull-mutable-atom.ts} +1 -1
- package/realtime-client/src/pull-selector-family-member.ts +42 -0
- package/realtime-client/src/pull-selector.ts +38 -0
- package/realtime-client/src/realtime-client-stores/client-main-store.ts +12 -2
- package/realtime-client/src/realtime-client-stores/client-sync-store.ts +7 -7
- package/realtime-client/src/sync-continuity.ts +368 -0
- package/realtime-react/dist/index.cjs +367 -27
- package/realtime-react/dist/index.cjs.map +1 -1
- package/realtime-react/dist/index.d.ts +24 -8
- package/realtime-react/dist/index.js +38 -22
- package/realtime-react/dist/index.js.map +1 -1
- package/realtime-react/src/index.ts +6 -5
- package/realtime-react/src/use-pull-atom-family-member.ts +21 -0
- package/realtime-react/src/{use-sync.ts → use-pull-atom.ts} +4 -4
- package/realtime-react/src/{use-pull-mutable.ts → use-pull-mutable-atom.ts} +4 -3
- package/realtime-react/src/use-pull-mutable-family-member.ts +9 -4
- package/realtime-react/src/use-pull-selector-family-member.ts +21 -0
- package/realtime-react/src/{use-pull.ts → use-pull-selector.ts} +7 -5
- package/realtime-react/src/use-push.ts +3 -2
- package/realtime-react/src/use-server-action.ts +3 -2
- package/realtime-react/src/use-sync-continuity.ts +12 -0
- package/realtime-server/dist/index.cjs +769 -371
- package/realtime-server/dist/index.cjs.map +1 -1
- package/realtime-server/dist/index.d.ts +130 -60
- package/realtime-server/dist/index.js +753 -361
- package/realtime-server/dist/index.js.map +1 -1
- package/realtime-server/src/index.ts +17 -3
- package/realtime-server/src/ipc-sockets/child-socket.ts +135 -0
- package/realtime-server/src/ipc-sockets/custom-socket.ts +90 -0
- package/realtime-server/src/ipc-sockets/index.ts +3 -0
- package/realtime-server/src/ipc-sockets/parent-socket.ts +185 -0
- package/realtime-server/src/realtime-action-receiver.ts +8 -5
- package/realtime-server/src/realtime-continuity-synchronizer.ts +376 -0
- package/realtime-server/src/realtime-family-provider.ts +30 -71
- package/realtime-server/src/realtime-mutable-family-provider.ts +24 -86
- package/realtime-server/src/realtime-server-stores/index.ts +4 -1
- package/realtime-server/src/realtime-server-stores/realtime-continuity-store.ts +109 -0
- package/realtime-server/src/realtime-server-stores/server-room-external-actions.ts +64 -0
- package/realtime-server/src/realtime-server-stores/server-room-external-store.ts +42 -0
- package/realtime-server/src/realtime-server-stores/server-sync-store.ts +51 -98
- package/realtime-server/src/realtime-server-stores/server-user-store.ts +14 -29
- package/realtime-server/src/realtime-state-receiver.ts +0 -1
- package/realtime-testing/dist/index.cjs +34 -32
- package/realtime-testing/dist/index.cjs.map +1 -1
- package/realtime-testing/dist/index.d.ts +1 -0
- package/realtime-testing/dist/index.js +33 -31
- package/realtime-testing/dist/index.js.map +1 -1
- package/realtime-testing/src/setup-realtime-test.tsx +44 -32
- package/src/atom.ts +49 -31
- package/src/logger.ts +14 -5
- package/src/selector.ts +44 -25
- package/src/subscribe.ts +2 -1
- package/src/timeline.ts +4 -4
- package/src/transaction.ts +13 -17
- package/src/validators.ts +15 -9
- package/dist/chunk-H4Q5FTPZ.js +0 -11
- package/dist/chunk-H4Q5FTPZ.js.map +0 -1
- package/internal/src/set-state/copy-mutable-in-transaction.ts +0 -19
- package/realtime-client/src/sync-server-action.ts +0 -170
- package/realtime-client/src/sync-state.ts +0 -19
- package/realtime-react/src/use-pull-family-member.ts +0 -16
- package/realtime-react/src/use-sync-server-action.ts +0 -16
- package/realtime-server/src/realtime-action-synchronizer.ts +0 -152
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { IMPLICIT, Subject } from "atom.io/internal"
|
|
2
|
+
import { parseJson, stringifyJson } from "atom.io/json"
|
|
3
|
+
import type { Json } from "atom.io/json"
|
|
4
|
+
|
|
5
|
+
import { SetRTX } from "atom.io/transceivers/set-rtx"
|
|
6
|
+
import { CustomSocket } from "./custom-socket"
|
|
7
|
+
import type { EventBuffer, Events } from "./custom-socket"
|
|
8
|
+
|
|
9
|
+
export class SubjectSocket<
|
|
10
|
+
I extends Events,
|
|
11
|
+
O extends Events,
|
|
12
|
+
> extends CustomSocket<I, O> {
|
|
13
|
+
public in: Subject<[string, ...Json.Serializable[]]>
|
|
14
|
+
public out: Subject<[string, ...Json.Serializable[]]>
|
|
15
|
+
public id = `no_id_retrieved`
|
|
16
|
+
public disposalFunctions: (() => void)[] = []
|
|
17
|
+
|
|
18
|
+
public constructor(id: string) {
|
|
19
|
+
super((...args) => {
|
|
20
|
+
this.out.next(args as any)
|
|
21
|
+
return this
|
|
22
|
+
})
|
|
23
|
+
this.id = id
|
|
24
|
+
this.in = new Subject()
|
|
25
|
+
this.out = new Subject()
|
|
26
|
+
this.in.subscribe(`socket`, (event) => {
|
|
27
|
+
this.handleEvent(...(event as [string, ...I[keyof I]]))
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public dispose(): void {
|
|
32
|
+
for (const dispose of this.disposalFunctions) {
|
|
33
|
+
dispose()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class ParentSocket<
|
|
39
|
+
I extends Events & {
|
|
40
|
+
[id in string as `user:${id}`]: [string, ...Json.Serializable[]]
|
|
41
|
+
} & {
|
|
42
|
+
/* eslint-disable quotes */
|
|
43
|
+
"user-joins": [string]
|
|
44
|
+
"user-leaves": [string]
|
|
45
|
+
/* eslint-enable quotes */
|
|
46
|
+
},
|
|
47
|
+
O extends Events & {
|
|
48
|
+
[id in string as `relay:${id}`]: [string, ...Json.Serializable[]]
|
|
49
|
+
},
|
|
50
|
+
> extends CustomSocket<I, O> {
|
|
51
|
+
protected incompleteData = ``
|
|
52
|
+
protected unprocessedEvents: string[] = []
|
|
53
|
+
protected relays: Map<string, SubjectSocket<any, any>>
|
|
54
|
+
protected relayServices: ((
|
|
55
|
+
socket: SubjectSocket<any, any>,
|
|
56
|
+
) => (() => void) | void)[]
|
|
57
|
+
protected process: NodeJS.Process
|
|
58
|
+
|
|
59
|
+
public id = `#####`
|
|
60
|
+
|
|
61
|
+
protected log(...args: any[]): void {
|
|
62
|
+
this.process.stderr.write(
|
|
63
|
+
stringifyJson(
|
|
64
|
+
args.map((arg) =>
|
|
65
|
+
arg instanceof SetRTX
|
|
66
|
+
? `{ ${arg.toJSON().members.join(` | `)} }`
|
|
67
|
+
: arg,
|
|
68
|
+
),
|
|
69
|
+
) + `\x03`,
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
public logger = {
|
|
73
|
+
info: (...args: any[]): void => this.log(`i`, ...args),
|
|
74
|
+
warn: (...args: any[]): void => this.log(`w`, ...args),
|
|
75
|
+
error: (...args: any[]): void => this.log(`e`, ...args),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public constructor() {
|
|
79
|
+
super((event, ...args) => {
|
|
80
|
+
const stringifiedEvent = JSON.stringify([event, ...args])
|
|
81
|
+
this.process.stdout.write(stringifiedEvent + `\x03`)
|
|
82
|
+
return this
|
|
83
|
+
})
|
|
84
|
+
this.process = process
|
|
85
|
+
this.process.stdin.resume()
|
|
86
|
+
this.relays = new Map()
|
|
87
|
+
this.relayServices = []
|
|
88
|
+
|
|
89
|
+
this.process.stdin.on(
|
|
90
|
+
`data`,
|
|
91
|
+
<Event extends keyof I>(buffer: EventBuffer<string, I[Event]>) => {
|
|
92
|
+
const chunk = buffer.toString()
|
|
93
|
+
this.unprocessedEvents.push(...chunk.split(`\x03`))
|
|
94
|
+
const newInput = this.unprocessedEvents.shift()
|
|
95
|
+
this.incompleteData += newInput || ``
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const parsedEvent = parseJson(this.incompleteData)
|
|
99
|
+
this.logger.info(`🎰`, `received`, parsedEvent)
|
|
100
|
+
this.handleEvent(...(parsedEvent as [string, ...I[keyof I]]))
|
|
101
|
+
while (this.unprocessedEvents.length > 0) {
|
|
102
|
+
const event = this.unprocessedEvents.shift()
|
|
103
|
+
if (event) {
|
|
104
|
+
if (this.unprocessedEvents.length === 0) {
|
|
105
|
+
this.incompleteData = event
|
|
106
|
+
}
|
|
107
|
+
const parsedEvent = parseJson(event)
|
|
108
|
+
this.handleEvent(...(parsedEvent as [string, ...I[keyof I]]))
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
this.incompleteData = ``
|
|
112
|
+
} catch (thrown) {
|
|
113
|
+
if (thrown instanceof Error) {
|
|
114
|
+
this.logger.error(`❗`, thrown.message, thrown.cause, thrown.stack)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
this.on(`exit`, () => {
|
|
121
|
+
process.exit(0)
|
|
122
|
+
})
|
|
123
|
+
process.on(`exit`, () => {
|
|
124
|
+
this.logger.info(`🔥`, this.id, `exited`)
|
|
125
|
+
process.exit(0)
|
|
126
|
+
})
|
|
127
|
+
process.on(`end`, () => {
|
|
128
|
+
this.logger.info(`🔥`, this.id, `ended`)
|
|
129
|
+
process.exit(0)
|
|
130
|
+
})
|
|
131
|
+
process.on(`SIGTERM`, () => {
|
|
132
|
+
this.logger.error(`🔥`, this.id, `terminated`)
|
|
133
|
+
process.exit(0)
|
|
134
|
+
})
|
|
135
|
+
process.on(`SIGINT`, () => {
|
|
136
|
+
this.logger.error(`🔥`, this.id, `interrupted`)
|
|
137
|
+
process.exit(0)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
if (process.pid) {
|
|
141
|
+
this.id = process.pid?.toString()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.on(`user-joins`, (username) => {
|
|
145
|
+
this.logger.info(`👤`, `user`, username, `joined`)
|
|
146
|
+
const relay = new SubjectSocket(`user:${username}`)
|
|
147
|
+
this.relays.set(username, relay)
|
|
148
|
+
this.logger.info(
|
|
149
|
+
`🔗`,
|
|
150
|
+
`attaching services:`,
|
|
151
|
+
`[${[...this.relayServices.keys()].join(`, `)}]`,
|
|
152
|
+
)
|
|
153
|
+
for (const attachServices of this.relayServices) {
|
|
154
|
+
const cleanup = attachServices(relay)
|
|
155
|
+
if (cleanup) {
|
|
156
|
+
relay.disposalFunctions.push(cleanup)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
this.on(`user:${username}`, (...data) => {
|
|
160
|
+
relay.in.next(data)
|
|
161
|
+
})
|
|
162
|
+
relay.out.subscribe(`socket`, (data) => {
|
|
163
|
+
this.emit(...(data as [string, ...O[keyof O]]))
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
this.on(`user-leaves`, (username) => {
|
|
168
|
+
const relay = this.relays.get(username)
|
|
169
|
+
this.off(`relay:${username}`)
|
|
170
|
+
if (relay) {
|
|
171
|
+
relay.dispose()
|
|
172
|
+
this.relays.delete(username)
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
process.stdout.write(`✨`)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
public relay(
|
|
180
|
+
attachServices: (socket: SubjectSocket<any, any>) => (() => void) | void,
|
|
181
|
+
): void {
|
|
182
|
+
this.logger.info(`🔗`, `running relay method`)
|
|
183
|
+
this.relayServices.push(attachServices)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import * as AtomIO from "atom.io"
|
|
2
|
-
import { IMPLICIT } from "atom.io/internal"
|
|
1
|
+
import type * as AtomIO from "atom.io"
|
|
2
|
+
import { IMPLICIT, actUponStore } from "atom.io/internal"
|
|
3
|
+
import type { JsonIO } from "atom.io/json"
|
|
3
4
|
|
|
4
5
|
import type { ServerConfig } from "."
|
|
5
6
|
|
|
@@ -8,15 +9,17 @@ export function realtimeActionReceiver({
|
|
|
8
9
|
socket,
|
|
9
10
|
store = IMPLICIT.STORE,
|
|
10
11
|
}: ServerConfig) {
|
|
11
|
-
return function actionReceiver<ƒ extends
|
|
12
|
+
return function actionReceiver<ƒ extends JsonIO>(
|
|
12
13
|
tx: AtomIO.TransactionToken<ƒ>,
|
|
13
14
|
): () => void {
|
|
14
|
-
const fillTransactionRequest = (
|
|
15
|
+
const fillTransactionRequest = (
|
|
16
|
+
update: Pick<AtomIO.TransactionUpdate<ƒ>, `id` | `params`>,
|
|
17
|
+
) => {
|
|
15
18
|
const performanceKey = `tx-run:${tx.key}:${update.id}`
|
|
16
19
|
const performanceKeyStart = `${performanceKey}:start`
|
|
17
20
|
const performanceKeyEnd = `${performanceKey}:end`
|
|
18
21
|
performance.mark(performanceKeyStart)
|
|
19
|
-
|
|
22
|
+
actUponStore<ƒ>(tx, update.id, store)(...update.params)
|
|
20
23
|
performance.mark(performanceKeyEnd)
|
|
21
24
|
const metric = performance.measure(
|
|
22
25
|
performanceKey,
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import type * as AtomIO from "atom.io"
|
|
2
|
+
import {
|
|
3
|
+
IMPLICIT,
|
|
4
|
+
actUponStore,
|
|
5
|
+
findInStore,
|
|
6
|
+
getFromStore,
|
|
7
|
+
getJsonToken,
|
|
8
|
+
isRootStore,
|
|
9
|
+
subscribeToState,
|
|
10
|
+
subscribeToTransaction,
|
|
11
|
+
} from "atom.io/internal"
|
|
12
|
+
import type { Json, JsonIO } from "atom.io/json"
|
|
13
|
+
import type { ContinuityToken } from "atom.io/realtime"
|
|
14
|
+
|
|
15
|
+
import type { ServerConfig, Socket } from "."
|
|
16
|
+
import { socketAtoms, usersOfSockets } from "."
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
redactTransactionUpdateContent,
|
|
20
|
+
userUnacknowledgedQueues,
|
|
21
|
+
} from "./realtime-server-stores"
|
|
22
|
+
|
|
23
|
+
export type RealtimeContinuitySynchronizer = ReturnType<
|
|
24
|
+
typeof realtimeContinuitySynchronizer
|
|
25
|
+
>
|
|
26
|
+
export function realtimeContinuitySynchronizer({
|
|
27
|
+
socket: initialSocket,
|
|
28
|
+
store = IMPLICIT.STORE,
|
|
29
|
+
}: ServerConfig) {
|
|
30
|
+
return function synchronizer(continuity: ContinuityToken): () => void {
|
|
31
|
+
let socket: Socket | null = initialSocket
|
|
32
|
+
|
|
33
|
+
const continuityKey = continuity.key
|
|
34
|
+
const userKeyState = findInStore(
|
|
35
|
+
usersOfSockets.states.userKeyOfSocket,
|
|
36
|
+
socket.id,
|
|
37
|
+
store,
|
|
38
|
+
)
|
|
39
|
+
const userKey = getFromStore(userKeyState, store)
|
|
40
|
+
if (!userKey) {
|
|
41
|
+
store.logger.error(
|
|
42
|
+
`❌`,
|
|
43
|
+
`continuity`,
|
|
44
|
+
continuityKey,
|
|
45
|
+
`Tried to create a synchronizer for a socket (${socket.id}) that is not connected to a user.`,
|
|
46
|
+
)
|
|
47
|
+
return () => {}
|
|
48
|
+
}
|
|
49
|
+
const socketKeyState = findInStore(
|
|
50
|
+
usersOfSockets.states.socketKeyOfUser,
|
|
51
|
+
userKey,
|
|
52
|
+
store,
|
|
53
|
+
)
|
|
54
|
+
subscribeToState(
|
|
55
|
+
socketKeyState,
|
|
56
|
+
({ newValue: newSocketKey }) => {
|
|
57
|
+
store.logger.info(
|
|
58
|
+
`👋`,
|
|
59
|
+
`continuity`,
|
|
60
|
+
continuityKey,
|
|
61
|
+
`seeing ${userKey} on new socket ${newSocketKey}`,
|
|
62
|
+
)
|
|
63
|
+
if (newSocketKey === null) {
|
|
64
|
+
store.logger.error(
|
|
65
|
+
`❌`,
|
|
66
|
+
`continuity`,
|
|
67
|
+
continuityKey,
|
|
68
|
+
`Tried to create a synchronizer for a user (${userKey}) that is not connected to a socket.`,
|
|
69
|
+
)
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
const newSocketState = findInStore(socketAtoms, newSocketKey, store)
|
|
73
|
+
const newSocket = getFromStore(newSocketState, store)
|
|
74
|
+
socket = newSocket
|
|
75
|
+
},
|
|
76
|
+
`sync-continuity:${continuityKey}:${userKey}`,
|
|
77
|
+
store,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
const userUnacknowledgedQueue = findInStore(
|
|
81
|
+
userUnacknowledgedQueues,
|
|
82
|
+
userKey,
|
|
83
|
+
store,
|
|
84
|
+
)
|
|
85
|
+
const userUnacknowledgedUpdates = getFromStore(
|
|
86
|
+
userUnacknowledgedQueue,
|
|
87
|
+
store,
|
|
88
|
+
)
|
|
89
|
+
const unsubscribeFunctions: (() => void)[] = []
|
|
90
|
+
|
|
91
|
+
const revealPerspectives = (): (() => void) => {
|
|
92
|
+
const unsubscribeFunctions: (() => void)[] = []
|
|
93
|
+
for (const perspective of continuity.perspectives) {
|
|
94
|
+
const { viewAtoms } = perspective
|
|
95
|
+
const userViewState = findInStore(viewAtoms, userKey, store)
|
|
96
|
+
const unsubscribe = subscribeToState(
|
|
97
|
+
userViewState,
|
|
98
|
+
({ oldValue, newValue }) => {
|
|
99
|
+
const oldKeys = oldValue.map((token) => token.key)
|
|
100
|
+
const newKeys = newValue.map((token) => token.key)
|
|
101
|
+
const concealed = oldValue.filter(
|
|
102
|
+
(token) => !newKeys.includes(token.key),
|
|
103
|
+
)
|
|
104
|
+
const revealed = newValue
|
|
105
|
+
.filter((token) => !oldKeys.includes(token.key))
|
|
106
|
+
.flatMap((token) => {
|
|
107
|
+
const resourceToken =
|
|
108
|
+
token.type === `mutable_atom` ? getJsonToken(token) : token
|
|
109
|
+
const resource = getFromStore(resourceToken, store)
|
|
110
|
+
return [resourceToken, resource]
|
|
111
|
+
})
|
|
112
|
+
store.logger.info(
|
|
113
|
+
`👁`,
|
|
114
|
+
`atom`,
|
|
115
|
+
perspective.resourceAtoms.key,
|
|
116
|
+
`${userKey} has a new perspective`,
|
|
117
|
+
{ oldKeys, newKeys, revealed, concealed },
|
|
118
|
+
)
|
|
119
|
+
if (revealed.length > 0) {
|
|
120
|
+
socket?.emit(`reveal:${continuityKey}`, revealed)
|
|
121
|
+
}
|
|
122
|
+
if (concealed.length > 0) {
|
|
123
|
+
socket?.emit(`conceal:${continuityKey}`, concealed)
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
`sync-continuity:${continuityKey}:${userKey}:perspective:${perspective.resourceAtoms.key}`,
|
|
127
|
+
store,
|
|
128
|
+
)
|
|
129
|
+
unsubscribeFunctions.push(unsubscribe)
|
|
130
|
+
}
|
|
131
|
+
return () => {
|
|
132
|
+
for (const unsubscribe of unsubscribeFunctions) unsubscribe()
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const unsubscribeFromPerspectives = revealPerspectives()
|
|
136
|
+
|
|
137
|
+
const sendInitialPayload = () => {
|
|
138
|
+
const initialPayload: Json.Serializable[] = []
|
|
139
|
+
for (const atom of continuity.globals) {
|
|
140
|
+
const resourceToken =
|
|
141
|
+
atom.type === `mutable_atom` ? getJsonToken(atom) : atom
|
|
142
|
+
initialPayload.push(resourceToken, getFromStore(atom, store))
|
|
143
|
+
}
|
|
144
|
+
for (const perspective of continuity.perspectives) {
|
|
145
|
+
const { viewAtoms, resourceAtoms } = perspective
|
|
146
|
+
const userViewState = findInStore(viewAtoms, userKey, store)
|
|
147
|
+
const userView = getFromStore(userViewState, store)
|
|
148
|
+
store.logger.info(`👁`, `atom`, resourceAtoms.key, `${userKey} can see`, {
|
|
149
|
+
viewAtoms,
|
|
150
|
+
resourceAtoms,
|
|
151
|
+
userView,
|
|
152
|
+
})
|
|
153
|
+
for (const visibleToken of userView) {
|
|
154
|
+
const resourceToken =
|
|
155
|
+
visibleToken.type === `mutable_atom`
|
|
156
|
+
? getJsonToken(visibleToken)
|
|
157
|
+
: visibleToken
|
|
158
|
+
const resource = getFromStore(resourceToken, store)
|
|
159
|
+
|
|
160
|
+
initialPayload.push(resourceToken, resource)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const epoch = isRootStore(store)
|
|
165
|
+
? store.transactionMeta.epoch.get(continuityKey) ?? null
|
|
166
|
+
: null
|
|
167
|
+
|
|
168
|
+
socket?.emit(`continuity-init:${continuityKey}`, epoch, initialPayload)
|
|
169
|
+
|
|
170
|
+
for (const transaction of continuity.actions) {
|
|
171
|
+
const unsubscribeFromTransaction = subscribeToTransaction(
|
|
172
|
+
transaction,
|
|
173
|
+
(update) => {
|
|
174
|
+
try {
|
|
175
|
+
const visibleKeys = continuity.globals
|
|
176
|
+
.map((atom) => atom.key)
|
|
177
|
+
.concat(
|
|
178
|
+
continuity.perspectives.flatMap((perspective) => {
|
|
179
|
+
const { viewAtoms } = perspective
|
|
180
|
+
const userPerspectiveTokenState = findInStore(
|
|
181
|
+
viewAtoms,
|
|
182
|
+
userKey,
|
|
183
|
+
store,
|
|
184
|
+
)
|
|
185
|
+
const visibleTokens = getFromStore(
|
|
186
|
+
userPerspectiveTokenState,
|
|
187
|
+
store,
|
|
188
|
+
)
|
|
189
|
+
return visibleTokens.map((token) => {
|
|
190
|
+
const key =
|
|
191
|
+
token.type === `mutable_atom`
|
|
192
|
+
? `*` + token.key
|
|
193
|
+
: token.key
|
|
194
|
+
return key
|
|
195
|
+
})
|
|
196
|
+
}),
|
|
197
|
+
)
|
|
198
|
+
const redactedUpdates = redactTransactionUpdateContent(
|
|
199
|
+
visibleKeys,
|
|
200
|
+
update.updates,
|
|
201
|
+
)
|
|
202
|
+
const redactedUpdate = {
|
|
203
|
+
...update,
|
|
204
|
+
updates: redactedUpdates,
|
|
205
|
+
}
|
|
206
|
+
// setIntoStore(
|
|
207
|
+
// userUnacknowledgedQueue,
|
|
208
|
+
// (updates) => {
|
|
209
|
+
// if (redactedUpdate) {
|
|
210
|
+
// updates.push(redactedUpdate)
|
|
211
|
+
// updates.sort((a, b) => a.epoch - b.epoch)
|
|
212
|
+
// }
|
|
213
|
+
// return updates
|
|
214
|
+
// },
|
|
215
|
+
// store,
|
|
216
|
+
// )
|
|
217
|
+
|
|
218
|
+
socket?.emit(
|
|
219
|
+
`tx-new:${continuityKey}`,
|
|
220
|
+
redactedUpdate as Json.Serializable,
|
|
221
|
+
)
|
|
222
|
+
} catch (thrown) {
|
|
223
|
+
if (thrown instanceof Error) {
|
|
224
|
+
store.logger.error(
|
|
225
|
+
`❌`,
|
|
226
|
+
`continuity`,
|
|
227
|
+
continuityKey,
|
|
228
|
+
`failed to send update from transaction ${transaction.key} to ${userKey}`,
|
|
229
|
+
thrown.message,
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
`sync-continuity:${continuityKey}:${userKey}`,
|
|
235
|
+
store,
|
|
236
|
+
)
|
|
237
|
+
unsubscribeFunctions.push(unsubscribeFromTransaction)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
socket.off(`get:${continuityKey}`, sendInitialPayload)
|
|
241
|
+
socket.on(`get:${continuityKey}`, sendInitialPayload)
|
|
242
|
+
|
|
243
|
+
const fillTransactionRequest = (
|
|
244
|
+
update: Pick<AtomIO.TransactionUpdate<JsonIO>, `id` | `key` | `params`>,
|
|
245
|
+
) => {
|
|
246
|
+
store.logger.info(`🛎️`, `continuity`, continuityKey, `received`, update)
|
|
247
|
+
const transactionKey = update.key
|
|
248
|
+
const updateId = update.id
|
|
249
|
+
const performanceKey = `tx-run:${transactionKey}:${updateId}`
|
|
250
|
+
const performanceKeyStart = `${performanceKey}:start`
|
|
251
|
+
const performanceKeyEnd = `${performanceKey}:end`
|
|
252
|
+
performance.mark(performanceKeyStart)
|
|
253
|
+
try {
|
|
254
|
+
actUponStore(
|
|
255
|
+
{ type: `transaction`, key: transactionKey },
|
|
256
|
+
updateId,
|
|
257
|
+
store,
|
|
258
|
+
)(...update.params)
|
|
259
|
+
} catch (thrown) {
|
|
260
|
+
if (thrown instanceof Error) {
|
|
261
|
+
store.logger.error(
|
|
262
|
+
`❌`,
|
|
263
|
+
`continuity`,
|
|
264
|
+
continuityKey,
|
|
265
|
+
`failed to run transaction ${transactionKey} with update ${updateId}`,
|
|
266
|
+
thrown.message,
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
performance.mark(performanceKeyEnd)
|
|
271
|
+
const metric = performance.measure(
|
|
272
|
+
performanceKey,
|
|
273
|
+
performanceKeyStart,
|
|
274
|
+
performanceKeyEnd,
|
|
275
|
+
)
|
|
276
|
+
store?.logger.info(
|
|
277
|
+
`🚀`,
|
|
278
|
+
`transaction`,
|
|
279
|
+
transactionKey,
|
|
280
|
+
updateId,
|
|
281
|
+
metric.duration,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
const valuesOfCardsViewKey = `valuesOfCardsView("${userKey}")`
|
|
285
|
+
const rootsOfCardValueView =
|
|
286
|
+
store.selectorAtoms.getRelatedKeys(valuesOfCardsViewKey)
|
|
287
|
+
const myCardValueView = store.valueMap.get(valuesOfCardsViewKey)
|
|
288
|
+
|
|
289
|
+
store.logger.info(
|
|
290
|
+
`👁`,
|
|
291
|
+
`continuity`,
|
|
292
|
+
continuityKey,
|
|
293
|
+
`seeing ${userKey} card values`,
|
|
294
|
+
{
|
|
295
|
+
valuesOfCardsViewKey,
|
|
296
|
+
rootsOfCardValueView,
|
|
297
|
+
myCardValueView,
|
|
298
|
+
},
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
socket.off(`tx-run:${continuityKey}`, fillTransactionRequest)
|
|
302
|
+
socket.on(`tx-run:${continuityKey}`, fillTransactionRequest)
|
|
303
|
+
|
|
304
|
+
// let i = 0
|
|
305
|
+
// let n = 1
|
|
306
|
+
// let retryTimeout: NodeJS.Timeout | undefined
|
|
307
|
+
// const trackClientAcknowledgement = (epoch: number) => {
|
|
308
|
+
// store.logger.info(
|
|
309
|
+
// `👍`,
|
|
310
|
+
// `continuity`,
|
|
311
|
+
// continuityKey,
|
|
312
|
+
// `${userKey} acknowledged epoch ${epoch}`,
|
|
313
|
+
// )
|
|
314
|
+
// const isUnacknowledged = userUnacknowledgedUpdates[0]?.epoch === epoch
|
|
315
|
+
// if (isUnacknowledged) {
|
|
316
|
+
// setIntoStore(
|
|
317
|
+
// userUnacknowledgedQueue,
|
|
318
|
+
// (updates) => {
|
|
319
|
+
// updates.shift()
|
|
320
|
+
// return updates
|
|
321
|
+
// },
|
|
322
|
+
// store,
|
|
323
|
+
// )
|
|
324
|
+
// }
|
|
325
|
+
// }
|
|
326
|
+
// subscribeToState(
|
|
327
|
+
// userUnacknowledgedQueue,
|
|
328
|
+
// ({ newValue }) => {
|
|
329
|
+
// if (newValue.length === 0) {
|
|
330
|
+
// clearInterval(retryTimeout)
|
|
331
|
+
// socket?.off(`ack:${continuityKey}`, trackClientAcknowledgement)
|
|
332
|
+
// retryTimeout = undefined
|
|
333
|
+
// }
|
|
334
|
+
// if (newValue.length > 0) {
|
|
335
|
+
// if (retryTimeout) {
|
|
336
|
+
// return
|
|
337
|
+
// }
|
|
338
|
+
|
|
339
|
+
// socket?.on(`ack:${continuityKey}`, trackClientAcknowledgement)
|
|
340
|
+
|
|
341
|
+
// retryTimeout = setInterval(() => {
|
|
342
|
+
// i++
|
|
343
|
+
// if (i === n) {
|
|
344
|
+
// n += i
|
|
345
|
+
// const toEmit = newValue[0]
|
|
346
|
+
// if (!toEmit) return
|
|
347
|
+
// store.logger.info(
|
|
348
|
+
// `🔄`,
|
|
349
|
+
// `continuity`,
|
|
350
|
+
// continuityKey,
|
|
351
|
+
// `${store.config.name} retrying ${userKey}`,
|
|
352
|
+
// socket?.id,
|
|
353
|
+
// newValue,
|
|
354
|
+
// )
|
|
355
|
+
// socket?.emit(
|
|
356
|
+
// `tx-new:${continuityKey}`,
|
|
357
|
+
// toEmit as Json.Serializable,
|
|
358
|
+
// )
|
|
359
|
+
// }
|
|
360
|
+
// }, 250)
|
|
361
|
+
// }
|
|
362
|
+
// },
|
|
363
|
+
// `sync-continuity:${continuityKey}:${userKey}`,
|
|
364
|
+
// store,
|
|
365
|
+
// )
|
|
366
|
+
|
|
367
|
+
return () => {
|
|
368
|
+
// clearInterval(retryTimeout)
|
|
369
|
+
for (const unsubscribe of unsubscribeFunctions) unsubscribe()
|
|
370
|
+
// socket?.off(`ack:${continuityKey}`, trackClientAcknowledgement)
|
|
371
|
+
unsubscribeFromPerspectives()
|
|
372
|
+
socket?.off(`get:${continuityKey}`, sendInitialPayload)
|
|
373
|
+
socket?.off(`tx-run:${continuityKey}`, fillTransactionRequest)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|