@arcote.tech/arc 0.4.10 → 0.5.1

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,70 @@
1
+ import type { ArcObjectAny } from "../../elements/object";
2
+ import type { ArcToken, ArcTokenAny } from "../../token/token";
3
+ import type { TokenInstanceAny } from "../../token/token-instance";
4
+ import type { $type } from "../../utils/types/get-type";
5
+ import type { ArcContextElement } from "../context-element";
6
+ import type { ElementContext } from "../element-context";
7
+ /**
8
+ * Unified protection check function.
9
+ *
10
+ * Return value determines behavior:
11
+ * - `false` → access denied
12
+ * - `true` → access allowed, no data filter
13
+ * - `Record` → access allowed + merged into WHERE clause (query context)
14
+ */
15
+ export type FnProtectionCheck<T extends ArcTokenAny> = (tokenInstance: TokenInstanceAny) => boolean | Record<string, unknown> | Promise<boolean | Record<string, unknown>>;
16
+ /**
17
+ * Protection configuration for an ArcFunction.
18
+ */
19
+ export type FnProtection = {
20
+ token: ArcTokenAny;
21
+ check: FnProtectionCheck<any>;
22
+ };
23
+ /**
24
+ * Extract token params type from a token definition.
25
+ */
26
+ type TokenParams<T extends ArcTokenAny> = T extends {
27
+ paramsSchema: infer P extends {
28
+ deserialize: (...args: any) => any;
29
+ };
30
+ } ? $type<P> : Record<string, any>;
31
+ /**
32
+ * Auth context injected when protectedBy() is used.
33
+ */
34
+ export type FnAuthContext<T extends ArcTokenAny> = {
35
+ params: TokenParams<T>;
36
+ tokenName: T extends ArcToken<infer Name, any, any, any> ? Name : string;
37
+ };
38
+ /**
39
+ * Extract union of token types from protections array.
40
+ */
41
+ type ProtectionTokens<Protections extends FnProtection[]> = Protections[number]["token"];
42
+ /**
43
+ * ArcFunction data — single generic container for all function state.
44
+ */
45
+ export type ArcFunctionData = {
46
+ params: ArcObjectAny | null;
47
+ result: ArcObjectAny | null;
48
+ queryElements: ArcContextElement<any>[];
49
+ mutationElements: ArcContextElement<any>[];
50
+ protections: FnProtection[];
51
+ handler: Function | false | null;
52
+ description?: string;
53
+ };
54
+ /**
55
+ * Unified function context — single source of truth for all handle() signatures.
56
+ *
57
+ * Computes ctx from:
58
+ * - query/mutate deps → ElementContext
59
+ * - protections → $auth
60
+ * - ExtraCtx → injected by wrapping element (e.g. aggregate event emitters)
61
+ */
62
+ export type FnContext<Data extends ArcFunctionData, ExtraCtx = {}> = ElementContext<Data["queryElements"], Data["mutationElements"]> & (Data["protections"] extends [] ? object : {
63
+ $auth: FnAuthContext<ProtectionTokens<Data["protections"]>>;
64
+ }) & ExtraCtx;
65
+ /**
66
+ * Handler function type for ArcFunction.
67
+ */
68
+ export type FnHandler<Data extends ArcFunctionData, ExtraCtx = {}> = (ctx: FnContext<Data, ExtraCtx>, params: Data["params"] extends ArcObjectAny ? $type<Data["params"]> : void) => Promise<Data["result"] extends ArcObjectAny ? $type<Data["result"]> : void>;
69
+ export {};
70
+ //# sourceMappingURL=arc-function-data.d.ts.map
@@ -0,0 +1,119 @@
1
+ import type { ArcObjectAny, ArcRawShape } from "../../elements/object";
2
+ import { ArcObject } from "../../elements/object";
3
+ import type { ModelAdapters } from "../../model/model-adapters";
4
+ import type { ArcTokenAny } from "../../token/token";
5
+ import type { TokenInstanceAny } from "../../token/token-instance";
6
+ import type { Merge } from "../../utils";
7
+ import type { $type } from "../../utils/types/get-type";
8
+ import type { ArcContextElement } from "../context-element";
9
+ import type { ArcFunctionData, FnContext, FnProtection, FnProtectionCheck } from "./arc-function-data";
10
+ /**
11
+ * ArcFunction — unified composable core for all function-like elements.
12
+ *
13
+ * Manages params, result, query/mutate deps, protection, and handler
14
+ * in a single immutable builder. Elements (Command, Listener, Route, Tool,
15
+ * Aggregate methods) hold this as a private `#fn` field and delegate to it.
16
+ *
17
+ * @typeParam Data — single generic container (Merge pattern)
18
+ * @typeParam ExtraCtx — additional context injected by wrapping element
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * const fn = new ArcFunction(defaultData)
23
+ * .withParams({ title: string() })
24
+ * .withResult({ id: string() })
25
+ * .query([usersView])
26
+ * .protectedBy(adminToken, t => t.canI('create'))
27
+ * .handle(async (ctx, params) => {
28
+ * // ctx: { query: { usersView }, $auth: { params, tokenName } }
29
+ * return { id: '123' };
30
+ * });
31
+ * ```
32
+ */
33
+ export declare class ArcFunction<const Data extends ArcFunctionData, ExtraCtx = {}> {
34
+ readonly data: Data;
35
+ constructor(data: Data);
36
+ withParams<const S extends ArcRawShape>(schema: S | ArcObject<S>): ArcFunction<Merge<Data, {
37
+ params: ArcObject<S>;
38
+ }>, ExtraCtx>;
39
+ withResult<const S extends ArcRawShape>(schema: S | ArcObject<S>): ArcFunction<Merge<Data, {
40
+ result: ArcObject<S>;
41
+ }>, ExtraCtx>;
42
+ query<const Elements extends ArcContextElement<any>[]>(elements: Elements): ArcFunction<Merge<Data, {
43
+ queryElements: Elements;
44
+ }>, ExtraCtx>;
45
+ mutate<const Elements extends ArcContextElement<any>[]>(elements: Elements): ArcFunction<Merge<Data, {
46
+ mutationElements: Elements;
47
+ }>, ExtraCtx>;
48
+ protectedBy<T extends ArcTokenAny>(token: T, check: FnProtectionCheck<T>): ArcFunction<Merge<Data, {
49
+ protections: [...Data["protections"] extends infer P extends FnProtection[] ? P : [], {
50
+ token: T;
51
+ check: typeof check;
52
+ }];
53
+ }>, ExtraCtx>;
54
+ description<const Desc extends string>(desc: Desc): ArcFunction<Merge<Data, {
55
+ description: Desc;
56
+ }>, ExtraCtx>;
57
+ handle<Handler extends ((ctx: FnContext<Data, ExtraCtx>, ...args: Data["params"] extends ArcObjectAny ? [$type<Data["params"]>] : []) => Promise<any>) | false>(handler: Handler): ArcFunction<Merge<Data, {
58
+ handler: Handler;
59
+ }>, ExtraCtx>;
60
+ get isPublic(): boolean;
61
+ get hasProtections(): boolean;
62
+ get protections(): FnProtection[];
63
+ get handler(): false | Function | null;
64
+ get params(): ArcObjectAny | null;
65
+ get result(): ArcObjectAny | null;
66
+ /**
67
+ * Verify all protections pass for given token instances.
68
+ * All protections must pass (logical AND).
69
+ */
70
+ verifyProtections(tokens: TokenInstanceAny[]): Promise<boolean>;
71
+ /**
72
+ * Build element context from query/mutate deps.
73
+ * Returns { query, mutate } accessor — $auth and ExtraCtx injected by element.
74
+ */
75
+ buildContext(adapters: ModelAdapters): any;
76
+ /**
77
+ * Convert params/result schemas to JSON Schema format.
78
+ */
79
+ toJsonSchema(): {
80
+ params: any;
81
+ result: any;
82
+ };
83
+ }
84
+ export declare const defaultFunctionData: {
85
+ readonly params: null;
86
+ readonly result: null;
87
+ readonly queryElements: [];
88
+ readonly mutationElements: [];
89
+ readonly protections: [];
90
+ readonly handler: null;
91
+ readonly description: undefined;
92
+ };
93
+ export type DefaultFunctionData = typeof defaultFunctionData;
94
+ /**
95
+ * Create a new ArcFunction with default empty state.
96
+ */
97
+ export declare function arcFunction(): ArcFunction<{
98
+ readonly params: null;
99
+ readonly result: null;
100
+ readonly queryElements: [];
101
+ readonly mutationElements: [];
102
+ readonly protections: [];
103
+ readonly handler: null;
104
+ readonly description: undefined;
105
+ }, {}>;
106
+ /**
107
+ * Create an ArcFunction with a pre-set ExtraCtx type.
108
+ * Used by aggregates to inject event emitters / $query into the context.
109
+ */
110
+ export declare function arcFunctionWithCtx<ExtraCtx>(): ArcFunction<{
111
+ readonly params: null;
112
+ readonly result: null;
113
+ readonly queryElements: [];
114
+ readonly mutationElements: [];
115
+ readonly protections: [];
116
+ readonly handler: null;
117
+ readonly description: undefined;
118
+ }, ExtraCtx>;
119
+ //# sourceMappingURL=arc-function.d.ts.map
@@ -0,0 +1,3 @@
1
+ export * from "./arc-function";
2
+ export * from "./arc-function-data";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -8,6 +8,7 @@ export * from "./command";
8
8
  export * from "./context-element";
