shokupan 0.13.1 → 0.14.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 (97) hide show
  1. package/dist/{analyzer-BOtveWL-.cjs → analyzer-BZSVGTmP.cjs} +5 -4
  2. package/dist/analyzer-BZSVGTmP.cjs.map +1 -0
  3. package/dist/{analyzer-B0fMzeIo.js → analyzer-Faojwm7c.js} +5 -4
  4. package/dist/analyzer-Faojwm7c.js.map +1 -0
  5. package/dist/{analyzer.impl-CUDO6vpn.cjs → analyzer.impl-5aCqtook.cjs} +28 -11
  6. package/dist/analyzer.impl-5aCqtook.cjs.map +1 -0
  7. package/dist/{analyzer.impl-DmHe92Oi.js → analyzer.impl-COdN69gL.js} +28 -11
  8. package/dist/analyzer.impl-COdN69gL.js.map +1 -0
  9. package/dist/ast-analyzer-worker-C3jrQ8VR.js +184 -0
  10. package/dist/ast-analyzer-worker-C3jrQ8VR.js.map +1 -0
  11. package/dist/ast-analyzer-worker-D_uYkqmY.cjs +184 -0
  12. package/dist/ast-analyzer-worker-D_uYkqmY.cjs.map +1 -0
  13. package/dist/cli.cjs +1 -1
  14. package/dist/cli.js +1 -1
  15. package/dist/context.d.ts +39 -4
  16. package/dist/decorators/di.d.ts +31 -0
  17. package/dist/decorators/hooks.d.ts +28 -0
  18. package/dist/decorators/http.d.ts +60 -0
  19. package/dist/decorators/index.d.ts +8 -0
  20. package/dist/decorators/mcp.d.ts +48 -0
  21. package/dist/decorators/util/container.d.ts +36 -0
  22. package/dist/decorators/websocket.d.ts +172 -0
  23. package/dist/index-BP7v0Hiv.cjs +12216 -0
  24. package/dist/index-BP7v0Hiv.cjs.map +1 -0
  25. package/dist/index-CUNBeZKj.js +12176 -0
  26. package/dist/index-CUNBeZKj.js.map +1 -0
  27. package/dist/index.cjs +137 -10518
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.ts +1 -2
  30. package/dist/index.js +137 -10477
  31. package/dist/index.js.map +1 -1
  32. package/dist/{json-parser-COdZ0fqY.cjs → json-parser-BA0mUgMF.cjs} +3 -3
  33. package/dist/json-parser-BA0mUgMF.cjs.map +1 -0
  34. package/dist/{json-parser-B3dnQmCC.js → json-parser-BFM-SnBR.js} +3 -3
  35. package/dist/json-parser-BFM-SnBR.js.map +1 -0
  36. package/dist/knex-DDPXR-sQ.js +218 -0
  37. package/dist/knex-DDPXR-sQ.js.map +1 -0
  38. package/dist/knex-DghF-jjm.cjs +240 -0
  39. package/dist/knex-DghF-jjm.cjs.map +1 -0
  40. package/dist/level-BU87Jbus.js +184 -0
  41. package/dist/level-BU87Jbus.js.map +1 -0
  42. package/dist/level-DNFl2n-m.cjs +184 -0
  43. package/dist/level-DNFl2n-m.cjs.map +1 -0
  44. package/dist/plugins/application/api-explorer/static/explorer-client.mjs +54 -28
  45. package/dist/plugins/application/asyncapi/plugin.d.ts +1 -0
  46. package/dist/plugins/application/asyncapi/static/asyncapi-client.mjs +22 -11
  47. package/dist/plugins/application/dashboard/fetch-interceptor.d.ts +3 -1
  48. package/dist/plugins/application/dashboard/metrics-collector.d.ts +5 -3
  49. package/dist/plugins/application/dashboard/plugin.d.ts +36 -3
  50. package/dist/plugins/application/dashboard/static/requests.js +517 -53
  51. package/dist/plugins/application/dashboard/static/tabs.js +2 -2
  52. package/dist/plugins/application/error-view/index.d.ts +25 -0
  53. package/dist/plugins/application/error-view/reason-phrases.d.ts +1 -0
  54. package/dist/plugins/application/openapi/analyzer.d.ts +3 -1
  55. package/dist/plugins/application/openapi/analyzer.impl.d.ts +4 -2
  56. package/dist/router.d.ts +52 -21
  57. package/dist/shokupan.d.ts +25 -11
  58. package/dist/sqlite-CLrcTkti.js +180 -0
  59. package/dist/sqlite-CLrcTkti.js.map +1 -0
  60. package/dist/sqlite-n7FQ6Ja6.cjs +180 -0
  61. package/dist/sqlite-n7FQ6Ja6.cjs.map +1 -0
  62. package/dist/surreal-6QONU6xa.cjs +210 -0
  63. package/dist/surreal-6QONU6xa.cjs.map +1 -0
  64. package/dist/surreal-w7DeGVI-.js +188 -0
  65. package/dist/surreal-w7DeGVI-.js.map +1 -0
  66. package/dist/util/adapter/datastore/knex.d.ts +29 -0
  67. package/dist/util/adapter/datastore/level.d.ts +26 -0
  68. package/dist/util/adapter/datastore/sqlite.d.ts +24 -0
  69. package/dist/util/adapter/datastore/surreal.d.ts +29 -0
  70. package/dist/util/adapter/datastore.d.ts +59 -0
  71. package/dist/util/adapter/h3.d.ts +8 -0
  72. package/dist/util/adapter/index.d.ts +1 -0
  73. package/dist/util/ast-analyzer-worker.d.ts +77 -0
  74. package/dist/util/ast-worker-thread.d.ts +1 -0
  75. package/dist/util/cookie-parser.d.ts +6 -0
  76. package/dist/util/env-loader.d.ts +7 -0
  77. package/dist/util/html.d.ts +15 -0
  78. package/dist/util/ide.d.ts +9 -0
  79. package/dist/util/logger.d.ts +25 -0
  80. package/dist/util/query-string.d.ts +8 -0
  81. package/dist/util/response-transformer.d.ts +87 -0
  82. package/dist/util/symbol.d.ts +1 -0
  83. package/dist/util/types.d.ts +116 -42
  84. package/dist/websocket.d.ts +163 -0
  85. package/package.json +27 -1
  86. package/dist/analyzer-B0fMzeIo.js.map +0 -1
  87. package/dist/analyzer-BOtveWL-.cjs.map +0 -1
  88. package/dist/analyzer.impl-CUDO6vpn.cjs.map +0 -1
  89. package/dist/analyzer.impl-DmHe92Oi.js.map +0 -1
  90. package/dist/json-parser-B3dnQmCC.js.map +0 -1
  91. package/dist/json-parser-COdZ0fqY.cjs.map +0 -1
  92. package/dist/plugins/application/error-view/views/error.d.ts +0 -2
  93. package/dist/plugins/application/error-view/views/status.d.ts +0 -2
  94. package/dist/util/decorators.d.ts +0 -134
  95. package/dist/util/di.d.ts +0 -13
  96. /package/dist/{util → decorators/util}/metadata.d.ts +0 -0
  97. /package/dist/{util → decorators/util}/stack.d.ts +0 -0
