@arcote.tech/arc 0.3.2 → 0.3.4
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/context-element/static-view/index.d.ts +3 -0
- package/dist/context-element/static-view/static-view-data.d.ts +15 -0
- package/dist/context-element/static-view/static-view.d.ts +129 -0
- package/dist/index.js +55 -10
- package/dist/streaming/streaming-live-query.d.ts +2 -0
- package/dist/streaming/streaming-query-cache.d.ts +25 -1
- package/package.json +1 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static View Data - Type definitions for static view configuration
|
|
3
|
+
*/
|
|
4
|
+
import type { ArcObjectAny } from "../../elements/object";
|
|
5
|
+
/**
|
|
6
|
+
* Static view data structure
|
|
7
|
+
*/
|
|
8
|
+
export interface ArcStaticViewData {
|
|
9
|
+
name: string;
|
|
10
|
+
id: any;
|
|
11
|
+
schema: ArcObjectAny;
|
|
12
|
+
items: any[];
|
|
13
|
+
description?: string;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=static-view-data.d.ts.map
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Arc Static View - Read-only view with hardcoded data
|
|
3
|
+
*
|
|
4
|
+
* Static views contain data defined at build time, not populated from events.
|
|
5
|
+
* Useful for product catalogs, pricing tiers, configuration options, etc.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Define items at build time via addItems()
|
|
9
|
+
* - Query via find/findOne (same interface as regular views)
|
|
10
|
+
* - No event handlers (read-only)
|
|
11
|
+
* - No database storage (in-memory)
|
|
12
|
+
* - Always public (no protectBy)
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const products = staticView("products", productId, {
|
|
17
|
+
* name: string(),
|
|
18
|
+
* price: number(),
|
|
19
|
+
* }).addItems([
|
|
20
|
+
* { _id: "basic", name: "Basic Plan", price: 9.99 },
|
|
21
|
+
* { _id: "pro", name: "Pro Plan", price: 29.99 },
|
|
22
|
+
* ]);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
import type { ArcObjectAny, ArcRawShape } from "../../elements/object";
|
|
26
|
+
import { ArcObject } from "../../elements/object";
|
|
27
|
+
import type { ModelAdapters } from "../../model/model-adapters";
|
|
28
|
+
import type { Merge } from "../../utils";
|
|
29
|
+
import type { $type } from "../../utils/types/get-type";
|
|
30
|
+
import { ArcContextElement } from "../context-element";
|
|
31
|
+
import type { ArcStaticViewData } from "./static-view-data";
|
|
32
|
+
/** Item type for static view */
|
|
33
|
+
export type ArcStaticViewItem<Id, Schema extends ArcObjectAny> = {
|
|
34
|
+
_id: Id extends {
|
|
35
|
+
deserialize: (...args: any) => any;
|
|
36
|
+
} ? $type<Id> : Id;
|
|
37
|
+
} & $type<Schema>;
|
|
38
|
+
/**
|
|
39
|
+
* Arc Static View class
|
|
40
|
+
*/
|
|
41
|
+
export declare class ArcStaticView<const Data extends ArcStaticViewData> extends ArcContextElement<Data["name"]> {
|
|
42
|
+
private readonly data;
|
|
43
|
+
constructor(data: Data);
|
|
44
|
+
/**
|
|
45
|
+
* Get view ID schema
|
|
46
|
+
*/
|
|
47
|
+
get id(): Data["id"];
|
|
48
|
+
/**
|
|
49
|
+
* Get view schema
|
|
50
|
+
*/
|
|
51
|
+
get schema(): Data["schema"];
|
|
52
|
+
/**
|
|
53
|
+
* Get static items
|
|
54
|
+
*/
|
|
55
|
+
get items(): Data["items"];
|
|
56
|
+
/**
|
|
57
|
+
* Set view description for documentation
|
|
58
|
+
*/
|
|
59
|
+
description<const Desc extends string>(description: Desc): ArcStaticView<Merge<Data, {
|
|
60
|
+
description: Desc;
|
|
61
|
+
}>>;
|
|
62
|
+
/**
|
|
63
|
+
* Add static items to the view
|
|
64
|
+
*/
|
|
65
|
+
addItems<const Items extends ArcStaticViewItem<Data["id"], Data["schema"]>[]>(items: Items): ArcStaticView<Merge<Data, {
|
|
66
|
+
items: Items;
|
|
67
|
+
}>>;
|
|
68
|
+
/**
|
|
69
|
+
* Generate query context for this static view
|
|
70
|
+
* Returns find/findOne methods that query the static items
|
|
71
|
+
*/
|
|
72
|
+
queryContext(_adapters: ModelAdapters): {
|
|
73
|
+
find: (options?: {
|
|
74
|
+
where?: Record<string, any>;
|
|
75
|
+
orderBy?: Record<string, "asc" | "desc">;
|
|
76
|
+
limit?: number;
|
|
77
|
+
}) => Promise<Data["items"]>;
|
|
78
|
+
findOne: (where?: Record<string, any>) => Promise<Data["items"][number] | undefined>;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Check if an item matches a where clause
|
|
82
|
+
*/
|
|
83
|
+
private matchesWhere;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Create a new static view with the given name, ID schema, and data schema
|
|
87
|
+
*
|
|
88
|
+
* @param name - Unique view name
|
|
89
|
+
* @param id - ID schema for view records
|
|
90
|
+
* @param schema - Data schema for view records
|
|
91
|
+
* @returns New static view instance ready for configuration
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* const plans = staticView("plans", planId, {
|
|
96
|
+
* name: string(),
|
|
97
|
+
* price: number(),
|
|
98
|
+
* }).addItems([
|
|
99
|
+
* { _id: "free", name: "Free", price: 0 },
|
|
100
|
+
* { _id: "pro", name: "Pro", price: 19.99 },
|
|
101
|
+
* ]);
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export declare function staticView<const Name extends string, Id, Schema extends ArcObjectAny | ArcRawShape>(name: Name, id: Id, schema: Schema): ArcStaticView<{
|
|
105
|
+
name: Name;
|
|
106
|
+
id: Id;
|
|
107
|
+
schema: ArcObject<ArcRawShape, [{
|
|
108
|
+
name: "type";
|
|
109
|
+
validator: (value: any) => false | {
|
|
110
|
+
current: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function";
|
|
111
|
+
expected: "object";
|
|
112
|
+
};
|
|
113
|
+
}, {
|
|
114
|
+
name: "schema";
|
|
115
|
+
validator: (value: any) => false | {
|
|
116
|
+
[x: string]: unknown;
|
|
117
|
+
};
|
|
118
|
+
}]> | (Schema & ArcObject<any, any>);
|
|
119
|
+
items: [];
|
|
120
|
+
}>;
|
|
121
|
+
/**
|
|
122
|
+
* Type alias for any static view (used in collections)
|
|
123
|
+
*/
|
|
124
|
+
export type ArcStaticViewAny = ArcStaticView<any>;
|
|
125
|
+
/**
|
|
126
|
+
* Helper type to extract static view record type
|
|
127
|
+
*/
|
|
128
|
+
export type ArcStaticViewRecord<View extends ArcStaticViewAny> = View extends ArcStaticView<infer Data> ? ArcStaticViewItem<Data["id"], Data["schema"]> : never;
|
|
129
|
+
//# sourceMappingURL=static-view.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -3536,6 +3536,7 @@ function mutationExecutor(model) {
|
|
|
3536
3536
|
class StreamingQueryCache {
|
|
3537
3537
|
stores = new Map;
|
|
3538
3538
|
views = [];
|
|
3539
|
+
activeStreams = new Map;
|
|
3539
3540
|
registerViews(views) {
|
|
3540
3541
|
this.views = views;
|
|
3541
3542
|
for (const view3 of views) {
|
|
@@ -3550,6 +3551,42 @@ class StreamingQueryCache {
|
|
|
3550
3551
|
}
|
|
3551
3552
|
return this.stores.get(viewName);
|
|
3552
3553
|
}
|
|
3554
|
+
hasData(viewName) {
|
|
3555
|
+
const store = this.stores.get(viewName);
|
|
3556
|
+
return store ? store.hasData() : false;
|
|
3557
|
+
}
|
|
3558
|
+
hasActiveStream(viewName) {
|
|
3559
|
+
return this.activeStreams.has(viewName);
|
|
3560
|
+
}
|
|
3561
|
+
registerStream(viewName, createStream) {
|
|
3562
|
+
const existing = this.activeStreams.get(viewName);
|
|
3563
|
+
if (existing) {
|
|
3564
|
+
existing.refCount++;
|
|
3565
|
+
return {
|
|
3566
|
+
unsubscribe: () => this.unregisterStream(viewName),
|
|
3567
|
+
wasReused: true
|
|
3568
|
+
};
|
|
3569
|
+
}
|
|
3570
|
+
const streamConn = createStream();
|
|
3571
|
+
this.activeStreams.set(viewName, {
|
|
3572
|
+
unsubscribe: streamConn.unsubscribe,
|
|
3573
|
+
refCount: 1
|
|
3574
|
+
});
|
|
3575
|
+
return {
|
|
3576
|
+
unsubscribe: () => this.unregisterStream(viewName),
|
|
3577
|
+
wasReused: false
|
|
3578
|
+
};
|
|
3579
|
+
}
|
|
3580
|
+
unregisterStream(viewName) {
|
|
3581
|
+
const stream = this.activeStreams.get(viewName);
|
|
3582
|
+
if (!stream)
|
|
3583
|
+
return;
|
|
3584
|
+
stream.refCount--;
|
|
3585
|
+
if (stream.refCount <= 0) {
|
|
3586
|
+
stream.unsubscribe();
|
|
3587
|
+
this.activeStreams.delete(viewName);
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3553
3590
|
setViewData(viewName, items) {
|
|
3554
3591
|
const store = this.stores.get(viewName);
|
|
3555
3592
|
if (store) {
|
|
@@ -3587,6 +3624,10 @@ class StreamingQueryCache {
|
|
|
3587
3624
|
}
|
|
3588
3625
|
}
|
|
3589
3626
|
clear() {
|
|
3627
|
+
for (const stream of this.activeStreams.values()) {
|
|
3628
|
+
stream.unsubscribe();
|
|
3629
|
+
}
|
|
3630
|
+
this.activeStreams.clear();
|
|
3590
3631
|
for (const store of this.stores.values()) {
|
|
3591
3632
|
store.clear();
|
|
3592
3633
|
}
|
|
@@ -3596,6 +3637,9 @@ class StreamingQueryCache {
|
|
|
3596
3637
|
class StreamingStore {
|
|
3597
3638
|
data = new Map;
|
|
3598
3639
|
listeners = new Set;
|
|
3640
|
+
hasData() {
|
|
3641
|
+
return this.data.size > 0;
|
|
3642
|
+
}
|
|
3599
3643
|
setAll(items) {
|
|
3600
3644
|
this.data.clear();
|
|
3601
3645
|
for (const item of items) {
|
|
@@ -3716,7 +3760,6 @@ function streamingLiveQuery(model, queryFn, callback, options) {
|
|
|
3716
3760
|
const { queryWire, cache, authToken } = options;
|
|
3717
3761
|
let currentResult = undefined;
|
|
3718
3762
|
const unsubscribers = [];
|
|
3719
|
-
const streamConnections = [];
|
|
3720
3763
|
const queriedViews = new Set;
|
|
3721
3764
|
const queryContext = new Proxy({}, {
|
|
3722
3765
|
get(_target, viewName) {
|
|
@@ -3747,14 +3790,19 @@ function streamingLiveQuery(model, queryFn, callback, options) {
|
|
|
3747
3790
|
await queryFn(queryContext);
|
|
3748
3791
|
for (const viewName of queriedViews) {
|
|
3749
3792
|
const store = cache.getStore(viewName);
|
|
3750
|
-
const
|
|
3793
|
+
const cacheUnsub = store.subscribe(() => {
|
|
3751
3794
|
executeQuery();
|
|
3752
3795
|
});
|
|
3753
|
-
unsubscribers.push(
|
|
3754
|
-
const
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3796
|
+
unsubscribers.push(cacheUnsub);
|
|
3797
|
+
const { unsubscribe: streamUnsub, wasReused } = cache.registerStream(viewName, () => {
|
|
3798
|
+
return queryWire.stream(viewName, {}, (data) => {
|
|
3799
|
+
cache.setViewData(viewName, data);
|
|
3800
|
+
}, authToken);
|
|
3801
|
+
});
|
|
3802
|
+
unsubscribers.push(streamUnsub);
|
|
3803
|
+
if (wasReused && cache.hasData(viewName)) {
|
|
3804
|
+
executeQuery();
|
|
3805
|
+
}
|
|
3758
3806
|
}
|
|
3759
3807
|
};
|
|
3760
3808
|
setupStreams();
|
|
@@ -3766,9 +3814,6 @@ function streamingLiveQuery(model, queryFn, callback, options) {
|
|
|
3766
3814
|
for (const unsub of unsubscribers) {
|
|
3767
3815
|
unsub();
|
|
3768
3816
|
}
|
|
3769
|
-
for (const conn of streamConnections) {
|
|
3770
|
-
conn.unsubscribe();
|
|
3771
|
-
}
|
|
3772
3817
|
}
|
|
3773
3818
|
};
|
|
3774
3819
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Uses SSE stream to get initial data and updates from server.
|
|
5
5
|
* Uses StreamingQueryCache for local state and reactivity.
|
|
6
|
+
* Deduplicates SSE streams - multiple queries on same view share one stream.
|
|
6
7
|
*/
|
|
7
8
|
import type { QueryWire } from "../adapters/query-wire";
|
|
8
9
|
import type { ArcContextAny } from "../context/context";
|
|
@@ -18,6 +19,7 @@ export interface StreamingLiveQueryOptions {
|
|
|
18
19
|
* Create a streaming live query
|
|
19
20
|
*
|
|
20
21
|
* Opens SSE stream to server, populates cache, and reacts to changes.
|
|
22
|
+
* Reuses existing streams for the same view (deduplication via cache).
|
|
21
23
|
*/
|
|
22
24
|
export declare function streamingLiveQuery<C extends ArcContextAny, TResult>(model: Model<C>, queryFn: (q: QueryContext<C>) => Promise<TResult>, callback: (data: TResult) => void, options: StreamingLiveQueryOptions): LiveQueryResult<TResult>;
|
|
23
25
|
//# sourceMappingURL=streaming-live-query.d.ts.map
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* - Supports reactive queries with listeners
|
|
10
10
|
* - Applies view handlers for local event emission
|
|
11
11
|
* - Receives updates from SSE stream
|
|
12
|
+
* - Deduplicates SSE streams (one stream per view)
|
|
12
13
|
*/
|
|
13
14
|
import type { ArcEventAny } from "../context-element/event/event";
|
|
14
15
|
import type { ArcEventInstance } from "../context-element/event/instance";
|
|
@@ -28,6 +29,7 @@ export interface StreamingQueryCacheStore<Item extends {
|
|
|
28
29
|
export declare class StreamingQueryCache {
|
|
29
30
|
private stores;
|
|
30
31
|
private views;
|
|
32
|
+
private activeStreams;
|
|
31
33
|
/**
|
|
32
34
|
* Register views that this cache will handle
|
|
33
35
|
*/
|
|
@@ -38,6 +40,28 @@ export declare class StreamingQueryCache {
|
|
|
38
40
|
getStore<Item extends {
|
|
39
41
|
_id: string;
|
|
40
42
|
}>(viewName: string): StreamingQueryCacheStore<Item>;
|
|
43
|
+
/**
|
|
44
|
+
* Check if a store has any data
|
|
45
|
+
*/
|
|
46
|
+
hasData(viewName: string): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Check if a stream is already active for a view
|
|
49
|
+
*/
|
|
50
|
+
hasActiveStream(viewName: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Register an active stream for a view (increment ref count if exists)
|
|
53
|
+
* Returns object with unsubscribe function and whether stream was reused
|
|
54
|
+
*/
|
|
55
|
+
registerStream(viewName: string, createStream: () => {
|
|
56
|
+
unsubscribe: () => void;
|
|
57
|
+
}): {
|
|
58
|
+
unsubscribe: () => void;
|
|
59
|
+
wasReused: boolean;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Unregister from a stream (decrement ref count, close if zero)
|
|
63
|
+
*/
|
|
64
|
+
private unregisterStream;
|
|
41
65
|
/**
|
|
42
66
|
* Set initial data for a view (from SSE stream)
|
|
43
67
|
*/
|
|
@@ -50,7 +74,7 @@ export declare class StreamingQueryCache {
|
|
|
50
74
|
*/
|
|
51
75
|
applyEvent(event: ArcEventInstance<ArcEventAny>): Promise<void>;
|
|
52
76
|
/**
|
|
53
|
-
* Clear all cached data
|
|
77
|
+
* Clear all cached data and close all streams
|
|
54
78
|
*/
|
|
55
79
|
clear(): void;
|
|
56
80
|
}
|
package/package.json
CHANGED