@arcote.tech/arc 0.1.10 → 0.1.12

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,5 @@
1
+ export type CommandDefinition = {
2
+ readonly commandName: string;
3
+ readonly parameters: any;
4
+ };
5
+ //# sourceMappingURL=command-definition.d.ts.map
@@ -21,11 +21,15 @@ export type ArcCommandContext<E extends ArcContextElementAny[]> = ArcContextElem
21
21
  export type RemoveFalseAndResolvedPromiseFromArray<T extends any[]> = T extends [infer First, ...infer Rest] ? First extends false ? RemoveFalseAndResolvedPromiseFromArray<Rest> : First extends Promise<{
22
22
  default: infer Inner;
23
23
  }> ? RemoveFalseAndResolvedPromiseFromArray<[Inner, ...Rest]> : [First, ...RemoveFalseAndResolvedPromiseFromArray<Rest>] : [];
24
+ export type FindElementByName<E extends ArcContextElementAny[], Name extends string> = E extends [
25
+ infer First extends ArcContextElementAny,
26
+ ...infer Rest extends ArcContextElementAny[]
27
+ ] ? First["name"] extends Name ? First : FindElementByName<Rest, Name> : never;
24
28
  export declare class ArcContext<const E extends ArcContextElementAny[]> {
25
29
  readonly elements: E;
26
30
  private elementsSet;
27
31
  constructor(elements: E);
28
- get<Element extends E[number]>(name: Element["name"]): Element;
32
+ get<Name extends E[number]["name"]>(name: Name): FindElementByName<E, Name>;
29
33
  getSyncListeners(eventType: string, authContext: AuthContext): EventListener<any>[];
30
34
  getAsyncListeners(eventType: string, authContext: AuthContext): EventListener<any>[];
31
35
  pipe<const NewElements extends (ArcContextElementAny | false | Promise<{
@@ -31,6 +31,7 @@ export type ArcEventInstance<Event extends ArcEventAny> = {
31
31
  payload: Event["payload"] extends ArcObjectAny ? $type<Event["payload"]> : undefined;
32
32
  } & EventMetadata;
33
33
  export type ArcEventAny = ArcEvent<any, any>;
34
+ export type ArcEventPayload<Event extends ArcEventAny> = Event extends ArcEvent<any, infer PayloadShape extends ArcObjectAny> ? $type<PayloadShape> : undefined;
34
35
  export declare function event<const Name extends string, PayloadShape extends ArcObjectAny | ArcRawShape | undefined>(name: Name, payload?: PayloadShape): ArcEvent<Name, PayloadShape extends ArcRawShape ? ArcObject<PayloadShape, [{
35
36
  name: "type";
36
37
  validator: (value: any) => false | {
@@ -7,4 +7,5 @@ export * from "./query-builder-context";
7
7
  export * from "./query-builders";
8
8
  export * from "./query-cache";
9
9
  export * from "./reactive-query";
10
+ export * from "./translator";
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,4 @@
1
+ import type { ArcEventAny, ArcEventInstance, ArcEventPayload } from "./event";
2
+ export type Translation<From extends ArcEventAny, To extends ArcEventAny> = (event: ArcEventInstance<From>) => ArcEventPayload<To>;
3
+ export declare function translate<From extends ArcEventAny, To extends ArcEventAny>(from: From, to: To, translation: Translation<From, To>): import("..").ArcListener<`${From["name"]}To${To["name"]}Translator`, [To], [From]>;
4
+ //# sourceMappingURL=translator.d.ts.map
@@ -6,7 +6,7 @@ export declare class MasterStoreState<Item extends {
6
6
  _id: string;
7
7
  }> extends StoreState<Item> {
8
8
  constructor(storeName: string, dataStorage: DataStorage, deserialize?: (data: any) => Item);
9
- applyChangeAndReturnEvent(transaction: ReadWriteTransaction, change: StoreStateChange<Item>): Promise<{
9
+ applyChangeAndReturnEvent(transaction: ReadWriteTransaction, change: StoreStateChange<Item>, transactionCache?: Map<string, Item>): Promise<{
10
10
  from: Item | null;
11
11
  to: Item | null;
12
12
  event: ListenerEvent<Item>;
@@ -2,6 +2,7 @@ import type { DatabaseAgnosticColumnInfo } from "../database/database-store";
2
2
  import { type objectUtil, type util } from "../utils";
3
3
  import { ArcAbstract, type Validators } from "./abstract";
4
4
  import type { ArcElement } from "./element";
5
+ import { ArcOptional } from "./optional";
5
6
  export declare type ArcRawShape = {
6
7
  [k: string]: ArcElement;
7
8
  };
@@ -64,13 +65,19 @@ export declare class ArcObject<E extends ArcRawShape, V extends Validators = [
64
65
  pick<Keys extends keyof E>(...keys: Keys[]): ArcObject<Pick<E, Keys>, V>;
65
66
  omit<Keys extends keyof E>(...keys: Keys[]): ArcObject<Omit<E, Keys>, V>;
66
67
  entries(): [string, ArcElement][];
68
+ keys(): (keyof E)[];
67
69
  toJsonSchema(): any;
68
70
  /** Generate database-agnostic column information */
69
71
  getColumnData(): DatabaseAgnosticColumnInfo;
70
72
  /** Merge this ArcObject with another shape */
71
73
  merge<OtherShape extends ArcRawShape>(otherShape: OtherShape): ArcObject<E & OtherShape, V>;
74
+ /** Create a partial version of this ArcObject where all fields are optional */
75
+ partial(): ArcObjectPartial<this>;
72
76
  }
73
77
  export type ArcObjectAny = ArcObject<any, any>;
78
+ export type ArcObjectPartial<T extends ArcObjectAny> = T extends ArcObject<infer E, infer V> ? ArcObject<{
79
+ [K in keyof E]: ArcOptional<E[K]>;
80
+ }, V> : never;
74
81
  export type ArcObjectKeys<T extends ArcObjectAny> = T extends ArcObject<infer E, any> ? Exclude<keyof E, symbol> : never;
75
82
  export type ArcObjectValueByKey<T extends ArcObjectAny, K extends ArcObjectKeys<T>> = T extends ArcObject<infer E, any> ? E[K] : never;
76
83
  export type ArcRawShapeType<Shape extends ArcRawShape> = {
package/dist/index.js CHANGED
@@ -414,6 +414,9 @@ class ArcObject extends ArcAbstract {
414
414
  entries() {
415
415
  return Object.entries(this.rawShape);
416
416
  }
417
+ keys() {
418
+ return Object.keys(this.rawShape);
419
+ }
417
420
  toJsonSchema() {
418
421
  const properties = {};
419
422
  const required = [];
@@ -450,6 +453,13 @@ class ArcObject extends ArcAbstract {
450
453
  const mergedShape = { ...this.rawShape, ...otherShape };
451
454
  return new ArcObject(mergedShape);
452
455
  }
456
+ partial() {
457
+ const partialShape = Object.fromEntries(Object.entries(this.rawShape).map(([key, element]) => [
458
+ key,
459
+ element instanceof ArcOptional ? element : new ArcOptional(element)
460
+ ]));
461
+ return new ArcObject(partialShape);
462
+ }
453
463
  }
454
464
  function object(element) {
455
465
  return new ArcObject(element);
@@ -1611,14 +1621,16 @@ var sharedEventDatabaseSchema = null;
1611
1621
  function getSharedEventDatabaseSchema() {
1612
1622
  if (!sharedEventDatabaseSchema) {
1613
1623
  sharedEventDatabaseSchema = {
1614
- tables: [{
1615
- name: "events",
1616
- schema: eventSchema,
1617
- options: {
1618
- softDelete: false,
1619
- versioning: true
1624
+ tables: [
1625
+ {
1626
+ name: "events",
1627
+ schema: eventSchema,
1628
+ options: {
1629
+ softDelete: false,
1630
+ versioning: true
1631
+ }
1620
1632
  }
1621
- }]
1633
+ ]
1622
1634
  };
1623
1635
  }
1624
1636
  return sharedEventDatabaseSchema;
@@ -1885,6 +1897,104 @@ class ReactiveQueryBuilder {
1885
1897
  function reactive(fn) {
1886
1898
  return (context2) => new ReactiveQueryBuilder(context2, fn);
1887
1899
  }
1900
+ // listener/listener.ts
1901
+ class ArcListener extends ArcContextElement {
1902
+ name;
1903
+ _description;
1904
+ _elements;
1905
+ _eventElements;
1906
+ _handler;
1907
+ _isAsync = false;
1908
+ constructor(name) {
1909
+ super();
1910
+ this.name = name;
1911
+ }
1912
+ use(elements) {
1913
+ const clone = this.clone();
1914
+ clone._elements = elements;
1915
+ return clone;
1916
+ }
1917
+ description(description) {
1918
+ const clone = this.clone();
1919
+ clone._description = description;
1920
+ return clone;
1921
+ }
1922
+ listenTo(events) {
1923
+ const clone = this.clone();
1924
+ clone._eventElements = events;
1925
+ return clone;
1926
+ }
1927
+ async() {
1928
+ const clone = this.clone();
1929
+ clone._isAsync = true;
1930
+ return clone;
1931
+ }
1932
+ handle(handler) {
1933
+ const clone = this.clone();
1934
+ clone._handler = handler;
1935
+ return clone;
1936
+ }
1937
+ observer = (authContext) => {
1938
+ if (!this._handler || !this._elements || !this._eventElements) {
1939
+ return {};
1940
+ }
1941
+ const eventTypes = this._eventElements.filter((element2) => element2 instanceof ArcEvent).map((event2) => event2.name);
1942
+ return eventTypes.reduce((acc, eventType) => {
1943
+ acc[eventType] = {
1944
+ handler: async (event2, dataStorage, publishEvent) => {
1945
+ const ctx = new Proxy({}, {
1946
+ get: (target, name) => {
1947
+ if (name === "$auth") {
1948
+ return authContext;
1949
+ }
1950
+ if (name === "get") {
1951
+ return (e) => {
1952
+ const element3 = this._elements.find((element4) => element4.name === e.name);
1953
+ if (!element3) {
1954
+ throw new Error(`Element "${String(e.name)}" not found in listener "${this.name}"`);
1955
+ }
1956
+ if (!element3.commandContext) {
1957
+ throw new Error(`Element "${String(e.name)}" does not have a command context`);
1958
+ }
1959
+ return element3.commandContext(dataStorage, publishEvent, authContext);
1960
+ };
1961
+ }
1962
+ const element2 = this._elements.find((element3) => element3.name === name);
1963
+ if (!element2) {
1964
+ throw new Error(`Element "${element2}" not found in listener "${this.name}"`);
1965
+ }
1966
+ if (!element2.commandContext) {
1967
+ throw new Error(`Element "${String(name)}" does not have a command context`);
1968
+ }
1969
+ return element2.commandContext(dataStorage, publishEvent, authContext);
1970
+ }
1971
+ });
1972
+ await this._handler(ctx, event2);
1973
+ },
1974
+ isAsync: this._isAsync
1975
+ };
1976
+ return acc;
1977
+ }, {});
1978
+ };
1979
+ clone() {
1980
+ const clone = new ArcListener(this.name);
1981
+ clone._description = this._description;
1982
+ clone._elements = this._elements;
1983
+ clone._eventElements = this._eventElements;
1984
+ clone._handler = this._handler;
1985
+ clone._isAsync = this._isAsync;
1986
+ return clone;
1987
+ }
1988
+ }
1989
+ function listener(name) {
1990
+ return new ArcListener(name);
1991
+ }
1992
+ // context/translator.ts
1993
+ function translate(from, to, translation) {
1994
+ return listener(`${from.name}To${to.name}Translator`).listenTo([from]).use([to]).handle((ctx, event2) => {
1995
+ ctx.get(from).emit(translation(event2));
1996
+ });
1997
+ }
1888
1998
  // data-storage/data-storage.abstract.ts
1889
1999
  class DataStorage {
1890
2000
  async commitChanges(changes) {
@@ -1970,12 +2080,12 @@ class StoreState {
1970
2080
  applySerializedChanges(changes) {
1971
2081
  return Promise.all(changes.map((change) => this.applyChange(change)));
1972
2082
  }
1973
- unsubscribe(listener) {
1974
- this.listeners.delete(listener);
2083
+ unsubscribe(listener3) {
2084
+ this.listeners.delete(listener3);
1975
2085
  }
1976
2086
  notifyListeners(events) {
1977
- for (const listener of this.listeners.values()) {
1978
- listener.callback(events);
2087
+ for (const listener3 of this.listeners.values()) {
2088
+ listener3.callback(events);
1979
2089
  }
1980
2090
  }
1981
2091
  }
@@ -2069,9 +2179,9 @@ class ForkedStoreState extends StoreState {
2069
2179
  this.notifyListeners(events);
2070
2180
  }
2071
2181
  }
2072
- async find(options, listener) {
2073
- if (listener) {
2074
- this.listeners.set(listener, { callback: listener });
2182
+ async find(options, listener3) {
2183
+ if (listener3) {
2184
+ this.listeners.set(listener3, { callback: listener3 });
2075
2185
  }
2076
2186
  const parentResults = await this.master.find(options);
2077
2187
  const results = new Map;
@@ -2131,10 +2241,13 @@ class MasterStoreState extends StoreState {
2131
2241
  constructor(storeName, dataStorage, deserialize) {
2132
2242
  super(storeName, dataStorage, deserialize);
2133
2243
  }
2134
- async applyChangeAndReturnEvent(transaction, change) {
2244
+ async applyChangeAndReturnEvent(transaction, change, transactionCache) {
2135
2245
  if (change.type === "set") {
2136
2246
  await transaction.set(this.storeName, change.data);
2137
2247
  const item = this.deserialize ? this.deserialize(change.data) : change.data;
2248
+ if (transactionCache) {
2249
+ transactionCache.set(change.data._id, item);
2250
+ }
2138
2251
  return {
2139
2252
  from: null,
2140
2253
  to: item,
@@ -2158,10 +2271,18 @@ class MasterStoreState extends StoreState {
2158
2271
  };
2159
2272
  }
2160
2273
  if (change.type === "modify") {
2161
- const existing = await transaction.find(this.storeName, { where: { _id: change.id } }).then((results) => results[0]);
2274
+ let existing;
2275
+ if (transactionCache && transactionCache.has(change.id)) {
2276
+ existing = transactionCache.get(change.id);
2277
+ } else {
2278
+ existing = await transaction.find(this.storeName, { where: { _id: change.id } }).then((results) => results[0]);
2279
+ }
2162
2280
  const updated = existing ? deepMerge(existing, change.data) : { _id: change.id, ...change.data };
2163
2281
  await transaction.set(this.storeName, updated);
2164
2282
  const item = this.deserialize ? this.deserialize(updated) : updated;
2283
+ if (transactionCache) {
2284
+ transactionCache.set(change.id, item);
2285
+ }
2165
2286
  return {
2166
2287
  from: null,
2167
2288
  to: item,
@@ -2173,10 +2294,18 @@ class MasterStoreState extends StoreState {
2173
2294
  };
2174
2295
  }
2175
2296
  if (change.type === "mutate") {
2176
- const existing = await transaction.find(this.storeName, { where: { _id: change.id } }).then((results) => results[0]);
2297
+ let existing;
2298
+ if (transactionCache && transactionCache.has(change.id)) {
2299
+ existing = transactionCache.get(change.id);
2300
+ } else {
2301
+ existing = await transaction.find(this.storeName, { where: { _id: change.id } }).then((results) => results[0]);
2302
+ }
2177
2303
  const updated = apply2(existing || {}, change.patches);
2178
2304
  await transaction.set(this.storeName, updated);
2179
2305
  const item = this.deserialize ? this.deserialize(updated) : updated;
2306
+ if (transactionCache) {
2307
+ transactionCache.set(change.id, item);
2308
+ }
2180
2309
  return {
2181
2310
  from: null,
2182
2311
  to: item,
@@ -2191,16 +2320,18 @@ class MasterStoreState extends StoreState {
2191
2320
  }
2192
2321
  async applyChange(change) {
2193
2322
  const transaction = await this.dataStorage.getReadWriteTransaction();
2194
- const { event: event3, from, to } = await this.applyChangeAndReturnEvent(transaction, change);
2323
+ const transactionCache = new Map;
2324
+ const { event: event3, from, to } = await this.applyChangeAndReturnEvent(transaction, change, transactionCache);
2195
2325
  await transaction.commit();
2196
2326
  this.notifyListeners([event3]);
2197
2327
  return { from, to };
2198
2328
  }
2199
2329
  async applyChanges(changes) {
2200
2330
  const transaction = await this.dataStorage.getReadWriteTransaction();
2331
+ const transactionCache = new Map;
2201
2332
  const events = [];
2202
2333
  for (const change of changes) {
2203
- const { event: event3 } = await this.applyChangeAndReturnEvent(transaction, change);
2334
+ const { event: event3 } = await this.applyChangeAndReturnEvent(transaction, change, transactionCache);
2204
2335
  if (event3)
2205
2336
  events.push(event3);
2206
2337
  }
@@ -2209,9 +2340,9 @@ class MasterStoreState extends StoreState {
2209
2340
  this.notifyListeners(events);
2210
2341
  }
2211
2342
  }
2212
- async find(options, listener) {
2213
- if (listener) {
2214
- this.listeners.set(listener, { callback: listener });
2343
+ async find(options, listener3) {
2344
+ if (listener3) {
2345
+ this.listeners.set(listener3, { callback: listener3 });
2215
2346
  }
2216
2347
  const transaction = await this.dataStorage.getReadTransaction();
2217
2348
  const results = await transaction.find(this.storeName, options);
@@ -2439,7 +2570,7 @@ class PostgreSQLReadTransaction {
2439
2570
  }
2440
2571
  let query2 = `
2441
2572
  SELECT *
2442
- FROM "${store}"
2573
+ FROM "${table.name}"
2443
2574
  WHERE ${whereClause.sql}
2444
2575
  ${orderByClause}
2445
2576
  `;
@@ -2471,7 +2602,7 @@ class PostgreSQLReadWriteTransaction extends PostgreSQLReadTransaction {
2471
2602
  if (!table) {
2472
2603
  throw new Error(`Store ${store} not found`);
2473
2604
  }
2474
- const query2 = `UPDATE "${store}" SET "deleted" = $1, "lastUpdate" = $2 WHERE "${table.primaryKey}" = $3`;
2605
+ const query2 = `UPDATE "${table.name}" SET "deleted" = $1, "lastUpdate" = $2 WHERE "${table.primaryKey}" = $3`;
2475
2606
  this.queries.push({
2476
2607
  sql: query2,
2477
2608
  params: [true, new Date().toISOString(), id3]
@@ -3002,7 +3133,7 @@ class SQLiteReadTransaction {
3002
3133
  }
3003
3134
  const query2 = `
3004
3135
  SELECT *
3005
- FROM ${store}
3136
+ FROM ${table.name}
3006
3137
  WHERE ${whereClause.sql}
3007
3138
  ${orderByClause}
3008
3139
  ${limit ? `LIMIT ${limit}` : ""}
@@ -3024,7 +3155,7 @@ class SQLiteReadWriteTransaction extends SQLiteReadTransaction {
3024
3155
  if (!table) {
3025
3156
  throw new Error(`Store ${store} not found`);
3026
3157
  }
3027
- const query2 = `UPDATE ${store} SET deleted = 1, lastUpdate = ? WHERE ${table.primaryKey} = ?`;
3158
+ const query2 = `UPDATE ${table.name} SET deleted = 1, lastUpdate = ? WHERE ${table.primaryKey} = ?`;
3028
3159
  await this.db.exec(query2, [new Date().toISOString(), id3]);
3029
3160
  }
3030
3161
  async set(store, item) {
@@ -3364,99 +3495,7 @@ var createSQLiteAdapterFactory = (db) => {
3364
3495
  return adapter;
3365
3496
  };
3366
3497
  };
3367
- // listener/listener.ts
3368
- class ArcListener extends ArcContextElement {
3369
- name;
3370
- _description;
3371
- _elements;
3372
- _eventElements;
3373
- _handler;
3374
- _isAsync = false;
3375
- constructor(name) {
3376
- super();
3377
- this.name = name;
3378
- }
3379
- use(elements) {
3380
- const clone = this.clone();
3381
- clone._elements = elements;
3382
- return clone;
3383
- }
3384
- description(description) {
3385
- const clone = this.clone();
3386
- clone._description = description;
3387
- return clone;
3388
- }
3389
- listenTo(events) {
3390
- const clone = this.clone();
3391
- clone._eventElements = events;
3392
- return clone;
3393
- }
3394
- async() {
3395
- const clone = this.clone();
3396
- clone._isAsync = true;
3397
- return clone;
3398
- }
3399
- handle(handler) {
3400
- const clone = this.clone();
3401
- clone._handler = handler;
3402
- return clone;
3403
- }
3404
- observer = (authContext) => {
3405
- if (!this._handler || !this._elements || !this._eventElements) {
3406
- return {};
3407
- }
3408
- const eventTypes = this._eventElements.filter((element3) => element3 instanceof ArcEvent).map((event3) => event3.name);
3409
- return eventTypes.reduce((acc, eventType) => {
3410
- acc[eventType] = {
3411
- handler: async (event3, dataStorage, publishEvent) => {
3412
- const ctx = new Proxy({}, {
3413
- get: (target, name) => {
3414
- if (name === "$auth") {
3415
- return authContext;
3416
- }
3417
- if (name === "get") {
3418
- return (e) => {
3419
- const element4 = this._elements.find((element5) => element5.name === e.name);
3420
- if (!element4) {
3421
- throw new Error(`Element "${String(e.name)}" not found in listener "${this.name}"`);
3422
- }
3423
- if (!element4.commandContext) {
3424
- throw new Error(`Element "${String(e.name)}" does not have a command context`);
3425
- }
3426
- return element4.commandContext(dataStorage, publishEvent, authContext);
3427
- };
3428
- }
3429
- const element3 = this._elements.find((element4) => element4.name === name);
3430
- if (!element3) {
3431
- throw new Error(`Element "${element3}" not found in listener "${this.name}"`);
3432
- }
3433
- if (!element3.commandContext) {
3434
- throw new Error(`Element "${String(name)}" does not have a command context`);
3435
- }
3436
- return element3.commandContext(dataStorage, publishEvent, authContext);
3437
- }
3438
- });
3439
- await this._handler(ctx, event3);
3440
- },
3441
- isAsync: this._isAsync
3442
- };
3443
- return acc;
3444
- }, {});
3445
- };
3446
- clone() {
3447
- const clone = new ArcListener(this.name);
3448
- clone._description = this._description;
3449
- clone._elements = this._elements;
3450
- clone._eventElements = this._eventElements;
3451
- clone._handler = this._handler;
3452
- clone._isAsync = this._isAsync;
3453
- return clone;
3454
- }
3455
- }
3456
- function listener(name) {
3457
- return new ArcListener(name);
3458
- }
3459
- // model/model.ts
3498
+ // model/event-publisher.ts
3460
3499
  class EventPublisher {
3461
3500
  context;
3462
3501
  dataStorage;
@@ -3509,6 +3548,7 @@ class EventPublisher {
3509
3548
  }
3510
3549
  }
3511
3550
 
3551
+ // model/model.ts
3512
3552
  class ModelBase {
3513
3553
  token = null;
3514
3554
  setAuthToken(token) {
@@ -3528,12 +3568,18 @@ class Model extends ModelBase {
3528
3568
  this.catchErrorCallback = catchErrorCallback;
3529
3569
  }
3530
3570
  async query(queryBuilderFn, authContext) {
3571
+ if (!this.dataStorage) {
3572
+ throw new Error("DataStorage is required for query operations");
3573
+ }
3531
3574
  const queryContext = new QueryBuilderContext(this.queryCache, this.dataStorage);
3532
3575
  const queryBuilder = this.context.queryBuilder(queryContext, authContext);
3533
3576
  const query2 = queryBuilderFn(queryBuilder).toQuery(queryContext);
3534
3577
  return query2.run(this.dataStorage);
3535
3578
  }
3536
3579
  subscribe(queryBuilderFn, callback, authContext) {
3580
+ if (!this.dataStorage) {
3581
+ throw new Error("DataStorage is required for subscribe operations");
3582
+ }
3537
3583
  const queryContext = new QueryBuilderContext(this.queryCache, this.dataStorage);
3538
3584
  const queryBuilder = this.context.queryBuilder(queryContext, authContext);
3539
3585
  const query2 = queryBuilderFn(queryBuilder).toQuery(queryContext);
@@ -3560,6 +3606,9 @@ class Model extends ModelBase {
3560
3606
  throw new Error(`Element "${String(name)}" does not have a command client`);
3561
3607
  }
3562
3608
  return async (arg) => {
3609
+ if (!this.dataStorage) {
3610
+ throw new Error("DataStorage is required for command operations");
3611
+ }
3563
3612
  const forkedDataStorage = this.dataStorage.fork();
3564
3613
  const eventPublisher = new EventPublisher(this.context, this.dataStorage, authContext);
3565
3614
  const publishEvent = async (event3) => {
@@ -3572,7 +3621,9 @@ class Model extends ModelBase {
3572
3621
  eventPublisher.runAsyncListeners();
3573
3622
  return result;
3574
3623
  } catch (error) {
3575
- this.catchErrorCallback(error);
3624
+ if (this.catchErrorCallback) {
3625
+ this.catchErrorCallback(error);
3626
+ }
3576
3627
  return error;
3577
3628
  }
3578
3629
  };
@@ -3594,6 +3645,9 @@ class Model extends ModelBase {
3594
3645
  if (!handler) {
3595
3646
  throw new Error(`Method ${method} not allowed for route ${String(name)}`);
3596
3647
  }
3648
+ if (!this.dataStorage) {
3649
+ throw new Error("DataStorage is required for route operations");
3650
+ }
3597
3651
  const forkedDataStorage = this.dataStorage.fork();
3598
3652
  const eventPublisher = new EventPublisher(this.context, this.dataStorage, authContext);
3599
3653
  const publishEvent = async (event3) => {
@@ -3606,7 +3660,9 @@ class Model extends ModelBase {
3606
3660
  eventPublisher.runAsyncListeners();
3607
3661
  return result;
3608
3662
  } catch (error) {
3609
- this.catchErrorCallback(error);
3663
+ if (this.catchErrorCallback) {
3664
+ this.catchErrorCallback(error);
3665
+ }
3610
3666
  throw error;
3611
3667
  }
3612
3668
  };
@@ -3623,6 +3679,8 @@ class RemoteModelClient extends ModelBase {
3623
3679
  apiBaseUrl;
3624
3680
  client;
3625
3681
  catchErrorCallback;
3682
+ queryCache = new QueryCache;
3683
+ inFlightRequests = new Map;
3626
3684
  constructor(context3, apiBaseUrl, client, catchErrorCallback) {
3627
3685
  super();
3628
3686
  this.context = context3;
@@ -3630,6 +3688,26 @@ class RemoteModelClient extends ModelBase {
3630
3688
  this.client = client;
3631
3689
  this.catchErrorCallback = catchErrorCallback;
3632
3690
  }
3691
+ generateCacheKey(element3, queryType, params) {
3692
+ return `${element3}:${queryType}:${JSON.stringify(params)}`;
3693
+ }
3694
+ async executeQuery(queryTracking) {
3695
+ const response = await fetch(`${this.apiBaseUrl}/query`, {
3696
+ method: "POST",
3697
+ headers: this.getAuthHeaders(),
3698
+ body: JSON.stringify({
3699
+ query: {
3700
+ element: queryTracking.element,
3701
+ queryType: queryTracking.queryType,
3702
+ params: queryTracking.params
3703
+ }
3704
+ })
3705
+ });
3706
+ if (!response.ok) {
3707
+ throw new Error(`Query failed: ${response.statusText}`);
3708
+ }
3709
+ return await response.json();
3710
+ }
3633
3711
  schemaContainsBlobOrFile(schema) {
3634
3712
  if (!schema)
3635
3713
  return false;
@@ -3747,21 +3825,23 @@ class RemoteModelClient extends ModelBase {
3747
3825
  }
3748
3826
  });
3749
3827
  queryBuilderFn(queryBuilderProxy);
3750
- const response = await fetch(`${this.apiBaseUrl}/query`, {
3751
- method: "POST",
3752
- headers: this.getAuthHeaders(),
3753
- body: JSON.stringify({
3754
- query: {
3755
- element: queryTracking.element,
3756
- queryType: queryTracking.queryType,
3757
- params: queryTracking.params
3758
- }
3759
- })
3760
- });
3761
- if (!response.ok) {
3762
- throw new Error(`Query failed: ${response.statusText}`);
3828
+ const cacheKey = this.generateCacheKey(queryTracking.element, queryTracking.queryType, queryTracking.params);
3829
+ const cachedResult = this.queryCache.get([cacheKey]);
3830
+ if (cachedResult) {
3831
+ return cachedResult;
3832
+ }
3833
+ if (this.inFlightRequests.has(cacheKey)) {
3834
+ return this.inFlightRequests.get(cacheKey);
3835
+ }
3836
+ const requestPromise = this.executeQuery(queryTracking);
3837
+ this.inFlightRequests.set(cacheKey, requestPromise);
3838
+ try {
3839
+ const result = await requestPromise;
3840
+ this.queryCache.set([cacheKey], result);
3841
+ return result;
3842
+ } finally {
3843
+ this.inFlightRequests.delete(cacheKey);
3763
3844
  }
3764
- return await response.json();
3765
3845
  }
3766
3846
  subscribe(queryBuilderFn, callback, authContext) {
3767
3847
  const result = this.query(queryBuilderFn, authContext);
@@ -4534,6 +4614,7 @@ function view(name, id3, schema) {
4534
4614
  }
4535
4615
  export {
4536
4616
  view,
4617
+ translate,
4537
4618
  stringEnum,
4538
4619
  string,
4539
4620
  staticView,
@@ -0,0 +1,12 @@
1
+ import type { ArcContextAny, AuthContext } from "../context";
2
+ import type { DataStorage } from "../data-storage";
3
+ export declare class EventPublisher<C extends ArcContextAny> {
4
+ private readonly context;
5
+ private readonly dataStorage;
6
+ private readonly authContext;
7
+ private asyncEvents;
8
+ constructor(context: C, dataStorage: DataStorage, authContext: AuthContext);
9
+ publishEvent(event: any, commandDataStorage: DataStorage): Promise<void>;
10
+ runAsyncListeners(): Promise<void>;
11
+ }
12
+ //# sourceMappingURL=EventPublisher.d.ts.map
@@ -0,0 +1,12 @@
1
+ import type { ArcContextAny, AuthContext } from "../context";
2
+ import type { DataStorage } from "../data-storage";
3
+ export declare class EventPublisher<C extends ArcContextAny> {
4
+ private readonly context;
5
+ private readonly dataStorage;
6
+ private readonly authContext;
7
+ private asyncEvents;
8
+ constructor(context: C, dataStorage: DataStorage, authContext: AuthContext);
9
+ publishEvent(event: any, commandDataStorage: DataStorage): Promise<void>;
10
+ runAsyncListeners(): Promise<void>;
11
+ }
12
+ //# sourceMappingURL=event-publisher.d.ts.map
@@ -19,10 +19,10 @@ export declare abstract class ModelBase<C extends ArcContextAny> {
19
19
  }
20
20
  export declare class Model<C extends ArcContextAny> extends ModelBase<C> {
21
21
  private readonly context;
22
- private readonly dataStorage;
23
- private readonly catchErrorCallback;
22
+ private readonly dataStorage?;
23
+ private readonly catchErrorCallback?;
24
24
  private queryCache;
25
- constructor(context: C, dataStorage: DataStorage, catchErrorCallback: (error: any) => void);
25
+ constructor(context: C, dataStorage?: DataStorage | undefined, catchErrorCallback?: ((error: any) => void) | undefined);
26
26
  query<Q extends IArcQueryBuilder>(queryBuilderFn: UnaryFunction<ReturnType<C["queryBuilder"]>, Q>, authContext: AuthContext): Promise<ReturnType<Q["toQuery"]>["lastResult"]>;
27
27
  subscribe<Q extends IArcQueryBuilder>(queryBuilderFn: UnaryFunction<ReturnType<C["queryBuilder"]>, Q>, callback: (result: ReturnType<Q["toQuery"]>["lastResult"]) => void, authContext: AuthContext): {
28
28
  result: ReturnType<Q["toQuery"]>["lastResult"];
@@ -37,7 +37,17 @@ export declare class RemoteModelClient<C extends ArcContextAny> extends ModelBas
37
37
  private readonly apiBaseUrl;
38
38
  private readonly client;
39
39
  private readonly catchErrorCallback;
40
+ private queryCache;
41
+ private inFlightRequests;
40
42
  constructor(context: C, apiBaseUrl: string, client: string, catchErrorCallback: (error: any) => void);
43
+ /**
44
+ * Generate a cache key from query details
45
+ */
46
+ private generateCacheKey;
47
+ /**
48
+ * Execute the actual HTTP query request
49
+ */
50
+ private executeQuery;
41
51
  /**
42
52
  * Check if a schema contains blob or file elements
43
53
  */
@@ -0,0 +1,8 @@
1
+ export type QueryDefinition<TResult = any> = {
2
+ readonly contextElementName: string;
3
+ readonly queryAlias: string;
4
+ readonly parameters: any[];
5
+ readonly restrictions?: any;
6
+ };
7
+ export type QueryDefinitionResult<QD extends QueryDefinition> = QD extends QueryDefinition<infer T> ? T : unknown;
8
+ //# sourceMappingURL=query-definition.d.ts.map
@@ -0,0 +1,43 @@
1
+ import type { ArcContextAny } from "../context";
2
+ import type { AuthContext } from "../context/element";
3
+ import type { DataStorage } from "../data-storage";
4
+ import { QueryDefinition } from "../strategies";
5
+ export declare class QueryHandler<C extends ArcContextAny> {
6
+ private model;
7
+ constructor(context: C, dataStorage: DataStorage, catchErrorCallback?: (error: any) => void);
8
+ /**
9
+ * Handle HTTP query requests
10
+ */
11
+ handleQuery(queryDefinition: QueryDefinition, authContext: AuthContext): Promise<any>;
12
+ /**
13
+ * Handle HTTP query requests with streaming for real-time updates
14
+ */
15
+ handleQueryWithStream(queryDefinition: QueryDefinition, authContext: AuthContext, streamCallback: (result: any) => void): Promise<any>;
16
+ /**
17
+ * Handle query sync requests for cache strategy
18
+ */
19
+ handleQuerySync(queryDefinition: QueryDefinition, authContext: AuthContext, syncData: {
20
+ id: string;
21
+ version?: string;
22
+ }[]): Promise<{
23
+ updates: any[];
24
+ }>;
25
+ }
26
+ /**
27
+ * Helper function to create HTTP handlers for different frameworks
28
+ */
29
+ export declare function createHttpQueryHandlers<C extends ArcContextAny>(context: C, dataStorage: DataStorage, catchErrorCallback?: (error: any) => void): {
30
+ /**
31
+ * Handle POST /query
32
+ */
33
+ handleQueryRequest: (req: Request) => Promise<Response>;
34
+ /**
35
+ * Handle POST /query-stream/:streamId
36
+ */
37
+ handleQueryStreamRequest: (req: Request, streamId: string) => Promise<Response>;
38
+ /**
39
+ * Handle POST /query-sync
40
+ */
41
+ handleQuerySyncRequest: (req: Request) => Promise<Response>;
42
+ };
43
+ //# sourceMappingURL=query-handler.d.ts.map
@@ -0,0 +1,20 @@
1
+ import type { ArcContextAny } from "../context";
2
+ import type { AuthContext } from "../context/element";
3
+ import type { DataStorage } from "../data-storage";
4
+ import type { QueryDefinition } from "../query/query-definition";
5
+ import type { CommandDefinition } from "../command/command-definition";
6
+ import type { ModelStrategy } from "./model-strategy";
7
+ export declare class CacheStrategy<C extends ArcContextAny> implements ModelStrategy {
8
+ private readonly dataStorage;
9
+ private dataStorageStrategy;
10
+ private leaderStrategy;
11
+ constructor(context: C, dataStorage: DataStorage, apiBaseUrl: string);
12
+ setAuthToken(token: string | null): void;
13
+ query<TResult = any>(queryDefinition: QueryDefinition<TResult>, authContext: AuthContext, callback?: (result: TResult) => void): Promise<TResult>;
14
+ command(commandDefinition: CommandDefinition, authContext: AuthContext): Promise<any>;
15
+ private extractSyncData;
16
+ private syncWithLeader;
17
+ private updateCache;
18
+ private establishLiveUpdates;
19
+ }
20
+ //# sourceMappingURL=cache-strategy.d.ts.map
@@ -0,0 +1,15 @@
1
+ import type { ArcContextAny } from "../context";
2
+ import type { AuthContext } from "../context/element";
3
+ import type { DataStorage } from "../data-storage";
4
+ import type { QueryDefinition } from "../query/query-definition";
5
+ import type { CommandDefinition } from "../command/command-definition";
6
+ import type { ModelStrategy } from "./model-strategy";
7
+ export declare class DataStorageStrategy<C extends ArcContextAny> implements ModelStrategy {
8
+ private readonly context;
9
+ readonly dataStorage: DataStorage;
10
+ private readonly catchErrorCallback?;
11
+ constructor(context: C, dataStorage: DataStorage, catchErrorCallback?: ((error: any) => void) | undefined);
12
+ query<TResult = any>(queryDefinition: QueryDefinition<TResult>, authContext: AuthContext, callback?: (result: TResult) => void): Promise<TResult>;
13
+ command(commandDefinition: CommandDefinition, authContext: AuthContext): Promise<any>;
14
+ }
15
+ //# sourceMappingURL=datastorage-strategy.d.ts.map
@@ -0,0 +1,7 @@
1
+ export * from "./model-strategy";
2
+ export * from "./datastorage-strategy";
3
+ export * from "./leader-strategy";
4
+ export * from "./cache-strategy";
5
+ export type { QueryDefinition } from "../query/query-definition";
6
+ export type { CommandDefinition } from "../command/command-definition";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ import type { AuthContext } from "../context/element";
2
+ import type { QueryDefinition } from "../query/query-definition";
3
+ import type { CommandDefinition } from "../command/command-definition";
4
+ import type { ModelStrategy } from "./model-strategy";
5
+ export declare class LeaderStrategy implements ModelStrategy {
6
+ private readonly apiBaseUrl;
7
+ private token;
8
+ constructor(apiBaseUrl: string);
9
+ setAuthToken(token: string | null): void;
10
+ query<TResult = any>(queryDefinition: QueryDefinition<TResult>, authContext: AuthContext, callback?: (result: TResult) => void): Promise<TResult>;
11
+ private queryWithStream;
12
+ private readStream;
13
+ command(commandDefinition: CommandDefinition, authContext: AuthContext): Promise<any>;
14
+ private getAuthHeaders;
15
+ }
16
+ //# sourceMappingURL=leader-strategy.d.ts.map
@@ -0,0 +1,9 @@
1
+ import type { AuthContext } from "../context/element";
2
+ import type { QueryDefinition } from "../query/query-definition";
3
+ import type { CommandDefinition } from "../command/command-definition";
4
+ export interface ModelStrategy {
5
+ query<TResult = any>(queryDefinition: QueryDefinition<TResult>, authContext: AuthContext, callback?: (result: TResult) => void): Promise<TResult>;
6
+ command(commandDefinition: CommandDefinition, authContext: AuthContext): Promise<any>;
7
+ setAuthToken?(token: string | null): void;
8
+ }
9
+ //# sourceMappingURL=model-strategy.d.ts.map
@@ -0,0 +1,20 @@
1
+ import type { AuthContext } from "../context/element";
2
+ export interface QueryDefinition {
3
+ contextElementName: string;
4
+ queryAlias: string;
5
+ parameters: any[];
6
+ restrictions?: any;
7
+ }
8
+ export interface CommandDefinition {
9
+ contextElementName: string;
10
+ parameters: any;
11
+ }
12
+ export interface ModelStrategy {
13
+ query(queryDefinition: QueryDefinition, authContext: AuthContext, callback?: (result: any) => void): Promise<any>;
14
+ command(commandDefinition: CommandDefinition, authContext: AuthContext): Promise<any>;
15
+ setAuthToken?(token: string | null): void;
16
+ }
17
+ export interface QueryStrategy extends ModelStrategy {
18
+ execute(queryDefinition: QueryDefinition, authContext: AuthContext, callback?: (result: any) => void): Promise<any>;
19
+ }
20
+ //# sourceMappingURL=query-strategy.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=new-query-builder.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.1.10",
4
+ "version": "0.1.12",
5
5
  "private": false,
6
6
  "author": "Przemysław Krasiński [arcote.tech]",
7
7
  "description": "Arc is a framework designed to align code closely with business logic, streamlining development and enhancing productivity.",