@arcote.tech/arc 0.3.3 → 0.3.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/context-element/index.d.ts +7 -0
- 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 +110 -5
- package/dist/streaming/streaming-live-query.d.ts +2 -0
- package/dist/streaming/streaming-query-cache.d.ts +9 -2
- package/package.json +1 -1
|
@@ -8,5 +8,12 @@ export * from "./context-element";
|
|
|
8
8
|
export * from "./event";
|
|
9
9
|
export * from "./listener";
|
|
10
10
|
export * from "./route";
|
|
11
|
+
export * from "./static-view";
|
|
11
12
|
export * from "./view";
|
|
13
|
+
import type { ArcStaticViewAny, ArcStaticViewRecord } from "./static-view";
|
|
14
|
+
import type { ArcViewAny, ArcViewRecord } from "./view";
|
|
15
|
+
/**
|
|
16
|
+
* Helper type to extract record type from any view (regular or static)
|
|
17
|
+
*/
|
|
18
|
+
export type ArcAnyViewRecord<View extends ArcViewAny | ArcStaticViewAny> = View extends ArcViewAny ? ArcViewRecord<View> : View extends ArcStaticViewAny ? ArcStaticViewRecord<View> : never;
|
|
12
19
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -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: (Schema & ArcObject<any, any>) | 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
|
+
}]>;
|
|
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
|
@@ -1931,6 +1931,84 @@ function route(name) {
|
|
|
1931
1931
|
isPublic: false
|
|
1932
1932
|
});
|
|
1933
1933
|
}
|
|
1934
|
+
// src/context-element/static-view/static-view.ts
|
|
1935
|
+
class ArcStaticView extends ArcContextElement {
|
|
1936
|
+
data;
|
|
1937
|
+
constructor(data) {
|
|
1938
|
+
super(data.name);
|
|
1939
|
+
this.data = data;
|
|
1940
|
+
}
|
|
1941
|
+
get id() {
|
|
1942
|
+
return this.data.id;
|
|
1943
|
+
}
|
|
1944
|
+
get schema() {
|
|
1945
|
+
return this.data.schema;
|
|
1946
|
+
}
|
|
1947
|
+
get items() {
|
|
1948
|
+
return this.data.items;
|
|
1949
|
+
}
|
|
1950
|
+
description(description) {
|
|
1951
|
+
return new ArcStaticView({
|
|
1952
|
+
...this.data,
|
|
1953
|
+
description
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1956
|
+
addItems(items) {
|
|
1957
|
+
return new ArcStaticView({
|
|
1958
|
+
...this.data,
|
|
1959
|
+
items
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
queryContext(_adapters) {
|
|
1963
|
+
const items = this.data.items;
|
|
1964
|
+
return {
|
|
1965
|
+
find: async (options) => {
|
|
1966
|
+
let results = [...items];
|
|
1967
|
+
if (options?.where) {
|
|
1968
|
+
results = results.filter((item) => this.matchesWhere(item, options.where));
|
|
1969
|
+
}
|
|
1970
|
+
if (options?.orderBy) {
|
|
1971
|
+
const entries = Object.entries(options.orderBy);
|
|
1972
|
+
results.sort((a, b) => {
|
|
1973
|
+
for (const [key, direction] of entries) {
|
|
1974
|
+
const aVal = a[key];
|
|
1975
|
+
const bVal = b[key];
|
|
1976
|
+
if (aVal < bVal)
|
|
1977
|
+
return direction === "asc" ? -1 : 1;
|
|
1978
|
+
if (aVal > bVal)
|
|
1979
|
+
return direction === "asc" ? 1 : -1;
|
|
1980
|
+
}
|
|
1981
|
+
return 0;
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
if (options?.limit) {
|
|
1985
|
+
results = results.slice(0, options.limit);
|
|
1986
|
+
}
|
|
1987
|
+
return results;
|
|
1988
|
+
},
|
|
1989
|
+
findOne: async (where) => {
|
|
1990
|
+
if (!where)
|
|
1991
|
+
return items[0];
|
|
1992
|
+
const item = items.find((item2) => this.matchesWhere(item2, where));
|
|
1993
|
+
return item;
|
|
1994
|
+
}
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
matchesWhere(item, where) {
|
|
1998
|
+
return Object.entries(where).every(([key, value]) => {
|
|
1999
|
+
return item[key] === value;
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
function staticView(name, id2, schema) {
|
|
2004
|
+
const schemaObj = schema instanceof ArcObject ? schema : new ArcObject(schema);
|
|
2005
|
+
return new ArcStaticView({
|
|
2006
|
+
name,
|
|
2007
|
+
id: id2,
|
|
2008
|
+
schema: schemaObj,
|
|
2009
|
+
items: []
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
1934
2012
|
// src/context-element/view/view.ts
|
|
1935
2013
|
class ArcView extends ArcContextElement {
|
|
1936
2014
|
data;
|
|
@@ -3551,6 +3629,10 @@ class StreamingQueryCache {
|
|
|
3551
3629
|
}
|
|
3552
3630
|
return this.stores.get(viewName);
|
|
3553
3631
|
}
|
|
3632
|
+
hasData(viewName) {
|
|
3633
|
+
const store = this.stores.get(viewName);
|
|
3634
|
+
return store ? store.hasData() : false;
|
|
3635
|
+
}
|
|
3554
3636
|
hasActiveStream(viewName) {
|
|
3555
3637
|
return this.activeStreams.has(viewName);
|
|
3556
3638
|
}
|
|
@@ -3558,14 +3640,20 @@ class StreamingQueryCache {
|
|
|
3558
3640
|
const existing = this.activeStreams.get(viewName);
|
|
3559
3641
|
if (existing) {
|
|
3560
3642
|
existing.refCount++;
|
|
3561
|
-
return
|
|
3643
|
+
return {
|
|
3644
|
+
unsubscribe: () => this.unregisterStream(viewName),
|
|
3645
|
+
wasReused: true
|
|
3646
|
+
};
|
|
3562
3647
|
}
|
|
3563
3648
|
const streamConn = createStream();
|
|
3564
3649
|
this.activeStreams.set(viewName, {
|
|
3565
3650
|
unsubscribe: streamConn.unsubscribe,
|
|
3566
3651
|
refCount: 1
|
|
3567
3652
|
});
|
|
3568
|
-
return
|
|
3653
|
+
return {
|
|
3654
|
+
unsubscribe: () => this.unregisterStream(viewName),
|
|
3655
|
+
wasReused: false
|
|
3656
|
+
};
|
|
3569
3657
|
}
|
|
3570
3658
|
unregisterStream(viewName) {
|
|
3571
3659
|
const stream = this.activeStreams.get(viewName);
|
|
@@ -3627,6 +3715,9 @@ class StreamingQueryCache {
|
|
|
3627
3715
|
class StreamingStore {
|
|
3628
3716
|
data = new Map;
|
|
3629
3717
|
listeners = new Set;
|
|
3718
|
+
hasData() {
|
|
3719
|
+
return this.data.size > 0;
|
|
3720
|
+
}
|
|
3630
3721
|
setAll(items) {
|
|
3631
3722
|
this.data.clear();
|
|
3632
3723
|
for (const item of items) {
|
|
@@ -3743,17 +3834,25 @@ class StreamingEventPublisher {
|
|
|
3743
3834
|
}
|
|
3744
3835
|
}
|
|
3745
3836
|
// src/streaming/streaming-live-query.ts
|
|
3837
|
+
function isStaticView(element2) {
|
|
3838
|
+
return element2 && "items" in element2 && !("getHandlers" in element2);
|
|
3839
|
+
}
|
|
3746
3840
|
function streamingLiveQuery(model, queryFn, callback, options) {
|
|
3747
3841
|
const { queryWire, cache, authToken } = options;
|
|
3748
3842
|
let currentResult = undefined;
|
|
3749
3843
|
const unsubscribers = [];
|
|
3750
3844
|
const queriedViews = new Set;
|
|
3845
|
+
const staticViews = new Set;
|
|
3751
3846
|
const queryContext = new Proxy({}, {
|
|
3752
3847
|
get(_target, viewName) {
|
|
3753
3848
|
const element2 = model.context.get(viewName);
|
|
3754
3849
|
if (!element2) {
|
|
3755
3850
|
throw new Error(`View '${viewName}' not found in context`);
|
|
3756
3851
|
}
|
|
3852
|
+
if (isStaticView(element2)) {
|
|
3853
|
+
staticViews.add(viewName);
|
|
3854
|
+
return element2.queryContext(model.getAdapters());
|
|
3855
|
+
}
|
|
3757
3856
|
queriedViews.add(viewName);
|
|
3758
3857
|
return {
|
|
3759
3858
|
find: async (findOptions = {}) => {
|
|
@@ -3774,21 +3873,25 @@ function streamingLiveQuery(model, queryFn, callback, options) {
|
|
|
3774
3873
|
};
|
|
3775
3874
|
const setupStreams = async () => {
|
|
3776
3875
|
queriedViews.clear();
|
|
3876
|
+
staticViews.clear();
|
|
3777
3877
|
await queryFn(queryContext);
|
|
3878
|
+
if (queriedViews.size === 0 && staticViews.size > 0) {
|
|
3879
|
+
executeQuery();
|
|
3880
|
+
return;
|
|
3881
|
+
}
|
|
3778
3882
|
for (const viewName of queriedViews) {
|
|
3779
3883
|
const store = cache.getStore(viewName);
|
|
3780
3884
|
const cacheUnsub = store.subscribe(() => {
|
|
3781
3885
|
executeQuery();
|
|
3782
3886
|
});
|
|
3783
3887
|
unsubscribers.push(cacheUnsub);
|
|
3784
|
-
const
|
|
3785
|
-
const streamUnsub = cache.registerStream(viewName, () => {
|
|
3888
|
+
const { unsubscribe: streamUnsub, wasReused } = cache.registerStream(viewName, () => {
|
|
3786
3889
|
return queryWire.stream(viewName, {}, (data) => {
|
|
3787
3890
|
cache.setViewData(viewName, data);
|
|
3788
3891
|
}, authToken);
|
|
3789
3892
|
});
|
|
3790
3893
|
unsubscribers.push(streamUnsub);
|
|
3791
|
-
if (
|
|
3894
|
+
if (wasReused && cache.hasData(viewName)) {
|
|
3792
3895
|
executeQuery();
|
|
3793
3896
|
}
|
|
3794
3897
|
}
|
|
@@ -4234,6 +4337,7 @@ export {
|
|
|
4234
4337
|
stringEnum,
|
|
4235
4338
|
string,
|
|
4236
4339
|
streamingLiveQuery,
|
|
4340
|
+
staticView,
|
|
4237
4341
|
secureDataStorage,
|
|
4238
4342
|
route,
|
|
4239
4343
|
resolveQueryChange,
|
|
@@ -4287,6 +4391,7 @@ export {
|
|
|
4287
4391
|
ArcToken,
|
|
4288
4392
|
ArcStringEnum,
|
|
4289
4393
|
ArcString,
|
|
4394
|
+
ArcStaticView,
|
|
4290
4395
|
ArcRoute,
|
|
4291
4396
|
ArcRecord,
|
|
4292
4397
|
ArcOr,
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Uses SSE stream to get initial data and updates from server.
|
|
5
5
|
* Uses StreamingQueryCache for local state and reactivity.
|
|
6
6
|
* Deduplicates SSE streams - multiple queries on same view share one stream.
|
|
7
|
+
* Static views are handled directly without SSE.
|
|
7
8
|
*/
|
|
8
9
|
import type { QueryWire } from "../adapters/query-wire";
|
|
9
10
|
import type { ArcContextAny } from "../context/context";
|
|
@@ -20,6 +21,7 @@ export interface StreamingLiveQueryOptions {
|
|
|
20
21
|
*
|
|
21
22
|
* Opens SSE stream to server, populates cache, and reacts to changes.
|
|
22
23
|
* Reuses existing streams for the same view (deduplication via cache).
|
|
24
|
+
* Static views return data directly without SSE.
|
|
23
25
|
*/
|
|
24
26
|
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>;
|
|
25
27
|
//# sourceMappingURL=streaming-live-query.d.ts.map
|
|
@@ -40,17 +40,24 @@ export declare class StreamingQueryCache {
|
|
|
40
40
|
getStore<Item extends {
|
|
41
41
|
_id: string;
|
|
42
42
|
}>(viewName: string): StreamingQueryCacheStore<Item>;
|
|
43
|
+
/**
|
|
44
|
+
* Check if a store has any data
|
|
45
|
+
*/
|
|
46
|
+
hasData(viewName: string): boolean;
|
|
43
47
|
/**
|
|
44
48
|
* Check if a stream is already active for a view
|
|
45
49
|
*/
|
|
46
50
|
hasActiveStream(viewName: string): boolean;
|
|
47
51
|
/**
|
|
48
52
|
* Register an active stream for a view (increment ref count if exists)
|
|
49
|
-
* Returns
|
|
53
|
+
* Returns object with unsubscribe function and whether stream was reused
|
|
50
54
|
*/
|
|
51
55
|
registerStream(viewName: string, createStream: () => {
|
|
52
56
|
unsubscribe: () => void;
|
|
53
|
-
}):
|
|
57
|
+
}): {
|
|
58
|
+
unsubscribe: () => void;
|
|
59
|
+
wasReused: boolean;
|
|
60
|
+
};
|
|
54
61
|
/**
|
|
55
62
|
* Unregister from a stream (decrement ref count, close if zero)
|
|
56
63
|
*/
|
package/package.json
CHANGED