9
9
  export * from "./element-context";
10
10
  export * from "./event";
11
+ export * from "./function";
11
12
  export * from "./listener";
12
13
  export * from "./route";
13
14
  export * from "./static-view";
@@ -1,8 +1,10 @@
1
1
  import type { ModelAdapters } from "../../model/model-adapters";
2
2
  import type { Merge } from "../../utils";
3
3
  import { ArcContextElement, type ArcEnvironment } from "../context-element";
4
+ import type { ElementContext } from "../element-context";
4
5
  import type { ArcEventAny } from "../event/event";
5
6
  import type { ArcEventInstance } from "../event/instance";
7
+ import { ArcFunction } from "../function/arc-function";
6
8
  /**
7
9
  * Listener Data - Configuration for a listener
8
10
  */
@@ -16,27 +18,9 @@ export interface ArcListenerData {
16
18
  isAsync: boolean;
17
19
  }
18
20
  /**
19
- * Listener Context - Available to listener handlers
21
+ * Listener Context - derived from ArcFunction's query/mutate deps
20
22
  */
21
- export type ArcListenerContext<QueryElements extends ArcContextElement<any>[], MutationElements extends ArcContextElement<any>[]> = {
22
- [K in QueryElements[number] as K["name"]]: K extends {
23
- queryContext: (adapters: ModelAdapters) => infer R;
24
- } ? R : never;
25
- } & {
26
- [K in MutationElements[number] as K["name"]]: K extends {
27
- mutateContext: (adapters: ModelAdapters) => infer R;
28
- } ? R : never;
29
- } & {
30
- get: <T extends ArcContextElement<any>>(element: T) => T extends {
31
- queryContext: (adapters: ModelAdapters) => infer R;
32
- } ? R : T extends {
33
- mutateContext: (adapters: ModelAdapters) => infer R;
34
- } ? R : never;
35
- $auth?: {
36
- params: any;
37
- tokenName: string;
38
- };
39
- };
23
+ export type ArcListenerContext<QueryElements extends ArcContextElement<any>[], MutationElements extends ArcContextElement<any>[]> = ElementContext<QueryElements, MutationElements>;
40
24
  /**
41
25
  * Listener Handler - Function that handles events
42
26
  */
