@shaferllc/keel 0.36.0 → 0.58.0

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.
Files changed (55) hide show
  1. package/README.md +14 -2
  2. package/dist/core/application.d.ts +58 -2
  3. package/dist/core/application.js +99 -3
  4. package/dist/core/authorization.d.ts +52 -0
  5. package/dist/core/authorization.js +97 -0
  6. package/dist/core/broadcasting.d.ts +49 -0
  7. package/dist/core/broadcasting.js +84 -0
  8. package/dist/core/broker.d.ts +398 -0
  9. package/dist/core/broker.js +602 -0
  10. package/dist/core/crypto.d.ts +1 -1
  11. package/dist/core/crypto.js +12 -3
  12. package/dist/core/database.d.ts +26 -1
  13. package/dist/core/database.js +65 -2
  14. package/dist/core/decorators.d.ts +39 -0
  15. package/dist/core/decorators.js +72 -0
  16. package/dist/core/exceptions.d.ts +77 -6
  17. package/dist/core/exceptions.js +168 -10
  18. package/dist/core/helpers.d.ts +6 -0
  19. package/dist/core/helpers.js +12 -0
  20. package/dist/core/http/kernel.js +14 -2
  21. package/dist/core/http/router.d.ts +14 -0
  22. package/dist/core/http/router.js +30 -1
  23. package/dist/core/index.d.ts +28 -6
  24. package/dist/core/index.js +15 -3
  25. package/dist/core/logger.d.ts +5 -0
  26. package/dist/core/logger.js +24 -2
  27. package/dist/core/migrations.js +3 -3
  28. package/dist/core/model.d.ts +19 -1
  29. package/dist/core/model.js +72 -4
  30. package/dist/core/provider.d.ts +13 -4
  31. package/dist/core/provider.js +12 -2
  32. package/dist/core/redis.d.ts +78 -0
  33. package/dist/core/redis.js +176 -0
  34. package/dist/core/request-logger.d.ts +26 -0
  35. package/dist/core/request-logger.js +48 -0
  36. package/dist/core/request.d.ts +17 -1
  37. package/dist/core/request.js +27 -1
  38. package/dist/core/scheduler.d.ts +60 -0
  39. package/dist/core/scheduler.js +166 -0
  40. package/dist/core/session.js +17 -2
  41. package/dist/core/storage.d.ts +57 -0
  42. package/dist/core/storage.js +98 -0
  43. package/dist/core/template.d.ts +50 -0
  44. package/dist/core/template.js +753 -0
  45. package/dist/core/testing.d.ts +54 -0
  46. package/dist/core/testing.js +141 -0
  47. package/dist/core/transformer.d.ts +89 -0
  48. package/dist/core/transformer.js +152 -0
  49. package/dist/core/validation.d.ts +20 -0
  50. package/dist/core/validation.js +52 -1
  51. package/dist/core/vite.d.ts +117 -0
  52. package/dist/core/vite.js +258 -0
  53. package/dist/vite/index.d.ts +40 -0
  54. package/dist/vite/index.js +146 -0
  55. package/package.json +16 -1
