khotan-data 0.1.0 → 0.2.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.
@@ -1,9 +1,24 @@
1
1
  import { PgDatabase } from 'drizzle-orm/pg-core';
2
2
 
3
+ /**
4
+ * Derives the CLI auth token: HMAC-SHA256 over the timestamp, keyed by the
5
+ * KHOTAN_SECRET. One-way, so the raw secret (the encryption key) never travels
6
+ * over the wire — even a token captured from a dev log can't be reversed into
7
+ * the secret. Exported so the CLI can compute the same value.
8
+ */
9
+ declare function deriveCliToken(secret: string, timestamp: string): Promise<string>;
10
+ type ResourceConnectField = string | [string, ...string[]];
11
+ interface ResourcePlugParticipation {
12
+ uniqueIdentifier: string;
13
+ }
14
+ interface ResourceMappingRegistration {
15
+ connectField: ResourceConnectField;
16
+ plugs?: Record<string, ResourcePlugParticipation>;
17
+ }
3
18
  interface ResourceRegistration {
4
19
  name: string;
5
- connectField: string;
6
20
  description?: string;
21
+ mapping: ResourceMappingRegistration;
7
22
  }
8
23
  type FlowType = "inflow" | "outflow" | "relay" | "webhook";
9
24
  type KhotanRunStatus = "pending" | "running" | "completed" | "partial" | "failed" | "cancelled";
@@ -19,31 +34,64 @@ interface FlowRunResult {
19
34
  error?: string | null;
20
35
  metadata?: Record<string, unknown> | null;
21
36
  }
