@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.
@@ -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: 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 unsub = store.subscribe(() => {
3793
+ const cacheUnsub = store.subscribe(() => {
3751
3794
  executeQuery();
3752
3795
  });
3753
- unsubscribers.push(unsub);
3754
- const streamConn = queryWire.stream(viewName, {}, (data) => {
3755
- cache.setViewData(viewName, data);
3756
- }, authToken);
3757
- streamConnections.push(streamConn);
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arcote.tech/arc",
3
3
  "type": "module",
4
- "version": "0.3.2",
4
+ "version": "0.3.4",
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",