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.
- package/dist/{analyzer-BOtveWL-.cjs → analyzer-BZSVGTmP.cjs} +5 -4
- package/dist/analyzer-BZSVGTmP.cjs.map +1 -0
- package/dist/{analyzer-B0fMzeIo.js → analyzer-Faojwm7c.js} +5 -4
- package/dist/analyzer-Faojwm7c.js.map +1 -0
- package/dist/{analyzer.impl-CUDO6vpn.cjs → analyzer.impl-5aCqtook.cjs} +28 -11
- package/dist/analyzer.impl-5aCqtook.cjs.map +1 -0
- package/dist/{analyzer.impl-DmHe92Oi.js → analyzer.impl-COdN69gL.js} +28 -11
- package/dist/analyzer.impl-COdN69gL.js.map +1 -0
- package/dist/ast-analyzer-worker-C3jrQ8VR.js +184 -0
- package/dist/ast-analyzer-worker-C3jrQ8VR.js.map +1 -0
- package/dist/ast-analyzer-worker-D_uYkqmY.cjs +184 -0
- package/dist/ast-analyzer-worker-D_uYkqmY.cjs.map +1 -0
- package/dist/cli.cjs +1 -1
- package/dist/cli.js +1 -1
- package/dist/context.d.ts +39 -4
- package/dist/decorators/di.d.ts +31 -0
- package/dist/decorators/hooks.d.ts +28 -0
- package/dist/decorators/http.d.ts +60 -0
- package/dist/decorators/index.d.ts +8 -0
- package/dist/decorators/mcp.d.ts +48 -0
- package/dist/decorators/util/container.d.ts +36 -0
- package/dist/decorators/websocket.d.ts +172 -0
- package/dist/index-BP7v0Hiv.cjs +12216 -0
- package/dist/index-BP7v0Hiv.cjs.map +1 -0
- package/dist/index-CUNBeZKj.js +12176 -0
- package/dist/index-CUNBeZKj.js.map +1 -0
- package/dist/index.cjs +137 -10518
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.js +137 -10477
- package/dist/index.js.map +1 -1
- package/dist/{json-parser-COdZ0fqY.cjs → json-parser-BA0mUgMF.cjs} +3 -3
- package/dist/json-parser-BA0mUgMF.cjs.map +1 -0
- package/dist/{json-parser-B3dnQmCC.js → json-parser-BFM-SnBR.js} +3 -3
- package/dist/json-parser-BFM-SnBR.js.map +1 -0
- package/dist/knex-DDPXR-sQ.js +218 -0
- package/dist/knex-DDPXR-sQ.js.map +1 -0
- package/dist/knex-DghF-jjm.cjs +240 -0
- package/dist/knex-DghF-jjm.cjs.map +1 -0
- package/dist/level-BU87Jbus.js +184 -0
- package/dist/level-BU87Jbus.js.map +1 -0
- package/dist/level-DNFl2n-m.cjs +184 -0
- package/dist/level-DNFl2n-m.cjs.map +1 -0
- package/dist/plugins/application/api-explorer/static/explorer-client.mjs +54 -28
- package/dist/plugins/application/asyncapi/plugin.d.ts +1 -0
- package/dist/plugins/application/asyncapi/static/asyncapi-client.mjs +22 -11
- package/dist/plugins/application/dashboard/fetch-interceptor.d.ts +3 -1
- package/dist/plugins/application/dashboard/metrics-collector.d.ts +5 -3
- package/dist/plugins/application/dashboard/plugin.d.ts +36 -3
- package/dist/plugins/application/dashboard/static/requests.js +517 -53
- package/dist/plugins/application/dashboard/static/tabs.js +2 -2
- package/dist/plugins/application/error-view/index.d.ts +25 -0
- package/dist/plugins/application/error-view/reason-phrases.d.ts +1 -0
- package/dist/plugins/application/openapi/analyzer.d.ts +3 -1
- package/dist/plugins/application/openapi/analyzer.impl.d.ts +4 -2
- package/dist/router.d.ts +52 -21
- package/dist/shokupan.d.ts +25 -11
- package/dist/sqlite-CLrcTkti.js +180 -0
- package/dist/sqlite-CLrcTkti.js.map +1 -0
- package/dist/sqlite-n7FQ6Ja6.cjs +180 -0
- package/dist/sqlite-n7FQ6Ja6.cjs.map +1 -0
- package/dist/surreal-6QONU6xa.cjs +210 -0
- package/dist/surreal-6QONU6xa.cjs.map +1 -0
- package/dist/surreal-w7DeGVI-.js +188 -0
- package/dist/surreal-w7DeGVI-.js.map +1 -0
- package/dist/util/adapter/datastore/knex.d.ts +29 -0
- package/dist/util/adapter/datastore/level.d.ts +26 -0
- package/dist/util/adapter/datastore/sqlite.d.ts +24 -0
- package/dist/util/adapter/datastore/surreal.d.ts +29 -0
- package/dist/util/adapter/datastore.d.ts +59 -0
- package/dist/util/adapter/h3.d.ts +8 -0
- package/dist/util/adapter/index.d.ts +1 -0
- package/dist/util/ast-analyzer-worker.d.ts +77 -0
- package/dist/util/ast-worker-thread.d.ts +1 -0
- package/dist/util/cookie-parser.d.ts +6 -0
- package/dist/util/env-loader.d.ts +7 -0
- package/dist/util/html.d.ts +15 -0
- package/dist/util/ide.d.ts +9 -0
- package/dist/util/logger.d.ts +25 -0
- package/dist/util/query-string.d.ts +8 -0
- package/dist/util/response-transformer.d.ts +87 -0
- package/dist/util/symbol.d.ts +1 -0
- package/dist/util/types.d.ts +116 -42
- package/dist/websocket.d.ts +163 -0
- package/package.json +27 -1
- package/dist/analyzer-B0fMzeIo.js.map +0 -1
- package/dist/analyzer-BOtveWL-.cjs.map +0 -1
- package/dist/analyzer.impl-CUDO6vpn.cjs.map +0 -1
- package/dist/analyzer.impl-DmHe92Oi.js.map +0 -1
- package/dist/json-parser-B3dnQmCC.js.map +0 -1
- package/dist/json-parser-COdZ0fqY.cjs.map +0 -1
- package/dist/plugins/application/error-view/views/error.d.ts +0 -2
- package/dist/plugins/application/error-view/views/status.d.ts +0 -2
- package/dist/util/decorators.d.ts +0 -134
- package/dist/util/di.d.ts +0 -13
- /package/dist/{util → decorators/util}/metadata.d.ts +0 -0
- /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 =
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
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.
|
package/dist/shokupan.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Server } from 'bun';
|
|
2
2
|
import { ShokupanRouter } from './router';
|
|
3
|
-
import {
|
|
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():
|
|
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
|