@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.
@@ -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,3 @@
1
+ export * from "./static-view";
2
+ export * from "./static-view-data";
3
+ //# 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 () => this.unregisterStream(viewName);
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 () => this.unregisterStream(viewName);
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 streamAlreadyExists = cache.hasActiveStream(viewName);
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 (streamAlreadyExists) {
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 a function to unregister when done
53
+ * Returns object with unsubscribe function and whether stream was reused
50
54
  */
51
55
  registerStream(viewName: string, createStream: () => {
52
56
  unsubscribe: () => void;
53
- }): () => void;
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arcote.tech/arc",
3
3
  "type": "module",
4
- "version": "0.3.3",
4
+ "version": "0.3.5",
5
5
  "private": false,
6
6
  "author": "Przemysław Krasiński [arcote.tech]",
7
7
  "description": "Arc framework core rewrite with improved event emission and type safety",