@@ -3,8 +3,8 @@ function switchTab(tabId) {
3
3
  console.log('Switching to tab:', tabId);
4
4
  // Update buttons
5
5
  document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
6
- // Find the button that was clicked
7
- const btn = Array.from(document.querySelectorAll('.tab-btn')).find(b => b.getAttribute('onclick') === `switchTab('${tabId}')`);
6
+ // Find the button that was clicked using robust data-tab attribute
7
+ const btn = document.querySelector(`.tab-btn[data-tab="${tabId}"]`);
8
8
  if (btn) btn.classList.add('active');
9
9
 
10
10
  // Update content
@@ -5,6 +5,31 @@ export interface ErrorViewConfig {
5
5
  * Theme for syntax highlighting (default 'dark')
6
6
  */
7
7
  theme?: 'light' | 'dark';
8
+ /**
9
+ * Show the graphical status view instead of the error view if not in development mode.
10
+ * Default: true
11
+ */
12
+ productionStatusView?: boolean;
13
+ /**
14
+ * Show the detailed error view if in development mode.
15
+ * Default: true
16
+ */
17
+ developmentErrorView?: boolean;
18
+ /**
19
+ * Hide the code snippet from the detailed error view.
20
+ * Default: false
21
+ */
22
+ hideCode?: boolean;
23
+ /**
24
+ * Hide the stack trace from the detailed error view.
25
+ * Default: false
26
+ */
27
+ hideStacktrace?: boolean;
28
+ /**
29
+ * Hide the error message from the status view.
30
+ * Default: false
31
+ */
32
+ hideErrorMessage?: boolean;
8
33
  }
9
34
  export declare class ErrorView implements ShokupanPlugin {
10
35
  private config;
@@ -0,0 +1 @@
1
+ export declare function getReasonPhrase(status: number): string;
@@ -1,3 +1,4 @@
1
+ import { Logger } from '../../../util/logger';
1
2
  import { ApplicationInstance } from './analyzer.impl';
2
3
  export type { ApplicationInstance, RouteInfo } from './analyzer.impl';
3
4
  /**
@@ -10,8 +11,9 @@ export type { ApplicationInstance, RouteInfo } from './analyzer.impl';
10
11
  export declare class OpenAPIAnalyzer {
11
12
  private rootDir;
12
13
  private entrypoint?;
14
+ private logger?;
13
15
  private analyzerImpl;
14
- constructor(rootDir: string, entrypoint?: string);
16
+ constructor(rootDir: string, entrypoint?: string, logger?: Logger);
15
17
  /**
16
18
  * Main analysis entry point.
17
19
  * Dynamically imports the implementation and runs the analysis.
@@ -1,3 +1,4 @@
1
+ import { Logger } from '../../../util/logger';
1
2
  /**
2
3
  * Route information extracted from AST
3
4
  */
@@ -53,7 +54,7 @@ interface DependencyInfo {
53
54
  export interface ApplicationInstance {
54
55
  name: string;
55
56
  filePath: string;
56
- className: 'Shokupan' | 'ShokupanRouter' | 'Controller';
57
+ className: 'Shokupan' | 'ShokupanRouter' | 'Controller' | 'ShokupanWebsocketRouter';
57
58
  controllerPrefix?: string;
58
59
  routes: RouteInfo[];
59
60
  mounted: MountInfo[];
@@ -100,12 +101,13 @@ export interface MiddlewareInfo {
100
101
  */
101
102
  export declare class OpenAPIAnalyzer {
102
103
  private rootDir;
104
+ private logger?;
103
105
  private files;
104
106
  private applications;
105
107
  private program?;
106
108
  private entrypoint?;
107
109
  private imports;
108
- constructor(rootDir: string, entrypoint?: string);
110
+ constructor(rootDir: string, entrypoint?: string, logger?: Logger);
109
111
  /**
110
112
  * Main analysis entry point
111
113
  */
package/dist/router.d.ts CHANGED
@@ -109,6 +109,8 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
109
109
  blockOnOpenApiGen: boolean;
110
110
  enableAsyncApiGen: boolean;
111
111
  blockOnAsyncApiGen: boolean;
112
+ enableAsyncAstScanning?: boolean;
113
+ astAnalysisTimeout?: number;
112
114
  reusePort: boolean;
113
115
  controllersOnly: boolean;
114
116
  enableTracing?: boolean;
@@ -116,6 +118,8 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
116
118
  jsonParser?: "native" | "parse-json" | "secure-json-parse";
117
119
  autoBackpressureFeedback?: boolean;
118
120
  autoBackpressureLevel?: number;
121
+ enableAutoContentNegotiation?: boolean;
122
+ defaultResponseTransformer?: string;
119
123
  enableMiddlewareTracking: boolean;
120
124
  middlewareTrackingMaxCapacity?: number;
121
125
  enableHTTPBridge?: boolean;
@@ -123,30 +127,21 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
123
127
  idGenerator?: () => string;
124
128
  middlewareTrackingTTL?: number;
125
129
  httpLogger: (ctx: ShokupanContext<Record<string, any>, Record<string, string>>) => void;
126
- logger: {
127
- verbose: boolean;
128
- info: (msg: string, props: Record<string, any>) => void;
129
- debug: (msg: string, props: Record<string, any>) => void;
130
- warning: (msg: string, props: Record<string, any>) => void;
131
- error: (msg: string, props: Record<string, any>) => void;
132
- fatal: (msg: string, props: Record<string, any>) => void;
133
- };
130
+ logger: import('./util/logger').Logger;
134
131
  readTimeout: number;
135
132
  maxBodySize?: number;
136
133
  requestTimeout: number;
137
134
  writeTimeout: number;
138
135
  renderer: JSXRenderer;
139
136
  serverFactory: import('.').ServerFactory;
140
- adapter?: "bun" | "node" | "wintercg" | import('./util/adapter').ServerAdapter;
137
+ adapter?: "bun" | "node" | "wintercg" | "h3" | import('./util/adapter').ServerAdapter;
141
138
  fileSystem?: import('./util/adapter/filesystem').FileSystemAdapter;
142
139
  hooks: ShokupanHooks<Record<string, any>> | ShokupanHooks<Record<string, any>>[];
143
140
  validateStatusCodes: boolean;
144
- surreal?: {
145
- engines?: import('surrealdb').Engines;
146
- url?: string;
147
- connectOptions?: import('surrealdb').ConnectOptions;
148
- namespace?: string;
149
- database?: string;
141
+ surreal?: any;
142
+ datastore?: {
143
+ adapter: "surreal" | "sqlite" | "level" | "knex";
144
+ options?: any;
150
145
  };
151
146
  aiPlugin?: {
152
147
  enabled?: boolean;
@@ -176,8 +171,10 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
176
171
  }>;
177
172
  };
178
173
  defaultSecurityHeaders?: boolean | any;
174
+ ide?: string;
179
175
  }>;
180
176
  get root(): Shokupan<any>;
177
+ get logger(): import('./util/logger').Logger;
181
178
  [$routes]: ShokupanRoute[];
182
179
  private trie;
183
180
  metadata?: RouteMetadata;
@@ -234,10 +231,6 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
234
231
  };
235
232
  constructor(config?: ShokupanRouteConfig);
236
233
  private isRouterInstance;
237
- /**
238
- * Registers an event handler for WebSocket.
239
- */
240
- event(name: string, handler: ShokupanHandler<T>): this;
241
234
  /**
242
235
  * Registers a lifecycle hook dynamically.
243
236
  */
@@ -271,9 +264,9 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
271
264
  */
272
265
  getEventHandlers(): Map<string, ShokupanHandler<T>[]>;
273
266
  /**
274
- * Mounts a controller instance to a path prefix.
267
+ * Mounts a controller instance or WebSocket router to a path prefix.
275
268
  *
276
- * Controller can be a convection router or an arbitrary class.
269
+ * Controller can be a convention router, WebSocket router, or an arbitrary class.
277
270
  *
278
271
  * Routes are derived from method names:
279
272
  * - get(ctx) -> GET /prefix/
@@ -281,6 +274,16 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
281
274
  * - postCreate(ctx) -> POST /prefix/create
282
275
  */
283
276
  mount(prefix: string, controller: ShokupanController | ShokupanController<T> | ShokupanRouter | ShokupanRouter<T> | Record<string, any>): this;
277
+ /**
278
+ * Mount a WebSocket router.
279
+ * @internal
280
+ */
281
+ private mountWebSocketRouter;
282
+ /**
283
+ * Mount a WebSocket controller (decorated with @WebsocketController).
284
+ * @internal
285
+ */
286
+ private mountWebSocketController;
284
287
  /**
285
288
  * Returns all routes attached to this router and its descendants.
286
289
  */
@@ -454,6 +457,34 @@ export declare class ShokupanRouter<T extends Record<string, any> = Record<strin
454
457
  * @param handlers - Route handler functions
455
458
  */
456
459
  head<Path extends string>(path: Path, spec: MethodAPISpec, handler: ShokupanHandler<T, RouteParams<Path>>, ...handlers: ShokupanHandler<T, RouteParams<Path>>[]): any;
460
+ /**
461
+ * Adds a WebSocket route that handles its own upgrade logic.
462
+ *
463
+ * Unless you need to handle the upgrade manually, you should use a `ShokupanWebsocketRouter` or `WebsocketController` instead.
464
+ *
465
+ * Routes registered with `.socket()` will NOT be automatically upgraded by Shokupan's WebSocket handling. You must implement
466
+ * all event handlers manually. You have been warned.
467
+ *
468
+ * @param path - URL path for the WebSocket endpoint
469
+ * @param handler - Route handler that will manually handle the upgrade
470
+ *
471
+ * @example
472
+ * ```ts
473
+ * router.socket("/ws", (ctx) => {
474
+ * const success = ctx.upgrade({
475
+ * data: {
476
+ * handler: {
477
+ * open: (ws) => console.log("Connected"),
478
+ * message: (ws, msg) => ws.send(msg),
479
+ * close: (ws) => console.log("Disconnected")
480
+ * }
481
+ * }
482
+ * });
483
+ * if (!success) return ctx.text("Upgrade failed", 400);
484
+ * });
485
+ * ```
486
+ */
487
+ socket<Path extends string>(path: Path, handler: ShokupanHandler<T, RouteParams<Path>>): this;
457
488
  /**
458
489
  * Adds a guard to the router that applies to all routes added **after** this point.
459
490
  * Guards must return true or call `ctx.next()` to allow the request to continue.
@@ -1,9 +1,10 @@
1
1
  import { Server } from 'bun';
2
2
  import { ShokupanRouter } from './router';
3
- import { SurrealDatastore } from './util/datastore';
3
+ import { DatastoreAdapter } from './util/adapter/datastore';
4
4
  import { ShokupanRequest } from './util/request';
5
+ import { ResponseTransformer, ResponseTransformerRegistry } from './util/response-transformer';
5
6
  import { $dispatch } from './util/symbol';
6
- import { Middleware, ProcessResult, RequestOptions, ShokupanConfig, ShokupanPlugin } from './util/types';
7
+ import { ErrorHandler, Middleware, ProcessResult, RequestOptions, ShokupanConfig, ShokupanPlugin } from './util/types';
7
8
  /**
8
9
  * Shokupan Application
9
10
  *
@@ -80,17 +81,30 @@ export declare class Shokupan<T = any> extends ShokupanRouter<T> {
80
81
  private httpServer?;
81
82
  private datastore?;
82
83
  dbPromise?: Promise<any>;
84
+ responseTransformerRegistry: ResponseTransformerRegistry;
85
+ private errorHandlers;
83
86
  private rootTrie?;
84
- get db(): SurrealDatastore | undefined;
85
- get logger(): {
86
- verbose: boolean;
87
- info: (msg: string, props: Record<string, any>) => void;
88
- debug: (msg: string, props: Record<string, any>) => void;
89
- warning: (msg: string, props: Record<string, any>) => void;
90
- error: (msg: string, props: Record<string, any>) => void;
91
- fatal: (msg: string, props: Record<string, any>) => void;
92
- };
87
+ get db(): DatastoreAdapter | undefined;
88
+ get logger(): import('./util/logger').Logger;
93
89
  constructor(applicationConfig?: ShokupanConfig);
90
+ /**
91
+ * Register a custom response transformer
92
+ * @param transformer The transformer to register
93
+ */
94
+ registerResponseTransformer(transformer: ResponseTransformer): this;
95
+ /**
96
+ * Register a global error handler for a specific error type.
97
+ * Handlers are checked in reverse order of registration (LIFO).
98
+ *
99
+ * @param type The error class constructor (e.g., Error, CustomError)
100
+ * @param handler The handler function
101
+ */
102
+ onStrictError<T>(type: new (...args: any[]) => T, handler: ErrorHandler<T>): this;
103
+ /**
104
+ * Set the default response transformer content type
105
+ * @param contentType The content type to use as default
106
+ */
107
+ setDefaultResponseType(contentType: string): this;
94
108
  private initDatastore;
95
109
  /**
96
110
  * Adds middleware to the application.
@@ -0,0 +1,180 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { c as createLogger } from "./index-CUNBeZKj.js";
3
+ class SqliteAdapter {
4
+ constructor(options = {}) {
5
+ this.options = options;
6
+ this.db = new Database(options.filename || ":memory:");
7
+ process.on("exit", async () => {
8
+ this.db.close();
9
+ });
10
+ }
11
+ name = "sqlite";
12
+ db;
13
+ logger = createLogger("sqlite-adapter");
14
+ tables = /* @__PURE__ */ new Set();
15
+ async connect() {
16
+ return;
17
+ }
18
+ async disconnect() {
19
+ this.db.close();
20
+ }
21
+ async setupSchema() {
22
+ const tables = [
23
+ "failed_requests",
24
+ "sessions",
25
+ "users",
26
+ "idempotency_keys",
27
+ "middleware_tracking",
28
+ "requests",
29
+ "metrics",
30
+ "idempotency"
31
+ // 'idempotency' used in plugin.ts but 'idempotency_keys' used elsewhere? Check code.
32
+ // The plugin code used `new RecordId('idempotency', key)`, so table name is 'idempotency'.
33
+ // The shokupan.ts defined 'idempotency_keys'.
34
+ // We'll create both or just handle lazily.
35
+ ];
36
+ for (const table of tables) {
37
+ await this.ensureTable(table);
38
+ }
39
+ }
40
+ ensureTable(table) {
41
+ if (this.tables.has(table)) return;
42
+ this.db.run(`CREATE TABLE IF NOT EXISTS "${table}" (
43
+ id TEXT PRIMARY KEY,
44
+ data JSON,
45
+ created_at INTEGER,
46
+ updated_at INTEGER
47
+ )`);
48
+ this.tables.add(table);
49
+ }
50
+ async get(table, id) {
51
+ this.ensureTable(table);
52
+ const stmt = this.db.prepare(`SELECT data FROM "${table}" WHERE id = ?`);
53
+ const res = stmt.get(id);
54
+ if (!res || !res.data) return null;
55
+ try {
56
+ return JSON.parse(res.data);
57
+ } catch (e) {
58
+ return null;
59
+ }
60
+ }
61
+ async create(table, id, data) {
62
+ this.ensureTable(table);
63
+ const now = Date.now();
64
+ const serialized = JSON.stringify(data);
65
+ try {
66
+ this.db.run(
67
+ `INSERT INTO "${table}" (id, data, created_at, updated_at) VALUES (?, ?, ?, ?)`,
68
+ [id, serialized, now, now]
69
+ );
70
+ return data;
71
+ } catch (err) {
72
+ if (err.message.includes("constraint failed")) {
73
+ throw new Error(`Record ${id} already exists`);
74
+ }
75
+ throw err;
76
+ }
77
+ }
78
+ async update(table, id, data) {
79
+ this.ensureTable(table);
80
+ const current = await this.get(table, id);
81
+ if (!current) throw new Error(`Record ${id} does not exist`);
82
+ const updated = { ...current, ...data };
83
+ const serialized = JSON.stringify(updated);
84
+ const now = Date.now();
85
+ this.db.run(
86
+ `UPDATE "${table}" SET data = ?, updated_at = ? WHERE id = ?`,
87
+ [serialized, now, id]
88
+ );
89
+ return updated;
90
+ }
91
+ async upsert(table, id, data) {
92
+ this.ensureTable(table);
93
+ const now = Date.now();
94
+ const serialized = JSON.stringify(data);
95
+ this.db.run(
96
+ `INSERT INTO "${table}" (id, data, created_at, updated_at) VALUES (?, ?, ?, ?)
97
+ ON CONFLICT(id) DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`,
98
+ [id, serialized, now, now]
99
+ );
100
+ return data;
101
+ }
102
+ async delete(table, id) {
103
+ this.ensureTable(table);
104
+ this.db.run(`DELETE FROM "${table}" WHERE id = ?`, [id]);
105
+ }
106
+ async count(table, query) {
107
+ this.ensureTable(table);
108
+ const { sql, params } = this.buildWhere(query);
109
+ const res = this.db.prepare(`SELECT COUNT(*) as count FROM "${table}" ${sql}`).get(...params);
110
+ return res.count;
111
+ }
112
+ async deleteMany(table, query) {
113
+ this.ensureTable(table);
114
+ const { sql, params } = this.buildWhere(query);
115
+ if (query?.limit || query?.sort) {
116
+ const selectQ = this.buildWhere(query);
117
+ let orderSql = "";
118
+ if (query.sort) {
119
+ const parts = Object.entries(query.sort).map(([k, v]) => {
120
+ return `json_extract(data, '$.${k}') ${v.toUpperCase()}`;
121
+ });
122
+ if (parts.length) orderSql = " ORDER BY " + parts.join(", ");
123
+ }
124
+ let limitSql = "";
125
+ if (query.limit) limitSql = ` LIMIT ${query.limit}`;
126
+ const rows = this.db.prepare(`SELECT id FROM "${table}" ${selectQ.sql} ${orderSql} ${limitSql}`).all(...selectQ.params);
127
+ if (rows.length === 0) return;
128
+ const ids = rows.map((r) => r.id);
129
+ const placeholders = ids.map(() => "?").join(",");
130
+ this.db.run(`DELETE FROM "${table}" WHERE id IN (${placeholders})`, ids);
131
+ } else {
132
+ this.db.run(`DELETE FROM "${table}" ${sql}`, params);
133
+ }
134
+ }
135
+ async findMany(table, query) {
136
+ this.ensureTable(table);
137
+ const { sql, params } = this.buildWhere(query);
138
+ let orderSql = "";
139
+ if (query?.sort) {
140
+ const parts = Object.entries(query.sort).map(([k, v]) => {
141
+ return `json_extract(data, '$.${k}') ${v.toUpperCase()}`;
142
+ });
143
+ if (parts.length) orderSql = " ORDER BY " + parts.join(", ");
144
+ }
145
+ let limitSql = "";
146
+ if (query?.limit) limitSql = ` LIMIT ${query.limit}`;
147
+ if (query?.offset) limitSql += ` OFFSET ${query.offset}`;
148
+ const rows = this.db.prepare(`SELECT data FROM "${table}" ${sql} ${orderSql} ${limitSql}`).all(...params);
149
+ return rows.map((r) => JSON.parse(r.data));
150
+ }
151
+ buildWhere(query) {
152
+ if (!query) return { sql: "", params: [] };
153
+ let clauses = [];
154
+ let params = [];
155
+ if (query.where) {
156
+ Object.entries(query.where).forEach(([k, v]) => {
157
+ clauses.push(`json_extract(data, '$.${k}') = ?`);
158
+ params.push(v);
159
+ });
160
+ }
161
+ if (query.gt) {
162
+ Object.entries(query.gt).forEach(([k, v]) => {
163
+ clauses.push(`json_extract(data, '$.${k}') > ?`);
164
+ params.push(v);
165
+ });
166
+ }
167
+ if (query.lt) {
168
+ Object.entries(query.lt).forEach(([k, v]) => {
169
+ clauses.push(`json_extract(data, '$.${k}') < ?`);
170
+ params.push(v);
171
+ });
172
+ }
173
+ if (clauses.length === 0) return { sql: "", params: [] };
174
+ return { sql: "WHERE " + clauses.join(" AND "), params };
175
+ }
176
+ }
177
+ export {
178
+ SqliteAdapter
179
+ };
180
+ //# sourceMappingURL=sqlite-CLrcTkti.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-CLrcTkti.js","sources":["../src/util/adapter/datastore/sqlite.ts"],"sourcesContent":["import { Database } from 'bun:sqlite';\nimport { createLogger } from '../../logger';\nimport type { DatastoreAdapter, QueryOptions } from '../datastore';\n\nexport class SqliteAdapter implements DatastoreAdapter {\n name = 'sqlite';\n private db: Database;\n private logger = createLogger('sqlite-adapter');\n private tables = new Set<string>();\n\n constructor(\n private options: { filename?: string; } = {}\n ) {\n this.db = new Database(options.filename || ':memory:');\n process.on(\"exit\", async () => {\n this.db.close();\n });\n }\n\n async connect(): Promise<void> {\n // Bun SQLite is synchronous/immediate open\n return;\n }\n\n async disconnect(): Promise<void> {\n this.db.close();\n }\n\n async setupSchema(): Promise<void> {\n // In SQLite we need to create tables eagerly or lazily. \n // We'll pre-create standard tables but also ensureTable in methods.\n // For 'schemaless' behavior, we use a single 'data' JSON column.\n // Also 'id' as primary key.\n // We might want created_at/updated_at.\n\n const tables = [\n 'failed_requests', 'sessions', 'users', 'idempotency_keys',\n 'middleware_tracking', 'requests', 'metrics', 'idempotency' // 'idempotency' used in plugin.ts but 'idempotency_keys' used elsewhere? Check code. \n // The plugin code used `new RecordId('idempotency', key)`, so table name is 'idempotency'.\n // The shokupan.ts defined 'idempotency_keys'. \n // We'll create both or just handle lazily.\n ];\n\n for (const table of tables) {\n await this.ensureTable(table);\n }\n }\n\n private ensureTable(table: string) {\n if (this.tables.has(table)) return;\n\n this.db.run(`CREATE TABLE IF NOT EXISTS \"${table}\" (\n id TEXT PRIMARY KEY,\n data JSON,\n created_at INTEGER,\n updated_at INTEGER\n )`);\n\n this.tables.add(table);\n }\n\n async get<T>(table: string, id: string): Promise<T | null> {\n this.ensureTable(table);\n const stmt = this.db.prepare(`SELECT data FROM \"${table}\" WHERE id = ?`);\n const res = stmt.get(id) as { data: string; } | null;\n if (!res || !res.data) return null;\n\n try {\n return JSON.parse(res.data) as T;\n } catch (e) {\n return null;\n }\n }\n\n async create<T>(table: string, id: string, data: T): Promise<T> {\n this.ensureTable(table);\n const now = Date.now();\n // data usually includes id in Shokupan logic? Or maybe not.\n // We store full object in data, including id if present.\n const serialized = JSON.stringify(data);\n\n try {\n this.db.run(\n `INSERT INTO \"${table}\" (id, data, created_at, updated_at) VALUES (?, ?, ?, ?)`,\n [id, serialized, now, now]\n );\n return data;\n } catch (err: any) {\n if (err.message.includes('constraint failed')) {\n // Duplicate\n throw new Error(`Record ${id} already exists`);\n }\n throw err;\n }\n }\n\n async update<T>(table: string, id: string, data: Partial<T>): Promise<T> {\n this.ensureTable(table);\n\n // SQLite JSON patch is tricky without json_patch() extension or complex queries.\n // Simplest is Read-Modify-Write for now, as concurrency is single-process-ish with Bun (mostly).\n // Or usage of json_patch in newer SQLite versions (Bun uses recent sqlite).\n\n // Let's try Read-Modify-Write for safety/simplicity first.\n const current = await this.get<T>(table, id);\n if (!current) throw new Error(`Record ${id} does not exist`);\n\n const updated = { ...current, ...data };\n const serialized = JSON.stringify(updated);\n const now = Date.now();\n\n this.db.run(\n `UPDATE \"${table}\" SET data = ?, updated_at = ? WHERE id = ?`,\n [serialized, now, id]\n );\n\n return updated;\n }\n\n async upsert<T>(table: string, id: string, data: T): Promise<T> {\n this.ensureTable(table);\n const now = Date.now();\n const serialized = JSON.stringify(data);\n\n // SQLite UPSERT syntax\n this.db.run(\n `INSERT INTO \"${table}\" (id, data, created_at, updated_at) VALUES (?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`,\n [id, serialized, now, now]\n );\n\n return data;\n }\n\n async delete(table: string, id: string): Promise<void> {\n this.ensureTable(table);\n this.db.run(`DELETE FROM \"${table}\" WHERE id = ?`, [id]);\n }\n\n async count(table: string, query?: QueryOptions): Promise<number> {\n this.ensureTable(table);\n const { sql, params } = this.buildWhere(query);\n const res = this.db.prepare(`SELECT COUNT(*) as count FROM \"${table}\" ${sql}`).get(...params) as { count: number; };\n return res.count;\n }\n\n async deleteMany(table: string, query?: QueryOptions): Promise<void> {\n this.ensureTable(table);\n const { sql, params } = this.buildWhere(query);\n // SQLite DELETE with LIMIT/ORDER is a compile-time option `SQLITE_ENABLE_UPDATE_DELETE_LIMIT`.\n // Bun ships with it? Assuming yes, or standard DELETE WHERE works. \n // If query has LIMIT/ORDER, we have to check support.\n // Safest: SELECT ids -> DELETE by ids if LIMIT/ORDER used?\n\n if (query?.limit || query?.sort) {\n // Complex delete\n const selectQ = this.buildWhere(query);\n // We need to order/limit the selection of IDs\n let orderSql = '';\n if (query.sort) {\n const parts = Object.entries(query.sort).map(([k, v]) => {\n return `json_extract(data, '$.${k}') ${v.toUpperCase()}`;\n });\n if (parts.length) orderSql = ' ORDER BY ' + parts.join(', ');\n }\n let limitSql = '';\n if (query.limit) limitSql = ` LIMIT ${query.limit}`;\n\n const rows = this.db.prepare(`SELECT id FROM \"${table}\" ${selectQ.sql} ${orderSql} ${limitSql}`).all(...selectQ.params) as { id: string; }[];\n\n if (rows.length === 0) return;\n\n const ids = rows.map(r => r.id);\n // Batch delete\n const placeholders = ids.map(() => '?').join(',');\n this.db.run(`DELETE FROM \"${table}\" WHERE id IN (${placeholders})`, ids);\n } else {\n this.db.run(`DELETE FROM \"${table}\" ${sql}`, params);\n }\n }\n\n async findMany<T>(table: string, query?: QueryOptions): Promise<T[]> {\n this.ensureTable(table);\n const { sql, params } = this.buildWhere(query);\n\n let orderSql = '';\n if (query?.sort) {\n const parts = Object.entries(query.sort).map(([k, v]) => {\n // Extract from JSON\n return `json_extract(data, '$.${k}') ${v.toUpperCase()}`;\n });\n if (parts.length) orderSql = ' ORDER BY ' + parts.join(', ');\n }\n\n let limitSql = '';\n if (query?.limit) limitSql = ` LIMIT ${query.limit}`;\n if (query?.offset) limitSql += ` OFFSET ${query.offset}`;\n\n const rows = this.db.prepare(`SELECT data FROM \"${table}\" ${sql} ${orderSql} ${limitSql}`).all(...params) as { data: string; }[];\n return rows.map(r => JSON.parse(r.data));\n }\n\n private buildWhere(query?: QueryOptions): { sql: string, params: any[]; } {\n if (!query) return { sql: '', params: [] };\n\n let clauses: string[] = [];\n let params: any[] = [];\n\n if (query.where) {\n Object.entries(query.where).forEach(([k, v]) => {\n clauses.push(`json_extract(data, '$.${k}') = ?`);\n params.push(v);\n });\n }\n\n if (query.gt) {\n Object.entries(query.gt).forEach(([k, v]) => {\n clauses.push(`json_extract(data, '$.${k}') > ?`);\n params.push(v);\n });\n }\n\n if (query.lt) {\n Object.entries(query.lt).forEach(([k, v]) => {\n clauses.push(`json_extract(data, '$.${k}') < ?`);\n params.push(v);\n });\n }\n\n if (clauses.length === 0) return { sql: '', params: [] };\n return { sql: 'WHERE ' + clauses.join(' AND '), params };\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const bun_sqlite = require("bun:sqlite");
4
+ const index = require("./index-BP7v0Hiv.cjs");
5
+ class SqliteAdapter {
6
+ constructor(options = {}) {
7
+ this.options = options;
8
+ this.db = new bun_sqlite.Database(options.filename || ":memory:");
9
+ process.on("exit", async () => {
10
+ this.db.close();
11
+ });
12
+ }
13
+ name = "sqlite";
14
+ db;
15
+ logger = index.createLogger("sqlite-adapter");
16
+ tables = /* @__PURE__ */ new Set();
17
+ async connect() {
18
+ return;
19
+ }
20
+ async disconnect() {
21
+ this.db.close();
22
+ }
23
+ async setupSchema() {
24
+ const tables = [
25
+ "failed_requests",
26
+ "sessions",
27
+ "users",
28
+ "idempotency_keys",
29
+ "middleware_tracking",
30
+ "requests",
31
+ "metrics",
32
+ "idempotency"
33
+ // 'idempotency' used in plugin.ts but 'idempotency_keys' used elsewhere? Check code.
34
+ // The plugin code used `new RecordId('idempotency', key)`, so table name is 'idempotency'.
35
+ // The shokupan.ts defined 'idempotency_keys'.
36
+ // We'll create both or just handle lazily.
37
+ ];
38
+ for (const table of tables) {
39
+ await this.ensureTable(table);
40
+ }
41
+ }
42
+ ensureTable(table) {
43
+ if (this.tables.has(table)) return;
44
+ this.db.run(`CREATE TABLE IF NOT EXISTS "${table}" (
45
+ id TEXT PRIMARY KEY,
46
+ data JSON,
47
+ created_at INTEGER,
48
+ updated_at INTEGER
49
+ )`);
50
+ this.tables.add(table);
51
+ }
52
+ async get(table, id) {
53
+ this.ensureTable(table);
54
+ const stmt = this.db.prepare(`SELECT data FROM "${table}" WHERE id = ?`);
55
+ const res = stmt.get(id);
56
+ if (!res || !res.data) return null;
57
+ try {
58
+ return JSON.parse(res.data);
59
+ } catch (e) {
60
+ return null;
61
+ }
62
+ }
63
+ async create(table, id, data) {
64
+ this.ensureTable(table);
65
+ const now = Date.now();
66
+ const serialized = JSON.stringify(data);
67
+ try {
68
+ this.db.run(
69
+ `INSERT INTO "${table}" (id, data, created_at, updated_at) VALUES (?, ?, ?, ?)`,
70
+ [id, serialized, now, now]
71
+ );
72
+ return data;
73
+ } catch (err) {
74
+ if (err.message.includes("constraint failed")) {
75
+ throw new Error(`Record ${id} already exists`);
76
+ }
77
+ throw err;
78
+ }
79
+ }
80
+ async update(table, id, data) {
81
+ this.ensureTable(table);
82
+ const current = await this.get(table, id);
83
+ if (!current) throw new Error(`Record ${id} does not exist`);
84
+ const updated = { ...current, ...data };
85
+ const serialized = JSON.stringify(updated);
86
+ const now = Date.now();
87
+ this.db.run(
88
+ `UPDATE "${table}" SET data = ?, updated_at = ? WHERE id = ?`,
89
+ [serialized, now, id]
90
+ );
91
+ return updated;
92
+ }
93
+ async upsert(table, id, data) {
94
+ this.ensureTable(table);
95
+ const now = Date.now();
96
+ const serialized = JSON.stringify(data);
97
+ this.db.run(
98
+ `INSERT INTO "${table}" (id, data, created_at, updated_at) VALUES (?, ?, ?, ?)
99
+ ON CONFLICT(id) DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`,
100
+ [id, serialized, now, now]
101
+ );
102
+ return data;
103
+ }
104
+ async delete(table, id) {
105
+ this.ensureTable(table);
106
+ this.db.run(`DELETE FROM "${table}" WHERE id = ?`, [id]);
107
+ }
108
+ async count(table, query) {
109
+ this.ensureTable(table);
110
+ const { sql, params } = this.buildWhere(query);
111
+ const res = this.db.prepare(`SELECT COUNT(*) as count FROM "${table}" ${sql}`).get(...params);
112
+ return res.count;
113
+ }
114
+ async deleteMany(table, query) {
115
+ this.ensureTable(table);
116
+ const { sql, params } = this.buildWhere(query);
117
+ if (query?.limit || query?.sort) {
118
+ const selectQ = this.buildWhere(query);
119
+ let orderSql = "";
120
+ if (query.sort) {
121
+ const parts = Object.entries(query.sort).map(([k, v]) => {
122
+ return `json_extract(data, '$.${k}') ${v.toUpperCase()}`;
123
+ });
124
+ if (parts.length) orderSql = " ORDER BY " + parts.join(", ");
125
+ }
126
+ let limitSql = "";
127
+ if (query.limit) limitSql = ` LIMIT ${query.limit}`;
128
+ const rows = this.db.prepare(`SELECT id FROM "${table}" ${selectQ.sql} ${orderSql} ${limitSql}`).all(...selectQ.params);
129
+ if (rows.length === 0) return;
130
+ const ids = rows.map((r) => r.id);
131
+ const placeholders = ids.map(() => "?").join(",");
132
+ this.db.run(`DELETE FROM "${table}" WHERE id IN (${placeholders})`, ids);
133
+ } else {
134
+ this.db.run(`DELETE FROM "${table}" ${sql}`, params);
135
+ }
136
+ }
137
+ async findMany(table, query) {
138
+ this.ensureTable(table);
139
+ const { sql, params } = this.buildWhere(query);
140
+ let orderSql = "";
141
+ if (query?.sort) {
142
+ const parts = Object.entries(query.sort).map(([k, v]) => {
143
+ return `json_extract(data, '$.${k}') ${v.toUpperCase()}`;
144
+ });
145
+ if (parts.length) orderSql = " ORDER BY " + parts.join(", ");
146
+ }
147
+ let limitSql = "";
148
+ if (query?.limit) limitSql = ` LIMIT ${query.limit}`;
149
+ if (query?.offset) limitSql += ` OFFSET ${query.offset}`;
150
+ const rows = this.db.prepare(`SELECT data FROM "${table}" ${sql} ${orderSql} ${limitSql}`).all(...params);
151
+ return rows.map((r) => JSON.parse(r.data));
152
+ }
153
+ buildWhere(query) {
154
+ if (!query) return { sql: "", params: [] };
155
+ let clauses = [];
156
+ let params = [];
157
+ if (query.where) {
158
+ Object.entries(query.where).forEach(([k, v]) => {
159
+ clauses.push(`json_extract(data, '$.${k}') = ?`);
160
+ params.push(v);
161
+ });
162
+ }
163
+ if (query.gt) {
164
+ Object.entries(query.gt).forEach(([k, v]) => {
165
+ clauses.push(`json_extract(data, '$.${k}') > ?`);
166
+ params.push(v);
167
+ });
168
+ }
169
+ if (query.lt) {
170
+ Object.entries(query.lt).forEach(([k, v]) => {
171
+ clauses.push(`json_extract(data, '$.${k}') < ?`);
172
+ params.push(v);
173
+ });
174
+ }
175
+ if (clauses.length === 0) return { sql: "", params: [] };
176
+ return { sql: "WHERE " + clauses.join(" AND "), params };
177
+ }
178
+ }
179
+ exports.SqliteAdapter = SqliteAdapter;
180
+ //# sourceMappingURL=sqlite-n7FQ6Ja6.cjs.map