@knowlearning/agents 0.9.188 → 0.9.190
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/agents/attach-synced.js +83 -0
- package/agents/embedded.js +64 -13
- package/agents/generic/index.js +9 -1
- package/agents/generic/state.js +43 -7
- package/package.json +1 -1
- package/types.d.ts +6 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { applyPatch } from 'fast-json-patch'
|
|
2
|
+
import { standardJSONPatch } from '@knowlearning/patch-proxy'
|
|
3
|
+
|
|
4
|
+
// Wraps a state promise with a .synced(callback?) method that applies external
|
|
5
|
+
// patches to the live proxy in-place.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// const promise = attachSynced(watchers, (ctx, resolve) => {
|
|
9
|
+
// const p = new Promise(async (...) => {
|
|
10
|
+
// const proxy = new PatchProxy(data, patch => {
|
|
11
|
+
// if (ctx.applyingExternalUpdate) return
|
|
12
|
+
// // ... interact
|
|
13
|
+
// })
|
|
14
|
+
// resolve(proxy, qualifiedKey)
|
|
15
|
+
// resolvePromise(proxy)
|
|
16
|
+
// })
|
|
17
|
+
// return p
|
|
18
|
+
// })
|
|
19
|
+
export default function attachSynced(watchers, init) {
|
|
20
|
+
const ctx = { applyingExternalUpdate: false }
|
|
21
|
+
let shouldSync = false
|
|
22
|
+
let syncCallbacks = []
|
|
23
|
+
let syncRegistered = false
|
|
24
|
+
let resolvedProxy = null
|
|
25
|
+
let resolvedKey = null
|
|
26
|
+
|
|
27
|
+
function registerSyncWatcher() {
|
|
28
|
+
if (syncRegistered) return
|
|
29
|
+
syncRegistered = true
|
|
30
|
+
if (!watchers[resolvedKey]) watchers[resolvedKey] = []
|
|
31
|
+
watchers[resolvedKey].push(({ patch, state, ii }) => {
|
|
32
|
+
if (!patch) return
|
|
33
|
+
const pending = ctx.pendingInteractions?.size
|
|
34
|
+
? Promise.all(ctx.pendingInteractions)
|
|
35
|
+
: Promise.resolve()
|
|
36
|
+
pending.then(() => {
|
|
37
|
+
const isOwn = ctx.ownInteractions?.has(ii)
|
|
38
|
+
const echoBatch = isOwn ? ctx.ownInteractionBatches?.get(ii) : null
|
|
39
|
+
if (isOwn) {
|
|
40
|
+
ctx.ownInteractions.delete(ii)
|
|
41
|
+
if (echoBatch) ctx.ownInteractionBatches.delete(ii)
|
|
42
|
+
} else {
|
|
43
|
+
ctx.applyingExternalUpdate = true
|
|
44
|
+
applyPatch(resolvedProxy, standardJSONPatch(patch))
|
|
45
|
+
ctx.applyingExternalUpdate = false
|
|
46
|
+
}
|
|
47
|
+
if (echoBatch) {
|
|
48
|
+
echoBatch.state = state
|
|
49
|
+
if (echoBatch.remaining !== undefined) echoBatch.remaining -= 1
|
|
50
|
+
if ((echoBatch.remaining === undefined || echoBatch.remaining === 0) && !echoBatch.resolved) {
|
|
51
|
+
echoBatch.resolved = true
|
|
52
|
+
syncCallbacks.forEach(cb => cb(echoBatch.state, patch))
|
|
53
|
+
echoBatch.resolve()
|
|
54
|
+
}
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
syncCallbacks.forEach(cb => cb(state, patch))
|
|
58
|
+
if (isOwn && ctx.echoResolvers?.length) {
|
|
59
|
+
ctx.echoResolvers.shift()()
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolve(proxy, key) {
|
|
66
|
+
resolvedProxy = proxy
|
|
67
|
+
resolvedKey = key
|
|
68
|
+
if (shouldSync) registerSyncWatcher()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const promise = init(ctx, resolve)
|
|
72
|
+
|
|
73
|
+
promise.synced = function(callback) {
|
|
74
|
+
shouldSync = true
|
|
75
|
+
ctx.syncActive = true
|
|
76
|
+
if (!ctx.echoResolvers) ctx.echoResolvers = []
|
|
77
|
+
if (callback) syncCallbacks.push(callback)
|
|
78
|
+
if (resolvedProxy) registerSyncWatcher()
|
|
79
|
+
return promise
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return promise
|
|
83
|
+
}
|
package/agents/embedded.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { validate as isUUID, v1 as uuid } from 'uuid'
|
|
2
2
|
import PatchProxy from '@knowlearning/patch-proxy'
|
|
3
|
+
import attachSynced from './attach-synced.js'
|
|
3
4
|
import watchImplementation from './watch.js'
|
|
4
5
|
import sync from './sync.js'
|
|
5
6
|
|
|
@@ -10,6 +11,7 @@ export default function EmbeddedAgent(postMessage) {
|
|
|
10
11
|
const responses = {}
|
|
11
12
|
const watchers = {}
|
|
12
13
|
const sentUpdates = {}
|
|
14
|
+
const pendingEchoCallbacks = new Set()
|
|
13
15
|
|
|
14
16
|
const [ watch, removeWatcher ] = watchImplementation({ metadata, state, watchers, synced, sentUpdates, environment })
|
|
15
17
|
|
|
@@ -96,18 +98,63 @@ export default function EmbeddedAgent(postMessage) {
|
|
|
96
98
|
return send({ type: 'patch', root, scopes })
|
|
97
99
|
}
|
|
98
100
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
101
|
+
function state(scope, user, domain) {
|
|
102
|
+
return attachSynced(watchers, (ctx, resolveSync) => (async () => {
|
|
103
|
+
if (scope === undefined) {
|
|
104
|
+
const { context } = await environment()
|
|
105
|
+
scope = JSON.stringify(context)
|
|
106
|
+
}
|
|
107
|
+
const startState = await send({ type: 'state', scope, user, domain })
|
|
108
|
+
const { auth, domain: rootDomain } = await environment()
|
|
109
|
+
const d = !domain || domain === rootDomain ? '' : domain
|
|
110
|
+
const u = !user || auth.user === user ? '' : user
|
|
111
|
+
const key = isUUID(scope) ? scope : `${d}/${u}/${scope}`
|
|
112
|
+
if (!ctx.ownInteractions) ctx.ownInteractions = new Set()
|
|
113
|
+
if (!ctx.ownInteractionBatches) ctx.ownInteractionBatches = new Map()
|
|
114
|
+
if (!ctx.pendingInteractions) ctx.pendingInteractions = new Set()
|
|
115
|
+
if (!ctx.echoResolvers) ctx.echoResolvers = []
|
|
116
|
+
let currentEchoBatch = null
|
|
117
|
+
const proxy = new PatchProxy(startState, patch => {
|
|
118
|
+
// TODO: reject updates if user is not owner
|
|
119
|
+
if (ctx.applyingExternalUpdate) return
|
|
120
|
+
const activePatch = structuredClone(patch)
|
|
121
|
+
activePatch.forEach(entry => entry.path.unshift('active'))
|
|
122
|
+
let echoBatch
|
|
123
|
+
if (ctx.syncActive) {
|
|
124
|
+
if (!currentEchoBatch) {
|
|
125
|
+
let resolveEcho
|
|
126
|
+
const echoPromise = new Promise(r => resolveEcho = r)
|
|
127
|
+
echoBatch = currentEchoBatch = { remaining: 0, resolve: resolveEcho, resolved: false }
|
|
128
|
+
pendingEchoCallbacks.add(echoPromise)
|
|
129
|
+
echoPromise.then(() => pendingEchoCallbacks.delete(echoPromise))
|
|
130
|
+
Promise.resolve().then(() => { currentEchoBatch = null })
|
|
131
|
+
}
|
|
132
|
+
else echoBatch = currentEchoBatch
|
|
133
|
+
echoBatch.remaining += 1
|
|
134
|
+
}
|
|
135
|
+
const interactPromise = interact(scope, activePatch)
|
|
136
|
+
const p = interactPromise.then(
|
|
137
|
+
r => {
|
|
138
|
+
ctx.ownInteractions.add(r.ii)
|
|
139
|
+
if (echoBatch) ctx.ownInteractionBatches.set(r.ii, echoBatch)
|
|
140
|
+
ctx.pendingInteractions.delete(p)
|
|
141
|
+
},
|
|
142
|
+
() => {
|
|
143
|
+
if (echoBatch) {
|
|
144
|
+
echoBatch.remaining -= 1
|
|
145
|
+
if (echoBatch.remaining === 0 && !echoBatch.resolved) {
|
|
146
|
+
echoBatch.resolved = true
|
|
147
|
+
echoBatch.resolve()
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
ctx.pendingInteractions.delete(p)
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
ctx.pendingInteractions.add(p)
|
|
154
|
+
})
|
|
155
|
+
resolveSync(proxy, key)
|
|
156
|
+
return proxy
|
|
157
|
+
})())
|
|
111
158
|
}
|
|
112
159
|
|
|
113
160
|
function reset(scope) {
|
|
@@ -203,7 +250,11 @@ export default function EmbeddedAgent(postMessage) {
|
|
|
203
250
|
function logout() { return send({ type: 'logout' }) }
|
|
204
251
|
function disconnect() { return send({ type: 'disconnect' }) }
|
|
205
252
|
function reconnect() { return send({ type: 'reconnect' }) }
|
|
206
|
-
function synced() {
|
|
253
|
+
async function synced() {
|
|
254
|
+
const echoSnapshot = [...pendingEchoCallbacks]
|
|
255
|
+
await send({ type: 'synced' })
|
|
256
|
+
if (echoSnapshot.length) await Promise.all(echoSnapshot)
|
|
257
|
+
}
|
|
207
258
|
function close(info) { return send({ type: 'close', info }) }
|
|
208
259
|
function guarantee(script, namespaces, context) { return send({ type: 'guarantee', script, namespaces, context }) }
|
|
209
260
|
function response(id=lastRequestId) { return send({ type: 'response', id }) }
|
package/agents/generic/index.js
CHANGED
|
@@ -17,6 +17,7 @@ export default function Agent({ Connection, domain, token, sid, uuid, fetch, app
|
|
|
17
17
|
const watchers = {}
|
|
18
18
|
const keyToSubscriptionId = {}
|
|
19
19
|
const lastInteractionResponse = {}
|
|
20
|
+
const pendingEchoCallbacks = new Set()
|
|
20
21
|
|
|
21
22
|
log('INITIALIZING AGENT CONNECTION')
|
|
22
23
|
const [
|
|
@@ -47,6 +48,7 @@ export default function Agent({ Connection, domain, token, sid, uuid, fetch, app
|
|
|
47
48
|
interact,
|
|
48
49
|
fetch,
|
|
49
50
|
synced,
|
|
51
|
+
pendingEchoCallbacks,
|
|
50
52
|
metadata,
|
|
51
53
|
log
|
|
52
54
|
}
|
|
@@ -204,6 +206,12 @@ export default function Agent({ Connection, domain, token, sid, uuid, fetch, app
|
|
|
204
206
|
}
|
|
205
207
|
}
|
|
206
208
|
|
|
209
|
+
async function syncedWithEchoCallbacks() {
|
|
210
|
+
const echoSnapshot = [...pendingEchoCallbacks]
|
|
211
|
+
await synced()
|
|
212
|
+
if (echoSnapshot.length) await Promise.all(echoSnapshot)
|
|
213
|
+
}
|
|
214
|
+
|
|
207
215
|
function response() {
|
|
208
216
|
return lastMessageResponse()
|
|
209
217
|
}
|
|
@@ -224,7 +232,7 @@ export default function Agent({ Connection, domain, token, sid, uuid, fetch, app
|
|
|
224
232
|
reset,
|
|
225
233
|
metadata,
|
|
226
234
|
query,
|
|
227
|
-
synced,
|
|
235
|
+
synced: syncedWithEchoCallbacks,
|
|
228
236
|
disconnect,
|
|
229
237
|
reconnect,
|
|
230
238
|
debug,
|
package/agents/generic/state.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { v4 as uuid, validate as isUUID } from 'uuid'
|
|
2
2
|
import PatchProxy from '@knowlearning/patch-proxy'
|
|
3
|
+
import attachSynced from '../attach-synced.js'
|
|
3
4
|
|
|
4
|
-
export default function(scope='[]', user, domain, { keyToSubscriptionId, watchers, states, create, environment, lastMessageResponse, lastInteractionResponse, interact, log }) {
|
|
5
|
+
export default function(scope='[]', user, domain, { keyToSubscriptionId, watchers, states, create, environment, lastMessageResponse, lastInteractionResponse, interact, pendingEchoCallbacks, log }) {
|
|
5
6
|
let resolveMetadataPromise
|
|
6
7
|
let metadataPromise = new Promise(resolve => resolveMetadataPromise = resolve)
|
|
7
8
|
|
|
8
|
-
const statePromise = new Promise(async (resolveState, rejectState) => {
|
|
9
|
+
const statePromise = attachSynced(watchers, (ctx, resolveSync) => new Promise(async (resolveState, rejectState) => {
|
|
9
10
|
const { auth: { user: u }, domain: d, session } = await environment()
|
|
10
11
|
|
|
11
12
|
const qualifiedScope = isUUID(scope) ? scope : `${!domain || domain === d ? '' : domain}/${!user || user === u ? '' : user}/${scope}`
|
|
@@ -35,17 +36,52 @@ export default function(scope='[]', user, domain, { keyToSubscriptionId, watcher
|
|
|
35
36
|
const active = data.active
|
|
36
37
|
delete data.active
|
|
37
38
|
resolveMetadataPromise(data)
|
|
38
|
-
|
|
39
|
+
if (!ctx.ownInteractions) ctx.ownInteractions = new Set()
|
|
40
|
+
if (!ctx.ownInteractionBatches) ctx.ownInteractionBatches = new Map()
|
|
41
|
+
if (!ctx.pendingInteractions) ctx.pendingInteractions = new Set()
|
|
42
|
+
if (!ctx.echoResolvers) ctx.echoResolvers = []
|
|
43
|
+
let currentEchoBatch = null
|
|
44
|
+
const proxy = new PatchProxy(active || {}, patch => {
|
|
45
|
+
if (ctx.applyingExternalUpdate) return
|
|
39
46
|
const activePatch = structuredClone(patch)
|
|
40
47
|
activePatch.forEach(entry => entry.path.unshift('active'))
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
let echoBatch
|
|
49
|
+
if (ctx.syncActive && pendingEchoCallbacks) {
|
|
50
|
+
if (!currentEchoBatch) {
|
|
51
|
+
let resolveEcho
|
|
52
|
+
const echoPromise = new Promise(r => resolveEcho = r)
|
|
53
|
+
echoBatch = currentEchoBatch = { resolve: resolveEcho, resolved: false }
|
|
54
|
+
pendingEchoCallbacks.add(echoPromise)
|
|
55
|
+
echoPromise.then(() => pendingEchoCallbacks.delete(echoPromise))
|
|
56
|
+
Promise.resolve().then(() => { currentEchoBatch = null })
|
|
57
|
+
}
|
|
58
|
+
else echoBatch = currentEchoBatch
|
|
59
|
+
}
|
|
60
|
+
const interactPromise = interact(scope, activePatch)
|
|
61
|
+
const p = interactPromise.then(
|
|
62
|
+
r => {
|
|
63
|
+
ctx.ownInteractions.add(r.ii)
|
|
64
|
+
if (echoBatch) ctx.ownInteractionBatches.set(r.ii, echoBatch)
|
|
65
|
+
ctx.pendingInteractions.delete(p)
|
|
66
|
+
},
|
|
67
|
+
() => {
|
|
68
|
+
if (echoBatch && !echoBatch.resolved) {
|
|
69
|
+
echoBatch.resolved = true
|
|
70
|
+
echoBatch.resolve()
|
|
71
|
+
}
|
|
72
|
+
ctx.pendingInteractions.delete(p)
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
ctx.pendingInteractions.add(p)
|
|
76
|
+
})
|
|
77
|
+
resolveSync(proxy, qualifiedScope)
|
|
78
|
+
resolveState(proxy)
|
|
43
79
|
}
|
|
44
80
|
catch (error) {
|
|
45
81
|
rejectState(error)
|
|
46
82
|
}
|
|
47
|
-
})
|
|
83
|
+
}))
|
|
48
84
|
|
|
49
85
|
statePromise.metadata = metadataPromise
|
|
50
86
|
return statePromise
|
|
51
|
-
}
|
|
87
|
+
}
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -27,11 +27,16 @@ export interface AgentUploadInfo {
|
|
|
27
27
|
accept?: string;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
export interface SyncedStatePromise extends Promise<object> {
|
|
31
|
+
synced(callback?: (state: object, patch: object[]) => void): SyncedStatePromise;
|
|
32
|
+
metadata: Promise<object>;
|
|
33
|
+
}
|
|
34
|
+
|
|
30
35
|
export interface Agent {
|
|
31
36
|
login(provider: string): void;
|
|
32
37
|
logout(): void;
|
|
33
38
|
uuid(): string;
|
|
34
|
-
state(id: string, user?: string, domain?: string):
|
|
39
|
+
state(id: string, user?: string, domain?: string): SyncedStatePromise;
|
|
35
40
|
metadata(id: string, user?: string, domain?: string): Promise<object>;
|
|
36
41
|
watch(id: string, callback: (update: { state: object }) => void, user?: string, domain?: string): void;
|
|
37
42
|
upload(info?: AgentUploadInfo): Promise<string>;
|