@arcote.tech/arc 0.5.1 → 0.5.5
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 +11 -0
- package/dist/adapters/event-publisher.d.ts +5 -0
- package/dist/adapters/event-wire.d.ts +3 -0
- package/dist/context-element/aggregate/aggregate-data.d.ts +3 -0
- package/dist/context-element/aggregate/aggregate-element.d.ts +3 -2
- package/dist/context-element/aggregate/index.d.ts +1 -1
- package/dist/context-element/event/index.d.ts +1 -1
- package/dist/context-element/event/instance.d.ts +19 -0
- package/dist/context-element/function/arc-function-data.d.ts +13 -2
- package/dist/context-element/listener/listener.d.ts +14 -0
- package/dist/context-element/view/view-data.d.ts +5 -5
- package/dist/context-element/view/view.d.ts +3 -2
- package/dist/data-storage/index.d.ts +1 -0
- package/dist/data-storage/scoped-data-storage.d.ts +35 -0
- package/dist/index.js +314 -183
- package/package.json +1 -1
|
@@ -19,6 +19,17 @@ export declare class AuthAdapter {
|
|
|
19
19
|
* Auto-persists to localStorage when available.
|
|
20
20
|
*/
|
|
21
21
|
setToken(token: string | null, scope?: string): void;
|
|
22
|
+
/**
|
|
23
|
+
* Set an already-decoded token for a scope, bypassing JWT parsing.
|
|
24
|
+
*
|
|
25
|
+
* Used by listener auth reconstruction: events carry decoded auth context
|
|
26
|
+
* (tokenName + params) which has been validated at emit time. This method
|
|
27
|
+
* restores that context into a fresh AuthAdapter without requiring the
|
|
28
|
+
* original raw JWT.
|
|
29
|
+
*
|
|
30
|
+
* Does NOT touch localStorage — purely in-memory restoration.
|
|
31
|
+
*/
|
|
32
|
+
setDecoded(decoded: DecodedToken, scope?: string): void;
|
|
22
33
|
/**
|
|
23
34
|
* Load all persisted scope tokens from localStorage.
|
|
24
35
|
* Call once on app init before any queries.
|
|
@@ -27,6 +27,11 @@ export interface StoredEvent {
|
|
|
27
27
|
type: string;
|
|
28
28
|
payload: string;
|
|
29
29
|
createdAt: string;
|
|
30
|
+
/**
|
|
31
|
+
* JSON-serialized EventAuthContext snapshot from the mutation that emitted
|
|
32
|
+
* this event. Null for system events / cron / seed (no active auth scope).
|
|
33
|
+
*/
|
|
34
|
+
authContext: string | null;
|
|
30
35
|
}
|
|
31
36
|
/** Event tag record in the event_tags table */
|
|
32
37
|
export interface StoredEventTag {
|
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
* - Handles reconnection and sync state
|
|
8
8
|
* - Manages per-scope token caching
|
|
9
9
|
*/
|
|
10
|
+
import type { EventAuthContext } from "../context-element/event/instance";
|
|
10
11
|
export interface SyncableEvent {
|
|
11
12
|
localId: string;
|
|
12
13
|
type: string;
|
|
13
14
|
payload: any;
|
|
14
15
|
createdAt: string;
|
|
16
|
+
authContext: EventAuthContext | null;
|
|
15
17
|
}
|
|
16
18
|
export interface ReceivedEvent {
|
|
17
19
|
localId: string;
|
|
@@ -20,6 +22,7 @@ export interface ReceivedEvent {
|
|
|
20
22
|
payload: any;
|
|
21
23
|
createdAt: string;
|
|
22
24
|
clientId: string;
|
|
25
|
+
authContext: EventAuthContext | null;
|
|
23
26
|
}
|
|
24
27
|
import type { ContextDescriptor } from "../model/context-accessor";
|
|
25
28
|
type EventWireState = "disconnected" | "connecting" | "connected";
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { ArcIdAny } from "../../elements/id";
|
|
8
8
|
import type { ArcObjectAny } from "../../elements/object";
|
|
9
|
+
import type { ArcContextElement } from "../context-element";
|
|
9
10
|
import type { Simplify } from "../../utils";
|
|
10
11
|
import type { ArcEventAny } from "../event/event";
|
|
11
12
|
import type { ArcEventInstance } from "../event/instance";
|
|
@@ -34,6 +35,8 @@ export interface AggregateMutateMethodEntry {
|
|
|
34
35
|
readonly handler: Function | false | null;
|
|
35
36
|
readonly result?: any;
|
|
36
37
|
readonly cronExpression?: string;
|
|
38
|
+
readonly queryElements?: ArcContextElement<any>[];
|
|
39
|
+
readonly mutationElements?: ArcContextElement<any>[];
|
|
37
40
|
}
|
|
38
41
|
/**
|
|
39
42
|
* Cron method entry extracted at build time.
|
|
@@ -111,7 +111,7 @@ export declare class ArcAggregateElement<Name extends string = string, Id extend
|
|
|
111
111
|
data: any[];
|
|
112
112
|
version: number;
|
|
113
113
|
});
|
|
114
|
-
protectBy<T extends ArcTokenAny>(token: T, protectionFn: ViewProtectionFn<
|
|
114
|
+
protectBy<T extends ArcTokenAny>(token: T, protectionFn: ViewProtectionFn<T>): ArcAggregateElement<Name, Id, Schema, Events, MutateMethods, QueryMethods>;
|
|
115
115
|
event<const EventName extends string, PayloadShape extends ArcRawShape>(eventName: EventName, payload: PayloadShape, handler: (ctx: ArcViewHandlerContext<Id, Schema>, event: ArcEventInstance<InlineArcEvent<EventName, PayloadShape>>) => Promise<void>): ArcAggregateElement<Name, Id, Schema, [
|
|
116
116
|
...Events,
|
|
117
117
|
AggregateEventEntry<InlineArcEvent<EventName, PayloadShape>, Id, Schema, false>
|
|
@@ -196,8 +196,9 @@ export declare class ArcAggregateElement<Name extends string = string, Id extend
|
|
|
196
196
|
get cronMethods(): AggregateCronMethodEntry[];
|
|
197
197
|
queryContext(adapters: ModelAdapters): AggregateQueryContext<QueryMethods>;
|
|
198
198
|
private buildPrivateQuery;
|
|
199
|
-
private
|
|
199
|
+
private isScopeDenied;
|
|
200
200
|
private getAuth;
|
|
201
|
+
private getScopeRestrictions;
|
|
201
202
|
mutateContext(adapters: ModelAdapters): AggregateMutateMethodContext<MutateMethods>;
|
|
202
203
|
databaseStoreSchema(): DatabaseStoreSchema;
|
|
203
204
|
getSeeds(): {
|
|
@@ -11,5 +11,5 @@ export { aggregate } from "./aggregate-builder";
|
|
|
11
11
|
export { ArcAggregateElement, type AggregateConstructor, type AggregateConstructorAny, type AggregateData, type AggregateInstanceCtx, type AggregateRow, } from "./aggregate-element";
|
|
12
12
|
export { AggregateBase } from "./aggregate-base";
|
|
13
13
|
export type { AggregateCronMethodEntry, AggregateEventEntry, AggregateEventHandler, AggregateMutateMethodContext, AggregateMutateMethodEntry, AggregateQueryContext, AggregateQueryMethodEntry, AggregateStaticConfig, } from "./aggregate-data";
|
|
14
|
-
export type { ArcEventPayload } from "../event/instance";
|
|
14
|
+
export type { ArcEventPayload, EventAuthContext, EventMetadata } from "../event/instance";
|
|
15
15
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ArcEvent, event, type ArcEventAny } from "./event";
|
|
2
2
|
export type { ArcEventData } from "./event-data";
|
|
3
|
-
export type { ArcEventInstance, ArcEventPayload, EventMetadata, } from "./instance";
|
|
3
|
+
export type { ArcEventInstance, ArcEventPayload, EventAuthContext, EventMetadata, } from "./instance";
|
|
4
4
|
export type { ArcObjectAny, ArcRawShape } from "../../elements/object";
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
import type { $type } from "../../utils/types/get-type";
|
|
2
2
|
import type { ArcEventAny } from "./event";
|
|
3
|
+
/**
|
|
4
|
+
* Auth context captured at event emission time.
|
|
5
|
+
* Stored alongside the event in the event store so async listeners can
|
|
6
|
+
* reconstruct the original protection scope without losing security guarantees.
|
|
7
|
+
*
|
|
8
|
+
* Trust model: the event store is trusted (same threat surface as the
|
|
9
|
+
* aggregate state DB). No cryptographic re-verification — payload is
|
|
10
|
+
* authoritative because only authorized mutations could have written it.
|
|
11
|
+
*/
|
|
12
|
+
export type EventAuthContext = {
|
|
13
|
+
tokenName: string;
|
|
14
|
+
params: Record<string, any>;
|
|
15
|
+
};
|
|
3
16
|
/**
|
|
4
17
|
* Event metadata attached to every event instance
|
|
5
18
|
*/
|
|
6
19
|
export type EventMetadata = {
|
|
7
20
|
id: string;
|
|
8
21
|
createdAt: Date;
|
|
22
|
+
/**
|
|
23
|
+
* Auth context that authorized the mutation which emitted this event.
|
|
24
|
+
* `null` for system events emitted without an active auth scope —
|
|
25
|
+
* listeners with `protectBy()` will deny access for null-auth events.
|
|
26
|
+
*/
|
|
27
|
+
authContext: EventAuthContext | null;
|
|
9
28
|
};
|
|
10
29
|
/**
|
|
11
30
|
* Event instance type - represents an actual event occurrence
|
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
import type { ArcObjectAny } from "../../elements/object";
|
|
2
2
|
import type { ArcToken, ArcTokenAny } from "../../token/token";
|
|
3
|
-
import type { TokenInstanceAny } from "../../token/token-instance";
|
|
3
|
+
import type { TokenInstance, TokenInstanceAny } from "../../token/token-instance";
|
|
4
4
|
import type { $type } from "../../utils/types/get-type";
|
|
5
5
|
import type { ArcContextElement } from "../context-element";
|
|
6
6
|
import type { ElementContext } from "../element-context";
|
|
7
|
+
/**
|
|
8
|
+
* Extract typed TokenInstance from a token definition.
|
|
9
|
+
*
|
|
10
|
+
* Given `ArcToken<"workspace", { workspaceId: ArcId, role: ArcString }, ...>`,
|
|
11
|
+
* returns `TokenInstance<{ workspaceId: string, role: string }, RuleNames>`.
|
|
12
|
+
*/
|
|
13
|
+
export type TypedTokenInstance<T extends ArcTokenAny> = T extends ArcToken<any, infer ParamsShape, any, infer Rules> ? TokenInstance<$type<import("../../elements/object").ArcObject<ParamsShape>>, Rules extends Record<string, any> ? Extract<keyof Rules, string> : string> : TokenInstanceAny;
|
|
7
14
|
/**
|
|
8
15
|
* Unified protection check function.
|
|
9
16
|
*
|
|
17
|
+
* Callback receives a **typed** TokenInstance — `token.params.workspaceId`
|
|
18
|
+
* has full autocomplete and type safety.
|
|
19
|
+
*
|
|
10
20
|
* Return value determines behavior:
|
|
11
21
|
* - `false` → access denied
|
|
12
22
|
* - `true` → access allowed, no data filter
|
|
13
23
|
* - `Record` → access allowed + merged into WHERE clause (query context)
|
|
24
|
+
* + validated on writes (scope enforcement)
|
|
14
25
|
*/
|
|
15
|
-
export type FnProtectionCheck<T extends ArcTokenAny> = (tokenInstance:
|
|
26
|
+
export type FnProtectionCheck<T extends ArcTokenAny> = (tokenInstance: TypedTokenInstance<T>) => boolean | Record<string, unknown> | Promise<boolean | Record<string, unknown>>;
|
|
16
27
|
/**
|
|
17
28
|
* Protection configuration for an ArcFunction.
|
|
18
29
|
*/
|
|
@@ -76,6 +76,20 @@ export declare class ArcListener<const Data extends ArcListenerData> extends Arc
|
|
|
76
76
|
get isAsync(): boolean;
|
|
77
77
|
init(environment: ArcEnvironment, adapters: ModelAdapters): Promise<void>;
|
|
78
78
|
private handleEvent;
|
|
79
|
+
/**
|
|
80
|
+
* Build scoped adapters for the listener handler.
|
|
81
|
+
*
|
|
82
|
+
* Sync listeners run inline with the originating mutation and inherit the
|
|
83
|
+
* caller's auth context directly. Async listeners run after the mutation
|
|
84
|
+
* completes and lose request scope — they reconstruct auth from the
|
|
85
|
+
* `authContext` snapshotted into the event at emit time
|
|
86
|
+
* (see aggregate-element.ts emit handler).
|
|
87
|
+
*
|
|
88
|
+
* Events with `authContext: null` (system events / cron / seed) leave the
|
|
89
|
+
* adapters without auth — listeners with `protectBy()` will deny access
|
|
90
|
+
* downstream at the protection layer.
|
|
91
|
+
*/
|
|
92
|
+
private buildScopedAdapters;
|
|
79
93
|
destroy(): void;
|
|
80
94
|
}
|
|
81
95
|
export declare function listener<const Name extends string>(name: Name): ArcListener<{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { WhereCondition } from "../../data-storage/find-options";
|
|
2
2
|
import type { ArcObjectAny } from "../../elements/object";
|
|
3
|
-
import type { ArcTokenAny } from "../../token/token";
|
|
3
|
+
import type { ArcToken, ArcTokenAny } from "../../token/token";
|
|
4
4
|
import type { ArcContextElement } from "../context-element";
|
|
5
5
|
/**
|
|
6
6
|
* Protection config for views
|
|
@@ -9,11 +9,11 @@ import type { ArcContextElement } from "../context-element";
|
|
|
9
9
|
*/
|
|
10
10
|
export type ViewProtectionConfig = WhereCondition | false;
|
|
11
11
|
/**
|
|
12
|
-
* Protection function for views
|
|
13
|
-
* Receives token params and returns read conditions (where clause)
|
|
14
|
-
* Return false to deny access entirely
|
|
12
|
+
* Protection function for views/aggregates.
|
|
13
|
+
* Receives typed token params and returns read/write conditions (where clause).
|
|
14
|
+
* Return false to deny access entirely.
|
|
15
15
|
*/
|
|
16
|
-
export type ViewProtectionFn<
|
|
16
|
+
export type ViewProtectionFn<T extends ArcTokenAny = ArcTokenAny> = (params: T extends ArcToken<any, infer P, any, any> ? import("../../utils/types/get-type").$type<import("../../elements/object").ArcObject<P>> : Record<string, any>) => ViewProtectionConfig;
|
|
17
17
|
/**
|
|
18
18
|
* View protection configuration
|
|
19
19
|
*/
|
|
@@ -5,7 +5,7 @@ import type { ArcTokenAny } from "../../token/token";
|
|
|
5
5
|
import type { TokenInstanceAny } from "../../token/token-instance";
|
|
6
6
|
import type { Merge } from "../../utils";
|
|
7
7
|
import type { ArcViewHandlerContext, ArcViewItem, ArcViewQueryContext } from "./view-context";
|
|
8
|
-
import type { ArcViewData, ViewProtectionFn } from "./view-data";
|
|
8
|
+
import type { ArcViewData, ViewProtection, ViewProtectionFn } from "./view-data";
|
|
9
9
|
/**
|
|
10
10
|
* Arc View - Read-optimized projection of events
|
|
11
11
|
*
|
|
@@ -104,6 +104,7 @@ export declare class ArcView<const Data extends ArcViewData> extends ArcContextE
|
|
|
104
104
|
* @returns Context object with find/findOne methods
|
|
105
105
|
*/
|
|
106
106
|
queryContext(adapters: any): ArcViewQueryContext<Data["id"], Data["schema"]>;
|
|
107
|
+
private resolveProtection;
|
|
107
108
|
/**
|
|
108
109
|
* Get event handlers for this view
|
|
109
110
|
* Used by the framework to set up event listeners
|
|
@@ -144,7 +145,7 @@ export declare class ArcView<const Data extends ArcViewData> extends ArcContextE
|
|
|
144
145
|
/**
|
|
145
146
|
* Get all protection configurations
|
|
146
147
|
*/
|
|
147
|
-
get protections():
|
|
148
|
+
get protections(): ViewProtection[];
|
|
148
149
|
/**
|
|
149
150
|
* Get protection config for a specific token instance
|
|
150
151
|
*
|
|
@@ -9,5 +9,6 @@ export * from "./query-result-resolver";
|
|
|
9
9
|
export * from "./schema-extraction";
|
|
10
10
|
export * from "./store-state-fork";
|
|
11
11
|
export * from "./store-state-master";
|
|
12
|
+
export * from "./scoped-data-storage";
|
|
12
13
|
export * from "./store-state.abstract";
|
|
13
14
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { DataStorage, type QueryListenerCallback, type StoreStateChange } from "./data-storage.abstract";
|
|
2
|
+
import type { StoreState } from "./store-state.abstract";
|
|
3
|
+
import type { FindOptions } from "./find-options";
|
|
4
|
+
import type { ForkedDataStorage } from "./data-storage-forked";
|
|
5
|
+
import type { ReadTransaction, ReadWriteTransaction } from "./database-adapter";
|
|
6
|
+
export type ScopeRestrictions = Record<string, unknown>;
|
|
7
|
+
export type StorePermission = "read" | "read-write";
|
|
8
|
+
export declare class ScopedStore<Item extends {
|
|
9
|
+
_id: string;
|
|
10
|
+
}> {
|
|
11
|
+
#private;
|
|
12
|
+
constructor(inner: StoreState<Item>, restrictions: ScopeRestrictions, canWrite: boolean);
|
|
13
|
+
get storeName(): string;
|
|
14
|
+
find(options?: FindOptions<Item>, listener?: QueryListenerCallback<Item>): Promise<Item[]>;
|
|
15
|
+
set(item: Item): Promise<void>;
|
|
16
|
+
remove(id: string): Promise<void>;
|
|
17
|
+
modify(id: string, data: Partial<Item>): Promise<{
|
|
18
|
+
from: Item | null;
|
|
19
|
+
to: Item | null;
|
|
20
|
+
}>;
|
|
21
|
+
applyChanges(changes: StoreStateChange<Item>[]): Promise<void>;
|
|
22
|
+
unsubscribe(listener: QueryListenerCallback<Item>): void;
|
|
23
|
+
}
|
|
24
|
+
export declare class ScopedDataStorage extends DataStorage {
|
|
25
|
+
#private;
|
|
26
|
+
constructor(inner: DataStorage, allowedStores: Map<string, StorePermission>, restrictions: ScopeRestrictions);
|
|
27
|
+
getStore<Item extends {
|
|
28
|
+
_id: string;
|
|
29
|
+
}>(storeName: string): StoreState<Item>;
|
|
30
|
+
fork(): ForkedDataStorage;
|
|
31
|
+
getReadTransaction(): Promise<ReadTransaction>;
|
|
32
|
+
getReadWriteTransaction(): Promise<ReadWriteTransaction>;
|
|
33
|
+
}
|
|
34
|
+
export declare function createScopedDataStorage(inner: DataStorage, queryElementNames: string[], mutateElementNames: string[], restrictions: ScopeRestrictions): ScopedDataStorage;
|
|
35
|
+
//# sourceMappingURL=scoped-data-storage.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -50,6 +50,9 @@ class AuthAdapter {
|
|
|
50
50
|
localStorage.removeItem(TOKEN_PREFIX + scope);
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
+
setDecoded(decoded, scope = "default") {
|
|
54
|
+
this.scopes.set(scope, { raw: "", decoded });
|
|
55
|
+
}
|
|
53
56
|
loadPersisted() {
|
|
54
57
|
if (!hasLocalStorage())
|
|
55
58
|
return;
|
|
@@ -325,7 +328,8 @@ class EventWire {
|
|
|
325
328
|
localId: e.localId,
|
|
326
329
|
type: e.type,
|
|
327
330
|
payload: e.payload,
|
|
328
|
-
createdAt: e.createdAt
|
|
331
|
+
createdAt: e.createdAt,
|
|
332
|
+
authContext: e.authContext
|
|
329
333
|
}))
|
|
330
334
|
}));
|
|
331
335
|
}
|
|
@@ -541,11 +545,13 @@ class LocalEventPublisher {
|
|
|
541
545
|
}
|
|
542
546
|
async publish(event) {
|
|
543
547
|
const allChanges = [];
|
|
548
|
+
const eventAuthContext = event.authContext ?? null;
|
|
544
549
|
const storedEvent = {
|
|
545
550
|
_id: event.id,
|
|
546
551
|
type: event.type,
|
|
547
552
|
payload: JSON.stringify(event.payload),
|
|
548
|
-
createdAt: event.createdAt.toISOString()
|
|
553
|
+
createdAt: event.createdAt.toISOString(),
|
|
554
|
+
authContext: eventAuthContext ? JSON.stringify(eventAuthContext) : null
|
|
549
555
|
};
|
|
550
556
|
allChanges.push({
|
|
551
557
|
store: EVENT_TABLES.events,
|
|
@@ -1270,6 +1276,122 @@ function object(element) {
|
|
|
1270
1276
|
return new ArcObject(element);
|
|
1271
1277
|
}
|
|
1272
1278
|
|
|
1279
|
+
// src/data-storage/data-storage.abstract.ts
|
|
1280
|
+
class DataStorage {
|
|
1281
|
+
async commitChanges(changes) {
|
|
1282
|
+
await Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applyChanges(changes2)));
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// src/data-storage/scoped-data-storage.ts
|
|
1287
|
+
class ScopedStore {
|
|
1288
|
+
#inner;
|
|
1289
|
+
#restrictions;
|
|
1290
|
+
#canWrite;
|
|
1291
|
+
#storeName;
|
|
1292
|
+
constructor(inner, restrictions, canWrite) {
|
|
1293
|
+
this.#inner = inner;
|
|
1294
|
+
this.#restrictions = restrictions;
|
|
1295
|
+
this.#canWrite = canWrite;
|
|
1296
|
+
this.#storeName = inner.storeName;
|
|
1297
|
+
}
|
|
1298
|
+
get storeName() {
|
|
1299
|
+
return this.#storeName;
|
|
1300
|
+
}
|
|
1301
|
+
async find(options, listener) {
|
|
1302
|
+
const restricted = this.#applyReadRestrictions(options);
|
|
1303
|
+
return this.#inner.find(restricted, listener);
|
|
1304
|
+
}
|
|
1305
|
+
async set(item) {
|
|
1306
|
+
this.#assertWriteAccess();
|
|
1307
|
+
this.#validateScopeFields(item);
|
|
1308
|
+
return this.#inner.set(item);
|
|
1309
|
+
}
|
|
1310
|
+
async remove(id) {
|
|
1311
|
+
this.#assertWriteAccess();
|
|
1312
|
+
return this.#inner.remove(id);
|
|
1313
|
+
}
|
|
1314
|
+
async modify(id, data) {
|
|
1315
|
+
this.#assertWriteAccess();
|
|
1316
|
+
this.#validateScopeFields(data);
|
|
1317
|
+
return this.#inner.modify(id, data);
|
|
1318
|
+
}
|
|
1319
|
+
async applyChanges(changes) {
|
|
1320
|
+
this.#assertWriteAccess();
|
|
1321
|
+
for (const change of changes) {
|
|
1322
|
+
if (change.type === "set") {
|
|
1323
|
+
this.#validateScopeFields(change.data);
|
|
1324
|
+
} else if (change.type === "modify") {
|
|
1325
|
+
this.#validateScopeFields(change.data);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
return this.#inner.applyChanges(changes);
|
|
1329
|
+
}
|
|
1330
|
+
unsubscribe(listener) {
|
|
1331
|
+
this.#inner.unsubscribe(listener);
|
|
1332
|
+
}
|
|
1333
|
+
#applyReadRestrictions(options) {
|
|
1334
|
+
if (Object.keys(this.#restrictions).length === 0) {
|
|
1335
|
+
return options ?? {};
|
|
1336
|
+
}
|
|
1337
|
+
return {
|
|
1338
|
+
...options,
|
|
1339
|
+
where: { ...options?.where ?? {}, ...this.#restrictions }
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
#assertWriteAccess() {
|
|
1343
|
+
if (!this.#canWrite) {
|
|
1344
|
+
throw new Error(`Scope violation: write access denied to store "${this.#storeName}" (read-only)`);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
#validateScopeFields(data) {
|
|
1348
|
+
for (const [key, value] of Object.entries(this.#restrictions)) {
|
|
1349
|
+
if (key in data && data[key] !== value) {
|
|
1350
|
+
throw new Error(`Scope violation: field "${key}" must be "${value}", got "${data[key]}" in store "${this.#storeName}"`);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
class ScopedDataStorage extends DataStorage {
|
|
1357
|
+
#inner;
|
|
1358
|
+
#allowedStores;
|
|
1359
|
+
#restrictions;
|
|
1360
|
+
constructor(inner, allowedStores, restrictions) {
|
|
1361
|
+
super();
|
|
1362
|
+
this.#inner = inner;
|
|
1363
|
+
this.#allowedStores = allowedStores;
|
|
1364
|
+
this.#restrictions = restrictions;
|
|
1365
|
+
}
|
|
1366
|
+
getStore(storeName) {
|
|
1367
|
+
const permission = this.#allowedStores.get(storeName);
|
|
1368
|
+
if (!permission) {
|
|
1369
|
+
throw new Error(`Scope violation: access denied to store "${storeName}" (not declared in query/mutate)`);
|
|
1370
|
+
}
|
|
1371
|
+
const inner = this.#inner.getStore(storeName);
|
|
1372
|
+
return new ScopedStore(inner, this.#restrictions, permission === "read-write");
|
|
1373
|
+
}
|
|
1374
|
+
fork() {
|
|
1375
|
+
return this.#inner.fork();
|
|
1376
|
+
}
|
|
1377
|
+
getReadTransaction() {
|
|
1378
|
+
return this.#inner.getReadTransaction();
|
|
1379
|
+
}
|
|
1380
|
+
getReadWriteTransaction() {
|
|
1381
|
+
return this.#inner.getReadWriteTransaction();
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
function createScopedDataStorage(inner, queryElementNames, mutateElementNames, restrictions) {
|
|
1385
|
+
const allowedStores = new Map;
|
|
1386
|
+
for (const name of queryElementNames) {
|
|
1387
|
+
allowedStores.set(name, "read");
|
|
1388
|
+
}
|
|
1389
|
+
for (const name of mutateElementNames) {
|
|
1390
|
+
allowedStores.set(name, "read-write");
|
|
1391
|
+
}
|
|
1392
|
+
return new ScopedDataStorage(inner, allowedStores, restrictions);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1273
1395
|
// src/elements/abstract-primitive.ts
|
|
1274
1396
|
class ArcPrimitive extends ArcAbstract {
|
|
1275
1397
|
serialize(value) {
|
|
@@ -1460,6 +1582,56 @@ class ArcContextElement extends ArcFragmentBase {
|
|
|
1460
1582
|
}
|
|
1461
1583
|
}
|
|
1462
1584
|
|
|
1585
|
+
// src/context-element/element-context.ts
|
|
1586
|
+
function buildElementContext(queryElements, mutationElements, adapters) {
|
|
1587
|
+
const queryMap = new Map;
|
|
1588
|
+
const mutateMap = new Map;
|
|
1589
|
+
const queryProps = {};
|
|
1590
|
+
for (const element of queryElements) {
|
|
1591
|
+
if (element.queryContext) {
|
|
1592
|
+
const ctx = element.queryContext(adapters);
|
|
1593
|
+
queryProps[element.name] = ctx;
|
|
1594
|
+
queryMap.set(element, ctx);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
const mutateProps = {};
|
|
1598
|
+
for (const element of mutationElements) {
|
|
1599
|
+
if (element.mutateContext) {
|
|
1600
|
+
const ctx = element.mutateContext(adapters);
|
|
1601
|
+
mutateProps[element.name] = ctx;
|
|
1602
|
+
mutateMap.set(element, ctx);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
const queryFn = (element) => {
|
|
1606
|
+
const cached = queryMap.get(element);
|
|
1607
|
+
if (cached)
|
|
1608
|
+
return cached;
|
|
1609
|
+
if (element.queryContext) {
|
|
1610
|
+
const ctx = element.queryContext(adapters);
|
|
1611
|
+
queryMap.set(element, ctx);
|
|
1612
|
+
return ctx;
|
|
1613
|
+
}
|
|
1614
|
+
throw new Error(`Element "${element.name}" has no queryContext`);
|
|
1615
|
+
};
|
|
1616
|
+
Object.assign(queryFn, queryProps);
|
|
1617
|
+
const mutateFn = (element) => {
|
|
1618
|
+
const cached = mutateMap.get(element);
|
|
1619
|
+
if (cached)
|
|
1620
|
+
return cached;
|
|
1621
|
+
if (element.mutateContext) {
|
|
1622
|
+
const ctx = element.mutateContext(adapters);
|
|
1623
|
+
mutateMap.set(element, ctx);
|
|
1624
|
+
return ctx;
|
|
1625
|
+
}
|
|
1626
|
+
throw new Error(`Element "${element.name}" has no mutateContext`);
|
|
1627
|
+
};
|
|
1628
|
+
Object.assign(mutateFn, mutateProps);
|
|
1629
|
+
return {
|
|
1630
|
+
query: queryFn,
|
|
1631
|
+
mutate: mutateFn
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1463
1635
|
// src/elements/boolean.ts
|
|
1464
1636
|
class ArcBoolean extends ArcPrimitive {
|
|
1465
1637
|
hasToBeTrue() {
|
|
@@ -1609,11 +1781,14 @@ class ArcEvent extends ArcContextElement {
|
|
|
1609
1781
|
if (!adapters.eventPublisher) {
|
|
1610
1782
|
throw new Error(`Event "${this.data.name}" cannot be emitted: no eventPublisher adapter available`);
|
|
1611
1783
|
}
|
|
1784
|
+
const decoded = adapters.authAdapter?.getDecoded() ?? null;
|
|
1785
|
+
const authContext = decoded ? { tokenName: decoded.tokenName, params: decoded.params } : null;
|
|
1612
1786
|
const event = {
|
|
1613
1787
|
type: this.data.name,
|
|
1614
1788
|
payload,
|
|
1615
1789
|
id: this.eventId.generate(),
|
|
1616
|
-
createdAt: new Date
|
|
1790
|
+
createdAt: new Date,
|
|
1791
|
+
authContext
|
|
1617
1792
|
};
|
|
1618
1793
|
await adapters.eventPublisher.publish(event);
|
|
1619
1794
|
}
|
|
@@ -1697,56 +1872,6 @@ function event(name, payload) {
|
|
|
1697
1872
|
});
|
|
1698
1873
|
}
|
|
1699
1874
|
|
|
1700
|
-
// src/context-element/element-context.ts
|
|
1701
|
-
function buildElementContext(queryElements, mutationElements, adapters) {
|
|
1702
|
-
const queryMap = new Map;
|
|
1703
|
-
const mutateMap = new Map;
|
|
1704
|
-
const queryProps = {};
|
|
1705
|
-
for (const element of queryElements) {
|
|
1706
|
-
if (element.queryContext) {
|
|
1707
|
-
const ctx = element.queryContext(adapters);
|
|
1708
|
-
queryProps[element.name] = ctx;
|
|
1709
|
-
queryMap.set(element, ctx);
|
|
1710
|
-
}
|
|
1711
|
-
}
|
|
1712
|
-
const mutateProps = {};
|
|
1713
|
-
for (const element of mutationElements) {
|
|
1714
|
-
if (element.mutateContext) {
|
|
1715
|
-
const ctx = element.mutateContext(adapters);
|
|
1716
|
-
mutateProps[element.name] = ctx;
|
|
1717
|
-
mutateMap.set(element, ctx);
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
const queryFn = (element) => {
|
|
1721
|
-
const cached = queryMap.get(element);
|
|
1722
|
-
if (cached)
|
|
1723
|
-
return cached;
|
|
1724
|
-
if (element.queryContext) {
|
|
1725
|
-
const ctx = element.queryContext(adapters);
|
|
1726
|
-
queryMap.set(element, ctx);
|
|
1727
|
-
return ctx;
|
|
1728
|
-
}
|
|
1729
|
-
throw new Error(`Element "${element.name}" has no queryContext`);
|
|
1730
|
-
};
|
|
1731
|
-
Object.assign(queryFn, queryProps);
|
|
1732
|
-
const mutateFn = (element) => {
|
|
1733
|
-
const cached = mutateMap.get(element);
|
|
1734
|
-
if (cached)
|
|
1735
|
-
return cached;
|
|
1736
|
-
if (element.mutateContext) {
|
|
1737
|
-
const ctx = element.mutateContext(adapters);
|
|
1738
|
-
mutateMap.set(element, ctx);
|
|
1739
|
-
return ctx;
|
|
1740
|
-
}
|
|
1741
|
-
throw new Error(`Element "${element.name}" has no mutateContext`);
|
|
1742
|
-
};
|
|
1743
|
-
Object.assign(mutateFn, mutateProps);
|
|
1744
|
-
return {
|
|
1745
|
-
query: queryFn,
|
|
1746
|
-
mutate: mutateFn
|
|
1747
|
-
};
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
1875
|
// src/context-element/function/arc-function.ts
|
|
1751
1876
|
class ArcFunction {
|
|
1752
1877
|
data;
|
|
@@ -1881,11 +2006,14 @@ class AggregateBase {
|
|
|
1881
2006
|
if (!adapters.eventPublisher) {
|
|
1882
2007
|
throw new Error(`Cannot emit event "${arcEvent.name}": no eventPublisher adapter available`);
|
|
1883
2008
|
}
|
|
2009
|
+
const decoded = adapters.authAdapter?.getDecoded() ?? null;
|
|
2010
|
+
const authContext = decoded ? { tokenName: decoded.tokenName, params: decoded.params } : null;
|
|
1884
2011
|
const eventInstance = {
|
|
1885
2012
|
type: arcEvent.name,
|
|
1886
2013
|
payload,
|
|
1887
2014
|
id: `${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
1888
|
-
createdAt: new Date
|
|
2015
|
+
createdAt: new Date,
|
|
2016
|
+
authContext
|
|
1889
2017
|
};
|
|
1890
2018
|
await adapters.eventPublisher.publish(eventInstance);
|
|
1891
2019
|
}
|
|
@@ -1962,7 +2090,9 @@ class ArcAggregateElement extends ArcContextElement {
|
|
|
1962
2090
|
const entry = {
|
|
1963
2091
|
name: methodName,
|
|
1964
2092
|
params: configuredFn.data.params,
|
|
1965
|
-
handler: configuredFn.data.handler
|
|
2093
|
+
handler: configuredFn.data.handler,
|
|
2094
|
+
queryElements: configuredFn.data.queryElements?.length ? configuredFn.data.queryElements : undefined,
|
|
2095
|
+
mutationElements: configuredFn.data.mutationElements?.length ? configuredFn.data.mutationElements : undefined
|
|
1966
2096
|
};
|
|
1967
2097
|
return new ArcAggregateElement(this.name, this._id_factory, this.schema, this._events, this._protections, [...this._mutateMethods, entry], this._queryMethods, this._seeds);
|
|
1968
2098
|
}
|
|
@@ -2089,38 +2219,16 @@ class ArcAggregateElement extends ArcContextElement {
|
|
|
2089
2219
|
}
|
|
2090
2220
|
buildPrivateQuery(adapters) {
|
|
2091
2221
|
const viewName = this.name;
|
|
2092
|
-
const
|
|
2093
|
-
const
|
|
2094
|
-
if (protections.length === 0)
|
|
2095
|
-
return null;
|
|
2096
|
-
if (!adapters.authAdapter)
|
|
2097
|
-
return false;
|
|
2098
|
-
const decoded = adapters.authAdapter.getDecoded();
|
|
2099
|
-
if (!decoded)
|
|
2100
|
-
return false;
|
|
2101
|
-
for (const protection of protections) {
|
|
2102
|
-
if (protection.token.name === decoded.tokenName) {
|
|
2103
|
-
const restrictions = protection.protectionFn(decoded.params);
|
|
2104
|
-
if (restrictions === false)
|
|
2105
|
-
return false;
|
|
2106
|
-
return restrictions ?? {};
|
|
2107
|
-
}
|
|
2108
|
-
}
|
|
2109
|
-
return false;
|
|
2110
|
-
};
|
|
2111
|
-
const applyRestrictions = (options) => {
|
|
2112
|
-
const restrictions = getReadRestrictions();
|
|
2113
|
-
if (restrictions === false)
|
|
2114
|
-
return false;
|
|
2115
|
-
if (!restrictions || Object.keys(restrictions).length === 0) {
|
|
2116
|
-
return options || {};
|
|
2117
|
-
}
|
|
2118
|
-
const where = { ...options?.where || {}, ...restrictions };
|
|
2119
|
-
return { ...options, where };
|
|
2120
|
-
};
|
|
2222
|
+
const restrictions = this.getScopeRestrictions(adapters);
|
|
2223
|
+
const isDenied = this.isScopeDenied(adapters);
|
|
2121
2224
|
const findRows = async (options) => {
|
|
2122
|
-
if (adapters.dataStorage)
|
|
2225
|
+
if (adapters.dataStorage) {
|
|
2226
|
+
if (restrictions) {
|
|
2227
|
+
const scopedStore = new ScopedStore(adapters.dataStorage.getStore(viewName), restrictions, false);
|
|
2228
|
+
return scopedStore.find(options);
|
|
2229
|
+
}
|
|
2123
2230
|
return adapters.dataStorage.getStore(viewName).find(options);
|
|
2231
|
+
}
|
|
2124
2232
|
if (adapters.streamingCache)
|
|
2125
2233
|
return adapters.streamingCache.getStore(viewName).find(options);
|
|
2126
2234
|
if (adapters.queryWire)
|
|
@@ -2134,18 +2242,15 @@ class ArcAggregateElement extends ArcContextElement {
|
|
|
2134
2242
|
};
|
|
2135
2243
|
return {
|
|
2136
2244
|
find: async (options, mapper) => {
|
|
2137
|
-
|
|
2138
|
-
if (restricted === false)
|
|
2245
|
+
if (isDenied)
|
|
2139
2246
|
return [];
|
|
2140
|
-
const rows = await findRows(
|
|
2247
|
+
const rows = await findRows(options || {});
|
|
2141
2248
|
return mapper ? rows.map((row) => new mapper(row, adapters)) : rows.map(deserializeRow);
|
|
2142
2249
|
},
|
|
2143
2250
|
findOne: async (where, mapper) => {
|
|
2144
|
-
|
|
2145
|
-
if (restrictions === false)
|
|
2251
|
+
if (isDenied)
|
|
2146
2252
|
return;
|
|
2147
|
-
const
|
|
2148
|
-
const rows = await findRows({ where: mergedWhere });
|
|
2253
|
+
const rows = await findRows({ where: where || {} });
|
|
2149
2254
|
const row = rows[0];
|
|
2150
2255
|
if (!row)
|
|
2151
2256
|
return;
|
|
@@ -2153,35 +2258,22 @@ class ArcAggregateElement extends ArcContextElement {
|
|
|
2153
2258
|
}
|
|
2154
2259
|
};
|
|
2155
2260
|
}
|
|
2156
|
-
|
|
2157
|
-
const
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
return
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
if (
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
return adapters.queryWire.query(viewName, options);
|
|
2170
|
-
return [];
|
|
2171
|
-
};
|
|
2172
|
-
return {
|
|
2173
|
-
find: async (options, mapper) => {
|
|
2174
|
-
const rows = await findRows(options || {});
|
|
2175
|
-
return mapper ? rows.map((row) => new mapper(row, adapters)) : rows.map(deserializeRow);
|
|
2176
|
-
},
|
|
2177
|
-
findOne: async (where, mapper) => {
|
|
2178
|
-
const rows = await findRows({ where });
|
|
2179
|
-
const row = rows[0];
|
|
2180
|
-
if (!row)
|
|
2181
|
-
return;
|
|
2182
|
-
return mapper ? new mapper(row, adapters) : deserializeRow(row);
|
|
2261
|
+
isScopeDenied(adapters) {
|
|
2262
|
+
const protections = this._protections;
|
|
2263
|
+
if (protections.length === 0)
|
|
2264
|
+
return false;
|
|
2265
|
+
if (!adapters.authAdapter)
|
|
2266
|
+
return true;
|
|
2267
|
+
const decoded = adapters.authAdapter.getDecoded();
|
|
2268
|
+
if (!decoded)
|
|
2269
|
+
return true;
|
|
2270
|
+
for (const protection of protections) {
|
|
2271
|
+
if (protection.token.name === decoded.tokenName) {
|
|
2272
|
+
const result = protection.protectionFn(decoded.params);
|
|
2273
|
+
return result === false;
|
|
2183
2274
|
}
|
|
2184
|
-
}
|
|
2275
|
+
}
|
|
2276
|
+
return true;
|
|
2185
2277
|
}
|
|
2186
2278
|
getAuth(adapters) {
|
|
2187
2279
|
if (adapters.authAdapter) {
|
|
@@ -2190,12 +2282,33 @@ class ArcAggregateElement extends ArcContextElement {
|
|
|
2190
2282
|
}
|
|
2191
2283
|
return { params: {}, tokenName: "" };
|
|
2192
2284
|
}
|
|
2285
|
+
getScopeRestrictions(adapters) {
|
|
2286
|
+
const protections = this._protections;
|
|
2287
|
+
if (protections.length === 0)
|
|
2288
|
+
return null;
|
|
2289
|
+
if (!adapters.authAdapter)
|
|
2290
|
+
return null;
|
|
2291
|
+
const decoded = adapters.authAdapter.getDecoded();
|
|
2292
|
+
if (!decoded)
|
|
2293
|
+
return null;
|
|
2294
|
+
for (const protection of protections) {
|
|
2295
|
+
if (protection.token.name === decoded.tokenName) {
|
|
2296
|
+
const result = protection.protectionFn(decoded.params);
|
|
2297
|
+
if (result === false)
|
|
2298
|
+
return null;
|
|
2299
|
+
return result ?? null;
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
console.log(`[Scope:${this.name}] no matching protection`);
|
|
2303
|
+
return null;
|
|
2304
|
+
}
|
|
2193
2305
|
mutateContext(adapters) {
|
|
2194
2306
|
const events = this._events;
|
|
2195
|
-
const privateQuery = this.
|
|
2307
|
+
const privateQuery = this.buildPrivateQuery(adapters);
|
|
2196
2308
|
const auth = this.getAuth(adapters);
|
|
2197
2309
|
const aggregateName = this.name;
|
|
2198
|
-
const
|
|
2310
|
+
const scopeRestrictions = this.getScopeRestrictions(adapters);
|
|
2311
|
+
const buildMutateMethodCtx = (method) => {
|
|
2199
2312
|
const ctx = {};
|
|
2200
2313
|
for (const entry of events) {
|
|
2201
2314
|
const arcEvent = entry.event;
|
|
@@ -2206,11 +2319,21 @@ class ArcAggregateElement extends ArcContextElement {
|
|
|
2206
2319
|
if (!adapters.eventPublisher) {
|
|
2207
2320
|
throw new Error(`Cannot emit event "${arcEvent.name}": no eventPublisher adapter available`);
|
|
2208
2321
|
}
|
|
2322
|
+
if (scopeRestrictions) {
|
|
2323
|
+
for (const [key, value] of Object.entries(scopeRestrictions)) {
|
|
2324
|
+
if (key in payload && payload[key] !== value) {
|
|
2325
|
+
throw new Error(`Scope violation: event "${arcEvent.name}" field "${key}" must be "${value}", got "${payload[key]}"`);
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
const decoded = adapters.authAdapter?.getDecoded() ?? null;
|
|
2330
|
+
const authContext = decoded ? { tokenName: decoded.tokenName, params: decoded.params } : null;
|
|
2209
2331
|
const eventInstance = {
|
|
2210
2332
|
type: `${aggregateName}.${arcEvent.name}`,
|
|
2211
2333
|
payload,
|
|
2212
2334
|
id: `${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
2213
|
-
createdAt: new Date
|
|
2335
|
+
createdAt: new Date,
|
|
2336
|
+
authContext
|
|
2214
2337
|
};
|
|
2215
2338
|
await adapters.eventPublisher.publish(eventInstance);
|
|
2216
2339
|
}
|
|
@@ -2218,6 +2341,11 @@ class ArcAggregateElement extends ArcContextElement {
|
|
|
2218
2341
|
}
|
|
2219
2342
|
ctx.$auth = auth;
|
|
2220
2343
|
ctx.$query = privateQuery;
|
|
2344
|
+
if (method.queryElements?.length || method.mutationElements?.length) {
|
|
2345
|
+
const elementCtx = buildElementContext(method.queryElements ?? [], method.mutationElements ?? [], adapters);
|
|
2346
|
+
ctx.query = elementCtx.query;
|
|
2347
|
+
ctx.mutate = elementCtx.mutate;
|
|
2348
|
+
}
|
|
2221
2349
|
return ctx;
|
|
2222
2350
|
};
|
|
2223
2351
|
const result = {};
|
|
@@ -2233,7 +2361,7 @@ class ArcAggregateElement extends ArcContextElement {
|
|
|
2233
2361
|
} : undefined;
|
|
2234
2362
|
return adapters.commandWire.executeCommand(`${aggregateName}.${method.name}`, params, wireAuth);
|
|
2235
2363
|
}
|
|
2236
|
-
const ctx = buildMutateMethodCtx();
|
|
2364
|
+
const ctx = buildMutateMethodCtx(method);
|
|
2237
2365
|
return method.handler(ctx, params);
|
|
2238
2366
|
};
|
|
2239
2367
|
}
|
|
@@ -2461,9 +2589,10 @@ class ArcListener extends ArcContextElement {
|
|
|
2461
2589
|
async handleEvent(event2, adapters) {
|
|
2462
2590
|
if (!this.data.handler)
|
|
2463
2591
|
return;
|
|
2464
|
-
const
|
|
2465
|
-
|
|
2466
|
-
|
|
2592
|
+
const scopedAdapters = this.buildScopedAdapters(event2, adapters);
|
|
2593
|
+
const context2 = this.#fn.buildContext(scopedAdapters);
|
|
2594
|
+
if (scopedAdapters.authAdapter) {
|
|
2595
|
+
const decoded = scopedAdapters.authAdapter.getDecoded();
|
|
2467
2596
|
if (decoded) {
|
|
2468
2597
|
context2.$auth = {
|
|
2469
2598
|
params: decoded.params,
|
|
@@ -2479,6 +2608,21 @@ class ArcListener extends ArcContextElement {
|
|
|
2479
2608
|
await this.data.handler(context2, event2);
|
|
2480
2609
|
}
|
|
2481
2610
|
}
|
|
2611
|
+
buildScopedAdapters(event2, adapters) {
|
|
2612
|
+
if (adapters.authAdapter?.isAuthenticated()) {
|
|
2613
|
+
return adapters;
|
|
2614
|
+
}
|
|
2615
|
+
const authContext = event2.authContext;
|
|
2616
|
+
if (!authContext) {
|
|
2617
|
+
return adapters;
|
|
2618
|
+
}
|
|
2619
|
+
const scopedAuth = new AuthAdapter;
|
|
2620
|
+
scopedAuth.setDecoded({
|
|
2621
|
+
tokenName: authContext.tokenName,
|
|
2622
|
+
params: authContext.params
|
|
2623
|
+
});
|
|
2624
|
+
return { ...adapters, authAdapter: scopedAuth };
|
|
2625
|
+
}
|
|
2482
2626
|
destroy() {
|
|
2483
2627
|
for (const unsubscribe of this.unsubscribers) {
|
|
2484
2628
|
unsubscribe();
|
|
@@ -2739,74 +2883,63 @@ class ArcView extends ArcContextElement {
|
|
|
2739
2883
|
queryContext(adapters) {
|
|
2740
2884
|
const viewName = this.data.name;
|
|
2741
2885
|
const protections = this.data.protections || [];
|
|
2742
|
-
const
|
|
2743
|
-
if (protections.length === 0)
|
|
2744
|
-
return null;
|
|
2745
|
-
if (!adapters.authAdapter) {
|
|
2746
|
-
return false;
|
|
2747
|
-
}
|
|
2748
|
-
const decoded = adapters.authAdapter.getDecoded();
|
|
2749
|
-
if (!decoded) {
|
|
2750
|
-
return false;
|
|
2751
|
-
}
|
|
2752
|
-
for (const protection of protections) {
|
|
2753
|
-
if (protection.token.name === decoded.tokenName) {
|
|
2754
|
-
const restrictions = protection.protectionFn(decoded.params);
|
|
2755
|
-
if (restrictions === false)
|
|
2756
|
-
return false;
|
|
2757
|
-
return restrictions ?? {};
|
|
2758
|
-
}
|
|
2759
|
-
}
|
|
2760
|
-
return false;
|
|
2761
|
-
};
|
|
2762
|
-
const applyRestrictions = (options) => {
|
|
2763
|
-
const restrictions = getReadRestrictions();
|
|
2764
|
-
if (restrictions === false)
|
|
2765
|
-
return false;
|
|
2766
|
-
if (!restrictions || Object.keys(restrictions).length === 0) {
|
|
2767
|
-
return options || {};
|
|
2768
|
-
}
|
|
2769
|
-
const where = { ...options?.where || {}, ...restrictions };
|
|
2770
|
-
return { ...options, where };
|
|
2771
|
-
};
|
|
2886
|
+
const { restrictions, denied } = this.resolveProtection(protections, adapters);
|
|
2772
2887
|
return {
|
|
2773
2888
|
find: async (options) => {
|
|
2774
|
-
|
|
2775
|
-
if (restrictedOptions === false) {
|
|
2889
|
+
if (denied)
|
|
2776
2890
|
return [];
|
|
2777
|
-
}
|
|
2778
2891
|
if (adapters.dataStorage) {
|
|
2779
|
-
|
|
2780
|
-
|
|
2892
|
+
if (restrictions) {
|
|
2893
|
+
const scopedStore = new ScopedStore(adapters.dataStorage.getStore(viewName), restrictions, false);
|
|
2894
|
+
return scopedStore.find(options);
|
|
2895
|
+
}
|
|
2896
|
+
return adapters.dataStorage.getStore(viewName).find(options);
|
|
2781
2897
|
}
|
|
2782
2898
|
if (adapters.queryWire) {
|
|
2783
|
-
|
|
2899
|
+
const where = restrictions ? { ...options?.where || {}, ...restrictions } : options?.where;
|
|
2900
|
+
return adapters.queryWire.query(viewName, { ...options, where });
|
|
2784
2901
|
}
|
|
2785
|
-
console.warn(`View "${viewName}" query: no dataStorage or queryWire available`);
|
|
2786
2902
|
return [];
|
|
2787
2903
|
},
|
|
2788
2904
|
findOne: async (where) => {
|
|
2789
|
-
|
|
2790
|
-
if (restrictions === false) {
|
|
2905
|
+
if (denied)
|
|
2791
2906
|
return;
|
|
2792
|
-
}
|
|
2793
|
-
const mergedWhere = restrictions && Object.keys(restrictions).length > 0 ? { ...where, ...restrictions } : where;
|
|
2794
2907
|
if (adapters.dataStorage) {
|
|
2795
|
-
|
|
2796
|
-
|
|
2908
|
+
if (restrictions) {
|
|
2909
|
+
const scopedStore = new ScopedStore(adapters.dataStorage.getStore(viewName), restrictions, false);
|
|
2910
|
+
const results2 = await scopedStore.find({ where: where || {} });
|
|
2911
|
+
return results2[0];
|
|
2912
|
+
}
|
|
2913
|
+
const results = await adapters.dataStorage.getStore(viewName).find({ where });
|
|
2797
2914
|
return results[0];
|
|
2798
2915
|
}
|
|
2799
2916
|
if (adapters.queryWire) {
|
|
2800
|
-
const
|
|
2801
|
-
|
|
2802
|
-
});
|
|
2917
|
+
const mergedWhere = restrictions ? { ...where, ...restrictions } : where;
|
|
2918
|
+
const results = await adapters.queryWire.query(viewName, { where: mergedWhere });
|
|
2803
2919
|
return results[0];
|
|
2804
2920
|
}
|
|
2805
|
-
console.warn(`View "${viewName}" query: no dataStorage or queryWire available`);
|
|
2806
2921
|
return;
|
|
2807
2922
|
}
|
|
2808
2923
|
};
|
|
2809
2924
|
}
|
|
2925
|
+
resolveProtection(protections, adapters) {
|
|
2926
|
+
if (protections.length === 0)
|
|
2927
|
+
return { restrictions: null, denied: false };
|
|
2928
|
+
if (!adapters.authAdapter)
|
|
2929
|
+
return { restrictions: null, denied: true };
|
|
2930
|
+
const decoded = adapters.authAdapter.getDecoded();
|
|
2931
|
+
if (!decoded)
|
|
2932
|
+
return { restrictions: null, denied: true };
|
|
2933
|
+
for (const protection of protections) {
|
|
2934
|
+
if (protection.token.name === decoded.tokenName) {
|
|
2935
|
+
const result = protection.protectionFn(decoded.params);
|
|
2936
|
+
if (result === false)
|
|
2937
|
+
return { restrictions: null, denied: true };
|
|
2938
|
+
return { restrictions: result ?? null, denied: false };
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
return { restrictions: null, denied: true };
|
|
2942
|
+
}
|
|
2810
2943
|
getHandlers() {
|
|
2811
2944
|
return this.data.handler;
|
|
2812
2945
|
}
|
|
@@ -2925,7 +3058,8 @@ class ArcView extends ArcContextElement {
|
|
|
2925
3058
|
type: storedEvent.type,
|
|
2926
3059
|
payload,
|
|
2927
3060
|
id: storedEvent._id,
|
|
2928
|
-
createdAt: new Date(storedEvent.createdAt)
|
|
3061
|
+
createdAt: new Date(storedEvent.createdAt),
|
|
3062
|
+
authContext: storedEvent.authContext ? typeof storedEvent.authContext === "string" ? JSON.parse(storedEvent.authContext) : storedEvent.authContext : null
|
|
2929
3063
|
};
|
|
2930
3064
|
await handler(ctx, eventInstance);
|
|
2931
3065
|
}
|
|
@@ -2943,13 +3077,6 @@ function view(name, id2, schema) {
|
|
|
2943
3077
|
protections: []
|
|
2944
3078
|
});
|
|
2945
3079
|
}
|
|
2946
|
-
// src/data-storage/data-storage.abstract.ts
|
|
2947
|
-
class DataStorage {
|
|
2948
|
-
async commitChanges(changes) {
|
|
2949
|
-
await Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applyChanges(changes2)));
|
|
2950
|
-
}
|
|
2951
|
-
}
|
|
2952
|
-
|
|
2953
3080
|
// src/data-storage/store-state-fork.ts
|
|
2954
3081
|
import { apply } from "mutative";
|
|
2955
3082
|
|
|
@@ -4643,7 +4770,8 @@ class StreamingEventPublisher {
|
|
|
4643
4770
|
localId: event3.id,
|
|
4644
4771
|
type: event3.type,
|
|
4645
4772
|
payload: event3.payload,
|
|
4646
|
-
createdAt: event3.createdAt.toISOString()
|
|
4773
|
+
createdAt: event3.createdAt.toISOString(),
|
|
4774
|
+
authContext: event3.authContext ?? null
|
|
4647
4775
|
}
|
|
4648
4776
|
]);
|
|
4649
4777
|
}
|
|
@@ -5091,6 +5219,7 @@ export {
|
|
|
5091
5219
|
deepMerge,
|
|
5092
5220
|
date,
|
|
5093
5221
|
customId,
|
|
5222
|
+
createScopedDataStorage,
|
|
5094
5223
|
contextMerge,
|
|
5095
5224
|
context,
|
|
5096
5225
|
command,
|
|
@@ -5113,7 +5242,9 @@ export {
|
|
|
5113
5242
|
StoreState,
|
|
5114
5243
|
SecuredStoreState,
|
|
5115
5244
|
SecuredDataStorage,
|
|
5245
|
+
ScopedStore,
|
|
5116
5246
|
ScopedModel,
|
|
5247
|
+
ScopedDataStorage,
|
|
5117
5248
|
QueryWire,
|
|
5118
5249
|
ObservableDataStorage,
|
|
5119
5250
|
Model,
|
package/package.json
CHANGED