@@ -0,0 +1,398 @@
1
+ /**
2
+ * A service broker — a Moleculer-style backbone for service-oriented code.
3
+ * Register *services* (a name plus a bag of `actions` and `events`), then reach
4
+ * them by string name: `broker.call("users.get", { id })` runs an action and
5
+ * returns its result; `broker.emit("user.created", user)` fans an event out to
6
+ * every service that listens. Actions receive a `Context` and can call *other*
7
+ * actions or emit events through it, so a request threads its `meta` (auth,
8
+ * trace ids) all the way down.
9
+ *
10
+ * const broker = new Broker();
11
+ * broker.createService({
12
+ * name: "math",
13
+ * actions: {
14
+ * add: (ctx: Context<{ a: number; b: number }>) => ctx.params.a + ctx.params.b,
15
+ * },
16
+ * });
17
+ * await broker.start();
18
+ * await broker.call("math.add", { a: 2, b: 3 }); // 5
19
+ *
20
+ * Like the queue and Redis layers, clustering lives behind a pluggable seam: the
21
+ * default `LocalTransporter` is a single-node no-op, so the core imports no
22
+ * network client and runs on Node and the edge. Swap in a real `Transporter`
23
+ * (NATS, Redis, TCP) to span processes — the call/emit API doesn't change.
24
+ */
25
+ import { Logger } from "./logger.js";
26
+ import { Cache } from "./cache.js";
27
+ import type { Schema } from "./validation.js";
28
+ /** How an event was dispatched — `emit` (balanced) or `broadcast` (every listener). */
29
+ export type EventType = "emit" | "broadcast";
30
+ /** Per-call state, handed to every action and event handler. */
31
+ export interface Context<P = any> {
32
+ /** The call parameters (an action's arguments) or an event's payload. */
33
+ params: P;
34
+ /** Metadata that flows down through nested `call`s — auth, trace ids, locale. */
35
+ meta: Record<string, unknown>;
36
+ /** Scratch space shared between this call's hooks and handler; never propagated. */
37
+ locals: Record<string, unknown>;
38
+ /** Per-call headers — like `meta`, but transient: not carried into nested calls. */
39
+ headers: Record<string, unknown>;
40
+ /** The broker handling this call. */
41
+ broker: Broker;
42
+ /** The service whose handler is running. */
43
+ service: Service;
44
+ /** The action or event name currently executing. */
45
+ name: string;
46
+ /** The action being executed (absent in event handlers). */
47
+ action?: {
48
+ name: string;
49
+ };
50
+ /** The event being handled (absent in action handlers). */
51
+ event?: {
52
+ name: string;
53
+ type: EventType;
54
+ groups: string[];
55
+ };
56
+ /** In an event handler, the emitted event's name. */
57
+ eventName?: string;
58
+ /** In an event handler, how it was dispatched — `"emit"` or `"broadcast"`. */
59
+ eventType?: EventType;
60
+ /** In an event handler, the groups the event targeted. */
61
+ eventGroups?: string[];
62
+ /** The node this call originated on. */
63
+ nodeID: string;
64
+ /** A unique id for this call, for tracing/logging. */
65
+ id: string;
66
+ /** The `id` of the parent context in a nested call, or `null` at the root. */
67
+ parentID: string | null;
68
+ /** Call depth — `1` at the root, incremented on each nested `call`. */
69
+ level: number;
70
+ /** Full name of the service that invoked this call, or `null` at the root. */
71
+ caller: string | null;
72
+ /** A correlation id shared by every call in the same request tree. */
73
+ requestID: string;
74
+ /** Call another action, inheriting this context's `meta` and `requestID`. */
75
+ call<R = unknown>(action: string, params?: unknown, opts?: CallOptions): Promise<R>;
76
+ /** Call several actions at once, inheriting this context's `meta`. */
77
+ mcall<R = unknown>(defs: MCallDefs, opts?: MCallOptions): Promise<R>;
78
+ /** A serializable snapshot of this context (drops functions and live refs). */
79
+ toJSON(): Record<string, unknown>;
80
+ /** Emit a balanced event, inheriting this context's `meta`. */
81
+ emit(event: string, payload?: unknown, opts?: EmitOptions): Promise<void>;
82
+ /** Broadcast an event to every listener, inheriting this context's `meta`. */
83
+ broadcast(event: string, payload?: unknown, opts?: EmitOptions): Promise<void>;
84
+ }
85
+ export interface CallOptions {
86
+ /** Metadata merged into (and overriding) the parent context's `meta`. */
87
+ meta?: Record<string, unknown>;
88
+ /** Per-call headers — available as `ctx.headers`, not propagated downstream. */
89
+ headers?: Record<string, unknown>;
90
+ /** Correlation id for the request tree; generated if omitted. */
91
+ requestID?: string;
92
+ /** Milliseconds to wait before rejecting with a `RequestTimeoutError`. */
93
+ timeout?: number;
94
+ /** Retry the call this many times on failure (total attempts = retries + 1). */
95
+ retries?: number;
96
+ /** A value (or `(err, ctx) => value`) to return if every attempt fails. */
97
+ fallback?: unknown | ((err: Error, ctx: Context) => unknown);
98
+ /** @internal parent context whose `meta`/`requestID` this call inherits. */
99
+ parentCtx?: Context;
100
+ }
101
+ export interface EmitOptions {
102
+ meta?: Record<string, unknown>;
103
+ /** Restrict delivery to listeners in these groups (defaults to all listeners). */
104
+ groups?: string[];
105
+ }
106
+ /** The shape passed to `mcall` — an array of calls, or a map of them by key. */
107
+ export type MCallDefs = Array<{
108
+ action: string;
109
+ params?: unknown;
110
+ opts?: CallOptions;
111
+ }> | Record<string, {
112
+ action: string;
113
+ params?: unknown;
114
+ opts?: CallOptions;
115
+ }>;
116
+ export interface MCallOptions extends CallOptions {
117
+ /** Return `{ status, value | reason }` per call instead of failing on the first rejection. */
118
+ settled?: boolean;
119
+ }
120
+ /** An action handler — receives a `Context`, returns (or resolves to) a result. */
121
+ export type ActionHandler<P = any, R = any> = (ctx: Context<P>) => R | Promise<R>;
122
+ /** An event handler — `ctx.params` is the event payload. */
123
+ export type EventHandler<P = any> = (ctx: Context<P>) => void | Promise<void>;
124
+ /** A before-hook — runs before the handler; mutate `ctx.params`/`meta`/`locals`. */
125
+ export type BeforeHook<P = any> = (ctx: Context<P>) => void | Promise<void>;
126
+ /** An after-hook — receives the result and returns the (possibly transformed) result. */
127
+ export type AfterHook<P = any, R = any> = (ctx: Context<P>, res: R) => R | Promise<R>;
128
+ /** An error-hook — receives the thrown error; return a fallback or re-throw. */
129
+ export type ErrorHook<P = any> = (ctx: Context<P>, err: Error) => unknown;
130
+ /** How far an action is reachable. `private` actions are hidden from `broker.call`. */
131
+ export type Visibility = "published" | "public" | "protected" | "private";
132
+ /** Hooks bound to a single action, in its full definition. */
133
+ export interface ActionHooks {
134
+ before?: BeforeHook | BeforeHook[];
135
+ after?: AfterHook | AfterHook[];
136
+ error?: ErrorHook | ErrorHook[];
137
+ }
138
+ /** The full form of an action — a handler plus per-action options. */
139
+ export interface ActionDef {
140
+ handler: ActionHandler;
141
+ /** Reachability; defaults to `published`. `private` blocks `broker.call`. */
142
+ visibility?: Visibility;
143
+ /** Per-action timeout (ms); overrides the broker default, overridden by the call. */
144
+ timeout?: number;
145
+ /** Validate `ctx.params` against this schema before the handler runs. */
146
+ params?: Schema<unknown>;
147
+ /** Cache the result (needs `BrokerOptions.cacher`). `keys` limits the cache key to those params. */
148
+ cache?: boolean | {
149
+ ttl?: number;
150
+ keys?: string[];
151
+ };
152
+ /** Hooks that wrap just this action. */
153
+ hooks?: ActionHooks;
154
+ }
155
+ /** An action entry: a bare handler (shorthand) or a full `ActionDef`. */
156
+ export type ActionSchema = ActionHandler | ActionDef;
157
+ /** An event entry: a bare handler (shorthand) or a handler plus a `group`. */
158
+ export type EventSchema = EventHandler | {
159
+ group?: string;
160
+ handler: EventHandler;
161
+ };
162
+ /**
163
+ * Service-level hooks, keyed by action name. Keys may be `"*"` (all actions),
164
+ * an exact name, a `"a|b"` pipe list, or a `*` glob (`"get*"`).
165
+ */
166
+ export interface ServiceHooks {
167
+ before?: Record<string, BeforeHook | BeforeHook[]>;
168
+ after?: Record<string, AfterHook | AfterHook[]>;
169
+ error?: Record<string, ErrorHook | ErrorHook[]>;
170
+ }
171
+ /**
172
+ * The shape you hand to `createService`. Handlers and lifecycle hooks run with
173
+ * `this` bound to the live `Service`, so they can reach `this.settings`,
174
+ * `this.broker`, `this.logger`, and any `methods` you define.
175
+ */
176
+ export interface ServiceSchema {
177
+ /** Unique service name, e.g. `"users"`. Combined with `version` into a prefix. */
178
+ name: string;
179
+ /** Optional version; `2` or `"2"` namespaces actions as `v2.users.*`. */
180
+ version?: string | number;
181
+ /** Free-form config, readable from handlers as `this.settings`. */
182
+ settings?: Record<string, unknown>;
183
+ /** Arbitrary descriptive info, readable as `this.metadata`. */
184
+ metadata?: Record<string, unknown>;
185
+ /** Service name(s) that must be registered before this service's `started` runs. */
186
+ dependencies?: string | string[];
187
+ /** Reusable schemas merged into this one (this schema always wins on conflict). */
188
+ mixins?: ServiceSchema[];
189
+ /** Named actions — a bare handler, or a full `ActionDef`. */
190
+ actions?: Record<string, ActionSchema>;
191
+ /** Event listeners, keyed by event name (may be a glob, e.g. `"user.*"`). */
192
+ events?: Record<string, EventSchema>;
193
+ /** Private helpers, bound to the service and reachable as `this.<method>`. */
194
+ methods?: Record<string, (...args: any[]) => any>;
195
+ /** Hooks wrapping this service's actions. */
196
+ hooks?: ServiceHooks;
197
+ /** Called after mixins are merged, before the instance is created; receives the schema. */
198
+ merged?(this: void, schema: ServiceSchema): void;
199
+ /** Called synchronously when the service is created. */
200
+ created?(this: Service): void | Promise<void>;
201
+ /** Called during `broker.start()`, after every service is created. */
202
+ started?(this: Service): void | Promise<void>;
203
+ /** Called during `broker.stop()`, in reverse creation order. */
204
+ stopped?(this: Service): void | Promise<void>;
205
+ }
206
+ /** A normalized action ready to invoke — handlers and hooks already bound. */
207
+ interface NormalizedAction {
208
+ name: string;
209
+ handler: ActionHandler;
210
+ visibility: Visibility;
211
+ timeout?: number;
212
+ params?: Schema<unknown>;
213
+ cache?: boolean | {
214
+ ttl?: number;
215
+ keys?: string[];
216
+ };
217
+ hooks: {
218
+ before: BeforeHook[];
219
+ after: AfterHook[];
220
+ error: ErrorHook[];
221
+ };
222
+ }
223
+ interface NormalizedEvent {
224
+ handler: EventHandler;
225
+ group: string;
226
+ }
227
+ interface NormalizedServiceHooks {
228
+ before: Record<string, BeforeHook[]>;
229
+ after: Record<string, AfterHook[]>;
230
+ error: Record<string, ErrorHook[]>;
231
+ }
232
+ /** A live service instance. Bound as `this` inside handlers, methods, and hooks. */
233
+ export declare class Service {
234
+ readonly name: string;
235
+ readonly version?: string | number;
236
+ /** Versioned, dotted prefix — `"users"` or `"v2.users"`. */
237
+ readonly fullName: string;
238
+ readonly settings: Record<string, unknown>;
239
+ readonly metadata: Record<string, unknown>;
240
+ /** Services that must exist before this one's `started` hook runs. */
241
+ readonly dependencies: string[];
242
+ readonly broker: Broker;
243
+ readonly logger: Logger;
244
+ /** Internal callers for this service's own actions — `this.actions.foo(params)`. */
245
+ readonly actions: Record<string, (params?: unknown, opts?: CallOptions) => Promise<unknown>>;
246
+ /** Bound `methods` land here (and directly on the instance) — `this.<name>`. */
247
+ [key: string]: any;
248
+ /** @internal normalized actions, keyed by local (unprefixed) name. */
249
+ readonly _actions: Map<string, NormalizedAction>;
250
+ /** @internal normalized event listeners, keyed by pattern. */
251
+ readonly _events: Map<string, NormalizedEvent>;
252
+ /** @internal normalized service-level hooks. */
253
+ readonly _hooks: NormalizedServiceHooks;
254
+ constructor(broker: Broker, schema: ServiceSchema);
255
+ /** Wait until the named service(s) are registered on this broker. */
256
+ waitForServices(deps: string | string[], timeout?: number, interval?: number): Promise<void>;
257
+ /** @internal the merged schema, for lifecycle hooks. */
258
+ _schema: ServiceSchema;
259
+ }
260
+ /** Thrown by `call` when no action matches the requested name. */
261
+ export declare class ServiceNotFoundError extends Error {
262
+ readonly action: string;
263
+ constructor(action: string);
264
+ }
265
+ /** Thrown by `call` when an action exceeds its timeout. */
266
+ export declare class RequestTimeoutError extends Error {
267
+ readonly action: string;
268
+ readonly timeout: number;
269
+ constructor(action: string, timeout: number);
270
+ }
271
+ /**
272
+ * The seam where clustering plugs in. The default `LocalTransporter` is a
273
+ * single-node no-op; a real transporter (NATS, Redis, TCP) would register
274
+ * remote services with the broker on `connect` and relay published packets.
275
+ */
276
+ export interface Transporter {
277
+ connect(broker: Broker): Promise<void>;
278
+ disconnect(): Promise<void>;
279
+ }
280
+ /** The default transporter — a single node, nothing to connect. */
281
+ export declare class LocalTransporter implements Transporter {
282
+ connect(_broker: Broker): Promise<void>;
283
+ disconnect(): Promise<void>;
284
+ }
285
+ /**
286
+ * A broker middleware — wraps action calls and taps broker lifecycle. `localAction`
287
+ * receives the next handler in the chain and returns a replacement, so middlewares
288
+ * compose (the first in the array is the outermost). Great for logging, metrics,
289
+ * caching, or auth around every action.
290
+ */
291
+ export interface BrokerMiddleware {
292
+ name?: string;
293
+ localAction?(next: ActionHandler, action: string): ActionHandler;
294
+ started?(broker: Broker): void | Promise<void>;
295
+ stopped?(broker: Broker): void | Promise<void>;
296
+ }
297
+ export interface BrokerOptions {
298
+ /** This node's id. Defaults to a generated `node-<rand>`. */
299
+ nodeID?: string;
300
+ /** Clustering transport. Defaults to `LocalTransporter` (single node). */
301
+ transporter?: Transporter;
302
+ /** Default per-call timeout in ms. `0` (default) disables it. */
303
+ requestTimeout?: number;
304
+ /** Default number of retries per call on failure. `0` (default) disables it. */
305
+ retries?: number;
306
+ /** Cache for actions marked `cache` — a Keel `Cache` (memory, Redis, …). */
307
+ cacher?: Cache;
308
+ /** Logger to use; defaults to a fresh `Logger`. */
309
+ logger?: Logger;
310
+ /** Middlewares that wrap every action call and tap broker lifecycle. */
311
+ middlewares?: BrokerMiddleware[];
312
+ }
313
+ export declare class Broker {
314
+ readonly nodeID: string;
315
+ readonly logger: Logger;
316
+ private readonly transporter;
317
+ private readonly requestTimeout;
318
+ private readonly defaultRetries;
319
+ private readonly cacher?;
320
+ private readonly middlewares;
321
+ private readonly services;
322
+ /** action fullName → endpoint. */
323
+ private readonly actions;
324
+ private started;
325
+ private uid;
326
+ constructor(options?: BrokerOptions);
327
+ /** Register a service from a schema (flattening any `mixins`). Returns the instance. */
328
+ createService(rawSchema: ServiceSchema): Service;
329
+ /** Look up a local service by (versioned) name. */
330
+ getLocalService(name: string): Service | undefined;
331
+ /** Remove a service, running its `stopped` hook and unregistering its actions. */
332
+ destroyService(service: Service): Promise<void>;
333
+ /** Resolve once every named service is registered; polls until `timeout` (ms). */
334
+ waitForServices(deps: string | string[], timeout?: number, interval?: number): Promise<void>;
335
+ /** Connect the transporter and run every service's `started` hook. */
336
+ start(): Promise<void>;
337
+ /** Run every service's `stopped` hook (reverse order) and disconnect. */
338
+ stop(): Promise<void>;
339
+ /** A short unique id for tracing. */
340
+ generateUid(): string;
341
+ /** Invoke an action by name and resolve with its result. */
342
+ call<R = unknown>(action: string, params?: unknown, opts?: CallOptions): Promise<R>;
343
+ /** Whether a callable (non-private) action is registered. */
344
+ hasAction(name: string): boolean;
345
+ /** The names of every callable (non-private) action, sorted. */
346
+ listActions(): string[];
347
+ /** The full names of every registered service. */
348
+ listServices(): string[];
349
+ /** A registered service by full name or bare name. */
350
+ getService(name: string): Service | undefined;
351
+ /** Invoke several actions at once — pass an array or a keyed map; returns the same shape. */
352
+ mcall<R = unknown>(defs: MCallDefs, opts?: MCallOptions): Promise<R>;
353
+ /** The full call pipeline: build context → before hooks → handler → after hooks. */
354
+ private invoke;
355
+ /** Race a promise against a timeout, rejecting with `RequestTimeoutError`. */
356
+ private withTimeout;
357
+ /**
358
+ * Assemble the hook chains for an action. Before: service wildcard → service
359
+ * named → action. After/Error run in reverse: action → service named →
360
+ * service wildcard.
361
+ */
362
+ private resolveHooks;
363
+ /**
364
+ * Emit a *balanced* event: each listening service receives it once. In a
365
+ * multi-node cluster only one instance per service group is chosen; locally,
366
+ * with one instance per service, that's every listener.
367
+ */
368
+ emit(event: string, payload?: unknown, opts?: EmitOptions): Promise<void>;
369
+ /** Broadcast an event to *every* listener (all instances, all groups). */
370
+ broadcast(event: string, payload?: unknown, opts?: EmitOptions): Promise<void>;
371
+ /**
372
+ * Broadcast to every *local* listener only. On a single node this is identical
373
+ * to `broadcast`; the distinction matters once a real transporter would
374
+ * otherwise relay the event to remote nodes.
375
+ */
376
+ broadcastLocal(event: string, payload?: unknown, opts?: EmitOptions): Promise<void>;
377
+ private dispatch;
378
+ /** True if any registered service listens for the given event. */
379
+ hasEventListener(event: string): boolean;
380
+ /**
381
+ * Measure round-trip latency (and clock difference) to a node. With the local
382
+ * transporter there's no network, so both are ~0 — the hook is here for real
383
+ * transporters to implement.
384
+ */
385
+ ping(nodeID?: string): Promise<{
386
+ nodeID: string;
387
+ elapsedTime: number;
388
+ timeDiff: number;
389
+ }>;
390
+ /** A snapshot of registered action names — handy for debugging. */
391
+ get registeredActions(): string[];
392
+ private makeContext;
393
+ }
394
+ /** Register the default broker returned by `broker()`. */
395
+ export declare function setBroker(next: Broker): Broker;
396
+ /** The default broker instance. */
397
+ export declare function broker(): Broker;
398
+ export {};