37
+ interface BoundPlug {
38
+ get<T>(path: string, options?: {
39
+ params?: Record<string, unknown>;
40
+ headers?: Record<string, string>;
41
+ }): Promise<T>;
42
+ post<T>(path: string, options?: {
43
+ body?: unknown;
44
+ headers?: Record<string, string>;
45
+ }): Promise<T>;
46
+ put<T>(path: string, options?: {
47
+ body?: unknown;
48
+ headers?: Record<string, string>;
49
+ }): Promise<T>;
50
+ patch<T>(path: string, options?: {
51
+ body?: unknown;
52
+ headers?: Record<string, string>;
53
+ }): Promise<T>;
54
+ delete<T>(path: string, options?: {
55
+ headers?: Record<string, string>;
56
+ }): Promise<T>;
57
+ }
58
+ interface BindablePlug {
59
+ get<T>(path: string, options?: {
60
+ params?: Record<string, unknown>;
61
+ headers?: Record<string, string>;
62
+ vars?: Record<string, string>;
63
+ _setVars?: (updates: Record<string, string>) => Promise<void>;
64
+ }): Promise<T>;
65
+ post<T>(path: string, options?: {
66
+ body?: unknown;
67
+ headers?: Record<string, string>;
68
+ vars?: Record<string, string>;
69
+ _setVars?: (updates: Record<string, string>) => Promise<void>;
70
+ }): Promise<T>;
71
+ put<T>(path: string, options?: {
72
+ body?: unknown;
73
+ headers?: Record<string, string>;
74
+ vars?: Record<string, string>;
75
+ _setVars?: (updates: Record<string, string>) => Promise<void>;
76
+ }): Promise<T>;
77
+ patch<T>(path: string, options?: {
78
+ body?: unknown;
79
+ headers?: Record<string, string>;
80
+ vars?: Record<string, string>;
81
+ _setVars?: (updates: Record<string, string>) => Promise<void>;
82
+ }): Promise<T>;
83
+ delete<T>(path: string, options?: {
84
+ headers?: Record<string, string>;
85
+ vars?: Record<string, string>;
86
+ _setVars?: (updates: Record<string, string>) => Promise<void>;
87
+ }): Promise<T>;
88
+ }
22
89
  interface FlowRunContext {
23
- plug: {
24
- get<T>(path: string, options?: {
25
- params?: Record<string, unknown>;
26
- headers?: Record<string, string>;
27
- }): Promise<T>;
28
- post<T>(path: string, options?: {
29
- body?: unknown;
30
- headers?: Record<string, string>;
31
- }): Promise<T>;
32
- put<T>(path: string, options?: {
33
- body?: unknown;
34
- headers?: Record<string, string>;
35
- }): Promise<T>;
36
- patch<T>(path: string, options?: {
37
- body?: unknown;
38
- headers?: Record<string, string>;
39
- }): Promise<T>;
40
- delete<T>(path: string, options?: {
41
- headers?: Record<string, string>;
42
- }): Promise<T>;
43
- };
90
+ plug: BoundPlug;
44
91
  flow: {
45
92
  id: string;
46
93
  name: string;
94
+ plugName: string;
47
95
  type: FlowType;
48
96
  resource?: string | null;
49
97
  to?: string | null;
@@ -52,11 +100,13 @@ interface FlowRunContext {
52
100
  body?: unknown;
53
101
  vars: Record<string, string>;
54
102
  setVars(updates: Record<string, string>): Promise<void>;
103
+ cache(cacheName: string): CacheInstance;
55
104
  }
56
105
  interface FlowWorkflowContext {
57
106
  flow: {
58
107
  id: string;
59
108
  name: string;
109
+ plugName: string;
60
110
  type: FlowType;
61
111
  resource?: string | null;
62
112
  to?: string | null;
@@ -64,8 +114,11 @@ interface FlowWorkflowContext {
64
114
  runType: string;
65
115
  body?: unknown;
66
116
  vars: Record<string, string>;
117
+ plugVarsByName?: Record<string, Record<string, string>>;
67
118
  khotanRunId: string;
119
+ khotanInstanceId: string;
68
120
  }
121
+ declare function bindWorkflowPlug(plug: BindablePlug, ctx: FlowWorkflowContext, plugName?: string): BoundPlug;
69
122
  interface KhotanRunUpdate {
70
123
  type?: "progress" | "log" | "metric" | "error";
71
124
  message: string;
@@ -157,27 +210,98 @@ interface CatchRegistration {
157
210
  type: "catch";
158
211
  name: string;
159
212
  events?: string[];
160
- workflow: (ctx: {
161
- event: Record<string, unknown>;
162
- eventType: string;
163
- headers: Record<string, string>;
164
- khotanRunId: string;
165
- }) => Promise<void>;
213
+ workflow: (ctx: CatchWorkflowContext) => Promise<void>;
166
214
  }
167
215
  interface PassRegistration {
168
216
  type: "pass";
169
217
  name: string;
170
218
  to: string;
171
219
  events?: string[];
172
- workflow: (ctx: {
173
- event: Record<string, unknown>;
174
- eventType: string;
175
- headers: Record<string, string>;
176
- destVars: Record<string, string>;
177
- khotanRunId: string;
178
- }) => Promise<void>;
220
+ workflow: (ctx: PassWorkflowContext) => Promise<void>;
179
221
  }
180
222
  type WebhookRegistration = CatchRegistration | PassRegistration;
223
+ interface CacheScope {
224
+ plug?: string;
225
+ resource?: string;
226
+ flow?: string;
227
+ }
228
+ interface CacheRegistration {
229
+ name: string;
230
+ scope?: CacheScope;
231
+ ttl?: string | number;
232
+ }
233
+ interface CacheEntryRecord {
234
+ id: string;
235
+ cacheId: string;
236
+ key: string;
237
+ value: unknown;
238
+ expiresAt: Date | null;
239
+ createdAt?: Date | undefined;
240
+ updatedAt?: Date | undefined;
241
+ }
242
+ interface CacheInstance {
243
+ get<T = unknown>(key: string): Promise<T | null>;
244
+ set<T = unknown>(key: string, value: T): Promise<T>;
245
+ delete(key: string): Promise<void>;
246
+ }
247
+ interface CatchWorkflowContext {
248
+ event: Record<string, unknown>;
249
+ eventType: string;
250
+ headers: Record<string, string>;
251
+ khotanRunId: string;
252
+ khotanInstanceId: string;
253
+ }
254
+ interface PassWorkflowContext {
255
+ event: Record<string, unknown>;
256
+ eventType: string;
257
+ headers: Record<string, string>;
258
+ destVars: Record<string, string>;
259
+ khotanRunId: string;
260
+ khotanInstanceId: string;
261
+ }
262
+ interface KhotanWorkflowContextRef {
263
+ khotanInstanceId: string;
264
+ }
265
+ declare function khotanCache(ctx: KhotanWorkflowContextRef, cacheName: string): CacheInstance;
266
+ declare function khotanMappings(ctx: KhotanWorkflowContextRef): {
267
+ list: (params: {
268
+ resourceId: string;
269
+ limit?: number;
270
+ offset?: number;
271
+ search?: string;
272
+ }) => Promise<{
273
+ items: Record<string, unknown>[];
274
+ page: {
275
+ limit: number;
276
+ offset: number;
277
+ hasMore: boolean;
278
+ prevOffset: number;
279
+ nextOffset: number;
280
+ total: number;
281
+ };
282
+ }>;
283
+ lookup: (params: {
284
+ resourceId: string;
285
+ connectValue: string | string[];
286
+ } | {
287
+ resourceId: string;
288
+ plugName: string;
289
+ ref: string;
290
+ }) => Promise<Record<string, unknown> | null>;
291
+ upsert: (mapping: {
292
+ resourceId: string;
293
+ connectValue: string | string[];
294
+ refs: Record<string, string>;
295
+ metadata?: Record<string, unknown> | null;
296
+ }) => Promise<Record<string, unknown>>;
297
+ update: (id: string, mapping: {
298
+ resourceId: string;
299
+ connectValue: string | string[];
300
+ refs: Record<string, string>;
301
+ metadata?: Record<string, unknown> | null;
302
+ }) => Promise<Record<string, unknown>>;
303
+ delete: (id: string) => Promise<void>;
304
+ };
181
305
  interface VarField {
182
306
  readonly key: string;
183
307
  label: string;
@@ -289,11 +413,30 @@ interface KhotanAdapter {
289
413
  }>;
290
414
  upsertResource(resource: {
291
415
  name: string;
292
- connectField: string;
416
+ connectField: ResourceConnectField;
293
417
  description?: string | null;
294
418
  }): Promise<{
295
419
  id: string;
296
420
  }>;
421
+ upsertCache(cache: {
422
+ name: string;
423
+ scope?: CacheScope | null;
424
+ ttlSeconds?: number | null;
425
+ }): Promise<{
426
+ id: string;
427
+ }>;
428
+ getCacheByName(name: string): Promise<Record<string, unknown> | null>;
429
+ getCacheEntry(cacheId: string, key: string): Promise<Record<string, unknown> | null>;
430
+ upsertCacheEntry(entry: {
431
+ cacheId: string;
432
+ key: string;
433
+ value: unknown;
434
+ expiresAt?: Date | null;
435
+ }): Promise<{
436
+ id: string;
437
+ created: boolean;
438
+ }>;
439
+ deleteCacheEntry(cacheId: string, key: string): Promise<void>;
297
440
  listResources(): Promise<Record<string, unknown>[]>;
298
441
  getResource(id: string): Promise<Record<string, unknown> | null>;
299
442
  getResourceFlows(resourceId: string): Promise<Record<string, unknown>[]>;
@@ -308,9 +451,21 @@ interface KhotanAdapter {
308
451
  created: boolean;
309
452
  }>;
310
453
  getMapping(id: string): Promise<Record<string, unknown> | null>;
311
- listMappings(resourceId: string): Promise<Record<string, unknown>[]>;
454
+ listMappings(params: {
455
+ resourceId: string;
456
+ limit: number;
457
+ offset: number;
458
+ search?: string;
459
+ }): Promise<{
460
+ items: Record<string, unknown>[];
461
+ hasMore: boolean;
462
+ total: number;
463
+ }>;
312
464
  deleteMapping(id: string): Promise<void>;
313
465
  lookupMapping(params: {
466
+ resourceId: string;
467
+ connectValue: string;
468
+ } | {
314
469
  resourceId: string;
315
470
  plugName: string;
316
471
  ref: string;
@@ -403,11 +558,42 @@ interface KhotanAdapter {
403
558
  lastRunStatus: KhotanTerminalRunStatus;
404
559
  }): Promise<void>;
405
560
  }
561
+ /**
562
+ * Authorize an incoming request to the khotan management API.
563
+ *
564
+ * Return `true` to allow the request, `false` to reject it with `401`.
565
+ * The function receives the raw `Request`, so it composes directly with
566
+ * session libraries such as better-auth:
567
+ *
568
+ * ```ts
569
+ * authorize: async (request) => {
570
+ * const session = await auth.api.getSession({ headers: request.headers });
571
+ * return session?.user?.role === "admin";
572
+ * }
573
+ * ```
574
+ *
575
+ * Throwing is treated the same as returning `false`.
576
+ *
577
+ * The following routes are intentionally exempt and are NOT passed to
578
+ * `authorize` (they have their own protection):
579
+ * - Inbound webhooks (`POST .../webhook/:plug`) — verified per-plug via `onVerify`.
580
+ * - The cron dispatcher (`.../cron`) — protected by `CRON_SECRET`.
581
+ * - Debug routes (`.../debug...`) — gated by `KHOTAN_DEBUG` and disabled in production.
582
+ */
583
+ type KhotanAuthorize = (request: Request) => boolean | Promise<boolean>;
406
584
  interface KhotanConfig {
407
585
  adapter: KhotanAdapter;
408
586
  plugs: PlugRegistration[];
409
587
  resources?: ResourceRegistration[];
588
+ caches?: CacheRegistration[];
410
589
  secret?: string;
590
+ /**
591
+ * Gate every management route (plugs, variables, flows, runs, wires,
592
+ * mappings, caches, resources, webhook handlers/events) behind a custom
593
+ * authorization check. Strongly recommended for any deployed app — without
594
+ * it the management API is publicly accessible. See {@link KhotanAuthorize}.
595
+ */
596
+ authorize?: KhotanAuthorize;
411
597
  }
412
598
  type KhotanHandler = (request: Request) => Promise<Response>;
413
599
  interface WireInstance {
@@ -430,6 +616,44 @@ interface KhotanInstance {
430
616
  init(): Promise<void>;
431
617
  flow(flowNameOrId: string, options?: FlowSelectorOptions): FlowInstance;
432
618
  wire(plugName: string): WireInstance;
619
+ cache(cacheName: string): CacheInstance;
620
+ listMappings(params: {
621
+ resourceId: string;
622
+ limit?: number;
623
+ offset?: number;
624
+ search?: string;
625
+ }): Promise<{
626
+ items: Record<string, unknown>[];
627
+ page: {
628
+ limit: number;
629
+ offset: number;
630
+ hasMore: boolean;
631
+ prevOffset: number;
632
+ nextOffset: number;
633
+ total: number;
634
+ };
635
+ }>;
636
+ lookupMapping(params: {
637
+ resourceId: string;
638
+ connectValue: string | string[];
639
+ } | {
640
+ resourceId: string;
641
+ plugName: string;
642
+ ref: string;
643
+ }): Promise<Record<string, unknown> | null>;
644
+ upsertMapping(mapping: {
645
+ resourceId: string;
646
+ connectValue: string | string[];
647
+ refs: Record<string, string>;
648
+ metadata?: Record<string, unknown> | null;
649
+ }): Promise<Record<string, unknown>>;
650
+ updateMapping(id: string, mapping: {
651
+ resourceId: string;
652
+ connectValue: string | string[];
653
+ refs: Record<string, string>;
654
+ metadata?: Record<string, unknown> | null;
655
+ }): Promise<Record<string, unknown>>;
656
+ deleteMapping(id: string): Promise<void>;
433
657
  getVars(plugName: string): Promise<Record<string, string>>;
434
658
  setVars(plugName: string, vars: Record<string, string>): Promise<void>;
435
659
  clearVars(plugName: string): Promise<void>;
@@ -472,4 +696,4 @@ interface NextJsRouteHandlers {
472
696
  }
473
697
  declare function toNextJsHandler(factoryHandler: KhotanHandler): NextJsRouteHandlers;
474
698
 
475
- export { type CatchRegistration, type FlowInstance, type FlowRegistration, type FlowRunContext, type FlowRunResult, type FlowSelectorOptions, type FlowStartOptions, type FlowType, type FlowWorkflowContext, type KhotanAdapter, type KhotanConfig, type KhotanHandler, type KhotanInstance, type KhotanRunStatus, type KhotanRunUpdate, type KhotanTerminalRunStatus, type PassRegistration, type PlugRegistration, type ResourceRegistration, type VarField, type WebhookRegistration, type WireInstance, type WireRegistration, type WireSubscribeContext, type WireUnsubscribeContext, type WireVerifyContext, __setWorkflowGetRunForTests, __setWorkflowGetWritableForTests, __setWorkflowStartForTests, drizzleAdapter, khotan, sendUpdate, toNextJsHandler };
699
+ export { type BindablePlug, type BoundPlug, type CacheEntryRecord, type CacheInstance, type CacheRegistration, type CacheScope, type CatchRegistration, type CatchWorkflowContext, type FlowInstance, type FlowRegistration, type FlowRunContext, type FlowRunResult, type FlowSelectorOptions, type FlowStartOptions, type FlowType, type FlowWorkflowContext, type KhotanAdapter, type KhotanAuthorize, type KhotanConfig, type KhotanHandler, type KhotanInstance, type KhotanRunStatus, type KhotanRunUpdate, type KhotanTerminalRunStatus, type PassRegistration, type PassWorkflowContext, type PlugRegistration, type ResourceConnectField, type ResourceMappingRegistration, type ResourcePlugParticipation, type ResourceRegistration, type VarField, type WebhookRegistration, type WireInstance, type WireRegistration, type WireSubscribeContext, type WireUnsubscribeContext, type WireVerifyContext, __setWorkflowGetRunForTests, __setWorkflowGetWritableForTests, __setWorkflowStartForTests, bindWorkflowPlug, deriveCliToken, drizzleAdapter, khotan, khotanCache, khotanMappings, sendUpdate, toNextJsHandler };