@arcote.tech/arc 0.3.6 → 0.4.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/adapters/auth-adapter.d.ts +35 -20
- package/dist/adapters/command-wire.d.ts +4 -2
- package/dist/adapters/event-wire.d.ts +28 -3
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/query-wire.d.ts +6 -4
- package/dist/adapters/wire.d.ts +7 -11
- package/dist/context-element/aggregate/aggregate-base.d.ts +31 -0
- package/dist/context-element/aggregate/aggregate-builder.d.ts +139 -0
- package/dist/context-element/aggregate/aggregate-data.d.ts +114 -0
- package/dist/context-element/aggregate/aggregate-element.d.ts +69 -0
- package/dist/context-element/aggregate/index.d.ts +31 -0
- package/dist/context-element/aggregate/simple-aggregate.d.ts +94 -0
- package/dist/context-element/command/command-context.d.ts +3 -32
- package/dist/context-element/context-element.d.ts +7 -1
- package/dist/context-element/element-context.d.ts +39 -0
- package/dist/context-element/index.d.ts +2 -0
- package/dist/context-element/route/route.d.ts +8 -26
- package/dist/context-element/static-view/static-view.d.ts +2 -2
- package/dist/context-element/view/index.d.ts +1 -1
- package/dist/data-storage/store-state.abstract.d.ts +1 -0
- package/dist/elements/branded.d.ts +1 -0
- package/dist/elements/object.d.ts +2 -2
- package/dist/elements/optional.d.ts +1 -1
- package/dist/fragment/arc-fragment.d.ts +18 -0
- package/dist/fragment/index.d.ts +2 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1006 -458
- package/dist/model/context-accessor.d.ts +51 -0
- package/dist/model/index.d.ts +4 -0
- package/dist/model/live-query/live-query.d.ts +4 -26
- package/dist/model/model-adapters.d.ts +5 -0
- package/dist/model/model-like.d.ts +9 -0
- package/dist/model/model.d.ts +10 -1
- package/dist/model/mutation-executor/mutation-executor.d.ts +2 -2
- package/dist/model/scoped-model.d.ts +66 -0
- package/dist/model/scoped-wire-proxy.d.ts +23 -0
- package/dist/streaming/index.d.ts +0 -2
- package/dist/streaming/streaming-live-query.d.ts +10 -12
- package/dist/streaming/streaming-query-cache.d.ts +25 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,71 +1,126 @@
|
|
|
1
1
|
// src/adapters/auth-adapter.ts
|
|
2
|
+
var TOKEN_PREFIX = "arc:token:";
|
|
3
|
+
function hasLocalStorage() {
|
|
4
|
+
return typeof localStorage !== "undefined";
|
|
5
|
+
}
|
|
6
|
+
|
|
2
7
|
class AuthAdapter {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
setToken(token) {
|
|
6
|
-
this.token = token;
|
|
8
|
+
scopes = new Map;
|
|
9
|
+
setToken(token, scope = "default") {
|
|
7
10
|
if (!token) {
|
|
8
|
-
this.
|
|
11
|
+
this.scopes.delete(scope);
|
|
12
|
+
if (hasLocalStorage())
|
|
13
|
+
localStorage.removeItem(TOKEN_PREFIX + scope);
|
|
9
14
|
return;
|
|
10
15
|
}
|
|
11
16
|
try {
|
|
12
17
|
const parts = token.split(".");
|
|
13
18
|
if (parts.length !== 3) {
|
|
14
|
-
this.
|
|
19
|
+
this.scopes.delete(scope);
|
|
20
|
+
if (hasLocalStorage())
|
|
21
|
+
localStorage.removeItem(TOKEN_PREFIX + scope);
|
|
15
22
|
return;
|
|
16
23
|
}
|
|
17
24
|
const payload = JSON.parse(atob(parts[1]));
|
|
18
|
-
this.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
this.scopes.set(scope, {
|
|
26
|
+
raw: token,
|
|
27
|
+
decoded: {
|
|
28
|
+
tokenName: payload.tokenName,
|
|
29
|
+
params: payload.params || {},
|
|
30
|
+
iat: payload.iat,
|
|
31
|
+
exp: payload.exp
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
if (hasLocalStorage())
|
|
35
|
+
localStorage.setItem(TOKEN_PREFIX + scope, token);
|
|
24
36
|
} catch {
|
|
25
|
-
this.
|
|
37
|
+
this.scopes.delete(scope);
|
|
38
|
+
if (hasLocalStorage())
|
|
39
|
+
localStorage.removeItem(TOKEN_PREFIX + scope);
|
|
26
40
|
}
|
|
27
41
|
}
|
|
28
|
-
|
|
29
|
-
|
|
42
|
+
loadPersisted() {
|
|
43
|
+
if (!hasLocalStorage())
|
|
44
|
+
return;
|
|
45
|
+
for (let i = 0;i < localStorage.length; i++) {
|
|
46
|
+
const key = localStorage.key(i);
|
|
47
|
+
if (key?.startsWith(TOKEN_PREFIX)) {
|
|
48
|
+
const scope = key.slice(TOKEN_PREFIX.length);
|
|
49
|
+
const raw = localStorage.getItem(key);
|
|
50
|
+
if (raw) {
|
|
51
|
+
try {
|
|
52
|
+
const parts = raw.split(".");
|
|
53
|
+
if (parts.length !== 3)
|
|
54
|
+
continue;
|
|
55
|
+
const payload = JSON.parse(atob(parts[1]));
|
|
56
|
+
this.scopes.set(scope, {
|
|
57
|
+
raw,
|
|
58
|
+
decoded: {
|
|
59
|
+
tokenName: payload.tokenName,
|
|
60
|
+
params: payload.params || {},
|
|
61
|
+
iat: payload.iat,
|
|
62
|
+
exp: payload.exp
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
} catch {
|
|
66
|
+
localStorage.removeItem(key);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
30
71
|
}
|
|
31
|
-
|
|
32
|
-
return this.
|
|
72
|
+
getToken(scope = "default") {
|
|
73
|
+
return this.scopes.get(scope)?.raw ?? null;
|
|
33
74
|
}
|
|
34
|
-
|
|
35
|
-
return this.decoded
|
|
75
|
+
getDecoded(scope = "default") {
|
|
76
|
+
return this.scopes.get(scope)?.decoded ?? null;
|
|
36
77
|
}
|
|
37
|
-
|
|
38
|
-
return this.decoded?.
|
|
78
|
+
getParams(scope = "default") {
|
|
79
|
+
return this.scopes.get(scope)?.decoded?.params ?? null;
|
|
39
80
|
}
|
|
40
|
-
|
|
41
|
-
return this.decoded
|
|
81
|
+
getTokenName(scope = "default") {
|
|
82
|
+
return this.scopes.get(scope)?.decoded?.tokenName ?? null;
|
|
42
83
|
}
|
|
43
|
-
|
|
44
|
-
if (
|
|
84
|
+
isAuthenticated(scope) {
|
|
85
|
+
if (scope !== undefined) {
|
|
86
|
+
return this.scopes.has(scope);
|
|
87
|
+
}
|
|
88
|
+
return this.scopes.size > 0;
|
|
89
|
+
}
|
|
90
|
+
isExpired(scope = "default") {
|
|
91
|
+
const entry = this.scopes.get(scope);
|
|
92
|
+
if (!entry?.decoded?.exp)
|
|
45
93
|
return false;
|
|
46
|
-
return Date.now() >
|
|
94
|
+
return Date.now() > entry.decoded.exp;
|
|
95
|
+
}
|
|
96
|
+
getAllScopes() {
|
|
97
|
+
return Array.from(this.scopes.keys());
|
|
98
|
+
}
|
|
99
|
+
clear() {
|
|
100
|
+
if (hasLocalStorage()) {
|
|
101
|
+
for (const scope of this.scopes.keys()) {
|
|
102
|
+
localStorage.removeItem(TOKEN_PREFIX + scope);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
this.scopes.clear();
|
|
47
106
|
}
|
|
48
107
|
}
|
|
49
108
|
// src/adapters/wire.ts
|
|
50
109
|
class Wire {
|
|
51
|
-
token = null;
|
|
52
110
|
baseUrl;
|
|
53
111
|
constructor(baseUrl) {
|
|
54
112
|
this.baseUrl = baseUrl;
|
|
55
113
|
}
|
|
56
|
-
setAuthToken(token) {
|
|
57
|
-
this.token = token;
|
|
58
|
-
}
|
|
59
|
-
getAuthToken() {
|
|
60
|
-
return this.token;
|
|
61
|
-
}
|
|
62
114
|
getBaseUrl() {
|
|
63
115
|
return this.baseUrl;
|
|
64
116
|
}
|
|
65
|
-
async fetch(endpoint, options = {}) {
|
|
117
|
+
async fetch(endpoint, options = {}, auth) {
|
|
66
118
|
const headers = new Headers(options.headers);
|
|
67
|
-
if (
|
|
68
|
-
headers.set("Authorization", `Bearer ${
|
|
119
|
+
if (auth?.token) {
|
|
120
|
+
headers.set("Authorization", `Bearer ${auth.token}`);
|
|
121
|
+
}
|
|
122
|
+
if (auth?.scope) {
|
|
123
|
+
headers.set("X-Arc-Scope", auth.scope);
|
|
69
124
|
}
|
|
70
125
|
if (options.body && !headers.has("Content-Type") && typeof options.body === "string") {
|
|
71
126
|
headers.set("Content-Type", "application/json");
|
|
@@ -83,7 +138,7 @@ class CommandWire extends Wire {
|
|
|
83
138
|
constructor(baseUrl) {
|
|
84
139
|
super(baseUrl);
|
|
85
140
|
}
|
|
86
|
-
async executeCommand(name, params) {
|
|
141
|
+
async executeCommand(name, params, auth) {
|
|
87
142
|
const hasFile = this.containsFile(params);
|
|
88
143
|
let body;
|
|
89
144
|
let headers;
|
|
@@ -97,7 +152,7 @@ class CommandWire extends Wire {
|
|
|
97
152
|
method: "POST",
|
|
98
153
|
body,
|
|
99
154
|
headers
|
|
100
|
-
});
|
|
155
|
+
}, auth);
|
|
101
156
|
if (!response.ok) {
|
|
102
157
|
const errorText = await response.text();
|
|
103
158
|
throw new Error(`Command ${name} failed: ${response.status} ${errorText}`);
|
|
@@ -135,24 +190,39 @@ class EventWire {
|
|
|
135
190
|
instanceId;
|
|
136
191
|
ws = null;
|
|
137
192
|
state = "disconnected";
|
|
138
|
-
|
|
193
|
+
scopeTokens = new Map;
|
|
139
194
|
lastHostEventId = null;
|
|
140
195
|
pendingEvents = [];
|
|
141
196
|
onEventCallback;
|
|
142
197
|
onSyncedCallback;
|
|
143
198
|
reconnectTimeout;
|
|
144
199
|
syncRequested = false;
|
|
200
|
+
viewSubscriptions = new Map;
|
|
201
|
+
viewSubCounter = 0;
|
|
202
|
+
pendingViewSubs = [];
|
|
145
203
|
constructor(baseUrl) {
|
|
146
204
|
this.baseUrl = baseUrl;
|
|
147
205
|
this.instanceId = ++eventWireInstanceCounter;
|
|
148
206
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
this.
|
|
207
|
+
setScopeToken(scope, token) {
|
|
208
|
+
if (token === null) {
|
|
209
|
+
this.scopeTokens.delete(scope);
|
|
210
|
+
} else {
|
|
211
|
+
this.scopeTokens.set(scope, token);
|
|
212
|
+
}
|
|
213
|
+
if (this.state === "connected" && this.ws) {
|
|
214
|
+
if (token !== null) {
|
|
215
|
+
this.ws.send(JSON.stringify({
|
|
216
|
+
type: "scope:auth",
|
|
217
|
+
scope,
|
|
218
|
+
token
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
154
221
|
}
|
|
155
222
|
}
|
|
223
|
+
hasScopeTokens() {
|
|
224
|
+
return this.scopeTokens.size > 0;
|
|
225
|
+
}
|
|
156
226
|
setLastHostEventId(id) {
|
|
157
227
|
this.lastHostEventId = id;
|
|
158
228
|
}
|
|
@@ -171,15 +241,16 @@ class EventWire {
|
|
|
171
241
|
const host = typeof window !== "undefined" ? window.location.host : "localhost";
|
|
172
242
|
wsUrl = `${protocol}//${host}${this.baseUrl}`;
|
|
173
243
|
}
|
|
174
|
-
const
|
|
175
|
-
const url = `${wsUrl}/ws${tokenParam}`;
|
|
244
|
+
const url = `${wsUrl}/ws`;
|
|
176
245
|
try {
|
|
177
246
|
this.ws = new WebSocket(url);
|
|
178
247
|
this.ws.onopen = () => {
|
|
179
248
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
180
249
|
this.state = "connected";
|
|
250
|
+
this.sendAllScopeTokens();
|
|
181
251
|
this.requestSync();
|
|
182
252
|
this.flushPendingEvents();
|
|
253
|
+
this.flushPendingViewSubs();
|
|
183
254
|
} else {
|
|
184
255
|
console.log(`[EventWire] onopen called but ws is not OPEN, readyState:`, this.ws?.readyState);
|
|
185
256
|
}
|
|
@@ -254,9 +325,45 @@ class EventWire {
|
|
|
254
325
|
onSynced(callback) {
|
|
255
326
|
this.onSyncedCallback = callback;
|
|
256
327
|
}
|
|
328
|
+
subscribeQuery(descriptor, callback, scope) {
|
|
329
|
+
const subscriptionId = `qs_${++this.viewSubCounter}_${Date.now()}`;
|
|
330
|
+
this.viewSubscriptions.set(subscriptionId, callback);
|
|
331
|
+
if (this.state === "connected" && this.ws) {
|
|
332
|
+
this.ws.send(JSON.stringify({
|
|
333
|
+
type: "subscribe-query",
|
|
334
|
+
subscriptionId,
|
|
335
|
+
descriptor,
|
|
336
|
+
scope
|
|
337
|
+
}));
|
|
338
|
+
} else {
|
|
339
|
+
this.pendingViewSubs.push({ subscriptionId, descriptor, scope });
|
|
340
|
+
}
|
|
341
|
+
return subscriptionId;
|
|
342
|
+
}
|
|
343
|
+
unsubscribeQuery(subscriptionId) {
|
|
344
|
+
this.viewSubscriptions.delete(subscriptionId);
|
|
345
|
+
if (this.state === "connected" && this.ws) {
|
|
346
|
+
this.ws.send(JSON.stringify({
|
|
347
|
+
type: "unsubscribe-query",
|
|
348
|
+
subscriptionId
|
|
349
|
+
}));
|
|
350
|
+
}
|
|
351
|
+
this.pendingViewSubs = this.pendingViewSubs.filter((s) => s.subscriptionId !== subscriptionId);
|
|
352
|
+
}
|
|
257
353
|
getState() {
|
|
258
354
|
return this.state;
|
|
259
355
|
}
|
|
356
|
+
sendAllScopeTokens() {
|
|
357
|
+
if (!this.ws || this.state !== "connected")
|
|
358
|
+
return;
|
|
359
|
+
for (const [scope, token] of this.scopeTokens) {
|
|
360
|
+
this.ws.send(JSON.stringify({
|
|
361
|
+
type: "scope:auth",
|
|
362
|
+
scope,
|
|
363
|
+
token
|
|
364
|
+
}));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
260
367
|
handleMessage(message) {
|
|
261
368
|
switch (message.type) {
|
|
262
369
|
case "events":
|
|
@@ -274,6 +381,13 @@ class EventWire {
|
|
|
274
381
|
this.lastHostEventId = message.lastHostEventId;
|
|
275
382
|
}
|
|
276
383
|
break;
|
|
384
|
+
case "query-data": {
|
|
385
|
+
const cb = this.viewSubscriptions.get(message.subscriptionId);
|
|
386
|
+
if (cb) {
|
|
387
|
+
cb(message.data);
|
|
388
|
+
}
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
277
391
|
case "command-result":
|
|
278
392
|
break;
|
|
279
393
|
case "error":
|
|
@@ -298,6 +412,19 @@ class EventWire {
|
|
|
298
412
|
this.pendingEvents = [];
|
|
299
413
|
}
|
|
300
414
|
}
|
|
415
|
+
flushPendingViewSubs() {
|
|
416
|
+
if (!this.ws || this.state !== "connected")
|
|
417
|
+
return;
|
|
418
|
+
for (const sub of this.pendingViewSubs) {
|
|
419
|
+
this.ws.send(JSON.stringify({
|
|
420
|
+
type: "subscribe-query",
|
|
421
|
+
subscriptionId: sub.subscriptionId,
|
|
422
|
+
descriptor: sub.descriptor,
|
|
423
|
+
scope: sub.scope
|
|
424
|
+
}));
|
|
425
|
+
}
|
|
426
|
+
this.pendingViewSubs = [];
|
|
427
|
+
}
|
|
301
428
|
scheduleReconnect() {
|
|
302
429
|
if (this.reconnectTimeout)
|
|
303
430
|
return;
|
|
@@ -312,18 +439,18 @@ class QueryWire extends Wire {
|
|
|
312
439
|
constructor(baseUrl) {
|
|
313
440
|
super(baseUrl);
|
|
314
441
|
}
|
|
315
|
-
async query(viewName, options = {}) {
|
|
442
|
+
async query(viewName, options = {}, auth) {
|
|
316
443
|
const response = await this.fetch(`/query/${viewName}`, {
|
|
317
444
|
method: "POST",
|
|
318
445
|
body: JSON.stringify(options)
|
|
319
|
-
});
|
|
446
|
+
}, auth);
|
|
320
447
|
if (!response.ok) {
|
|
321
448
|
const errorText = await response.text();
|
|
322
449
|
throw new Error(`Query ${viewName} failed: ${response.status} ${errorText}`);
|
|
323
450
|
}
|
|
324
451
|
return await response.json();
|
|
325
452
|
}
|
|
326
|
-
stream(viewName, options, callback,
|
|
453
|
+
stream(viewName, options, callback, auth) {
|
|
327
454
|
const params = new URLSearchParams;
|
|
328
455
|
if (options?.where) {
|
|
329
456
|
params.set("where", JSON.stringify(options.where));
|
|
@@ -334,9 +461,11 @@ class QueryWire extends Wire {
|
|
|
334
461
|
if (options?.limit) {
|
|
335
462
|
params.set("limit", String(options.limit));
|
|
336
463
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
464
|
+
if (auth?.token) {
|
|
465
|
+
params.set("token", auth.token);
|
|
466
|
+
}
|
|
467
|
+
if (auth?.scope) {
|
|
468
|
+
params.set("scope", auth.scope);
|
|
340
469
|
}
|
|
341
470
|
const queryString = params.toString();
|
|
342
471
|
const url = `${this.getBaseUrl()}/stream/${viewName}${queryString ? `?${queryString}` : ""}`;
|
|
@@ -453,6 +582,18 @@ class LocalEventPublisher {
|
|
|
453
582
|
}
|
|
454
583
|
}
|
|
455
584
|
async notifySubscribers(event) {
|
|
585
|
+
const wildcardSubs = this.subscribers.get("*");
|
|
586
|
+
if (wildcardSubs) {
|
|
587
|
+
for (const callback of wildcardSubs) {
|
|
588
|
+
try {
|
|
589
|
+
const result = callback(event);
|
|
590
|
+
if (result instanceof Promise)
|
|
591
|
+
await result;
|
|
592
|
+
} catch (error) {
|
|
593
|
+
console.error(`[EventPublisher] Wildcard subscriber error:`, error);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
456
597
|
const subs = this.subscribers.get(event.type);
|
|
457
598
|
if (!subs || subs.size === 0)
|
|
458
599
|
return;
|
|
@@ -598,8 +739,8 @@ class ArcOptional {
|
|
|
598
739
|
return null;
|
|
599
740
|
}
|
|
600
741
|
deserialize(value) {
|
|
601
|
-
if (
|
|
602
|
-
return
|
|
742
|
+
if (value == null)
|
|
743
|
+
return;
|
|
603
744
|
return this.parent.deserialize(value);
|
|
604
745
|
}
|
|
605
746
|
validate(value) {
|
|
@@ -636,6 +777,9 @@ class ArcBranded {
|
|
|
636
777
|
this.parent = parent;
|
|
637
778
|
this.brand = brand;
|
|
638
779
|
}
|
|
780
|
+
get name() {
|
|
781
|
+
return this.brand;
|
|
782
|
+
}
|
|
639
783
|
serialize(value) {
|
|
640
784
|
return this.parent.serialize(value);
|
|
641
785
|
}
|
|
@@ -1109,187 +1253,6 @@ function object(element) {
|
|
|
1109
1253
|
return new ArcObject(element);
|
|
1110
1254
|
}
|
|
1111
1255
|
|
|
1112
|
-
// src/context-element/context-element.ts
|
|
1113
|
-
class ArcContextElement {
|
|
1114
|
-
name;
|
|
1115
|
-
constructor(name) {
|
|
1116
|
-
this.name = name;
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
// src/context-element/command/command.ts
|
|
1121
|
-
class ArcCommand extends ArcContextElement {
|
|
1122
|
-
data;
|
|
1123
|
-
constructor(data) {
|
|
1124
|
-
super(data.name);
|
|
1125
|
-
this.data = data;
|
|
1126
|
-
}
|
|
1127
|
-
description(description) {
|
|
1128
|
-
return new ArcCommand({
|
|
1129
|
-
...this.data,
|
|
1130
|
-
description
|
|
1131
|
-
});
|
|
1132
|
-
}
|
|
1133
|
-
get isPublic() {
|
|
1134
|
-
return !this.hasProtections;
|
|
1135
|
-
}
|
|
1136
|
-
query(elements) {
|
|
1137
|
-
return new ArcCommand({
|
|
1138
|
-
...this.data,
|
|
1139
|
-
queryElements: elements
|
|
1140
|
-
});
|
|
1141
|
-
}
|
|
1142
|
-
mutate(elements) {
|
|
1143
|
-
return new ArcCommand({
|
|
1144
|
-
...this.data,
|
|
1145
|
-
mutationElements: elements
|
|
1146
|
-
});
|
|
1147
|
-
}
|
|
1148
|
-
withParams(schema) {
|
|
1149
|
-
return new ArcCommand({
|
|
1150
|
-
...this.data,
|
|
1151
|
-
params: schema instanceof ArcObject ? schema : new ArcObject(schema)
|
|
1152
|
-
});
|
|
1153
|
-
}
|
|
1154
|
-
withResult(...schemas) {
|
|
1155
|
-
return new ArcCommand({
|
|
1156
|
-
...this.data,
|
|
1157
|
-
results: schemas
|
|
1158
|
-
});
|
|
1159
|
-
}
|
|
1160
|
-
handle(handler) {
|
|
1161
|
-
return new ArcCommand({
|
|
1162
|
-
...this.data,
|
|
1163
|
-
handler
|
|
1164
|
-
});
|
|
1165
|
-
}
|
|
1166
|
-
protectBy(token, check) {
|
|
1167
|
-
const existingProtections = this.data.protections || [];
|
|
1168
|
-
return new ArcCommand({
|
|
1169
|
-
...this.data,
|
|
1170
|
-
protections: [...existingProtections, { token, check }]
|
|
1171
|
-
});
|
|
1172
|
-
}
|
|
1173
|
-
get hasProtections() {
|
|
1174
|
-
return (this.data.protections?.length ?? 0) > 0;
|
|
1175
|
-
}
|
|
1176
|
-
get protections() {
|
|
1177
|
-
return this.data.protections || [];
|
|
1178
|
-
}
|
|
1179
|
-
async verifyProtections(tokens) {
|
|
1180
|
-
if (!this.data.protections || this.data.protections.length === 0) {
|
|
1181
|
-
return true;
|
|
1182
|
-
}
|
|
1183
|
-
for (const protection of this.data.protections) {
|
|
1184
|
-
const tokenInstance = tokens.find((t) => t.getTokenDefinition() === protection.token);
|
|
1185
|
-
if (!tokenInstance) {
|
|
1186
|
-
return false;
|
|
1187
|
-
}
|
|
1188
|
-
const allowed = await protection.check(tokenInstance);
|
|
1189
|
-
if (!allowed) {
|
|
1190
|
-
return false;
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
return true;
|
|
1194
|
-
}
|
|
1195
|
-
async init(environment, adapters) {
|
|
1196
|
-
if (environment === "server" && this.data.handler && adapters.commandWire?.registerCommandHandler) {
|
|
1197
|
-
adapters.commandWire.registerCommandHandler(this.data.name, async (params) => {
|
|
1198
|
-
const executeFunc = this.mutateContext(adapters);
|
|
1199
|
-
return await executeFunc(params);
|
|
1200
|
-
});
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
mutateContext(adapters) {
|
|
1204
|
-
const executeFunc = async (params) => {
|
|
1205
|
-
if (this.data.handler) {
|
|
1206
|
-
return await this.executeLocally(params, adapters);
|
|
1207
|
-
}
|
|
1208
|
-
if (!adapters.commandWire) {
|
|
1209
|
-
throw new Error(`Command "${this.data.name}" has no handler and no commandWire adapter available for remote execution`);
|
|
1210
|
-
}
|
|
1211
|
-
return await adapters.commandWire.executeCommand(this.data.name, params);
|
|
1212
|
-
};
|
|
1213
|
-
return Object.assign(executeFunc, { params: this.data.params });
|
|
1214
|
-
}
|
|
1215
|
-
async executeLocally(params, adapters) {
|
|
1216
|
-
if (!this.data.handler) {
|
|
1217
|
-
throw new Error(`Command "${this.data.name}" has no handler`);
|
|
1218
|
-
}
|
|
1219
|
-
const context2 = this.buildCommandContext(adapters);
|
|
1220
|
-
const result = await this.data.handler(context2, params);
|
|
1221
|
-
return result;
|
|
1222
|
-
}
|
|
1223
|
-
buildCommandContext(adapters) {
|
|
1224
|
-
const context2 = {};
|
|
1225
|
-
const elementMap = new Map;
|
|
1226
|
-
for (const element of this.data.queryElements) {
|
|
1227
|
-
if (element.queryContext) {
|
|
1228
|
-
const elementContext = element.queryContext(adapters);
|
|
1229
|
-
context2[element.name] = elementContext;
|
|
1230
|
-
elementMap.set(element, elementContext);
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
for (const element of this.data.mutationElements) {
|
|
1234
|
-
if (element.mutateContext) {
|
|
1235
|
-
const elementContext = element.mutateContext(adapters);
|
|
1236
|
-
context2[element.name] = elementContext;
|
|
1237
|
-
elementMap.set(element, elementContext);
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
context2.get = (element) => {
|
|
1241
|
-
const cached = elementMap.get(element);
|
|
1242
|
-
if (cached)
|
|
1243
|
-
return cached;
|
|
1244
|
-
if (element.queryContext) {
|
|
1245
|
-
const ctx = element.queryContext(adapters);
|
|
1246
|
-
elementMap.set(element, ctx);
|
|
1247
|
-
return ctx;
|
|
1248
|
-
}
|
|
1249
|
-
if (element.mutateContext) {
|
|
1250
|
-
const ctx = element.mutateContext(adapters);
|
|
1251
|
-
elementMap.set(element, ctx);
|
|
1252
|
-
return ctx;
|
|
1253
|
-
}
|
|
1254
|
-
throw new Error(`Element "${element.name}" has no context available`);
|
|
1255
|
-
};
|
|
1256
|
-
if (this.hasProtections && adapters.authAdapter) {
|
|
1257
|
-
const decoded = adapters.authAdapter.getDecoded();
|
|
1258
|
-
if (decoded) {
|
|
1259
|
-
context2.$auth = {
|
|
1260
|
-
params: decoded.params,
|
|
1261
|
-
tokenName: decoded.tokenName
|
|
1262
|
-
};
|
|
1263
|
-
} else {
|
|
1264
|
-
throw new Error(`Command "${this.data.name}" requires authentication but no valid token found`);
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
return context2;
|
|
1268
|
-
}
|
|
1269
|
-
toJsonSchema() {
|
|
1270
|
-
const parametersSchema = this.data.params ? this.data.params.toJsonSchema?.() ?? {
|
|
1271
|
-
type: "object",
|
|
1272
|
-
properties: {}
|
|
1273
|
-
} : { type: "object", properties: {} };
|
|
1274
|
-
return {
|
|
1275
|
-
type: "function",
|
|
1276
|
-
name: this.data.name,
|
|
1277
|
-
description: this.data.description ?? undefined,
|
|
1278
|
-
parameters: parametersSchema,
|
|
1279
|
-
strict: true
|
|
1280
|
-
};
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
function command(name) {
|
|
1284
|
-
return new ArcCommand({
|
|
1285
|
-
name,
|
|
1286
|
-
params: null,
|
|
1287
|
-
results: [],
|
|
1288
|
-
queryElements: [],
|
|
1289
|
-
mutationElements: [],
|
|
1290
|
-
protections: []
|
|
1291
|
-
});
|
|
1292
|
-
}
|
|
1293
1256
|
// src/elements/abstract-primitive.ts
|
|
1294
1257
|
class ArcPrimitive extends ArcAbstract {
|
|
1295
1258
|
serialize(value) {
|
|
@@ -1582,8 +1545,28 @@ function number() {
|
|
|
1582
1545
|
return new ArcNumber;
|
|
1583
1546
|
}
|
|
1584
1547
|
|
|
1585
|
-
// src/
|
|
1586
|
-
class
|
|
1548
|
+
// src/fragment/arc-fragment.ts
|
|
1549
|
+
class ArcFragmentBase {
|
|
1550
|
+
is(type) {
|
|
1551
|
+
return this.types.includes(type);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// src/context-element/context-element.ts
|
|
1556
|
+
class ArcContextElement extends ArcFragmentBase {
|
|
1557
|
+
name;
|
|
1558
|
+
types = ["context-element"];
|
|
1559
|
+
get id() {
|
|
1560
|
+
return this.name;
|
|
1561
|
+
}
|
|
1562
|
+
constructor(name) {
|
|
1563
|
+
super();
|
|
1564
|
+
this.name = name;
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
// src/context-element/event/event.ts
|
|
1569
|
+
class ArcEvent extends ArcContextElement {
|
|
1587
1570
|
data;
|
|
1588
1571
|
eventId = id("event");
|
|
1589
1572
|
constructor(data) {
|
|
@@ -1615,11 +1598,523 @@ class ArcEvent extends ArcContextElement {
|
|
|
1615
1598
|
}
|
|
1616
1599
|
};
|
|
1617
1600
|
}
|
|
1618
|
-
protectBy(token, protectionFn) {
|
|
1601
|
+
protectBy(token, protectionFn) {
|
|
1602
|
+
const existingProtections = this.data.protections || [];
|
|
1603
|
+
return new ArcEvent({
|
|
1604
|
+
...this.data,
|
|
1605
|
+
protections: [...existingProtections, { token, protectionFn }]
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
get hasProtections() {
|
|
1609
|
+
return (this.data.protections?.length ?? 0) > 0;
|
|
1610
|
+
}
|
|
1611
|
+
get protections() {
|
|
1612
|
+
return this.data.protections || [];
|
|
1613
|
+
}
|
|
1614
|
+
getProtectionFor(tokenInstance) {
|
|
1615
|
+
if (!this.data.protections) {
|
|
1616
|
+
return null;
|
|
1617
|
+
}
|
|
1618
|
+
for (const protection of this.data.protections) {
|
|
1619
|
+
if (tokenInstance.getTokenDefinition() === protection.token) {
|
|
1620
|
+
return protection.protectionFn(tokenInstance.params);
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
return null;
|
|
1624
|
+
}
|
|
1625
|
+
tags(tagFields) {
|
|
1626
|
+
return new ArcEvent({
|
|
1627
|
+
...this.data,
|
|
1628
|
+
tagFields
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
get tagFields() {
|
|
1632
|
+
return this.data.tagFields || [];
|
|
1633
|
+
}
|
|
1634
|
+
static _sharedSchema = null;
|
|
1635
|
+
static sharedDatabaseStoreSchema() {
|
|
1636
|
+
if (ArcEvent._sharedSchema) {
|
|
1637
|
+
return ArcEvent._sharedSchema;
|
|
1638
|
+
}
|
|
1639
|
+
const eventsSchema = new ArcObject({
|
|
1640
|
+
_id: new ArcString().primaryKey(),
|
|
1641
|
+
type: new ArcString().index(),
|
|
1642
|
+
payload: new ArcString,
|
|
1643
|
+
createdAt: new ArcString().index()
|
|
1644
|
+
});
|
|
1645
|
+
const tagsSchema = new ArcObject({
|
|
1646
|
+
_id: new ArcString().primaryKey(),
|
|
1647
|
+
eventId: new ArcString().index(),
|
|
1648
|
+
tagKey: new ArcString().index(),
|
|
1649
|
+
tagValue: new ArcString().index()
|
|
1650
|
+
});
|
|
1651
|
+
const syncStatusSchema = new ArcObject({
|
|
1652
|
+
_id: new ArcString().primaryKey(),
|
|
1653
|
+
eventId: new ArcString().index(),
|
|
1654
|
+
synced: new ArcBoolean().index(),
|
|
1655
|
+
timestamp: new ArcNumber,
|
|
1656
|
+
retryCount: new ArcNumber
|
|
1657
|
+
});
|
|
1658
|
+
ArcEvent._sharedSchema = {
|
|
1659
|
+
tables: [
|
|
1660
|
+
{ name: "events", schema: eventsSchema },
|
|
1661
|
+
{ name: "event_tags", schema: tagsSchema },
|
|
1662
|
+
{ name: "event_sync_status", schema: syncStatusSchema }
|
|
1663
|
+
]
|
|
1664
|
+
};
|
|
1665
|
+
return ArcEvent._sharedSchema;
|
|
1666
|
+
}
|
|
1667
|
+
databaseStoreSchema() {
|
|
1668
|
+
return ArcEvent.sharedDatabaseStoreSchema();
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
function event(name, payload) {
|
|
1672
|
+
return new ArcEvent({
|
|
1673
|
+
name,
|
|
1674
|
+
payload: payload ? payload instanceof ArcObject ? payload : new ArcObject(payload) : undefined,
|
|
1675
|
+
protections: []
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
// src/context-element/aggregate/aggregate-base.ts
|
|
1680
|
+
class AggregateBase {
|
|
1681
|
+
value;
|
|
1682
|
+
_id;
|
|
1683
|
+
#adapters;
|
|
1684
|
+
constructor(row, adapters) {
|
|
1685
|
+
this._id = row._id;
|
|
1686
|
+
const { _id, ...rest } = row;
|
|
1687
|
+
this.value = rest;
|
|
1688
|
+
this.#adapters = adapters;
|
|
1689
|
+
}
|
|
1690
|
+
toJSON() {
|
|
1691
|
+
return { _id: this._id, ...this.value };
|
|
1692
|
+
}
|
|
1693
|
+
get ctx() {
|
|
1694
|
+
const events = this.constructor.__aggregateEvents ?? [];
|
|
1695
|
+
const adapters = this.#adapters;
|
|
1696
|
+
const result = {};
|
|
1697
|
+
for (const entry of events) {
|
|
1698
|
+
const arcEvent = entry.event;
|
|
1699
|
+
result[arcEvent.name] = {
|
|
1700
|
+
emit: async (payload) => {
|
|
1701
|
+
if (!adapters.eventPublisher) {
|
|
1702
|
+
throw new Error(`Cannot emit event "${arcEvent.name}": no eventPublisher adapter available`);
|
|
1703
|
+
}
|
|
1704
|
+
const eventInstance = {
|
|
1705
|
+
type: arcEvent.name,
|
|
1706
|
+
payload,
|
|
1707
|
+
id: `${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
1708
|
+
createdAt: new Date
|
|
1709
|
+
};
|
|
1710
|
+
await adapters.eventPublisher.publish(eventInstance);
|
|
1711
|
+
}
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
return result;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// src/context-element/aggregate/aggregate-builder.ts
|
|
1719
|
+
function createInlineEvent(eventName, payload) {
|
|
1720
|
+
const po = payload instanceof ArcObject ? payload : new ArcObject(payload);
|
|
1721
|
+
return new ArcEvent({
|
|
1722
|
+
name: eventName,
|
|
1723
|
+
payload: po,
|
|
1724
|
+
protections: []
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
class AggregateBuilder {
|
|
1729
|
+
_name;
|
|
1730
|
+
_id;
|
|
1731
|
+
_schema;
|
|
1732
|
+
_events;
|
|
1733
|
+
_protections;
|
|
1734
|
+
_mutateMethods;
|
|
1735
|
+
_queryMethods;
|
|
1736
|
+
constructor(name, id2, schema, events = [], protections = [], mutateMethods = [], queryMethods = []) {
|
|
1737
|
+
this._name = name;
|
|
1738
|
+
this._id = id2;
|
|
1739
|
+
this._schema = schema;
|
|
1740
|
+
this._events = events;
|
|
1741
|
+
this._protections = protections;
|
|
1742
|
+
this._mutateMethods = mutateMethods;
|
|
1743
|
+
this._queryMethods = queryMethods;
|
|
1744
|
+
}
|
|
1745
|
+
protectBy(token, protectionFn) {
|
|
1746
|
+
return new AggregateBuilder(this._name, this._id, this._schema, this._events, [...this._protections, { token, protectionFn }], this._mutateMethods, this._queryMethods);
|
|
1747
|
+
}
|
|
1748
|
+
event(eventName, payload, handler) {
|
|
1749
|
+
const arcEvent = createInlineEvent(eventName, payload);
|
|
1750
|
+
const entry = {
|
|
1751
|
+
event: arcEvent,
|
|
1752
|
+
handler,
|
|
1753
|
+
isInline: true,
|
|
1754
|
+
isPublic: false
|
|
1755
|
+
};
|
|
1756
|
+
return new AggregateBuilder(this._name, this._id, this._schema, [...this._events, entry], this._protections, this._mutateMethods, this._queryMethods);
|
|
1757
|
+
}
|
|
1758
|
+
publicEvent(eventName, payload, handler) {
|
|
1759
|
+
const arcEvent = createInlineEvent(eventName, payload);
|
|
1760
|
+
const entry = {
|
|
1761
|
+
event: arcEvent,
|
|
1762
|
+
handler,
|
|
1763
|
+
isInline: true,
|
|
1764
|
+
isPublic: true
|
|
1765
|
+
};
|
|
1766
|
+
return new AggregateBuilder(this._name, this._id, this._schema, [...this._events, entry], this._protections, this._mutateMethods, this._queryMethods);
|
|
1767
|
+
}
|
|
1768
|
+
handleEvent(arcEvent, handler) {
|
|
1769
|
+
const entry = {
|
|
1770
|
+
event: arcEvent,
|
|
1771
|
+
handler,
|
|
1772
|
+
isInline: false,
|
|
1773
|
+
isPublic: false
|
|
1774
|
+
};
|
|
1775
|
+
return new AggregateBuilder(this._name, this._id, this._schema, [...this._events, entry], this._protections, this._mutateMethods, this._queryMethods);
|
|
1776
|
+
}
|
|
1777
|
+
mutateMethod(methodName, config, handler) {
|
|
1778
|
+
const paramsObj = config.params instanceof ArcObject ? config.params : new ArcObject(config.params);
|
|
1779
|
+
const entry = {
|
|
1780
|
+
name: methodName,
|
|
1781
|
+
params: paramsObj,
|
|
1782
|
+
handler
|
|
1783
|
+
};
|
|
1784
|
+
return new AggregateBuilder(this._name, this._id, this._schema, this._events, this._protections, [...this._mutateMethods, entry], this._queryMethods);
|
|
1785
|
+
}
|
|
1786
|
+
cron(expression) {
|
|
1787
|
+
if (this._mutateMethods.length === 0) {
|
|
1788
|
+
throw new Error("cron() must be called immediately after mutateMethod()");
|
|
1789
|
+
}
|
|
1790
|
+
const methods = [...this._mutateMethods];
|
|
1791
|
+
const lastIndex = methods.length - 1;
|
|
1792
|
+
methods[lastIndex] = { ...methods[lastIndex], cronExpression: expression };
|
|
1793
|
+
return new AggregateBuilder(this._name, this._id, this._schema, this._events, this._protections, methods, this._queryMethods);
|
|
1794
|
+
}
|
|
1795
|
+
clientQuery(methodName, handler) {
|
|
1796
|
+
const entry = {
|
|
1797
|
+
name: methodName,
|
|
1798
|
+
handler
|
|
1799
|
+
};
|
|
1800
|
+
return new AggregateBuilder(this._name, this._id, this._schema, this._events, this._protections, this._mutateMethods, [...this._queryMethods, entry]);
|
|
1801
|
+
}
|
|
1802
|
+
build() {
|
|
1803
|
+
const name = this._name;
|
|
1804
|
+
const id2 = this._id;
|
|
1805
|
+
const schema = this._schema;
|
|
1806
|
+
const events = this._events;
|
|
1807
|
+
const protections = this._protections;
|
|
1808
|
+
const mutateMethods = this._mutateMethods;
|
|
1809
|
+
const queryMethods = this._queryMethods;
|
|
1810
|
+
const cronMethods = [];
|
|
1811
|
+
for (const method of mutateMethods) {
|
|
1812
|
+
if (method.cronExpression) {
|
|
1813
|
+
cronMethods.push({
|
|
1814
|
+
aggregateName: name,
|
|
1815
|
+
methodName: method.name,
|
|
1816
|
+
cronExpression: method.cronExpression
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
const AggregateClass = class extends AggregateBase {
|
|
1821
|
+
static __aggregateName = name;
|
|
1822
|
+
static __aggregateId = id2;
|
|
1823
|
+
static __aggregateSchema = schema;
|
|
1824
|
+
static __aggregateEvents = events;
|
|
1825
|
+
static __aggregateProtections = protections;
|
|
1826
|
+
static __aggregateMutateMethods = mutateMethods;
|
|
1827
|
+
static __aggregateQueryMethods = queryMethods;
|
|
1828
|
+
static __aggregateCronMethods = cronMethods;
|
|
1829
|
+
static getEvent(eventName) {
|
|
1830
|
+
const entry = events.find((e) => e.isPublic && e.event.name === eventName);
|
|
1831
|
+
return entry?.event;
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
Object.defineProperty(AggregateClass, "name", {
|
|
1835
|
+
value: `${name}Aggregate`
|
|
1836
|
+
});
|
|
1837
|
+
return AggregateClass;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
function aggregate(name, id2, schema) {
|
|
1841
|
+
const schemaObj = schema instanceof ArcObject ? schema : new ArcObject(schema);
|
|
1842
|
+
return new AggregateBuilder(name, id2, schemaObj, [], [], [], []);
|
|
1843
|
+
}
|
|
1844
|
+
// src/context-element/aggregate/aggregate-element.ts
|
|
1845
|
+
class ArcAggregateElement extends ArcContextElement {
|
|
1846
|
+
ctor;
|
|
1847
|
+
schema;
|
|
1848
|
+
constructor(ctor) {
|
|
1849
|
+
super(ctor.__aggregateName);
|
|
1850
|
+
this.ctor = ctor;
|
|
1851
|
+
this.schema = ctor.__aggregateSchema;
|
|
1852
|
+
}
|
|
1853
|
+
getHandlers() {
|
|
1854
|
+
const handlers = {};
|
|
1855
|
+
for (const entry of this.ctor.__aggregateEvents) {
|
|
1856
|
+
handlers[entry.event.name] = entry.handler;
|
|
1857
|
+
}
|
|
1858
|
+
return handlers;
|
|
1859
|
+
}
|
|
1860
|
+
getElements() {
|
|
1861
|
+
return this.ctor.__aggregateEvents.map((e) => e.event);
|
|
1862
|
+
}
|
|
1863
|
+
queryContext(adapters) {
|
|
1864
|
+
const privateQuery = this.buildPrivateQuery(adapters);
|
|
1865
|
+
const queryMethods = this.ctor.__aggregateQueryMethods ?? [];
|
|
1866
|
+
const auth = this.getAuth(adapters);
|
|
1867
|
+
const result = {};
|
|
1868
|
+
for (const method of queryMethods) {
|
|
1869
|
+
result[method.name] = (...args) => {
|
|
1870
|
+
const ctx = { $query: privateQuery, $auth: auth };
|
|
1871
|
+
return method.handler(ctx, ...args);
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
return result;
|
|
1875
|
+
}
|
|
1876
|
+
buildPrivateQuery(adapters) {
|
|
1877
|
+
const viewName = this.ctor.__aggregateName;
|
|
1878
|
+
const protections = this.ctor.__aggregateProtections;
|
|
1879
|
+
const Ctor = this.ctor;
|
|
1880
|
+
const getReadRestrictions = () => {
|
|
1881
|
+
if (protections.length === 0)
|
|
1882
|
+
return null;
|
|
1883
|
+
if (!adapters.authAdapter)
|
|
1884
|
+
return false;
|
|
1885
|
+
const decoded = adapters.authAdapter.getDecoded();
|
|
1886
|
+
if (!decoded)
|
|
1887
|
+
return false;
|
|
1888
|
+
for (const protection of protections) {
|
|
1889
|
+
if (protection.token.name === decoded.tokenName) {
|
|
1890
|
+
const restrictions = protection.protectionFn(decoded.params);
|
|
1891
|
+
if (restrictions === false)
|
|
1892
|
+
return false;
|
|
1893
|
+
return restrictions ?? {};
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
return false;
|
|
1897
|
+
};
|
|
1898
|
+
const applyRestrictions = (options) => {
|
|
1899
|
+
const restrictions = getReadRestrictions();
|
|
1900
|
+
if (restrictions === false)
|
|
1901
|
+
return false;
|
|
1902
|
+
if (!restrictions || Object.keys(restrictions).length === 0) {
|
|
1903
|
+
return options || {};
|
|
1904
|
+
}
|
|
1905
|
+
const where = { ...options?.where || {}, ...restrictions };
|
|
1906
|
+
return { ...options, where };
|
|
1907
|
+
};
|
|
1908
|
+
const findRows = async (options) => {
|
|
1909
|
+
if (adapters.dataStorage) {
|
|
1910
|
+
return adapters.dataStorage.getStore(viewName).find(options);
|
|
1911
|
+
}
|
|
1912
|
+
if (adapters.streamingCache) {
|
|
1913
|
+
return adapters.streamingCache.getStore(viewName).find(options);
|
|
1914
|
+
}
|
|
1915
|
+
if (adapters.queryWire) {
|
|
1916
|
+
return adapters.queryWire.query(viewName, options);
|
|
1917
|
+
}
|
|
1918
|
+
return [];
|
|
1919
|
+
};
|
|
1920
|
+
const schema = this.ctor.__aggregateSchema;
|
|
1921
|
+
const deserializeRow = (row) => {
|
|
1922
|
+
const { _id, ...rest } = row;
|
|
1923
|
+
return { _id, ...schema.deserialize(rest) };
|
|
1924
|
+
};
|
|
1925
|
+
return {
|
|
1926
|
+
find: async (options, mapper) => {
|
|
1927
|
+
const restricted = applyRestrictions(options);
|
|
1928
|
+
if (restricted === false)
|
|
1929
|
+
return [];
|
|
1930
|
+
const rows = await findRows(restricted);
|
|
1931
|
+
return mapper ? rows.map((row) => new mapper(row, adapters)) : rows.map(deserializeRow);
|
|
1932
|
+
},
|
|
1933
|
+
findOne: async (where, mapper) => {
|
|
1934
|
+
const restrictions = getReadRestrictions();
|
|
1935
|
+
if (restrictions === false)
|
|
1936
|
+
return;
|
|
1937
|
+
const mergedWhere = restrictions && Object.keys(restrictions).length > 0 ? { ...where, ...restrictions } : where;
|
|
1938
|
+
const rows = await findRows({ where: mergedWhere });
|
|
1939
|
+
const row = rows[0];
|
|
1940
|
+
if (!row)
|
|
1941
|
+
return;
|
|
1942
|
+
return mapper ? new mapper(row, adapters) : deserializeRow(row);
|
|
1943
|
+
}
|
|
1944
|
+
};
|
|
1945
|
+
}
|
|
1946
|
+
getAuth(adapters) {
|
|
1947
|
+
if (adapters.authAdapter) {
|
|
1948
|
+
const decoded = adapters.authAdapter.getDecoded();
|
|
1949
|
+
return decoded ? { params: decoded.params, tokenName: decoded.tokenName } : { params: {}, tokenName: "" };
|
|
1950
|
+
}
|
|
1951
|
+
return { params: {}, tokenName: "" };
|
|
1952
|
+
}
|
|
1953
|
+
mutateContext(adapters) {
|
|
1954
|
+
const events = this.ctor.__aggregateEvents;
|
|
1955
|
+
const privateQuery = this.buildPrivateQuery(adapters);
|
|
1956
|
+
const auth = this.getAuth(adapters);
|
|
1957
|
+
const buildMutateMethodCtx = () => {
|
|
1958
|
+
const ctx = {};
|
|
1959
|
+
for (const entry of events) {
|
|
1960
|
+
const arcEvent = entry.event;
|
|
1961
|
+
ctx[arcEvent.name] = {
|
|
1962
|
+
emit: async (payload) => {
|
|
1963
|
+
if (!adapters.eventPublisher) {
|
|
1964
|
+
throw new Error(`Cannot emit event "${arcEvent.name}": no eventPublisher adapter available`);
|
|
1965
|
+
}
|
|
1966
|
+
const eventInstance = {
|
|
1967
|
+
type: arcEvent.name,
|
|
1968
|
+
payload,
|
|
1969
|
+
id: `${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
1970
|
+
createdAt: new Date
|
|
1971
|
+
};
|
|
1972
|
+
await adapters.eventPublisher.publish(eventInstance);
|
|
1973
|
+
}
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1976
|
+
ctx.$auth = auth;
|
|
1977
|
+
ctx.$query = privateQuery;
|
|
1978
|
+
return ctx;
|
|
1979
|
+
};
|
|
1980
|
+
const result = {};
|
|
1981
|
+
for (const method of this.ctor.__aggregateMutateMethods ?? []) {
|
|
1982
|
+
result[method.name] = async (params) => {
|
|
1983
|
+
if (!method.handler) {
|
|
1984
|
+
if (!adapters.commandWire) {
|
|
1985
|
+
throw new Error(`Method "${method.name}" is server-only but no commandWire (WebSocket) is available.`);
|
|
1986
|
+
}
|
|
1987
|
+
const wireAuth = adapters.scope ? { scope: adapters.scope.scopeName, token: adapters.scope.getToken() } : undefined;
|
|
1988
|
+
return adapters.commandWire.executeCommand(`${this.ctor.__aggregateName}.${method.name}`, params, wireAuth);
|
|
1989
|
+
}
|
|
1990
|
+
const ctx = buildMutateMethodCtx();
|
|
1991
|
+
return method.handler(ctx, params);
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
return result;
|
|
1995
|
+
}
|
|
1996
|
+
databaseStoreSchema() {
|
|
1997
|
+
const viewName = this.ctor.__aggregateName;
|
|
1998
|
+
const idSchema = new ArcObject({
|
|
1999
|
+
_id: new ArcString().primaryKey()
|
|
2000
|
+
});
|
|
2001
|
+
const viewSchema = idSchema.merge(this.ctor.__aggregateSchema.rawShape || {});
|
|
2002
|
+
const eventSchema = ArcEvent.sharedDatabaseStoreSchema();
|
|
2003
|
+
return {
|
|
2004
|
+
tables: [
|
|
2005
|
+
{
|
|
2006
|
+
name: viewName,
|
|
2007
|
+
schema: viewSchema
|
|
2008
|
+
},
|
|
2009
|
+
...eventSchema.tables
|
|
2010
|
+
]
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
// src/context-element/aggregate/index.ts
|
|
2015
|
+
function aggregateContextElement(ctor) {
|
|
2016
|
+
return new ArcAggregateElement(ctor);
|
|
2017
|
+
}
|
|
2018
|
+
// src/context-element/element-context.ts
|
|
2019
|
+
function buildElementContext(queryElements, mutationElements, adapters) {
|
|
2020
|
+
const context2 = {};
|
|
2021
|
+
const elementMap = new Map;
|
|
2022
|
+
for (const element of queryElements) {
|
|
2023
|
+
if (element.queryContext) {
|
|
2024
|
+
const ctx = element.queryContext(adapters);
|
|
2025
|
+
context2[element.name] = ctx;
|
|
2026
|
+
elementMap.set(element, ctx);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
for (const element of mutationElements) {
|
|
2030
|
+
if (element.mutateContext) {
|
|
2031
|
+
const ctx = element.mutateContext(adapters);
|
|
2032
|
+
context2[element.name] = ctx;
|
|
2033
|
+
elementMap.set(element, ctx);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
context2.get = (element) => {
|
|
2037
|
+
const cached = elementMap.get(element);
|
|
2038
|
+
if (cached)
|
|
2039
|
+
return cached;
|
|
2040
|
+
if (element.queryContext) {
|
|
2041
|
+
const ctx = element.queryContext(adapters);
|
|
2042
|
+
elementMap.set(element, ctx);
|
|
2043
|
+
return ctx;
|
|
2044
|
+
}
|
|
2045
|
+
if (element.mutateContext) {
|
|
2046
|
+
const ctx = element.mutateContext(adapters);
|
|
2047
|
+
elementMap.set(element, ctx);
|
|
2048
|
+
return ctx;
|
|
2049
|
+
}
|
|
2050
|
+
throw new Error(`Element "${element.name}" has no context available`);
|
|
2051
|
+
};
|
|
2052
|
+
context2.query = (element) => {
|
|
2053
|
+
if (!element.queryContext) {
|
|
2054
|
+
throw new Error(`Element "${element.name}" has no queryContext`);
|
|
2055
|
+
}
|
|
2056
|
+
return element.queryContext(adapters);
|
|
2057
|
+
};
|
|
2058
|
+
context2.mutate = (element) => {
|
|
2059
|
+
if (!element.mutateContext) {
|
|
2060
|
+
throw new Error(`Element "${element.name}" has no mutateContext`);
|
|
2061
|
+
}
|
|
2062
|
+
return element.mutateContext(adapters);
|
|
2063
|
+
};
|
|
2064
|
+
return context2;
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
// src/context-element/command/command.ts
|
|
2068
|
+
class ArcCommand extends ArcContextElement {
|
|
2069
|
+
data;
|
|
2070
|
+
constructor(data) {
|
|
2071
|
+
super(data.name);
|
|
2072
|
+
this.data = data;
|
|
2073
|
+
}
|
|
2074
|
+
description(description) {
|
|
2075
|
+
return new ArcCommand({
|
|
2076
|
+
...this.data,
|
|
2077
|
+
description
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
get isPublic() {
|
|
2081
|
+
return !this.hasProtections;
|
|
2082
|
+
}
|
|
2083
|
+
query(elements) {
|
|
2084
|
+
return new ArcCommand({
|
|
2085
|
+
...this.data,
|
|
2086
|
+
queryElements: elements
|
|
2087
|
+
});
|
|
2088
|
+
}
|
|
2089
|
+
mutate(elements) {
|
|
2090
|
+
return new ArcCommand({
|
|
2091
|
+
...this.data,
|
|
2092
|
+
mutationElements: elements
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
withParams(schema) {
|
|
2096
|
+
return new ArcCommand({
|
|
2097
|
+
...this.data,
|
|
2098
|
+
params: schema instanceof ArcObject ? schema : new ArcObject(schema)
|
|
2099
|
+
});
|
|
2100
|
+
}
|
|
2101
|
+
withResult(...schemas) {
|
|
2102
|
+
return new ArcCommand({
|
|
2103
|
+
...this.data,
|
|
2104
|
+
results: schemas
|
|
2105
|
+
});
|
|
2106
|
+
}
|
|
2107
|
+
handle(handler) {
|
|
2108
|
+
return new ArcCommand({
|
|
2109
|
+
...this.data,
|
|
2110
|
+
handler
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
2113
|
+
protectBy(token, check) {
|
|
1619
2114
|
const existingProtections = this.data.protections || [];
|
|
1620
|
-
return new
|
|
2115
|
+
return new ArcCommand({
|
|
1621
2116
|
...this.data,
|
|
1622
|
-
protections: [...existingProtections, { token,
|
|
2117
|
+
protections: [...existingProtections, { token, check }]
|
|
1623
2118
|
});
|
|
1624
2119
|
}
|
|
1625
2120
|
get hasProtections() {
|
|
@@ -1628,67 +2123,86 @@ class ArcEvent extends ArcContextElement {
|
|
|
1628
2123
|
get protections() {
|
|
1629
2124
|
return this.data.protections || [];
|
|
1630
2125
|
}
|
|
1631
|
-
|
|
1632
|
-
if (!this.data.protections) {
|
|
1633
|
-
return
|
|
2126
|
+
async verifyProtections(tokens) {
|
|
2127
|
+
if (!this.data.protections || this.data.protections.length === 0) {
|
|
2128
|
+
return true;
|
|
1634
2129
|
}
|
|
1635
2130
|
for (const protection of this.data.protections) {
|
|
1636
|
-
|
|
1637
|
-
|
|
2131
|
+
const tokenInstance = tokens.find((t) => t.getTokenDefinition() === protection.token);
|
|
2132
|
+
if (!tokenInstance) {
|
|
2133
|
+
return false;
|
|
2134
|
+
}
|
|
2135
|
+
const allowed = await protection.check(tokenInstance);
|
|
2136
|
+
if (!allowed) {
|
|
2137
|
+
return false;
|
|
1638
2138
|
}
|
|
1639
2139
|
}
|
|
1640
|
-
return
|
|
2140
|
+
return true;
|
|
1641
2141
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
2142
|
+
async init(environment, adapters) {
|
|
2143
|
+
if (environment === "server" && this.data.handler && adapters.commandWire?.registerCommandHandler) {
|
|
2144
|
+
adapters.commandWire.registerCommandHandler(this.data.name, async (params) => {
|
|
2145
|
+
const executeFunc = this.mutateContext(adapters);
|
|
2146
|
+
return await executeFunc(params);
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
1647
2149
|
}
|
|
1648
|
-
|
|
1649
|
-
|
|
2150
|
+
mutateContext(adapters) {
|
|
2151
|
+
const executeFunc = async (params) => {
|
|
2152
|
+
if (this.data.handler) {
|
|
2153
|
+
return await this.executeLocally(params, adapters);
|
|
2154
|
+
}
|
|
2155
|
+
if (!adapters.commandWire) {
|
|
2156
|
+
throw new Error(`Command "${this.data.name}" has no handler and no commandWire adapter available for remote execution`);
|
|
2157
|
+
}
|
|
2158
|
+
return await adapters.commandWire.executeCommand(this.data.name, params);
|
|
2159
|
+
};
|
|
2160
|
+
return Object.assign(executeFunc, { params: this.data.params });
|
|
1650
2161
|
}
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
return ArcEvent._sharedSchema;
|
|
2162
|
+
async executeLocally(params, adapters) {
|
|
2163
|
+
if (!this.data.handler) {
|
|
2164
|
+
throw new Error(`Command "${this.data.name}" has no handler`);
|
|
1655
2165
|
}
|
|
1656
|
-
const
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
payload: new ArcString,
|
|
1660
|
-
createdAt: new ArcString().index()
|
|
1661
|
-
});
|
|
1662
|
-
const tagsSchema = new ArcObject({
|
|
1663
|
-
_id: new ArcString().primaryKey(),
|
|
1664
|
-
eventId: new ArcString().index(),
|
|
1665
|
-
tagKey: new ArcString().index(),
|
|
1666
|
-
tagValue: new ArcString().index()
|
|
1667
|
-
});
|
|
1668
|
-
const syncStatusSchema = new ArcObject({
|
|
1669
|
-
_id: new ArcString().primaryKey(),
|
|
1670
|
-
eventId: new ArcString().index(),
|
|
1671
|
-
synced: new ArcBoolean().index(),
|
|
1672
|
-
timestamp: new ArcNumber,
|
|
1673
|
-
retryCount: new ArcNumber
|
|
1674
|
-
});
|
|
1675
|
-
ArcEvent._sharedSchema = {
|
|
1676
|
-
tables: [
|
|
1677
|
-
{ name: "events", schema: eventsSchema },
|
|
1678
|
-
{ name: "event_tags", schema: tagsSchema },
|
|
1679
|
-
{ name: "event_sync_status", schema: syncStatusSchema }
|
|
1680
|
-
]
|
|
1681
|
-
};
|
|
1682
|
-
return ArcEvent._sharedSchema;
|
|
2166
|
+
const context2 = this.buildCommandContext(adapters);
|
|
2167
|
+
const result = await this.data.handler(context2, params);
|
|
2168
|
+
return result;
|
|
1683
2169
|
}
|
|
1684
|
-
|
|
1685
|
-
|
|
2170
|
+
buildCommandContext(adapters) {
|
|
2171
|
+
const context2 = buildElementContext(this.data.queryElements, this.data.mutationElements, adapters);
|
|
2172
|
+
if (this.hasProtections && adapters.authAdapter) {
|
|
2173
|
+
const decoded = adapters.authAdapter.getDecoded();
|
|
2174
|
+
if (decoded) {
|
|
2175
|
+
context2.$auth = {
|
|
2176
|
+
params: decoded.params,
|
|
2177
|
+
tokenName: decoded.tokenName
|
|
2178
|
+
};
|
|
2179
|
+
} else {
|
|
2180
|
+
throw new Error(`Command "${this.data.name}" requires authentication but no valid token found`);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
return context2;
|
|
2184
|
+
}
|
|
2185
|
+
toJsonSchema() {
|
|
2186
|
+
const parametersSchema = this.data.params ? this.data.params.toJsonSchema?.() ?? {
|
|
2187
|
+
type: "object",
|
|
2188
|
+
properties: {}
|
|
2189
|
+
} : { type: "object", properties: {} };
|
|
2190
|
+
return {
|
|
2191
|
+
type: "function",
|
|
2192
|
+
name: this.data.name,
|
|
2193
|
+
description: this.data.description ?? undefined,
|
|
2194
|
+
parameters: parametersSchema,
|
|
2195
|
+
strict: true
|
|
2196
|
+
};
|
|
1686
2197
|
}
|
|
1687
2198
|
}
|
|
1688
|
-
function
|
|
1689
|
-
return new
|
|
2199
|
+
function command(name) {
|
|
2200
|
+
return new ArcCommand({
|
|
1690
2201
|
name,
|
|
1691
|
-
|
|
2202
|
+
params: null,
|
|
2203
|
+
results: [],
|
|
2204
|
+
queryElements: [],
|
|
2205
|
+
mutationElements: [],
|
|
1692
2206
|
protections: []
|
|
1693
2207
|
});
|
|
1694
2208
|
}
|
|
@@ -1941,38 +2455,7 @@ class ArcRoute extends ArcContextElement {
|
|
|
1941
2455
|
return true;
|
|
1942
2456
|
}
|
|
1943
2457
|
buildContext(adapters, authParams) {
|
|
1944
|
-
const context2 =
|
|
1945
|
-
const elementMap = new Map;
|
|
1946
|
-
for (const element of this.data.queryElements) {
|
|
1947
|
-
if (element.queryContext) {
|
|
1948
|
-
const elementContext = element.queryContext(adapters);
|
|
1949
|
-
context2[element.name] = elementContext;
|
|
1950
|
-
elementMap.set(element, elementContext);
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
for (const element of this.data.mutationElements) {
|
|
1954
|
-
if (element.mutateContext) {
|
|
1955
|
-
const elementContext = element.mutateContext(adapters);
|
|
1956
|
-
context2[element.name] = elementContext;
|
|
1957
|
-
elementMap.set(element, elementContext);
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
context2.get = (element) => {
|
|
1961
|
-
const cached = elementMap.get(element);
|
|
1962
|
-
if (cached)
|
|
1963
|
-
return cached;
|
|
1964
|
-
if (element.queryContext) {
|
|
1965
|
-
const ctx = element.queryContext(adapters);
|
|
1966
|
-
elementMap.set(element, ctx);
|
|
1967
|
-
return ctx;
|
|
1968
|
-
}
|
|
1969
|
-
if (element.mutateContext) {
|
|
1970
|
-
const ctx = element.mutateContext(adapters);
|
|
1971
|
-
elementMap.set(element, ctx);
|
|
1972
|
-
return ctx;
|
|
1973
|
-
}
|
|
1974
|
-
throw new Error(`Element "${element.name}" has no context available`);
|
|
1975
|
-
};
|
|
2458
|
+
const context2 = buildElementContext(this.data.queryElements, this.data.mutationElements, adapters);
|
|
1976
2459
|
if (authParams) {
|
|
1977
2460
|
context2.$auth = authParams;
|
|
1978
2461
|
}
|
|
@@ -2394,6 +2877,9 @@ class StoreState {
|
|
|
2394
2877
|
applySerializedChanges(changes) {
|
|
2395
2878
|
return Promise.all(changes.map((change) => this.applyChange(change)));
|
|
2396
2879
|
}
|
|
2880
|
+
get listenerCount() {
|
|
2881
|
+
return this.listeners.size;
|
|
2882
|
+
}
|
|
2397
2883
|
unsubscribe(listener4) {
|
|
2398
2884
|
this.listeners.delete(listener4);
|
|
2399
2885
|
}
|
|
@@ -3621,12 +4107,144 @@ class ArcStringEnum extends ArcAbstract {
|
|
|
3621
4107
|
function stringEnum(...values) {
|
|
3622
4108
|
return new ArcStringEnum(values);
|
|
3623
4109
|
}
|
|
4110
|
+
// src/model/context-accessor.ts
|
|
4111
|
+
function buildContextAccessor(context2, adapters, contextMethod, onCall) {
|
|
4112
|
+
const result = {};
|
|
4113
|
+
for (const element2 of context2.elements) {
|
|
4114
|
+
const ctxFn = element2[contextMethod];
|
|
4115
|
+
if (typeof ctxFn !== "function")
|
|
4116
|
+
continue;
|
|
4117
|
+
const methods = ctxFn.call(element2, adapters);
|
|
4118
|
+
if (!methods || typeof methods !== "object")
|
|
4119
|
+
continue;
|
|
4120
|
+
const wrapped = {};
|
|
4121
|
+
for (const [methodName, method] of Object.entries(methods)) {
|
|
4122
|
+
if (typeof method !== "function")
|
|
4123
|
+
continue;
|
|
4124
|
+
wrapped[methodName] = (...args) => onCall({ element: element2.name, method: methodName, args }, () => method(...args));
|
|
4125
|
+
}
|
|
4126
|
+
result[element2.name] = wrapped;
|
|
4127
|
+
}
|
|
4128
|
+
return result;
|
|
4129
|
+
}
|
|
4130
|
+
function executeDescriptor(descriptor, context2, adapters, contextMethod) {
|
|
4131
|
+
const element2 = context2.get(descriptor.element);
|
|
4132
|
+
if (!element2) {
|
|
4133
|
+
throw new Error(`Element '${descriptor.element}' not found in context`);
|
|
4134
|
+
}
|
|
4135
|
+
const ctxFn = element2[contextMethod];
|
|
4136
|
+
if (typeof ctxFn !== "function") {
|
|
4137
|
+
throw new Error(`Element '${descriptor.element}' has no ${contextMethod}`);
|
|
4138
|
+
}
|
|
4139
|
+
const methods = ctxFn.call(element2, adapters);
|
|
4140
|
+
const method = methods?.[descriptor.method];
|
|
4141
|
+
if (typeof method !== "function") {
|
|
4142
|
+
throw new Error(`Method '${descriptor.method}' not found on '${descriptor.element}'`);
|
|
4143
|
+
}
|
|
4144
|
+
return method(...descriptor.args);
|
|
4145
|
+
}
|
|
4146
|
+
|
|
4147
|
+
// src/model/scoped-model.ts
|
|
4148
|
+
class ScopedModel {
|
|
4149
|
+
context;
|
|
4150
|
+
scopeName;
|
|
4151
|
+
parent;
|
|
4152
|
+
authAdapter;
|
|
4153
|
+
scopedAdapters;
|
|
4154
|
+
tokenListeners = new Set;
|
|
4155
|
+
constructor(parent, scopeName) {
|
|
4156
|
+
this.parent = parent;
|
|
4157
|
+
this.context = parent.context;
|
|
4158
|
+
this.scopeName = scopeName;
|
|
4159
|
+
this.authAdapter = new AuthAdapter;
|
|
4160
|
+
this.scopedAdapters = {
|
|
4161
|
+
...parent.getAdapters(),
|
|
4162
|
+
authAdapter: this.authAdapter,
|
|
4163
|
+
scope: this
|
|
4164
|
+
};
|
|
4165
|
+
}
|
|
4166
|
+
setToken(token) {
|
|
4167
|
+
this.authAdapter.setToken(token);
|
|
4168
|
+
const eventWire = this.parent.getAdapters().eventWire;
|
|
4169
|
+
if (eventWire) {
|
|
4170
|
+
eventWire.setScopeToken(this.scopeName, token);
|
|
4171
|
+
if (token && eventWire.getState() === "disconnected") {
|
|
4172
|
+
eventWire.connect();
|
|
4173
|
+
}
|
|
4174
|
+
}
|
|
4175
|
+
for (const listener4 of this.tokenListeners) {
|
|
4176
|
+
listener4();
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
getToken() {
|
|
4180
|
+
return this.authAdapter.getToken();
|
|
4181
|
+
}
|
|
4182
|
+
getDecoded() {
|
|
4183
|
+
return this.authAdapter.getDecoded();
|
|
4184
|
+
}
|
|
4185
|
+
getParams() {
|
|
4186
|
+
return this.authAdapter.getParams();
|
|
4187
|
+
}
|
|
4188
|
+
isAuthenticated() {
|
|
4189
|
+
return this.authAdapter.isAuthenticated();
|
|
4190
|
+
}
|
|
4191
|
+
onTokenChange(listener4) {
|
|
4192
|
+
this.tokenListeners.add(listener4);
|
|
4193
|
+
return () => {
|
|
4194
|
+
this.tokenListeners.delete(listener4);
|
|
4195
|
+
};
|
|
4196
|
+
}
|
|
4197
|
+
getAdapters() {
|
|
4198
|
+
return this.scopedAdapters;
|
|
4199
|
+
}
|
|
4200
|
+
getEnvironment() {
|
|
4201
|
+
return this.parent.getEnvironment();
|
|
4202
|
+
}
|
|
4203
|
+
getAuth() {
|
|
4204
|
+
return { scope: this.scopeName, token: this.getToken() };
|
|
4205
|
+
}
|
|
4206
|
+
executeCommand(name, params) {
|
|
4207
|
+
const wire = this.parent.getAdapters().commandWire;
|
|
4208
|
+
if (!wire) {
|
|
4209
|
+
throw new Error(`Cannot execute command "${name}": no commandWire available.`);
|
|
4210
|
+
}
|
|
4211
|
+
return wire.executeCommand(name, params, this.getAuth());
|
|
4212
|
+
}
|
|
4213
|
+
remoteQuery(viewName, options) {
|
|
4214
|
+
const wire = this.parent.getAdapters().queryWire;
|
|
4215
|
+
if (!wire) {
|
|
4216
|
+
throw new Error(`Cannot query "${viewName}": no queryWire available.`);
|
|
4217
|
+
}
|
|
4218
|
+
return wire.query(viewName, options, this.getAuth());
|
|
4219
|
+
}
|
|
4220
|
+
subscribeQuery(descriptor, callback) {
|
|
4221
|
+
const wire = this.parent.getAdapters().eventWire;
|
|
4222
|
+
if (!wire) {
|
|
4223
|
+
throw new Error(`Cannot subscribe to query: no eventWire available.`);
|
|
4224
|
+
}
|
|
4225
|
+
return wire.subscribeQuery(descriptor, callback, this.scopeName);
|
|
4226
|
+
}
|
|
4227
|
+
get query() {
|
|
4228
|
+
return buildContextAccessor(this.context, this.scopedAdapters, "queryContext", (descriptor) => descriptor);
|
|
4229
|
+
}
|
|
4230
|
+
get command() {
|
|
4231
|
+
return buildContextAccessor(this.context, this.scopedAdapters, "mutateContext", (descriptor) => descriptor);
|
|
4232
|
+
}
|
|
4233
|
+
callQuery(descriptor) {
|
|
4234
|
+
return executeDescriptor(descriptor, this.context, this.scopedAdapters, "queryContext");
|
|
4235
|
+
}
|
|
4236
|
+
callCommand(descriptor) {
|
|
4237
|
+
return executeDescriptor(descriptor, this.context, this.scopedAdapters, "mutateContext");
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
|
|
3624
4241
|
// src/model/model.ts
|
|
3625
4242
|
class Model {
|
|
3626
4243
|
context;
|
|
3627
4244
|
adapters;
|
|
3628
4245
|
environment;
|
|
3629
4246
|
initialized = false;
|
|
4247
|
+
scopes = new Map;
|
|
3630
4248
|
constructor(context2, options) {
|
|
3631
4249
|
this.context = context2;
|
|
3632
4250
|
this.adapters = options.adapters;
|
|
@@ -3653,6 +4271,14 @@ class Model {
|
|
|
3653
4271
|
getEnvironment() {
|
|
3654
4272
|
return this.environment;
|
|
3655
4273
|
}
|
|
4274
|
+
scope(name) {
|
|
4275
|
+
let s = this.scopes.get(name);
|
|
4276
|
+
if (!s) {
|
|
4277
|
+
s = new ScopedModel(this, name);
|
|
4278
|
+
this.scopes.set(name, s);
|
|
4279
|
+
}
|
|
4280
|
+
return s;
|
|
4281
|
+
}
|
|
3656
4282
|
}
|
|
3657
4283
|
// src/model/mutation-executor/mutation-executor.ts
|
|
3658
4284
|
function mutationExecutor(model) {
|
|
@@ -3675,6 +4301,8 @@ class StreamingQueryCache {
|
|
|
3675
4301
|
stores = new Map;
|
|
3676
4302
|
views = [];
|
|
3677
4303
|
activeStreams = new Map;
|
|
4304
|
+
pendingUnsubscribes = new Map;
|
|
4305
|
+
static UNSUBSCRIBE_DELAY_MS = 5000;
|
|
3678
4306
|
registerViews(views) {
|
|
3679
4307
|
this.views = views;
|
|
3680
4308
|
for (const view3 of views) {
|
|
@@ -3697,6 +4325,11 @@ class StreamingQueryCache {
|
|
|
3697
4325
|
return this.activeStreams.has(viewName);
|
|
3698
4326
|
}
|
|
3699
4327
|
registerStream(viewName, createStream) {
|
|
4328
|
+
const pending = this.pendingUnsubscribes.get(viewName);
|
|
4329
|
+
if (pending) {
|
|
4330
|
+
clearTimeout(pending);
|
|
4331
|
+
this.pendingUnsubscribes.delete(viewName);
|
|
4332
|
+
}
|
|
3700
4333
|
const existing = this.activeStreams.get(viewName);
|
|
3701
4334
|
if (existing) {
|
|
3702
4335
|
existing.refCount++;
|
|
@@ -3721,14 +4354,37 @@ class StreamingQueryCache {
|
|
|
3721
4354
|
return;
|
|
3722
4355
|
stream.refCount--;
|
|
3723
4356
|
if (stream.refCount <= 0) {
|
|
3724
|
-
|
|
3725
|
-
|
|
4357
|
+
const timeout = setTimeout(() => {
|
|
4358
|
+
this.pendingUnsubscribes.delete(viewName);
|
|
4359
|
+
const current = this.activeStreams.get(viewName);
|
|
4360
|
+
if (current && current.refCount <= 0) {
|
|
4361
|
+
current.unsubscribe();
|
|
4362
|
+
this.activeStreams.delete(viewName);
|
|
4363
|
+
}
|
|
4364
|
+
}, StreamingQueryCache.UNSUBSCRIBE_DELAY_MS);
|
|
4365
|
+
this.pendingUnsubscribes.set(viewName, timeout);
|
|
3726
4366
|
}
|
|
3727
4367
|
}
|
|
3728
|
-
|
|
4368
|
+
subscribeQuery(descriptor, eventWire, scope) {
|
|
4369
|
+
const key = descriptor.element;
|
|
4370
|
+
const { unsubscribe } = this.registerStream(key, () => {
|
|
4371
|
+
const subId = eventWire.subscribeQuery(descriptor, (data) => {
|
|
4372
|
+
this.setViewData(descriptor.element, data);
|
|
4373
|
+
}, scope);
|
|
4374
|
+
return { unsubscribe: () => eventWire.unsubscribeQuery(subId) };
|
|
4375
|
+
});
|
|
4376
|
+
return unsubscribe;
|
|
4377
|
+
}
|
|
4378
|
+
setViewData(viewName, data) {
|
|
3729
4379
|
const store = this.stores.get(viewName);
|
|
3730
|
-
if (store)
|
|
3731
|
-
|
|
4380
|
+
if (!store)
|
|
4381
|
+
return;
|
|
4382
|
+
if (Array.isArray(data)) {
|
|
4383
|
+
store.setAll(data);
|
|
4384
|
+
} else if (data && typeof data === "object" && "_id" in data) {
|
|
4385
|
+
store.setAll([data]);
|
|
4386
|
+
} else {
|
|
4387
|
+
store.setAll([]);
|
|
3732
4388
|
}
|
|
3733
4389
|
}
|
|
3734
4390
|
async applyEvent(event3) {
|
|
@@ -3775,35 +4431,39 @@ class StreamingQueryCache {
|
|
|
3775
4431
|
class StreamingStore {
|
|
3776
4432
|
data = new Map;
|
|
3777
4433
|
listeners = new Set;
|
|
4434
|
+
initialized = false;
|
|
3778
4435
|
hasData() {
|
|
3779
|
-
return this.
|
|
4436
|
+
return this.initialized;
|
|
3780
4437
|
}
|
|
3781
4438
|
setAll(items) {
|
|
4439
|
+
this.initialized = true;
|
|
3782
4440
|
this.data.clear();
|
|
3783
4441
|
for (const item of items) {
|
|
3784
4442
|
this.data.set(item._id, item);
|
|
3785
4443
|
}
|
|
3786
|
-
this.notifyListeners();
|
|
4444
|
+
this.notifyListeners(null);
|
|
3787
4445
|
}
|
|
3788
4446
|
set(id3, item) {
|
|
3789
4447
|
this.data.set(id3, item);
|
|
3790
|
-
this.notifyListeners();
|
|
4448
|
+
this.notifyListeners([{ type: "set", id: id3, item }]);
|
|
3791
4449
|
}
|
|
3792
4450
|
modify(id3, updates) {
|
|
3793
4451
|
const existing = this.data.get(id3);
|
|
3794
4452
|
if (existing) {
|
|
3795
|
-
|
|
3796
|
-
this.
|
|
4453
|
+
const updated = { ...existing, ...updates };
|
|
4454
|
+
this.data.set(id3, updated);
|
|
4455
|
+
this.notifyListeners([{ type: "set", id: id3, item: updated }]);
|
|
3797
4456
|
}
|
|
3798
4457
|
}
|
|
3799
4458
|
remove(id3) {
|
|
3800
4459
|
if (this.data.delete(id3)) {
|
|
3801
|
-
this.notifyListeners();
|
|
4460
|
+
this.notifyListeners([{ type: "delete", id: id3, item: null }]);
|
|
3802
4461
|
}
|
|
3803
4462
|
}
|
|
3804
4463
|
clear() {
|
|
4464
|
+
this.initialized = false;
|
|
3805
4465
|
this.data.clear();
|
|
3806
|
-
this.notifyListeners();
|
|
4466
|
+
this.notifyListeners(null);
|
|
3807
4467
|
}
|
|
3808
4468
|
find(options = {}) {
|
|
3809
4469
|
let results = Array.from(this.data.values());
|
|
@@ -3822,9 +4482,9 @@ class StreamingStore {
|
|
|
3822
4482
|
this.listeners.delete(listener4);
|
|
3823
4483
|
};
|
|
3824
4484
|
}
|
|
3825
|
-
notifyListeners() {
|
|
4485
|
+
notifyListeners(events) {
|
|
3826
4486
|
for (const listener4 of this.listeners) {
|
|
3827
|
-
listener4();
|
|
4487
|
+
listener4(events);
|
|
3828
4488
|
}
|
|
3829
4489
|
}
|
|
3830
4490
|
}
|
|
@@ -3893,126 +4553,6 @@ class StreamingEventPublisher {
|
|
|
3893
4553
|
}
|
|
3894
4554
|
}
|
|
3895
4555
|
}
|
|
3896
|
-
// src/streaming/streaming-live-query.ts
|
|
3897
|
-
function isStaticView(element2) {
|
|
3898
|
-
return element2 && "items" in element2 && !("getHandlers" in element2);
|
|
3899
|
-
}
|
|
3900
|
-
function streamingLiveQuery(model, queryFn, callback, options) {
|
|
3901
|
-
const { queryWire, cache, authToken } = options;
|
|
3902
|
-
let currentResult = undefined;
|
|
3903
|
-
const unsubscribers = [];
|
|
3904
|
-
const queriedViews = new Set;
|
|
3905
|
-
const staticViews = new Set;
|
|
3906
|
-
const queryContext = new Proxy({}, {
|
|
3907
|
-
get(_target, viewName) {
|
|
3908
|
-
const element2 = model.context.get(viewName);
|
|
3909
|
-
if (!element2) {
|
|
3910
|
-
throw new Error(`View '${viewName}' not found in context`);
|
|
3911
|
-
}
|
|
3912
|
-
if (isStaticView(element2)) {
|
|
3913
|
-
staticViews.add(viewName);
|
|
3914
|
-
return element2.queryContext(model.getAdapters());
|
|
3915
|
-
}
|
|
3916
|
-
queriedViews.add(viewName);
|
|
3917
|
-
return {
|
|
3918
|
-
find: async (findOptions = {}) => {
|
|
3919
|
-
const store = cache.getStore(viewName);
|
|
3920
|
-
return store.find(findOptions);
|
|
3921
|
-
},
|
|
3922
|
-
findOne: async (where) => {
|
|
3923
|
-
const store = cache.getStore(viewName);
|
|
3924
|
-
return store.findOne(where);
|
|
3925
|
-
}
|
|
3926
|
-
};
|
|
3927
|
-
}
|
|
3928
|
-
});
|
|
3929
|
-
const executeQuery = async () => {
|
|
3930
|
-
const result = await queryFn(queryContext);
|
|
3931
|
-
currentResult = result;
|
|
3932
|
-
callback(result);
|
|
3933
|
-
};
|
|
3934
|
-
const setupStreams = async () => {
|
|
3935
|
-
queriedViews.clear();
|
|
3936
|
-
staticViews.clear();
|
|
3937
|
-
await queryFn(queryContext);
|
|
3938
|
-
if (queriedViews.size === 0 && staticViews.size > 0) {
|
|
3939
|
-
executeQuery();
|
|
3940
|
-
return;
|
|
3941
|
-
}
|
|
3942
|
-
for (const viewName of queriedViews) {
|
|
3943
|
-
const store = cache.getStore(viewName);
|
|
3944
|
-
const cacheUnsub = store.subscribe(() => {
|
|
3945
|
-
executeQuery();
|
|
3946
|
-
});
|
|
3947
|
-
unsubscribers.push(cacheUnsub);
|
|
3948
|
-
const { unsubscribe: streamUnsub, wasReused } = cache.registerStream(viewName, () => {
|
|
3949
|
-
return queryWire.stream(viewName, {}, (data) => {
|
|
3950
|
-
cache.setViewData(viewName, data);
|
|
3951
|
-
}, authToken);
|
|
3952
|
-
});
|
|
3953
|
-
unsubscribers.push(streamUnsub);
|
|
3954
|
-
if (wasReused && cache.hasData(viewName)) {
|
|
3955
|
-
executeQuery();
|
|
3956
|
-
}
|
|
3957
|
-
}
|
|
3958
|
-
};
|
|
3959
|
-
setupStreams();
|
|
3960
|
-
return {
|
|
3961
|
-
get result() {
|
|
3962
|
-
return currentResult;
|
|
3963
|
-
},
|
|
3964
|
-
unsubscribe: () => {
|
|
3965
|
-
for (const unsub of unsubscribers) {
|
|
3966
|
-
unsub();
|
|
3967
|
-
}
|
|
3968
|
-
}
|
|
3969
|
-
};
|
|
3970
|
-
}
|
|
3971
|
-
// src/model/live-query/live-query.ts
|
|
3972
|
-
function liveQuery(model, queryFn, callback) {
|
|
3973
|
-
let currentResult = undefined;
|
|
3974
|
-
const adapters = model.getAdapters();
|
|
3975
|
-
if (!adapters.dataStorage && adapters.streamingCache && adapters.queryWire) {
|
|
3976
|
-
return streamingLiveQuery(model, queryFn, callback, {
|
|
3977
|
-
queryWire: adapters.queryWire,
|
|
3978
|
-
cache: adapters.streamingCache,
|
|
3979
|
-
authToken: adapters.authAdapter?.getToken?.() ?? null
|
|
3980
|
-
});
|
|
3981
|
-
}
|
|
3982
|
-
if (!adapters.dataStorage && !adapters.queryWire) {
|
|
3983
|
-
throw new Error("liveQuery requires dataStorage or queryWire adapter");
|
|
3984
|
-
}
|
|
3985
|
-
const observableStorage = adapters.dataStorage ? observeQueries(adapters.dataStorage, () => {
|
|
3986
|
-
executeQuery();
|
|
3987
|
-
}) : null;
|
|
3988
|
-
const observableAdapters = {
|
|
3989
|
-
...adapters,
|
|
3990
|
-
dataStorage: observableStorage ?? undefined
|
|
3991
|
-
};
|
|
3992
|
-
const queryContext = new Proxy({}, {
|
|
3993
|
-
get(_target, viewName) {
|
|
3994
|
-
const element2 = model.context.get(viewName);
|
|
3995
|
-
if (!element2) {
|
|
3996
|
-
throw new Error(`View '${viewName}' not found in context`);
|
|
3997
|
-
}
|
|
3998
|
-
return element2.queryContext(observableAdapters);
|
|
3999
|
-
}
|
|
4000
|
-
});
|
|
4001
|
-
const executeQuery = async () => {
|
|
4002
|
-
const result = await queryFn(queryContext);
|
|
4003
|
-
currentResult = result;
|
|
4004
|
-
callback(result);
|
|
4005
|
-
};
|
|
4006
|
-
Promise.resolve().then(() => executeQuery());
|
|
4007
|
-
return {
|
|
4008
|
-
get result() {
|
|
4009
|
-
return currentResult;
|
|
4010
|
-
},
|
|
4011
|
-
unsubscribe: () => {
|
|
4012
|
-
observableStorage?.clear();
|
|
4013
|
-
}
|
|
4014
|
-
};
|
|
4015
|
-
}
|
|
4016
4556
|
// src/token/secured-data-storage.ts
|
|
4017
4557
|
class SecuredStoreState {
|
|
4018
4558
|
store;
|
|
@@ -4396,7 +4936,6 @@ export {
|
|
|
4396
4936
|
token,
|
|
4397
4937
|
stringEnum,
|
|
4398
4938
|
string,
|
|
4399
|
-
streamingLiveQuery,
|
|
4400
4939
|
staticView,
|
|
4401
4940
|
secureDataStorage,
|
|
4402
4941
|
route,
|
|
@@ -4408,11 +4947,11 @@ export {
|
|
|
4408
4947
|
number,
|
|
4409
4948
|
mutationExecutor,
|
|
4410
4949
|
murmurHash,
|
|
4411
|
-
liveQuery,
|
|
4412
4950
|
listener,
|
|
4413
4951
|
id,
|
|
4414
4952
|
file,
|
|
4415
4953
|
extractDatabaseAgnosticSchema,
|
|
4954
|
+
executeDescriptor,
|
|
4416
4955
|
event,
|
|
4417
4956
|
deepMerge,
|
|
4418
4957
|
date,
|
|
@@ -4421,11 +4960,15 @@ export {
|
|
|
4421
4960
|
context,
|
|
4422
4961
|
command,
|
|
4423
4962
|
checkItemMatchesWhere,
|
|
4963
|
+
buildElementContext,
|
|
4964
|
+
buildContextAccessor,
|
|
4424
4965
|
boolean,
|
|
4425
4966
|
blob,
|
|
4426
4967
|
array,
|
|
4427
4968
|
applyOrderByAndLimit,
|
|
4428
4969
|
any,
|
|
4970
|
+
aggregateContextElement,
|
|
4971
|
+
aggregate,
|
|
4429
4972
|
Wire,
|
|
4430
4973
|
TokenInstance,
|
|
4431
4974
|
TokenCache,
|
|
@@ -4434,6 +4977,7 @@ export {
|
|
|
4434
4977
|
StoreState,
|
|
4435
4978
|
SecuredStoreState,
|
|
4436
4979
|
SecuredDataStorage,
|
|
4980
|
+
ScopedModel,
|
|
4437
4981
|
QueryWire,
|
|
4438
4982
|
ObservableDataStorage,
|
|
4439
4983
|
Model,
|
|
@@ -4460,6 +5004,7 @@ export {
|
|
|
4460
5004
|
ArcNumber,
|
|
4461
5005
|
ArcListener,
|
|
4462
5006
|
ArcId,
|
|
5007
|
+
ArcFragmentBase,
|
|
4463
5008
|
ArcFile,
|
|
4464
5009
|
ArcEvent,
|
|
4465
5010
|
ArcDate,
|
|
@@ -4472,5 +5017,8 @@ export {
|
|
|
4472
5017
|
ArcBlob,
|
|
4473
5018
|
ArcArray,
|
|
4474
5019
|
ArcAny,
|
|
4475
|
-
|
|
5020
|
+
ArcAggregateElement,
|
|
5021
|
+
ArcAbstract,
|
|
5022
|
+
AggregateBuilder,
|
|
5023
|
+
AggregateBase
|
|
4476
5024
|
};
|