@@ -44,12 +28,8 @@ export type ArcListenerHandler<Context, EventElements extends ArcEventAny[]> = (
44
28
  /**
45
29
  * Arc Listener - Reactive side effect triggered by events
46
30
  *
47
- * Listeners react to events and can:
48
- * - Query state through query elements (views)
49
- * - Mutate state through mutation elements (events, commands)
50
- * - Run synchronously (blocking) or asynchronously (fire-and-forget)
51
- *
52
- * Listeners only run on the server.
31
+ * Uses ArcFunction internally (#fn) for unified query/mutate deps
32
+ * and context building. handle() is Listener-specific (event as 2nd arg).
53
33
  *
54
34
  * @example
55
35
  * ```typescript
@@ -58,104 +38,46 @@ export type ArcListenerHandler<Context, EventElements extends ArcEventAny[]> = (
58
38
  * .query([usersView])
59
39
  * .mutate([notificationSentEvent])
60
40
  * .handle(async (ctx, event) => {
61
- * const user = await ctx.usersView.findOne({ _id: event.payload.assignedTo });
41
+ * const user = await ctx.query.usersView.findOne({ _id: event.payload.assignedTo });
62
42
  * if (user) {
63
- * await ctx.notificationSentEvent.emit({ userId: user._id, message: "New task!" });
43
+ * await ctx.mutate.notificationSentEvent.emit({ userId: user._id, message: "New task!" });
64
44
  * }
65
45
  * });
66
46
  * ```
67
47
  */
68
48
  export declare class ArcListener<const Data extends ArcListenerData> extends ArcContextElement<Data["name"]> {
49
+ #private;
69
50
  private readonly data;
70
51
  private unsubscribers;
71
- constructor(data: Data);
72
- /**
73
- * Set listener description for documentation
74
- */
52
+ constructor(data: Data, fn?: ArcFunction<any>);
75
53
  description<const Desc extends string>(description: Desc): ArcListener<Merge<Data, {
76
54
  description: Desc;
77
55
  }>>;
78
- /**
79
- * Specify which events this listener reacts to
80
- *
81
- * @param events - Array of event elements to listen to
82
- */
83
56
  listenTo<const Events extends ArcEventAny[]>(events: Events): ArcListener<Merge<Data, {
84
57
  eventElements: Events;
85
58
  }>>;
86
- /**
87
- * Add query elements (views) that this listener needs to read from
88
- *
89
- * @param elements - Array of view elements to query
90
- */
91
59
  query<const Elements extends ArcContextElement<any>[]>(elements: Elements): ArcListener<Merge<Data, {
92
60
  queryElements: Elements;
93
61
  }>>;
94
- /**
95
- * Add mutation elements (events, commands) that this listener needs to modify state
96
- *
97
- * @param elements - Array of event/command elements to mutate with
98
- */
99
62
  mutate<const Elements extends ArcContextElement<any>[]>(elements: Elements): ArcListener<Merge<Data, {
100
63
  mutationElements: Elements;
101
64
  }>>;
102
- /**
103
- * Mark listener as async (fire-and-forget)
104
- * Async listeners don't block event processing
105
- */
106
65
  async(): ArcListener<Merge<Data, {
107
66
  isAsync: true;
108
67
  }>>;
109
68
  /**
110
- * Set the handler function for this listener
111
- *
112
- * @param handler - Function that handles events
69
+ * Set the handler function for this listener.
70
+ * ctx is computed from #fn's query/mutate deps, event typed from listenTo().
113
71
  */
114
72
  handle<Handler extends ArcListenerHandler<ArcListenerContext<Data["queryElements"], Data["mutationElements"]>, Data["eventElements"]>>(handler: Handler): ArcListener<Merge<Data, {
115
73
  handler: Handler;
116
74
  }>>;
117
- /**
118
- * Get the events this listener is subscribed to
119
- */
120
75
  get eventElements(): ArcEventAny[];
121
- /**
122
- * Check if listener is async
123
- */
124
76
  get isAsync(): boolean;
125
- /**
126
- * Initialize listener - subscribe to events
127
- * Only runs on server
128
- */
129
77
  init(environment: ArcEnvironment, adapters: ModelAdapters): Promise<void>;
130
- /**
131
- * Handle an incoming event
132
- */
133
78
  private handleEvent;
134
- /**
135
- * Build listener context with access to query and mutation elements
136
- */
137
- private buildListenerContext;
138
- /**
139
- * Cleanup - unsubscribe from all events
140
- */
141
79
  destroy(): void;
142
80
  }
143
- /**
144
- * Create a new listener with the given name
145
- *
146
- * @param name - Unique listener name
147
- * @returns New listener instance ready for configuration
148
- *
149
- * @example
150
- * ```typescript
151
- * const myListener = listener("myListener")
152
- * .listenTo([someEvent])
153
- * .mutate([anotherEvent])
154
- * .handle(async (ctx, event) => {
155
- * await ctx.anotherEvent.emit({ ... });
156
- * });
157
- * ```
158
- */
159
81
  export declare function listener<const Name extends string>(name: Name): ArcListener<{
160
82
  readonly name: Name;
161
83
  readonly eventElements: [];
@@ -163,8 +85,5 @@ export declare function listener<const Name extends string>(name: Name): ArcList
163
85
  readonly mutationElements: [];
164
86
  readonly isAsync: false;
165
87
  }>;
166
- /**
167
- * Type alias for any listener (used in collections)
168
- */
169
88
  export type ArcListenerAny = ArcListener<any>;
170
89
  //# sourceMappingURL=listener.d.ts.map
@@ -3,16 +3,14 @@ import type { ArcTokenAny } from "../../token/token";
3
3
  import type { TokenInstanceAny } from "../../token/token-instance";
4
4
  import type { Merge } from "../../utils";
5
5
  import { ArcContextElement } from "../context-element";
6
- import { type ElementContext } from "../element-context";
6
+ import type { ElementContext } from "../element-context";
7
+ import { ArcFunction } from "../function/arc-function";
7
8
  import type { ArcRouteData, HttpMethod, RouteHandler, RouteHandlers, RouteProtectionCheck } from "./route-data";
8
9
  /**
9
10
  * Arc Route - Custom HTTP endpoint handler
10
11
  *
11
- * Routes allow defining custom HTTP endpoints that can:
12
- * - Handle GET, POST, PUT, DELETE, PATCH requests
13
- * - Access views (query) and events/commands (mutate)
14
- * - Be public or protected by tokens
15
- * - Use path parameters (e.g., /users/:userId)
12
+ * Uses ArcFunction internally (#fn) for unified query/mutate deps,
13
+ * protection, and context building.
16
14
  *
17
15
  * @example
18
16
  * ```typescript
@@ -22,57 +20,32 @@ import type { ArcRouteData, HttpMethod, RouteHandler, RouteHandlers, RouteProtec
22
20
  * .mutate([orderPaidEvent])
23
21
  * .handle({
24
22
  * POST: async (ctx, req) => {
25
- * const body = await req.json();
26
- * await ctx.orderPaidEvent.emit({ ... });
23
+ * await ctx.mutate.orderPaidEvent.emit({ ... });
27
24
  * return new Response("OK");
28
25
  * }
29
26
  * });
30
27
  * ```
31
28
  */
32
29
  export declare class ArcRoute<const Data extends ArcRouteData> extends ArcContextElement<Data["name"]> {
30
+ #private;
33
31
  private readonly data;
34
- constructor(data: Data);
35
- /**
36
- * Set route description
37
- */
32
+ constructor(data: Data, fn?: ArcFunction<any>);
38
33
  description<const Desc extends string>(description: Desc): ArcRoute<Merge<Data, {
39
34
  description: Desc;
40
35
  }>>;
41
- /**
42
- * Set route path pattern
43
- * Supports path parameters like /users/:userId
44
- * Path will be prefixed with /route automatically
45
- */
46
36
  path<const P extends string>(path: P): ArcRoute<Merge<Data, {
47
37
  path: P;
48
38
  }>>;
49
- /**
50
- * Mark route as public (no authentication required)
51
- */
52
39
  public(): ArcRoute<Merge<Data, {
53
40
  isPublic: true;
54
41
  }>>;
55
- /**
56
- * Add query elements (views) that this route can read from
57
- */
58
42
  query<const Elements extends ArcContextElement<any>[]>(elements: Elements): ArcRoute<Merge<Data, {
59
43
  queryElements: Elements;
60
44
  }>>;
61
- /**
62
- * Add mutation elements (events, commands) that this route can use
63
- */
64
45
  mutate<const Elements extends ArcContextElement<any>[]>(elements: Elements): ArcRoute<Merge<Data, {
65
46
  mutationElements: Elements;
66
47
  }>>;
67
- /**
68
- * Add token-based protection to this route
69
- * Check function returns true to allow, false to deny
70
- * Token params available in ctx.$auth
71
- */
72
48
  protectBy<T extends ArcTokenAny>(token: T, check: RouteProtectionCheck<T>): ArcRoute<Data>;
73
- /**
74
- * Set route handlers for HTTP methods
75
- */
76
49
  handle<Handlers extends RouteHandlers<ElementContext<Data["queryElements"], Data["mutationElements"]> & {
77
50
  $auth?: {
78
51
  params: Record<string, any>;
@@ -81,53 +54,22 @@ export declare class ArcRoute<const Data extends ArcRouteData> extends ArcContex
81
54
  }>>(handlers: Handlers): ArcRoute<Merge<Data, {
82
55
  handlers: Handlers;
83
56
  }>>;
84
- /**
85
- * Get the route path (without /route prefix)
86
- */
87
57
  get routePath(): string;
88
- /**
89
- * Get the full route path (with /route prefix)
90
- */
91
58
  get fullPath(): string;
92
- /**
93
- * Check if route is public
94
- */
95
59
  get isPublic(): boolean;
96
- /**
97
- * Check if route has protections
98
- */
99
60
  get hasProtections(): boolean;
100
- /**
101
- * Get all protection configurations
102
- */
103
61
  get protections(): import("./route-data").RouteProtection[];
104
- /**
105
- * Get handler for a specific HTTP method
106
- */
107
62
  getHandler(method: HttpMethod): RouteHandler<any> | undefined;
108
- /**
109
- * Check if a pathname matches this route's path pattern
110
- * Returns match result with extracted params
111
- */
112
63
  matchesPath(pathname: string): {
113
64
  matches: boolean;
114
65
  params: Record<string, string>;
115
66
  };
116
- /**
117
- * Verify all protections pass for given token instances
118
- */
119
67
  verifyProtections(tokens: TokenInstanceAny[]): Promise<boolean>;
120
- /**
121
- * Build route context with access to query and mutation elements
122
- */
123
68
  buildContext(adapters: ModelAdapters, authParams?: {
124
69
  params: Record<string, any>;
125
70
  tokenName: string;
126
71
  }): any;
127
72
  }
128
- /**
129
- * Create a new route with the given name
130
- */
131
73
  export declare function route<const Name extends string>(name: Name): ArcRoute<{
132
74
  name: Name;
133
75
  path: undefined;
@@ -138,8 +80,5 @@ export declare function route<const Name extends string>(name: Name): ArcRoute<{
138
80
  protections: [];
139
81
  isPublic: false;
140
82
  }>;
141
- /**
142
- * Type alias for any route
143
- */
144
83
  export type ArcRouteAny = ArcRoute<any>;
145
84
  //# sourceMappingURL=route.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import type { DatabaseAgnosticColumnInfo } from "../data-storage/database-store";
2
2
  import type { FirstArgument } from "../utils/types/first-argument";
3
- import type { AddQuestionMarks } from "../utils";
3
+ import type { AddQuestionMarks, Simplify } from "../utils";
4
4
  import { ArcAbstract, type Validators } from "./abstract";
5
5
  import type { ArcElement } from "./element";
6
6
  import { ArcOptional } from "./optional";
@@ -35,11 +35,11 @@ export declare class ArcObject<E extends ArcRawShape, V extends Validators = [
35
35
  }): {
36
36
  [key in keyof E]: ReturnType<E[key]["serialize"]>;
37
37
  };
38
- deserialize(value: AddQuestionMarks<{
38
+ deserialize(value: Simplify<AddQuestionMarks<{
39
39
  [key in keyof E]: FirstArgument<E[key]["deserialize"]>;
40
- }>): AddQuestionMarks<{
40
+ }>>): Simplify<AddQuestionMarks<{
41
41
  [key in keyof E]: ReturnType<E[key]["deserialize"]>;
42
- }>;
42
+ }>>;
43
43
  deserializePath(path: string[], value: any): any;
44
44
  parsePartial(value: Partial<{
45
45
  [key in keyof E]: FirstArgument<E[key]["parse"]>;