@arcote.tech/arc 0.3.4 → 0.3.6

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.
@@ -4,6 +4,7 @@
4
4
  * Extends Wire to provide command-specific functionality:
5
5
  * - Execute commands remotely on the server
6
6
  * - Handle command request/response serialization
7
+ * - Automatic FormData handling for file uploads
7
8
  */
8
9
  import { Wire } from "./wire";
9
10
  export declare class CommandWire extends Wire {
@@ -16,6 +17,15 @@ export declare class CommandWire extends Wire {
16
17
  * @returns Command result from server
17
18
  */
18
19
  executeCommand(name: string, params: any): Promise<any>;
20
+ /**
21
+ * Check if object contains any File instances (recursively)
22
+ */
23
+ private containsFile;
24
+ /**
25
+ * Convert params object to FormData for file uploads
26
+ * Files are appended directly, other values are JSON-stringified
27
+ */
28
+ private toFormData;
19
29
  /**
20
30
  * Register a command handler on the server side
21
31
  * Called during init() for server environment
@@ -23,6 +23,7 @@ export interface ReceivedEvent {
23
23
  type EventWireState = "disconnected" | "connecting" | "connected";
24
24
  export declare class EventWire {
25
25
  private readonly baseUrl;
26
+ private instanceId;
26
27
  private ws;
27
28
  private state;
28
29
  private token;
@@ -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
@@ -104,7 +104,7 @@ export declare class ArcStaticView<const Data extends ArcStaticViewData> extends
104
104
  export declare function staticView<const Name extends string, Id, Schema extends ArcObjectAny | ArcRawShape>(name: Name, id: Id, schema: Schema): ArcStaticView<{
105
105
  name: Name;
106
106
  id: Id;
107
- schema: ArcObject<ArcRawShape, [{
107
+ schema: (Schema & ArcObject<any, any>) | ArcObject<ArcRawShape, [{
108
108
  name: "type";
109
109
  validator: (value: any) => false | {
110
110
  current: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function";
@@ -115,7 +115,7 @@ export declare function staticView<const Name extends string, Id, Schema extends
115
115
  validator: (value: any) => false | {
116
116
  [x: string]: unknown;
117
117
  };
118
- }]> | (Schema & ArcObject<any, any>);
118
+ }]>;
119
119
  items: [];
120
120
  }>;
121
121
  /**
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Tree module
3
+ *
4
+ * Provides hierarchical data structures with multiple node types
5
+ */
6
+ export * from "./tree";
7
+ export * from "./tree-context";
8
+ export * from "./tree-data";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,98 @@
1
+ import type { ArcObjectAny } from "../../elements/object";
2
+ import type { $type } from "../../utils/types/get-type";
3
+ import type { ArcTreeData } from "./tree-data";
4
+ /**
5
+ * Tree node as returned from queries
6
+ * Includes node data plus tree metadata
7
+ */
8
+ export interface TreeNode<NodeTypes extends Record<string, ArcObjectAny> = Record<string, ArcObjectAny>, T extends keyof NodeTypes = keyof NodeTypes> {
9
+ _id: string;
10
+ type: T;
11
+ parentId: string | null;
12
+ order: number;
13
+ treeId: string;
14
+ data: T extends keyof NodeTypes ? $type<NodeTypes[T]> : never;
15
+ }
16
+ /**
17
+ * Tree edge record
18
+ */
19
+ export interface TreeEdge {
20
+ _id: string;
21
+ childId: string;
22
+ parentId: string | null;
23
+ childType: string;
24
+ order: number;
25
+ treeId: string;
26
+ }
27
+ /**
28
+ * Options for tree find queries
29
+ */
30
+ export interface TreeFindOptions<NodeTypes extends Record<string, ArcObjectAny>> {
31
+ /** Filter by specific node types */
32
+ types?: (keyof NodeTypes)[];
33
+ /** Filter by parent ID (null for root nodes) */
34
+ parentId?: string | null;
35
+ /** Filter by tree ID (for multi-tenant) */
36
+ treeId?: string;
37
+ /** Sort order */
38
+ orderBy?: {
39
+ order: "asc" | "desc";
40
+ };
41
+ /** Limit results */
42
+ limit?: number;
43
+ /** Recursive depth (for findDescendants) */
44
+ depth?: number;
45
+ }
46
+ /**
47
+ * Query context for reading tree data
48
+ */
49
+ export interface TreeQueryContext<Data extends ArcTreeData> {
50
+ /** Find nodes with optional filtering */
51
+ find(options?: TreeFindOptions<Data["nodeTypes"]>): Promise<TreeNode<Data["nodeTypes"]>[]>;
52
+ /** Find single node by ID */
53
+ findOne(id: string): Promise<TreeNode<Data["nodeTypes"]> | undefined>;
54
+ /** Find direct children of a node */
55
+ findChildren(parentId: string | null, options?: {
56
+ types?: (keyof Data["nodeTypes"])[];
57
+ treeId?: string;
58
+ }): Promise<TreeNode<Data["nodeTypes"]>[]>;
59
+ /** Find ancestors (path to root) */
60
+ findAncestors(nodeId: string): Promise<TreeNode<Data["nodeTypes"]>[]>;
61
+ /** Find descendants recursively */
62
+ findDescendants(nodeId: string, depth?: number): Promise<TreeNode<Data["nodeTypes"]>[]>;
63
+ }
64
+ /**
65
+ * Mutation context for modifying tree data
66
+ */
67
+ export interface TreeMutationContext<Data extends ArcTreeData> {
68
+ /** Create a new node */
69
+ create<T extends keyof Data["nodeTypes"]>(type: T, data: $type<Data["nodeTypes"][T]>, options?: {
70
+ parentId?: string | null;
71
+ order?: number;
72
+ treeId?: string;
73
+ }): Promise<string>;
74
+ /** Update node data */
75
+ update<T extends keyof Data["nodeTypes"]>(id: string, data: Partial<$type<Data["nodeTypes"][T]>>): Promise<void>;
76
+ /** Delete node (with cascade) */
77
+ delete(id: string): Promise<void>;
78
+ /** Move node to new parent/position */
79
+ move(id: string, options: {
80
+ parentId?: string | null;
81
+ order?: number;
82
+ }): Promise<void>;
83
+ }
84
+ /**
85
+ * Serialized tree format for export/import
86
+ */
87
+ export interface SerializedTree<Data extends ArcTreeData> {
88
+ name: string;
89
+ treeId: string;
90
+ nodes: {
91
+ [K in keyof Data["nodeTypes"]]?: Array<{
92
+ _id: string;
93
+ data: $type<Data["nodeTypes"][K]>;
94
+ }>;
95
+ };
96
+ edges: TreeEdge[];
97
+ }
98
+ //# sourceMappingURL=tree-context.d.ts.map
@@ -0,0 +1,43 @@
1
+ import type { ArcObjectAny } from "../../elements/object";
2
+ import type { ArcTokenAny } from "../../token/token";
3
+ /**
4
+ * Protection function for tree access control
5
+ * Returns read/write conditions based on token params
6
+ */
7
+ export type TreeProtectionFn<T extends ArcTokenAny> = (params: T extends {
8
+ params: infer P;
9
+ } ? P : never) => {
10
+ read?: Record<string, any> | false;
11
+ write?: Record<string, any> | false;
12
+ } | false;
13
+ /**
14
+ * Tree protection configuration
15
+ */
16
+ export interface TreeProtection<T extends ArcTokenAny = ArcTokenAny> {
17
+ token: T;
18
+ protectionFn: TreeProtectionFn<T>;
19
+ }
20
+ /**
21
+ * Node type definition within a tree
22
+ */
23
+ export interface TreeNodeTypeDefinition<Name extends string = string, Schema extends ArcObjectAny = ArcObjectAny> {
24
+ name: Name;
25
+ schema: Schema;
26
+ }
27
+ /**
28
+ * Data structure for ArcTree configuration
29
+ * Follows Arc's factory pattern with immutable data
30
+ */
31
+ export interface ArcTreeData {
32
+ /** Unique tree name */
33
+ name: string;
34
+ /** Optional description for documentation */
35
+ description?: string;
36
+ /** Map of node type names to their schemas */
37
+ nodeTypes: Record<string, ArcObjectAny>;
38
+ /** Token-based protections */
39
+ protections: TreeProtection[];
40
+ /** Tree ID field name for multi-tenant support */
41
+ treeIdField?: string;
42
+ }
43
+ //# sourceMappingURL=tree-data.d.ts.map
@@ -0,0 +1,142 @@
1
+ import type { DatabaseStoreSchema } from "../../data-storage/database-store";
2
+ import { ArcObject, type ArcObjectAny, type ArcRawShape } from "../../elements/object";
3
+ import type { ModelAdapters } from "../../model/model-adapters";
4
+ import type { ArcTokenAny } from "../../token/token";
5
+ import type { Merge } from "../../utils";
6
+ import { ArcContextElement } from "../context-element";
7
+ import type { SerializedTree, TreeMutationContext, TreeQueryContext } from "./tree-context";
8
+ import type { ArcTreeData, TreeProtectionFn } from "./tree-data";
9
+ /**
10
+ * Arc Tree - Hierarchical data structure with multiple node types
11
+ *
12
+ * Trees enable defining hierarchical data where each node type has its own
13
+ * dedicated view/table. The tree provides a unified query interface that
14
+ * joins these views based on parent-child relationships.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const contentTree = tree("contentTree")
19
+ * .nodeType("texts", { text: string() })
20
+ * .nodeType("brollIdeas", { idea: string() })
21
+ * .nodeType("graphicIdeas", { idea: string() });
22
+ * ```
23
+ */
24
+ export declare class ArcTree<const Data extends ArcTreeData> extends ArcContextElement<Data["name"]> {
25
+ private readonly data;
26
+ constructor(data: Data);
27
+ /**
28
+ * Get tree name
29
+ */
30
+ get treeName(): Data["name"];
31
+ /**
32
+ * Get node types configuration
33
+ */
34
+ get nodeTypes(): Data["nodeTypes"];
35
+ /**
36
+ * Set tree description for documentation
37
+ */
38
+ description<const Desc extends string>(description: Desc): ArcTree<Merge<Data, {
39
+ description: Desc;
40
+ }>>;
41
+ /**
42
+ * Define a node type with its schema
43
+ * Each node type gets its own dedicated view/table
44
+ *
45
+ * @param typeName - Unique name for this node type within the tree
46
+ * @param schema - Schema defining the node's data fields
47
+ */
48
+ nodeType<TypeName extends string, Schema extends ArcRawShape | ArcObjectAny>(typeName: TypeName, schema: Schema): ArcTree<Merge<Data, {
49
+ nodeTypes: Data["nodeTypes"] & { [K in TypeName]: ArcObject<ArcRawShape, [{
50
+ name: "type";
51
+ validator: (value: any) => false | {
52
+ current: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function";
53
+ expected: "object";
54
+ };
55
+ }, {
56
+ name: "schema";
57
+ validator: (value: any) => false | {
58
+ [x: string]: unknown;
59
+ };
60
+ }]> | (Schema & ArcObject<any, any>); };
61
+ }>>;
62
+ /**
63
+ * Add token-based protection to this tree
64
+ * Defines read/write access conditions based on token params
65
+ */
66
+ protectBy<T extends ArcTokenAny>(token: T, protectionFn: TreeProtectionFn<T>): ArcTree<Data>;
67
+ /**
68
+ * Check if tree has protections
69
+ */
70
+ get hasProtections(): boolean;
71
+ /**
72
+ * Get all protection configurations
73
+ */
74
+ get protections(): import("./tree-data").TreeProtection<ArcTokenAny>[];
75
+ /**
76
+ * Generate all context elements (events, views) for this tree
77
+ * These should be registered in the Arc context
78
+ */
79
+ getElements(): ArcContextElement<any>[];
80
+ /**
81
+ * Generate query context for this tree
82
+ * Provides read access to tree data
83
+ */
84
+ queryContext(adapters: ModelAdapters): TreeQueryContext<Data>;
85
+ /**
86
+ * Generate mutation context for this tree
87
+ * Provides write access to tree data
88
+ */
89
+ mutateContext(adapters: ModelAdapters): TreeMutationContext<Data>;
90
+ /**
91
+ * Generate database store schema for this tree
92
+ * Returns schemas for all node type views + edges view
93
+ */
94
+ databaseStoreSchema(): DatabaseStoreSchema;
95
+ /**
96
+ * Serialize tree data to JSON format
97
+ * Exports nodes grouped by type and edges array
98
+ *
99
+ * @param adapters - Model adapters for data access
100
+ * @param treeId - Tree ID to serialize (defaults to "default")
101
+ */
102
+ serialize(adapters: ModelAdapters, treeId?: string): Promise<SerializedTree<Data>>;
103
+ /**
104
+ * Serialize a subtree starting from a specific node
105
+ * Includes only the specified node and its descendants
106
+ *
107
+ * @param adapters - Model adapters for data access
108
+ * @param nodeId - Root node ID of the subtree
109
+ */
110
+ serializeSubtree(adapters: ModelAdapters, nodeId: string): Promise<SerializedTree<Data>>;
111
+ /**
112
+ * Deserialize tree data from JSON format
113
+ * Validates node data against schemas and reconstructs relationships
114
+ *
115
+ * @param adapters - Model adapters for data access
116
+ * @param serialized - Serialized tree data
117
+ */
118
+ deserialize(adapters: ModelAdapters, serialized: SerializedTree<Data>): Promise<void>;
119
+ }
120
+ /**
121
+ * Create a new tree with the given name
122
+ *
123
+ * @param name - Unique tree name
124
+ * @returns New tree instance ready for configuration
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * const contentTree = tree("contentTree")
129
+ * .nodeType("texts", { text: string() })
130
+ * .nodeType("brollIdeas", { idea: string() });
131
+ * ```
132
+ */
133
+ export declare function tree<const Name extends string>(name: Name): ArcTree<{
134
+ name: Name;
135
+ nodeTypes: {};
136
+ protections: [];
137
+ }>;
138
+ /**
139
+ * Type alias for any tree (used in collections)
140
+ */
141
+ export type ArcTreeAny = ArcTree<any>;
142
+ //# sourceMappingURL=tree.d.ts.map
package/dist/index.js CHANGED
@@ -84,9 +84,19 @@ class CommandWire extends Wire {
84
84
  super(baseUrl);
85
85
  }
86
86
  async executeCommand(name, params) {
87
+ const hasFile = this.containsFile(params);
88
+ let body;
89
+ let headers;
90
+ if (hasFile) {
91
+ body = this.toFormData(params);
92
+ } else {
93
+ body = JSON.stringify(params);
94
+ headers = { "Content-Type": "application/json" };
95
+ }
87
96
  const response = await this.fetch(`/command/${name}`, {
88
97
  method: "POST",
89
- body: JSON.stringify(params)
98
+ body,
99
+ headers
90
100
  });
91
101
  if (!response.ok) {
92
102
  const errorText = await response.text();
@@ -94,10 +104,35 @@ class CommandWire extends Wire {
94
104
  }
95
105
  return await response.json();
96
106
  }
107
+ containsFile(obj) {
108
+ if (obj instanceof File || obj instanceof Blob)
109
+ return true;
110
+ if (obj === null || typeof obj !== "object")
111
+ return false;
112
+ if (Array.isArray(obj))
113
+ return obj.some((v) => this.containsFile(v));
114
+ return Object.values(obj).some((v) => this.containsFile(v));
115
+ }
116
+ toFormData(params) {
117
+ const formData = new FormData;
118
+ for (const [key, value] of Object.entries(params)) {
119
+ if (value instanceof File) {
120
+ formData.append(key, value, value.name);
121
+ } else if (value instanceof Blob) {
122
+ formData.append(key, value);
123
+ } else {
124
+ formData.append(key, JSON.stringify(value));
125
+ }
126
+ }
127
+ return formData;
128
+ }
97
129
  }
98
130
  // src/adapters/event-wire.ts
131
+ var eventWireInstanceCounter = 0;
132
+
99
133
  class EventWire {
100
134
  baseUrl;
135
+ instanceId;
101
136
  ws = null;
102
137
  state = "disconnected";
103
138
  token = null;
@@ -109,6 +144,7 @@ class EventWire {
109
144
  syncRequested = false;
110
145
  constructor(baseUrl) {
111
146
  this.baseUrl = baseUrl;
147
+ this.instanceId = ++eventWireInstanceCounter;
112
148
  }
113
149
  setAuthToken(token) {
114
150
  this.token = token;
@@ -127,16 +163,26 @@ class EventWire {
127
163
  if (this.state !== "disconnected")
128
164
  return;
129
165
  this.state = "connecting";
130
- const wsUrl = this.baseUrl.replace(/^http/, "ws");
166
+ let wsUrl;
167
+ if (this.baseUrl.startsWith("http://") || this.baseUrl.startsWith("https://")) {
168
+ wsUrl = this.baseUrl.replace(/^http/, "ws");
169
+ } else {
170
+ const protocol = typeof window !== "undefined" && window.location.protocol === "https:" ? "wss:" : "ws:";
171
+ const host = typeof window !== "undefined" ? window.location.host : "localhost";
172
+ wsUrl = `${protocol}//${host}${this.baseUrl}`;
173
+ }
131
174
  const tokenParam = this.token ? `?token=${this.token}` : "";
132
175
  const url = `${wsUrl}/ws${tokenParam}`;
133
176
  try {
134
177
  this.ws = new WebSocket(url);
135
178
  this.ws.onopen = () => {
136
- this.state = "connected";
137
- console.log(`EventWire connected`);
138
- this.requestSync();
139
- this.flushPendingEvents();
179
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
180
+ this.state = "connected";
181
+ this.requestSync();
182
+ this.flushPendingEvents();
183
+ } else {
184
+ console.log(`[EventWire] onopen called but ws is not OPEN, readyState:`, this.ws?.readyState);
185
+ }
140
186
  };
141
187
  this.ws.onmessage = (event) => {
142
188
  try {
@@ -146,7 +192,7 @@ class EventWire {
146
192
  console.error("EventWire: Failed to parse message", err);
147
193
  }
148
194
  };
149
- this.ws.onclose = () => {
195
+ this.ws.onclose = (event) => {
150
196
  this.state = "disconnected";
151
197
  this.ws = null;
152
198
  console.log("EventWire disconnected");
@@ -154,9 +200,14 @@ class EventWire {
154
200
  };
155
201
  this.ws.onerror = (err) => {
156
202
  console.error("EventWire error:", err);
203
+ if (this.state === "connecting") {
204
+ this.state = "disconnected";
205
+ this.ws = null;
206
+ }
157
207
  };
158
208
  } catch (err) {
159
209
  this.state = "disconnected";
210
+ this.ws = null;
160
211
  console.error("EventWire: Failed to connect", err);
161
212
  this.scheduleReconnect();
162
213
  }
@@ -174,19 +225,28 @@ class EventWire {
174
225
  this.syncRequested = false;
175
226
  }
176
227
  syncEvents(events) {
177
- if (this.state !== "connected" || !this.ws) {
228
+ const isActuallyConnected = this.ws && this.ws.readyState === WebSocket.OPEN;
229
+ if (!isActuallyConnected) {
230
+ if (this.state === "connected" && !this.ws) {
231
+ this.state = "disconnected";
232
+ }
178
233
  this.pendingEvents.push(...events);
234
+ if (this.state === "disconnected") {
235
+ this.connect();
236
+ }
179
237
  return;
180
238
  }
181
- this.ws.send(JSON.stringify({
182
- type: "sync-events",
183
- events: events.map((e) => ({
184
- localId: e.localId,
185
- type: e.type,
186
- payload: e.payload,
187
- createdAt: e.createdAt
188
- }))
189
- }));
239
+ if (this.ws) {
240
+ this.ws.send(JSON.stringify({
241
+ type: "sync-events",
242
+ events: events.map((e) => ({
243
+ localId: e.localId,
244
+ type: e.type,
245
+ payload: e.payload,
246
+ createdAt: e.createdAt
247
+ }))
248
+ }));
249
+ }
190
250
  }
191
251
  onEvent(callback) {
192
252
  this.onEventCallback = callback;
@@ -1931,6 +1991,84 @@ function route(name) {
1931
1991
  isPublic: false
1932
1992
  });
1933
1993
  }
1994
+ // src/context-element/static-view/static-view.ts
1995
+ class ArcStaticView extends ArcContextElement {
1996
+ data;
1997
+ constructor(data) {
1998
+ super(data.name);
1999
+ this.data = data;
2000
+ }
2001
+ get id() {
2002
+ return this.data.id;
2003
+ }
2004
+ get schema() {
2005
+ return this.data.schema;
2006
+ }
2007
+ get items() {
2008
+ return this.data.items;
2009
+ }
2010
+ description(description) {
2011
+ return new ArcStaticView({
2012
+ ...this.data,
2013
+ description
2014
+ });
2015
+ }
2016
+ addItems(items) {
2017
+ return new ArcStaticView({
2018
+ ...this.data,
2019
+ items
2020
+ });
2021
+ }
2022
+ queryContext(_adapters) {
2023
+ const items = this.data.items;
2024
+ return {
2025
+ find: async (options) => {
2026
+ let results = [...items];
2027
+ if (options?.where) {
2028
+ results = results.filter((item) => this.matchesWhere(item, options.where));
2029
+ }
2030
+ if (options?.orderBy) {
2031
+ const entries = Object.entries(options.orderBy);
2032
+ results.sort((a, b) => {
2033
+ for (const [key, direction] of entries) {
2034
+ const aVal = a[key];
2035
+ const bVal = b[key];
2036
+ if (aVal < bVal)
2037
+ return direction === "asc" ? -1 : 1;
2038
+ if (aVal > bVal)
2039
+ return direction === "asc" ? 1 : -1;
2040
+ }
2041
+ return 0;
2042
+ });
2043
+ }
2044
+ if (options?.limit) {
2045
+ results = results.slice(0, options.limit);
2046
+ }
2047
+ return results;
2048
+ },
2049
+ findOne: async (where) => {
2050
+ if (!where)
2051
+ return items[0];
2052
+ const item = items.find((item2) => this.matchesWhere(item2, where));
2053
+ return item;
2054
+ }
2055
+ };
2056
+ }
2057
+ matchesWhere(item, where) {
2058
+ return Object.entries(where).every(([key, value]) => {
2059
+ return item[key] === value;
2060
+ });
2061
+ }
2062
+ }
2063
+ function staticView(name, id2, schema) {
2064
+ const schemaObj = schema instanceof ArcObject ? schema : new ArcObject(schema);
2065
+ return new ArcStaticView({
2066
+ name,
2067
+ id: id2,
2068
+ schema: schemaObj,
2069
+ items: []
2070
+ });
2071
+ }
1934
2072
  // src/context-element/view/view.ts
1935
2073
  class ArcView extends ArcContextElement {
1936
2074
  data;
@@ -3756,17 +3894,25 @@ class StreamingEventPublisher {
3756
3894
  }
3757
3895
  }
3758
3896
  // src/streaming/streaming-live-query.ts
3897
+ function isStaticView(element2) {
3898
+ return element2 && "items" in element2 && !("getHandlers" in element2);
3899
+ }
3759
3900
  function streamingLiveQuery(model, queryFn, callback, options) {
3760
3901
  const { queryWire, cache, authToken } = options;
3761
3902
  let currentResult = undefined;
3762
3903
  const unsubscribers = [];
3763
3904
  const queriedViews = new Set;
3905
+ const staticViews = new Set;
3764
3906
  const queryContext = new Proxy({}, {
3765
3907
  get(_target, viewName) {
3766
3908
  const element2 = model.context.get(viewName);
3767
3909
  if (!element2) {
3768
3910
  throw new Error(`View '${viewName}' not found in context`);
3769
3911
  }
3912
+ if (isStaticView(element2)) {
3913
+ staticViews.add(viewName);
3914
+ return element2.queryContext(model.getAdapters());
3915
+ }
3770
3916
  queriedViews.add(viewName);
3771
3917
  return {
3772
3918
  find: async (findOptions = {}) => {
@@ -3787,7 +3933,12 @@ function streamingLiveQuery(model, queryFn, callback, options) {
3787
3933
  };
3788
3934
  const setupStreams = async () => {
3789
3935
  queriedViews.clear();
3936
+ staticViews.clear();
3790
3937
  await queryFn(queryContext);
3938
+ if (queriedViews.size === 0 && staticViews.size > 0) {
3939
+ executeQuery();
3940
+ return;
3941
+ }
3791
3942
  for (const viewName of queriedViews) {
3792
3943
  const store = cache.getStore(viewName);
3793
3944
  const cacheUnsub = store.subscribe(() => {
@@ -4246,6 +4397,7 @@ export {
4246
4397
  stringEnum,
4247
4398
  string,
4248
4399
  streamingLiveQuery,
4400
+ staticView,
4249
4401
  secureDataStorage,
4250
4402
  route,
4251
4403
  resolveQueryChange,
@@ -4299,6 +4451,7 @@ export {
4299
4451
  ArcToken,
4300
4452
  ArcStringEnum,
4301
4453
  ArcString,
4454
+ ArcStaticView,
4302
4455
  ArcRoute,
4303
4456
  ArcRecord,
4304
4457
  ArcOr,
@@ -0,0 +1,4 @@
1
+ export { ArcTelemetry, type ObservabilityMode, type TelemetryConfig, } from "./telemetry";
2
+ export { initBrowserTelemetry } from "./telemetry-browser";
3
+ export { initServerTelemetry } from "./telemetry-server";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,6 @@
1
+ import { ArcTelemetry, type TelemetryConfig } from "./telemetry";
2
+ /**
3
+ * Initialize OpenTelemetry for browser environment
4
+ */
5
+ export declare function initBrowserTelemetry(config: TelemetryConfig): ArcTelemetry;
6
+ //# sourceMappingURL=telemetry-browser.d.ts.map
@@ -0,0 +1,6 @@
1
+ import { ArcTelemetry, type TelemetryConfig } from "./telemetry";
2
+ /**
3
+ * Initialize OpenTelemetry for Node.js/Bun server environment
4
+ */
5
+ export declare function initServerTelemetry(config: TelemetryConfig): ArcTelemetry;
6
+ //# sourceMappingURL=telemetry-server.d.ts.map
@@ -0,0 +1,51 @@
1
+ import { type Span } from "@opentelemetry/api";
2
+ export type ObservabilityMode = "development" | "production" | "disabled";
3
+ export interface TelemetryConfig {
4
+ serviceName: string;
5
+ environment: "client" | "server";
6
+ endpoint?: string;
7
+ enabled?: boolean;
8
+ sampleRate?: number;
9
+ mode?: ObservabilityMode;
10
+ includePayloads?: boolean;
11
+ debug?: boolean;
12
+ }
13
+ /**
14
+ * Arc Telemetry - Wrapper around OpenTelemetry for Arc Framework
15
+ *
16
+ * Provides simplified API for tracing commands, events, and views
17
+ */
18
+ export declare class ArcTelemetry {
19
+ private tracer;
20
+ readonly config: TelemetryConfig;
21
+ constructor(config: TelemetryConfig);
22
+ /**
23
+ * Set the tracer instance (called by init functions)
24
+ */
25
+ setTracer(tracer: any): void;
26
+ /**
27
+ * Start a span with automatic error handling
28
+ */
29
+ startSpan<T>(name: string, fn: (span: Span) => Promise<T>, attributes?: Record<string, any>): Promise<T>;
30
+ /**
31
+ * Get current active span
32
+ */
33
+ getCurrentSpan(): Span | undefined;
34
+ /**
35
+ * Add attributes to current span
36
+ */
37
+ addAttributes(attributes: Record<string, any>): void;
38
+ /**
39
+ * Create a child span (must be manually ended)
40
+ */
41
+ createChildSpan(name: string, attributes?: Record<string, any>): Span | undefined;
42
+ /**
43
+ * Serialize value for span attributes (handles objects, arrays, etc)
44
+ */
45
+ private serializeValue;
46
+ /**
47
+ * Check if payloads should be included based on mode
48
+ */
49
+ shouldIncludePayloads(): boolean;
50
+ }
51
+ //# sourceMappingURL=telemetry.d.ts.map
@@ -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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arcote.tech/arc",
3
3
  "type": "module",
4
- "version": "0.3.4",
4
+ "version": "0.3.6",
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",