atom.io 0.18.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/dist/{chunk-OEVFAUPE.js → chunk-IZHOMSXA.js} +53 -11
- 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.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js.map +1 -1
- package/internal/dist/index.cjs +64 -34
- package/internal/dist/index.cjs.map +1 -1
- package/internal/dist/index.d.ts +1 -1
- package/internal/dist/index.js +64 -34
- package/internal/dist/index.js.map +1 -1
- package/internal/src/atom/delete-atom.ts +7 -6
- package/internal/src/caching.ts +6 -6
- package/internal/src/ingest-updates/ingest-atom-update.ts +6 -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 +4 -3
- package/internal/src/transaction/apply-transaction.ts +0 -1
- package/internal/src/transaction/set-epoch-number.ts +0 -1
- package/json/src/index.ts +3 -3
- package/package.json +241 -241
- package/react-devtools/dist/index.cjs.map +1 -1
- package/react-devtools/dist/index.js +1 -15
- package/react-devtools/dist/index.js.map +1 -1
- package/react-devtools/src/StateEditor.tsx +6 -6
- package/react-devtools/src/StateIndex.tsx +2 -2
- package/react-devtools/src/Updates.tsx +1 -1
- package/react-devtools/src/index.ts +3 -3
- package/realtime/dist/index.cjs +50 -2
- package/realtime/dist/index.cjs.map +1 -1
- package/realtime/dist/index.d.ts +110 -3
- package/realtime/dist/index.js +47 -4
- package/realtime/dist/index.js.map +1 -1
- package/realtime/src/index.ts +1 -0
- package/realtime/src/realtime-continuity.ts +14 -4
- package/realtime/src/shared-room-store.ts +48 -0
- package/realtime-client/dist/index.cjs +113 -200
- package/realtime-client/dist/index.cjs.map +1 -1
- package/realtime-client/dist/index.d.ts +2 -5
- package/realtime-client/dist/index.js +17 -161
- package/realtime-client/dist/index.js.map +1 -1
- package/realtime-client/src/index.ts +0 -2
- package/realtime-client/src/pull-mutable-atom-family-member.ts +5 -5
- package/realtime-client/src/realtime-client-stores/client-main-store.ts +10 -0
- package/realtime-client/src/sync-continuity.ts +56 -9
- package/realtime-react/dist/index.cjs +51 -26
- package/realtime-react/dist/index.cjs.map +1 -1
- package/realtime-react/dist/index.d.ts +2 -6
- package/realtime-react/dist/index.js +2 -17
- package/realtime-react/dist/index.js.map +1 -1
- package/realtime-react/src/index.ts +0 -2
- package/realtime-server/dist/index.cjs +399 -327
- package/realtime-server/dist/index.cjs.map +1 -1
- package/realtime-server/dist/index.d.ts +55 -60
- package/realtime-server/dist/index.js +394 -319
- package/realtime-server/dist/index.js.map +1 -1
- package/realtime-server/src/index.ts +2 -4
- 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-continuity-synchronizer.ts +225 -96
- package/realtime-server/src/realtime-server-stores/index.ts +2 -1
- package/realtime-server/src/realtime-server-stores/realtime-continuity-store.ts +50 -31
- 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 +49 -26
- package/realtime-testing/dist/index.cjs +8 -6
- package/realtime-testing/dist/index.cjs.map +1 -1
- package/realtime-testing/dist/index.d.ts +1 -0
- package/realtime-testing/dist/index.js +7 -6
- package/realtime-testing/dist/index.js.map +1 -1
- package/realtime-testing/src/setup-realtime-test.tsx +8 -6
- package/src/logger.ts +5 -1
- package/dist/chunk-OEVFAUPE.js.map +0 -1
- package/realtime-client/src/sync-server-action.ts +0 -168
- package/realtime-client/src/sync-state.ts +0 -19
- package/realtime-react/src/use-sync-server-action.ts +0 -17
- package/realtime-react/src/use-sync.ts +0 -17
- package/realtime-server/src/ipc-socket.ts +0 -230
- package/realtime-server/src/realtime-action-synchronizer.ts +0 -164
- package/realtime-server/src/realtime-server-stores/server-room-store.ts +0 -97
|
@@ -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
|
+
}
|
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
actUponStore,
|
|
5
5
|
findInStore,
|
|
6
6
|
getFromStore,
|
|
7
|
+
getJsonToken,
|
|
7
8
|
isRootStore,
|
|
8
|
-
setIntoStore,
|
|
9
9
|
subscribeToState,
|
|
10
10
|
subscribeToTransaction,
|
|
11
11
|
} from "atom.io/internal"
|
|
@@ -14,9 +14,9 @@ import type { ContinuityToken } from "atom.io/realtime"
|
|
|
14
14
|
|
|
15
15
|
import type { ServerConfig, Socket } from "."
|
|
16
16
|
import { socketAtoms, usersOfSockets } from "."
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
import {
|
|
19
|
-
|
|
19
|
+
redactTransactionUpdateContent,
|
|
20
20
|
userUnacknowledgedQueues,
|
|
21
21
|
} from "./realtime-server-stores"
|
|
22
22
|
|
|
@@ -88,69 +88,148 @@ export function realtimeContinuitySynchronizer({
|
|
|
88
88
|
)
|
|
89
89
|
const unsubscribeFunctions: (() => void)[] = []
|
|
90
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
|
+
|
|
91
137
|
const sendInitialPayload = () => {
|
|
92
138
|
const initialPayload: Json.Serializable[] = []
|
|
93
139
|
for (const atom of continuity.globals) {
|
|
94
|
-
|
|
140
|
+
const resourceToken =
|
|
141
|
+
atom.type === `mutable_atom` ? getJsonToken(atom) : atom
|
|
142
|
+
initialPayload.push(resourceToken, getFromStore(atom, store))
|
|
95
143
|
}
|
|
96
|
-
for (const
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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)
|
|
106
161
|
}
|
|
107
162
|
}
|
|
108
163
|
|
|
109
164
|
const epoch = isRootStore(store)
|
|
110
165
|
? store.transactionMeta.epoch.get(continuityKey) ?? null
|
|
111
166
|
: null
|
|
167
|
+
|
|
112
168
|
socket?.emit(`continuity-init:${continuityKey}`, epoch, initialPayload)
|
|
113
169
|
|
|
114
170
|
for (const transaction of continuity.actions) {
|
|
115
171
|
const unsubscribeFromTransaction = subscribeToTransaction(
|
|
116
172
|
transaction,
|
|
117
173
|
(update) => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
+
// )
|
|
135
217
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
`tx-new:${continuityKey}`,
|
|
152
|
-
redactedUpdate as Json.Serializable,
|
|
153
|
-
)
|
|
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
|
+
}
|
|
154
233
|
},
|
|
155
234
|
`sync-continuity:${continuityKey}:${userKey}`,
|
|
156
235
|
store,
|
|
@@ -164,17 +243,30 @@ export function realtimeContinuitySynchronizer({
|
|
|
164
243
|
const fillTransactionRequest = (
|
|
165
244
|
update: Pick<AtomIO.TransactionUpdate<JsonIO>, `id` | `key` | `params`>,
|
|
166
245
|
) => {
|
|
246
|
+
store.logger.info(`🛎️`, `continuity`, continuityKey, `received`, update)
|
|
167
247
|
const transactionKey = update.key
|
|
168
248
|
const updateId = update.id
|
|
169
249
|
const performanceKey = `tx-run:${transactionKey}:${updateId}`
|
|
170
250
|
const performanceKeyStart = `${performanceKey}:start`
|
|
171
251
|
const performanceKeyEnd = `${performanceKey}:end`
|
|
172
252
|
performance.mark(performanceKeyStart)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
+
}
|
|
178
270
|
performance.mark(performanceKeyEnd)
|
|
179
271
|
const metric = performance.measure(
|
|
180
272
|
performanceKey,
|
|
@@ -188,58 +280,95 @@ export function realtimeContinuitySynchronizer({
|
|
|
188
280
|
updateId,
|
|
189
281
|
metric.duration,
|
|
190
282
|
)
|
|
191
|
-
}
|
|
192
|
-
socket.off(`tx-run:${continuityKey}`, fillTransactionRequest)
|
|
193
|
-
socket.on(`tx-run:${continuityKey}`, fillTransactionRequest)
|
|
194
283
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
store.logger.info(
|
|
200
|
-
`🔄`,
|
|
201
|
-
`continuity`,
|
|
202
|
-
continuityKey,
|
|
203
|
-
`${store.config.name} retrying ${userKey} (${i}/${next})`,
|
|
204
|
-
socket?.id,
|
|
205
|
-
userUnacknowledgedUpdates,
|
|
206
|
-
)
|
|
207
|
-
if (toEmit && i === next) {
|
|
208
|
-
socket?.emit(`tx-new:${continuityKey}`, toEmit as Json.Serializable)
|
|
209
|
-
next *= 2
|
|
210
|
-
}
|
|
284
|
+
const valuesOfCardsViewKey = `valuesOfCardsView("${userKey}")`
|
|
285
|
+
const rootsOfCardValueView =
|
|
286
|
+
store.selectorAtoms.getRelatedKeys(valuesOfCardsViewKey)
|
|
287
|
+
const myCardValueView = store.valueMap.get(valuesOfCardsViewKey)
|
|
211
288
|
|
|
212
|
-
i++
|
|
213
|
-
}, 250)
|
|
214
|
-
const trackClientAcknowledgement = (epoch: number) => {
|
|
215
289
|
store.logger.info(
|
|
216
|
-
|
|
290
|
+
`👁`,
|
|
217
291
|
`continuity`,
|
|
218
292
|
continuityKey,
|
|
219
|
-
|
|
293
|
+
`seeing ${userKey} card values`,
|
|
294
|
+
{
|
|
295
|
+
valuesOfCardsViewKey,
|
|
296
|
+
rootsOfCardValueView,
|
|
297
|
+
myCardValueView,
|
|
298
|
+
},
|
|
220
299
|
)
|
|
221
|
-
i = 1
|
|
222
|
-
next = 1
|
|
223
|
-
const isUnacknowledged = userUnacknowledgedUpdates[0]?.epoch === epoch
|
|
224
|
-
|
|
225
|
-
if (isUnacknowledged) {
|
|
226
|
-
setIntoStore(
|
|
227
|
-
userUnacknowledgedQueue,
|
|
228
|
-
(updates) => {
|
|
229
|
-
updates.shift()
|
|
230
|
-
return updates
|
|
231
|
-
},
|
|
232
|
-
store,
|
|
233
|
-
)
|
|
234
|
-
}
|
|
235
300
|
}
|
|
236
|
-
socket.off(`
|
|
237
|
-
socket.on(`
|
|
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
|
+
// )
|
|
238
366
|
|
|
239
367
|
return () => {
|
|
240
|
-
clearInterval(
|
|
368
|
+
// clearInterval(retryTimeout)
|
|
241
369
|
for (const unsubscribe of unsubscribeFunctions) unsubscribe()
|
|
242
|
-
socket?.off(`ack:${continuityKey}`, trackClientAcknowledgement)
|
|
370
|
+
// socket?.off(`ack:${continuityKey}`, trackClientAcknowledgement)
|
|
371
|
+
unsubscribeFromPerspectives()
|
|
243
372
|
socket?.off(`get:${continuityKey}`, sendInitialPayload)
|
|
244
373
|
socket?.off(`tx-run:${continuityKey}`, fillTransactionRequest)
|
|
245
374
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { selectorFamily } from "atom.io"
|
|
2
2
|
import type { TransactionUpdate } from "atom.io"
|
|
3
|
+
import { IMPLICIT, getJsonToken, getUpdateToken } from "atom.io/internal"
|
|
3
4
|
import type { JsonIO } from "atom.io/json"
|
|
4
5
|
import { SyncGroup } from "atom.io/realtime"
|
|
5
|
-
import { completeUpdateAtoms } from "atom.io/realtime-server"
|
|
6
|
+
// import { completeUpdateAtoms } from "atom.io/realtime-server"
|
|
6
7
|
|
|
7
8
|
const redactorAtoms = selectorFamily<
|
|
8
9
|
(update: TransactionUpdate<any>) => TransactionUpdate<any>,
|
|
@@ -20,14 +21,21 @@ const redactorAtoms = selectorFamily<
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const userPerspectiveTokens = syncGroup.perspectives.flatMap(
|
|
23
|
-
({
|
|
24
|
-
const userPerspectiveToken = find(
|
|
24
|
+
({ viewAtoms }) => {
|
|
25
|
+
const userPerspectiveToken = find(viewAtoms, userId)
|
|
25
26
|
const userPerspective = get(userPerspectiveToken)
|
|
26
|
-
const visibleTokens = [...userPerspective].map((
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
const visibleTokens = [...userPerspective].map((token) => {
|
|
28
|
+
return token.type === `mutable_atom`
|
|
29
|
+
? getUpdateToken(token).key
|
|
30
|
+
: token.key
|
|
29
31
|
})
|
|
30
|
-
|
|
32
|
+
IMPLICIT.STORE.logger.info(
|
|
33
|
+
`🔭`,
|
|
34
|
+
`continuity`,
|
|
35
|
+
syncGroupKey,
|
|
36
|
+
`${userId} can see ${visibleTokens.length} tokens in ${viewAtoms.key}`,
|
|
37
|
+
visibleTokens,
|
|
38
|
+
)
|
|
31
39
|
return visibleTokens
|
|
32
40
|
},
|
|
33
41
|
)
|
|
@@ -36,6 +44,14 @@ const redactorAtoms = selectorFamily<
|
|
|
36
44
|
visible: string[],
|
|
37
45
|
transactionUpdate: TransactionUpdate<any>,
|
|
38
46
|
): TransactionUpdate<any> => {
|
|
47
|
+
IMPLICIT.STORE.logger.info(
|
|
48
|
+
`🖌`,
|
|
49
|
+
`continuity`,
|
|
50
|
+
syncGroupKey,
|
|
51
|
+
`redacting updates from ${transactionUpdate.epoch}:${transactionUpdate.key}:${transactionUpdate.id}`,
|
|
52
|
+
visible,
|
|
53
|
+
transactionUpdate.updates,
|
|
54
|
+
)
|
|
39
55
|
const updates = transactionUpdate.updates
|
|
40
56
|
.filter((update) => {
|
|
41
57
|
if (`newValue` in update) {
|
|
@@ -57,8 +73,10 @@ const redactorAtoms = selectorFamily<
|
|
|
57
73
|
}
|
|
58
74
|
const filter: (updates: TransactionUpdate<any>) => TransactionUpdate<any> =
|
|
59
75
|
(update) => {
|
|
60
|
-
const visibleKeys: string[] = syncGroup.globals.map(
|
|
61
|
-
|
|
76
|
+
const visibleKeys: string[] = syncGroup.globals.map((atomToken) =>
|
|
77
|
+
atomToken.type === `mutable_atom`
|
|
78
|
+
? getUpdateToken(atomToken).key
|
|
79
|
+
: atomToken.key,
|
|
62
80
|
)
|
|
63
81
|
visibleKeys.push(...userPerspectiveTokens)
|
|
64
82
|
return filterTransactionUpdate(visibleKeys, update)
|
|
@@ -66,25 +84,26 @@ const redactorAtoms = selectorFamily<
|
|
|
66
84
|
return filter
|
|
67
85
|
},
|
|
68
86
|
})
|
|
69
|
-
export const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
>({
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
87
|
+
// export const occludedUpdateSelectors = selectorFamily<
|
|
88
|
+
// Pick<
|
|
89
|
+
// TransactionUpdate<JsonIO>,
|
|
90
|
+
// `epoch` | `id` | `key` | `output` | `updates`
|
|
91
|
+
// > | null,
|
|
92
|
+
// { userId: string; syncGroupKey: string; updateId: string }
|
|
93
|
+
// >({
|
|
94
|
+
// key: `occludedUpdate`,
|
|
95
|
+
// get:
|
|
96
|
+
// ({ userId, syncGroupKey, updateId }) =>
|
|
97
|
+
// ({ get, find }) => {
|
|
98
|
+
// const updateState = find(completeUpdateAtoms, updateId)
|
|
99
|
+
// const update = get(updateState)
|
|
100
|
+
// const redactorKey = { userId, syncGroupKey }
|
|
101
|
+
// const redactorState = find(redactorAtoms, redactorKey)
|
|
102
|
+
// const redact = get(redactorState)
|
|
103
|
+
// if (update) {
|
|
104
|
+
// // return redact(update)
|
|
105
|
+
// return update
|
|
106
|
+
// }
|
|
107
|
+
// return null
|
|
108
|
+
// },
|
|
109
|
+
// })
|