@sonicjs-cms/core 3.0.0-beta.22 → 3.0.0-beta.24
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/adapters.cjs +512 -0
- package/dist/adapters.cjs.map +1 -0
- package/dist/adapters.d.cts +342 -0
- package/dist/adapters.d.ts +342 -0
- package/dist/adapters.js +501 -0
- package/dist/adapters.js.map +1 -0
- package/dist/admin-documents-form.template-55V2ZGWT.js +6 -0
- package/dist/{admin-documents-form.template-DDSH6ROU.js.map → admin-documents-form.template-55V2ZGWT.js.map} +1 -1
- package/dist/admin-documents-form.template-YNAVMJCV.cjs +19 -0
- package/dist/{admin-layout-catalyst.template-YQ4EMF2J.js.map → admin-documents-form.template-YNAVMJCV.cjs.map} +1 -1
- package/dist/admin-layout-catalyst.template-3XZXDGF7.cjs +21 -0
- package/dist/{admin-layout-catalyst.template-KDHKVLXR.cjs.map → admin-layout-catalyst.template-3XZXDGF7.cjs.map} +1 -1
- package/dist/admin-layout-catalyst.template-6UQGLCKI.js +7 -0
- package/dist/{admin-documents-form.template-LSZKGA5J.cjs.map → admin-layout-catalyst.template-6UQGLCKI.js.map} +1 -1
- package/dist/{chunk-FTXGZ3BF.cjs → chunk-37V3RET3.cjs} +21 -37
- package/dist/chunk-37V3RET3.cjs.map +1 -0
- package/dist/{chunk-3CT3SUR4.cjs → chunk-A2SN3RVL.cjs} +3 -3
- package/dist/chunk-A2SN3RVL.cjs.map +1 -0
- package/dist/{chunk-CRGUD4KC.cjs → chunk-BBEKHLTW.cjs} +9 -9
- package/dist/{chunk-CRGUD4KC.cjs.map → chunk-BBEKHLTW.cjs.map} +1 -1
- package/dist/{chunk-RMRJGMDE.js → chunk-BHND5PEV.js} +3 -3
- package/dist/{chunk-RMRJGMDE.js.map → chunk-BHND5PEV.js.map} +1 -1
- package/dist/{chunk-OBA2RYZN.js → chunk-BJROXF4N.js} +3 -3
- package/dist/{chunk-OBA2RYZN.js.map → chunk-BJROXF4N.js.map} +1 -1
- package/dist/{chunk-EUJAEB6W.cjs → chunk-BP52BFIV.cjs} +53 -4
- package/dist/chunk-BP52BFIV.cjs.map +1 -0
- package/dist/{chunk-PXNTCCPE.cjs → chunk-E66UVONV.cjs} +8 -8
- package/dist/{chunk-PXNTCCPE.cjs.map → chunk-E66UVONV.cjs.map} +1 -1
- package/dist/{chunk-CHWT5KBK.js → chunk-FHPEM77Y.js} +9 -25
- package/dist/chunk-FHPEM77Y.js.map +1 -0
- package/dist/{chunk-JFLZHOQP.cjs → chunk-I767LVN7.cjs} +531 -419
- package/dist/chunk-I767LVN7.cjs.map +1 -0
- package/dist/{chunk-N32OWET6.cjs → chunk-JMJM7DMM.cjs} +5 -5
- package/dist/{chunk-N32OWET6.cjs.map → chunk-JMJM7DMM.cjs.map} +1 -1
- package/dist/{chunk-QWSR4THH.cjs → chunk-KKNEYIZJ.cjs} +12 -5
- package/dist/chunk-KKNEYIZJ.cjs.map +1 -0
- package/dist/{chunk-73O4GPJ6.js → chunk-LNI3FYSI.js} +12 -5
- package/dist/chunk-LNI3FYSI.js.map +1 -0
- package/dist/{chunk-PYZYU7EK.cjs → chunk-QCJHCSOK.cjs} +36 -2
- package/dist/chunk-QCJHCSOK.cjs.map +1 -0
- package/dist/{chunk-MGHHPNJN.cjs → chunk-QJUJNTIU.cjs} +11 -59
- package/dist/chunk-QJUJNTIU.cjs.map +1 -0
- package/dist/{chunk-DOMXI5PM.js → chunk-RJ2O5IIE.js} +36 -2
- package/dist/chunk-RJ2O5IIE.js.map +1 -0
- package/dist/{chunk-5V62WT6M.js → chunk-THFZSDIU.js} +4 -2
- package/dist/chunk-THFZSDIU.js.map +1 -0
- package/dist/{chunk-2FCKKOA5.js → chunk-UIHIXTIS.js} +3 -3
- package/dist/chunk-UIHIXTIS.js.map +1 -0
- package/dist/{chunk-JQISFW6U.js → chunk-URIBTX27.js} +3 -3
- package/dist/{chunk-JQISFW6U.js.map → chunk-URIBTX27.js.map} +1 -1
- package/dist/{chunk-K623Q6WD.cjs → chunk-UYJQDDEE.cjs} +4 -2
- package/dist/chunk-UYJQDDEE.cjs.map +1 -0
- package/dist/{chunk-6KNTZ67K.js → chunk-VC5XVPLM.js} +234 -126
- package/dist/chunk-VC5XVPLM.js.map +1 -0
- package/dist/{chunk-I2DM27HP.js → chunk-VGEZNFWH.js} +49 -4
- package/dist/chunk-VGEZNFWH.js.map +1 -0
- package/dist/{chunk-GB2Q2DNK.js → chunk-VYQVDL2M.js} +5 -49
- package/dist/chunk-VYQVDL2M.js.map +1 -0
- package/dist/{define-plugin-DsKiGu1q.d.cts → define-plugin-DMcvEy_C.d.cts} +33 -0
- package/dist/{define-plugin-BYIey6sp.d.ts → define-plugin-D_6D9vVZ.d.ts} +33 -0
- package/dist/index.cjs +2587 -326
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -3
- package/dist/index.d.ts +30 -3
- package/dist/index.js +2281 -22
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +34 -34
- package/dist/middleware.js +5 -5
- package/dist/migrations-FGXZPKDC.cjs +13 -0
- package/dist/{migrations-UIMA6ZL6.cjs.map → migrations-FGXZPKDC.cjs.map} +1 -1
- package/dist/migrations-Z6D27NAW.js +4 -0
- package/dist/{migrations-HLK2QF4T.js.map → migrations-Z6D27NAW.js.map} +1 -1
- package/dist/plugins.cjs +40 -40
- package/dist/plugins.d.cts +1 -1
- package/dist/plugins.d.ts +1 -1
- package/dist/plugins.js +3 -3
- package/dist/routes.cjs +29 -29
- package/dist/routes.js +9 -9
- package/dist/services.cjs +18 -18
- package/dist/services.js +2 -2
- package/dist/templates.cjs +17 -17
- package/dist/templates.js +3 -3
- package/dist/utils.cjs +3 -3
- package/dist/utils.js +1 -1
- package/migrations/0001_core.sql +7 -1
- package/package.json +10 -3
- package/dist/admin-documents-form.template-DDSH6ROU.js +0 -6
- package/dist/admin-documents-form.template-LSZKGA5J.cjs +0 -19
- package/dist/admin-layout-catalyst.template-KDHKVLXR.cjs +0 -21
- package/dist/admin-layout-catalyst.template-YQ4EMF2J.js +0 -7
- package/dist/chunk-2FCKKOA5.js.map +0 -1
- package/dist/chunk-3CT3SUR4.cjs.map +0 -1
- package/dist/chunk-5V62WT6M.js.map +0 -1
- package/dist/chunk-6KNTZ67K.js.map +0 -1
- package/dist/chunk-73O4GPJ6.js.map +0 -1
- package/dist/chunk-CHWT5KBK.js.map +0 -1
- package/dist/chunk-DOMXI5PM.js.map +0 -1
- package/dist/chunk-EUJAEB6W.cjs.map +0 -1
- package/dist/chunk-FTXGZ3BF.cjs.map +0 -1
- package/dist/chunk-GB2Q2DNK.js.map +0 -1
- package/dist/chunk-I2DM27HP.js.map +0 -1
- package/dist/chunk-JFLZHOQP.cjs.map +0 -1
- package/dist/chunk-K623Q6WD.cjs.map +0 -1
- package/dist/chunk-MGHHPNJN.cjs.map +0 -1
- package/dist/chunk-PYZYU7EK.cjs.map +0 -1
- package/dist/chunk-QWSR4THH.cjs.map +0 -1
- package/dist/migrations-HLK2QF4T.js +0 -4
- package/dist/migrations-UIMA6ZL6.cjs +0 -13
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Runtime SQLite driver — D1Database-compatible adapter backed by better-sqlite3.
|
|
6
|
+
*
|
|
7
|
+
* Designed for Tier 1 self-hosted deployments (Docker, VPS, Node/Bun).
|
|
8
|
+
* The Cloudflare Workers path never imports this file.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* import { createSqliteDriver } from '@sonicjs-cms/core/adapters'
|
|
12
|
+
* const db = await createSqliteDriver('./data/sonicjs.db')
|
|
13
|
+
* // Pass `db` anywhere SonicJS expects a D1Database binding.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
type BindValue = number | string | bigint | Buffer | null;
|
|
17
|
+
interface D1Meta {
|
|
18
|
+
duration: number;
|
|
19
|
+
size_after?: number;
|
|
20
|
+
rows_read: number;
|
|
21
|
+
rows_written: number;
|
|
22
|
+
last_row_id: number;
|
|
23
|
+
changed_db: boolean;
|
|
24
|
+
changes: number;
|
|
25
|
+
}
|
|
26
|
+
interface D1Result<T = unknown> {
|
|
27
|
+
results: T[];
|
|
28
|
+
success: boolean;
|
|
29
|
+
meta: D1Meta;
|
|
30
|
+
}
|
|
31
|
+
declare class SqliteStatement {
|
|
32
|
+
private readonly sqlite;
|
|
33
|
+
private readonly sql;
|
|
34
|
+
private readonly binds;
|
|
35
|
+
constructor(sqlite: Database.Database, sql: string, binds?: BindValue[]);
|
|
36
|
+
bind(...args: unknown[]): SqliteStatement;
|
|
37
|
+
run(): Promise<D1Result<never>>;
|
|
38
|
+
all<T = unknown>(): Promise<D1Result<T>>;
|
|
39
|
+
first<T = unknown>(colName?: string): Promise<T | null>;
|
|
40
|
+
raw<T = unknown[]>(): Promise<T[]>;
|
|
41
|
+
execInBatch(): void;
|
|
42
|
+
}
|
|
43
|
+
interface SqliteDriver {
|
|
44
|
+
prepare(sql: string): SqliteStatement;
|
|
45
|
+
batch<T = unknown>(statements: SqliteStatement[]): Promise<Array<D1Result<T>>>;
|
|
46
|
+
exec(query: string): Promise<{
|
|
47
|
+
count: number;
|
|
48
|
+
duration: number;
|
|
49
|
+
}>;
|
|
50
|
+
dump(): Promise<ArrayBuffer>;
|
|
51
|
+
/** Close the underlying database file handle (for graceful shutdown). */
|
|
52
|
+
close(): void;
|
|
53
|
+
/** Path passed at creation time (`:memory:` for in-memory). */
|
|
54
|
+
readonly path: string;
|
|
55
|
+
}
|
|
56
|
+
interface SqliteDriverOptions {
|
|
57
|
+
/**
|
|
58
|
+
* Absolute or relative path to the SQLite database file, or `:memory:` for
|
|
59
|
+
* an in-memory database. Defaults to `:memory:`.
|
|
60
|
+
*/
|
|
61
|
+
dbPath?: string;
|
|
62
|
+
/**
|
|
63
|
+
* When true, applies the bundled SonicJS migrations (0001_core.sql,
|
|
64
|
+
* 0002_documents.sql) before returning. Defaults to true when the DB does
|
|
65
|
+
* not yet contain the `auth_user` table (i.e. first boot).
|
|
66
|
+
* Set to false if you manage migrations yourself via an external tool.
|
|
67
|
+
*/
|
|
68
|
+
autoMigrate?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Directory that contains the *.sql migration files. Defaults to the
|
|
71
|
+
* migrations/ folder bundled with `@sonicjs-cms/core`.
|
|
72
|
+
*/
|
|
73
|
+
migrationsPath?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Create a D1Database-compatible SQLite driver for self-hosted deployments.
|
|
77
|
+
*
|
|
78
|
+
* ```ts
|
|
79
|
+
* const db = await createSqliteDriver({ dbPath: './data/sonicjs.db' })
|
|
80
|
+
* // db satisfies D1Database — pass it directly as c.env.DB
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare function createSqliteDriver(options?: SqliteDriverOptions): Promise<SqliteDriver>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Filesystem storage driver — R2Bucket-compatible adapter for self-hosted deployments.
|
|
87
|
+
*
|
|
88
|
+
* Files land at `{storageDir}/{key}` preserving path structure.
|
|
89
|
+
* Metadata (httpMetadata + customMetadata) is stored alongside as `{key}.meta.json`.
|
|
90
|
+
*
|
|
91
|
+
* Usage:
|
|
92
|
+
* import { createFilesystemDriver } from '@sonicjs-cms/core/adapters'
|
|
93
|
+
* const bucket = createFilesystemDriver('./data/media')
|
|
94
|
+
* // Pass `bucket` anywhere SonicJS expects the MEDIA_BUCKET R2Bucket binding.
|
|
95
|
+
*/
|
|
96
|
+
interface R2HttpMetadata {
|
|
97
|
+
contentType?: string;
|
|
98
|
+
contentDisposition?: string;
|
|
99
|
+
contentEncoding?: string;
|
|
100
|
+
contentLanguage?: string;
|
|
101
|
+
cacheControl?: string;
|
|
102
|
+
cacheExpiry?: Date;
|
|
103
|
+
}
|
|
104
|
+
interface R2StoredMeta {
|
|
105
|
+
httpMetadata?: R2HttpMetadata;
|
|
106
|
+
customMetadata?: Record<string, string>;
|
|
107
|
+
}
|
|
108
|
+
interface R2ObjectBody {
|
|
109
|
+
key: string;
|
|
110
|
+
size: number;
|
|
111
|
+
etag: string;
|
|
112
|
+
httpMetadata: R2HttpMetadata;
|
|
113
|
+
customMetadata: Record<string, string>;
|
|
114
|
+
/** Web-standard ReadableStream over the file contents. */
|
|
115
|
+
body: ReadableStream;
|
|
116
|
+
/** Convenience: consume as ArrayBuffer. */
|
|
117
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
118
|
+
/** Convenience: consume as text. */
|
|
119
|
+
text(): Promise<string>;
|
|
120
|
+
}
|
|
121
|
+
interface R2ObjectInfo {
|
|
122
|
+
key: string;
|
|
123
|
+
size: number;
|
|
124
|
+
etag: string;
|
|
125
|
+
httpMetadata: R2HttpMetadata;
|
|
126
|
+
customMetadata: Record<string, string>;
|
|
127
|
+
}
|
|
128
|
+
interface PutOptions {
|
|
129
|
+
httpMetadata?: R2HttpMetadata;
|
|
130
|
+
customMetadata?: Record<string, string>;
|
|
131
|
+
}
|
|
132
|
+
interface StorageDriver {
|
|
133
|
+
put(key: string, value: ArrayBuffer | ReadableStream | Blob | ArrayBufferView | string, options?: PutOptions): Promise<R2ObjectInfo>;
|
|
134
|
+
get(key: string): Promise<R2ObjectBody | null>;
|
|
135
|
+
delete(key: string): Promise<void>;
|
|
136
|
+
head(key: string): Promise<R2ObjectInfo | null>;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Create an R2Bucket-compatible filesystem storage driver.
|
|
140
|
+
*
|
|
141
|
+
* ```ts
|
|
142
|
+
* const bucket = createFilesystemDriver('./data/media')
|
|
143
|
+
* // bucket satisfies R2Bucket — pass it as c.env.MEDIA_BUCKET
|
|
144
|
+
* ```
|
|
145
|
+
*
|
|
146
|
+
* @param storageDir Absolute or relative path to the root storage directory.
|
|
147
|
+
* Created automatically if it doesn't exist.
|
|
148
|
+
*/
|
|
149
|
+
declare function createFilesystemDriver(storageDir: string): StorageDriver;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* In-memory KV driver — KVNamespace-compatible adapter for self-hosted deployments.
|
|
153
|
+
*
|
|
154
|
+
* Values survive the process lifetime. For cross-restart persistence, wire the
|
|
155
|
+
* optional `persistPath` to a JSON file; entries are written on every put/delete.
|
|
156
|
+
*
|
|
157
|
+
* Usage:
|
|
158
|
+
* import { createMemoryKVDriver } from '@sonicjs-cms/core/adapters'
|
|
159
|
+
* const kv = createMemoryKVDriver()
|
|
160
|
+
* // Pass `kv` anywhere SonicJS expects a KVNamespace (CACHE_KV binding).
|
|
161
|
+
*/
|
|
162
|
+
interface KVPutOptions {
|
|
163
|
+
expirationTtl?: number;
|
|
164
|
+
/** Absolute Unix timestamp (seconds) at which the key expires. */
|
|
165
|
+
expiration?: number;
|
|
166
|
+
metadata?: unknown;
|
|
167
|
+
}
|
|
168
|
+
interface KVListOptions {
|
|
169
|
+
prefix?: string;
|
|
170
|
+
limit?: number;
|
|
171
|
+
cursor?: string;
|
|
172
|
+
}
|
|
173
|
+
interface KVListResult {
|
|
174
|
+
keys: Array<{
|
|
175
|
+
name: string;
|
|
176
|
+
expiration?: number;
|
|
177
|
+
}>;
|
|
178
|
+
list_complete: boolean;
|
|
179
|
+
cursor?: string;
|
|
180
|
+
}
|
|
181
|
+
interface KVGetWithMetadata<T> {
|
|
182
|
+
value: T | null;
|
|
183
|
+
metadata: unknown;
|
|
184
|
+
}
|
|
185
|
+
interface KVDriver {
|
|
186
|
+
get(key: string): Promise<string | null>;
|
|
187
|
+
get(key: string, type: 'text'): Promise<string | null>;
|
|
188
|
+
get<T = unknown>(key: string, type: 'json'): Promise<T | null>;
|
|
189
|
+
get(key: string, type: 'arrayBuffer'): Promise<ArrayBuffer | null>;
|
|
190
|
+
put(key: string, value: string | ArrayBuffer | ReadableStream, options?: KVPutOptions): Promise<void>;
|
|
191
|
+
delete(key: string): Promise<void>;
|
|
192
|
+
list(options?: KVListOptions): Promise<KVListResult>;
|
|
193
|
+
getWithMetadata<T = unknown>(key: string, type: 'json'): Promise<KVGetWithMetadata<T>>;
|
|
194
|
+
}
|
|
195
|
+
interface MemoryKVOptions {
|
|
196
|
+
/**
|
|
197
|
+
* Path to a JSON file used for persistence across restarts.
|
|
198
|
+
* The file is read on startup and written on every put/delete.
|
|
199
|
+
* Omit for a pure in-memory (ephemeral) store.
|
|
200
|
+
*/
|
|
201
|
+
persistPath?: string;
|
|
202
|
+
}
|
|
203
|
+
declare function createMemoryKVDriver(options?: MemoryKVOptions): KVDriver;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Synchronous Queue driver — Cloudflare Queue-compatible adapter for self-hosted deployments.
|
|
207
|
+
*
|
|
208
|
+
* Instead of enqueuing messages to a remote queue, `send()` immediately invokes
|
|
209
|
+
* the registered handler in-process. This means "queued" work (e.g. scheduled
|
|
210
|
+
* email jobs) runs synchronously before the HTTP response is returned — acceptable
|
|
211
|
+
* for low-volume self-host deployments.
|
|
212
|
+
*
|
|
213
|
+
* Usage:
|
|
214
|
+
* import { createSyncQueueDriver } from '@sonicjs-cms/core/adapters'
|
|
215
|
+
*
|
|
216
|
+
* const emailQueue = createSyncQueueDriver(async (messages) => {
|
|
217
|
+
* // same body as handleEmailQueueMessage() from email-templates-plugin
|
|
218
|
+
* for (const msg of messages) {
|
|
219
|
+
* await processJob(msg.body)
|
|
220
|
+
* msg.ack()
|
|
221
|
+
* }
|
|
222
|
+
* })
|
|
223
|
+
* // Pass emailQueue as c.env.EMAIL_QUEUE
|
|
224
|
+
*/
|
|
225
|
+
interface QueueMessage<T = unknown> {
|
|
226
|
+
id: string;
|
|
227
|
+
body: T;
|
|
228
|
+
timestamp: Date;
|
|
229
|
+
ack(): void;
|
|
230
|
+
retry(): void;
|
|
231
|
+
}
|
|
232
|
+
interface MessageBatch<T = unknown> {
|
|
233
|
+
readonly queue: string;
|
|
234
|
+
readonly messages: QueueMessage<T>[];
|
|
235
|
+
ackAll(): void;
|
|
236
|
+
retryAll(): void;
|
|
237
|
+
}
|
|
238
|
+
interface SendOptions {
|
|
239
|
+
delaySeconds?: number;
|
|
240
|
+
contentType?: string;
|
|
241
|
+
}
|
|
242
|
+
type QueueHandler<T> = (batch: MessageBatch<T>) => Promise<void>;
|
|
243
|
+
interface QueueDriver<T = unknown> {
|
|
244
|
+
/**
|
|
245
|
+
* Send a message. The registered handler is invoked synchronously (after
|
|
246
|
+
* `delaySeconds` have elapsed when non-zero, via a non-blocking `setTimeout`).
|
|
247
|
+
*/
|
|
248
|
+
send(body: T, options?: SendOptions): Promise<void>;
|
|
249
|
+
/**
|
|
250
|
+
* Send many messages in a single batch.
|
|
251
|
+
*/
|
|
252
|
+
sendBatch(messages: Array<{
|
|
253
|
+
body: T;
|
|
254
|
+
options?: SendOptions;
|
|
255
|
+
}>): Promise<void>;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Create a synchronous Queue driver.
|
|
259
|
+
*
|
|
260
|
+
* @param handler Called for each message (or batch). When `handler` is omitted,
|
|
261
|
+
* messages are logged and discarded — useful when EMAIL_QUEUE is
|
|
262
|
+
* optional and no handler is wired yet.
|
|
263
|
+
* @param queueName Optional queue name shown in MessageBatch (cosmetic).
|
|
264
|
+
*/
|
|
265
|
+
declare function createSyncQueueDriver<T = unknown>(handler?: QueueHandler<T>, queueName?: string): QueueDriver<T>;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Node / Bun server adapter for self-hosted SonicJS deployments.
|
|
269
|
+
*
|
|
270
|
+
* Wires the local SQLite DB driver, filesystem storage, in-memory KV, and
|
|
271
|
+
* optional sync queue into a SonicJS app so it can run outside Cloudflare Workers.
|
|
272
|
+
*
|
|
273
|
+
* ## Quick start (Node.js)
|
|
274
|
+
*
|
|
275
|
+
* ```ts
|
|
276
|
+
* import { serve } from '@hono/node-server'
|
|
277
|
+
* import { createSonicJSApp } from '@sonicjs-cms/core'
|
|
278
|
+
* import { createNodeSonicApp } from '@sonicjs-cms/core/adapters'
|
|
279
|
+
*
|
|
280
|
+
* const app = createSonicJSApp({ ... })
|
|
281
|
+
* const adapted = await createNodeSonicApp(app, {
|
|
282
|
+
* dbPath: './data/sonicjs.db',
|
|
283
|
+
* storagePath: './data/media',
|
|
284
|
+
* })
|
|
285
|
+
* serve({ fetch: adapted.fetch, port: 3000 })
|
|
286
|
+
* ```
|
|
287
|
+
*
|
|
288
|
+
* ## Quick start (Bun)
|
|
289
|
+
*
|
|
290
|
+
* ```ts
|
|
291
|
+
* import { createSonicJSApp } from '@sonicjs-cms/core'
|
|
292
|
+
* import { createNodeSonicApp } from '@sonicjs-cms/core/adapters'
|
|
293
|
+
*
|
|
294
|
+
* const app = createSonicJSApp({ ... })
|
|
295
|
+
* const adapted = await createNodeSonicApp(app, {
|
|
296
|
+
* dbPath: './data/sonicjs.db',
|
|
297
|
+
* storagePath: './data/media',
|
|
298
|
+
* })
|
|
299
|
+
* Bun.serve({ fetch: adapted.fetch, port: 3000 })
|
|
300
|
+
* ```
|
|
301
|
+
*/
|
|
302
|
+
|
|
303
|
+
interface NodeAdapterOptions {
|
|
304
|
+
/**
|
|
305
|
+
* Path to the SQLite database file.
|
|
306
|
+
* Defaults to `./data/sonicjs.db`.
|
|
307
|
+
*/
|
|
308
|
+
dbPath?: string;
|
|
309
|
+
/**
|
|
310
|
+
* Root directory for uploaded media files (replaces R2).
|
|
311
|
+
* Defaults to `./data/media`.
|
|
312
|
+
*/
|
|
313
|
+
storagePath?: string;
|
|
314
|
+
/**
|
|
315
|
+
* Path to a JSON file used to persist KV entries across restarts.
|
|
316
|
+
* Omit for ephemeral in-memory KV (TTL-based caching only).
|
|
317
|
+
*/
|
|
318
|
+
kvPersistPath?: string;
|
|
319
|
+
/**
|
|
320
|
+
* Environment variables injected as `c.env.*` (JWT_SECRET, CORS_ORIGINS, etc.).
|
|
321
|
+
* Values are merged with process.env so you can also use dotenv.
|
|
322
|
+
*/
|
|
323
|
+
env?: Record<string, string | undefined>;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Wrap a SonicJS Hono app with local adapter bindings injected before any
|
|
327
|
+
* SonicJS middleware (bootstrap, auth, routes) executes.
|
|
328
|
+
*
|
|
329
|
+
* The injection MUST run first — SonicJS's bootstrap middleware accesses
|
|
330
|
+
* `c.env.DB` on the very first request. If we append the middleware after
|
|
331
|
+
* `createSonicJSApp()` we'd lose the ordering race. Instead we wrap the
|
|
332
|
+
* sonicApp in a fresh Hono instance that injects env first, then routes
|
|
333
|
+
* all requests through the sonicApp.
|
|
334
|
+
*
|
|
335
|
+
* @param sonicApp The SonicJS Hono app created by `createSonicJSApp()`.
|
|
336
|
+
* @param options Adapter configuration.
|
|
337
|
+
*/
|
|
338
|
+
declare function createNodeSonicApp<E extends {
|
|
339
|
+
Bindings: Record<string, unknown>;
|
|
340
|
+
}>(sonicApp: Hono<E>, options?: NodeAdapterOptions): Promise<Hono<E>>;
|
|
341
|
+
|
|
342
|
+
export { type KVDriver, type KVGetWithMetadata, type KVListOptions, type KVListResult, type KVPutOptions, type MemoryKVOptions, type MessageBatch, type NodeAdapterOptions, type PutOptions, type QueueDriver, type QueueHandler, type QueueMessage, type R2HttpMetadata, type R2ObjectBody, type R2ObjectInfo, type R2StoredMeta, type SendOptions, type SqliteDriver, type SqliteDriverOptions, type StorageDriver, createFilesystemDriver, createMemoryKVDriver, createNodeSonicApp, createSqliteDriver, createSyncQueueDriver };
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Runtime SQLite driver — D1Database-compatible adapter backed by better-sqlite3.
|
|
6
|
+
*
|
|
7
|
+
* Designed for Tier 1 self-hosted deployments (Docker, VPS, Node/Bun).
|
|
8
|
+
* The Cloudflare Workers path never imports this file.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* import { createSqliteDriver } from '@sonicjs-cms/core/adapters'
|
|
12
|
+
* const db = await createSqliteDriver('./data/sonicjs.db')
|
|
13
|
+
* // Pass `db` anywhere SonicJS expects a D1Database binding.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
type BindValue = number | string | bigint | Buffer | null;
|
|
17
|
+
interface D1Meta {
|
|
18
|
+
duration: number;
|
|
19
|
+
size_after?: number;
|
|
20
|
+
rows_read: number;
|
|
21
|
+
rows_written: number;
|
|
22
|
+
last_row_id: number;
|
|
23
|
+
changed_db: boolean;
|
|
24
|
+
changes: number;
|
|
25
|
+
}
|
|
26
|
+
interface D1Result<T = unknown> {
|
|
27
|
+
results: T[];
|
|
28
|
+
success: boolean;
|
|
29
|
+
meta: D1Meta;
|
|
30
|
+
}
|
|
31
|
+
declare class SqliteStatement {
|
|
32
|
+
private readonly sqlite;
|
|
33
|
+
private readonly sql;
|
|
34
|
+
private readonly binds;
|
|
35
|
+
constructor(sqlite: Database.Database, sql: string, binds?: BindValue[]);
|
|
36
|
+
bind(...args: unknown[]): SqliteStatement;
|
|
37
|
+
run(): Promise<D1Result<never>>;
|
|
38
|
+
all<T = unknown>(): Promise<D1Result<T>>;
|
|
39
|
+
first<T = unknown>(colName?: string): Promise<T | null>;
|
|
40
|
+
raw<T = unknown[]>(): Promise<T[]>;
|
|
41
|
+
execInBatch(): void;
|
|
42
|
+
}
|
|
43
|
+
interface SqliteDriver {
|
|
44
|
+
prepare(sql: string): SqliteStatement;
|
|
45
|
+
batch<T = unknown>(statements: SqliteStatement[]): Promise<Array<D1Result<T>>>;
|
|
46
|
+
exec(query: string): Promise<{
|
|
47
|
+
count: number;
|
|
48
|
+
duration: number;
|
|
49
|
+
}>;
|
|
50
|
+
dump(): Promise<ArrayBuffer>;
|
|
51
|
+
/** Close the underlying database file handle (for graceful shutdown). */
|
|
52
|
+
close(): void;
|
|
53
|
+
/** Path passed at creation time (`:memory:` for in-memory). */
|
|
54
|
+
readonly path: string;
|
|
55
|
+
}
|
|
56
|
+
interface SqliteDriverOptions {
|
|
57
|
+
/**
|
|
58
|
+
* Absolute or relative path to the SQLite database file, or `:memory:` for
|
|
59
|
+
* an in-memory database. Defaults to `:memory:`.
|
|
60
|
+
*/
|
|
61
|
+
dbPath?: string;
|
|
62
|
+
/**
|
|
63
|
+
* When true, applies the bundled SonicJS migrations (0001_core.sql,
|
|
64
|
+
* 0002_documents.sql) before returning. Defaults to true when the DB does
|
|
65
|
+
* not yet contain the `auth_user` table (i.e. first boot).
|
|
66
|
+
* Set to false if you manage migrations yourself via an external tool.
|
|
67
|
+
*/
|
|
68
|
+
autoMigrate?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Directory that contains the *.sql migration files. Defaults to the
|
|
71
|
+
* migrations/ folder bundled with `@sonicjs-cms/core`.
|
|
72
|
+
*/
|
|
73
|
+
migrationsPath?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Create a D1Database-compatible SQLite driver for self-hosted deployments.
|
|
77
|
+
*
|
|
78
|
+
* ```ts
|
|
79
|
+
* const db = await createSqliteDriver({ dbPath: './data/sonicjs.db' })
|
|
80
|
+
* // db satisfies D1Database — pass it directly as c.env.DB
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare function createSqliteDriver(options?: SqliteDriverOptions): Promise<SqliteDriver>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Filesystem storage driver — R2Bucket-compatible adapter for self-hosted deployments.
|
|
87
|
+
*
|
|
88
|
+
* Files land at `{storageDir}/{key}` preserving path structure.
|
|
89
|
+
* Metadata (httpMetadata + customMetadata) is stored alongside as `{key}.meta.json`.
|
|
90
|
+
*
|
|
91
|
+
* Usage:
|
|
92
|
+
* import { createFilesystemDriver } from '@sonicjs-cms/core/adapters'
|
|
93
|
+
* const bucket = createFilesystemDriver('./data/media')
|
|
94
|
+
* // Pass `bucket` anywhere SonicJS expects the MEDIA_BUCKET R2Bucket binding.
|
|
95
|
+
*/
|
|
96
|
+
interface R2HttpMetadata {
|
|
97
|
+
contentType?: string;
|
|
98
|
+
contentDisposition?: string;
|
|
99
|
+
contentEncoding?: string;
|
|
100
|
+
contentLanguage?: string;
|
|
101
|
+
cacheControl?: string;
|
|
102
|
+
cacheExpiry?: Date;
|
|
103
|
+
}
|
|
104
|
+
interface R2StoredMeta {
|
|
105
|
+
httpMetadata?: R2HttpMetadata;
|
|
106
|
+
customMetadata?: Record<string, string>;
|
|
107
|
+
}
|
|
108
|
+
interface R2ObjectBody {
|
|
109
|
+
key: string;
|
|
110
|
+
size: number;
|
|
111
|
+
etag: string;
|
|
112
|
+
httpMetadata: R2HttpMetadata;
|
|
113
|
+
customMetadata: Record<string, string>;
|
|
114
|
+
/** Web-standard ReadableStream over the file contents. */
|
|
115
|
+
body: ReadableStream;
|
|
116
|
+
/** Convenience: consume as ArrayBuffer. */
|
|
117
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
118
|
+
/** Convenience: consume as text. */
|
|
119
|
+
text(): Promise<string>;
|
|
120
|
+
}
|
|
121
|
+
interface R2ObjectInfo {
|
|
122
|
+
key: string;
|
|
123
|
+
size: number;
|
|
124
|
+
etag: string;
|
|
125
|
+
httpMetadata: R2HttpMetadata;
|
|
126
|
+
customMetadata: Record<string, string>;
|
|
127
|
+
}
|
|
128
|
+
interface PutOptions {
|
|
129
|
+
httpMetadata?: R2HttpMetadata;
|
|
130
|
+
customMetadata?: Record<string, string>;
|
|
131
|
+
}
|
|
132
|
+
interface StorageDriver {
|
|
133
|
+
put(key: string, value: ArrayBuffer | ReadableStream | Blob | ArrayBufferView | string, options?: PutOptions): Promise<R2ObjectInfo>;
|
|
134
|
+
get(key: string): Promise<R2ObjectBody | null>;
|
|
135
|
+
delete(key: string): Promise<void>;
|
|
136
|
+
head(key: string): Promise<R2ObjectInfo | null>;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Create an R2Bucket-compatible filesystem storage driver.
|
|
140
|
+
*
|
|
141
|
+
* ```ts
|
|
142
|
+
* const bucket = createFilesystemDriver('./data/media')
|
|
143
|
+
* // bucket satisfies R2Bucket — pass it as c.env.MEDIA_BUCKET
|
|
144
|
+
* ```
|
|
145
|
+
*
|
|
146
|
+
* @param storageDir Absolute or relative path to the root storage directory.
|
|
147
|
+
* Created automatically if it doesn't exist.
|
|
148
|
+
*/
|
|
149
|
+
declare function createFilesystemDriver(storageDir: string): StorageDriver;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* In-memory KV driver — KVNamespace-compatible adapter for self-hosted deployments.
|
|
153
|
+
*
|
|
154
|
+
* Values survive the process lifetime. For cross-restart persistence, wire the
|
|
155
|
+
* optional `persistPath` to a JSON file; entries are written on every put/delete.
|
|
156
|
+
*
|
|
157
|
+
* Usage:
|
|
158
|
+
* import { createMemoryKVDriver } from '@sonicjs-cms/core/adapters'
|
|
159
|
+
* const kv = createMemoryKVDriver()
|
|
160
|
+
* // Pass `kv` anywhere SonicJS expects a KVNamespace (CACHE_KV binding).
|
|
161
|
+
*/
|
|
162
|
+
interface KVPutOptions {
|
|
163
|
+
expirationTtl?: number;
|
|
164
|
+
/** Absolute Unix timestamp (seconds) at which the key expires. */
|
|
165
|
+
expiration?: number;
|
|
166
|
+
metadata?: unknown;
|
|
167
|
+
}
|
|
168
|
+
interface KVListOptions {
|
|
169
|
+
prefix?: string;
|
|
170
|
+
limit?: number;
|
|
171
|
+
cursor?: string;
|
|
172
|
+
}
|
|
173
|
+
interface KVListResult {
|
|
174
|
+
keys: Array<{
|
|
175
|
+
name: string;
|
|
176
|
+
expiration?: number;
|
|
177
|
+
}>;
|
|
178
|
+
list_complete: boolean;
|
|
179
|
+
cursor?: string;
|
|
180
|
+
}
|
|
181
|
+
interface KVGetWithMetadata<T> {
|
|
182
|
+
value: T | null;
|
|
183
|
+
metadata: unknown;
|
|
184
|
+
}
|
|
185
|
+
interface KVDriver {
|
|
186
|
+
get(key: string): Promise<string | null>;
|
|
187
|
+
get(key: string, type: 'text'): Promise<string | null>;
|
|
188
|
+
get<T = unknown>(key: string, type: 'json'): Promise<T | null>;
|
|
189
|
+
get(key: string, type: 'arrayBuffer'): Promise<ArrayBuffer | null>;
|
|
190
|
+
put(key: string, value: string | ArrayBuffer | ReadableStream, options?: KVPutOptions): Promise<void>;
|
|
191
|
+
delete(key: string): Promise<void>;
|
|
192
|
+
list(options?: KVListOptions): Promise<KVListResult>;
|
|
193
|
+
getWithMetadata<T = unknown>(key: string, type: 'json'): Promise<KVGetWithMetadata<T>>;
|
|
194
|
+
}
|
|
195
|
+
interface MemoryKVOptions {
|
|
196
|
+
/**
|
|
197
|
+
* Path to a JSON file used for persistence across restarts.
|
|
198
|
+
* The file is read on startup and written on every put/delete.
|
|
199
|
+
* Omit for a pure in-memory (ephemeral) store.
|
|
200
|
+
*/
|
|
201
|
+
persistPath?: string;
|
|
202
|
+
}
|
|
203
|
+
declare function createMemoryKVDriver(options?: MemoryKVOptions): KVDriver;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Synchronous Queue driver — Cloudflare Queue-compatible adapter for self-hosted deployments.
|
|
207
|
+
*
|
|
208
|
+
* Instead of enqueuing messages to a remote queue, `send()` immediately invokes
|
|
209
|
+
* the registered handler in-process. This means "queued" work (e.g. scheduled
|
|
210
|
+
* email jobs) runs synchronously before the HTTP response is returned — acceptable
|
|
211
|
+
* for low-volume self-host deployments.
|
|
212
|
+
*
|
|
213
|
+
* Usage:
|
|
214
|
+
* import { createSyncQueueDriver } from '@sonicjs-cms/core/adapters'
|
|
215
|
+
*
|
|
216
|
+
* const emailQueue = createSyncQueueDriver(async (messages) => {
|
|
217
|
+
* // same body as handleEmailQueueMessage() from email-templates-plugin
|
|
218
|
+
* for (const msg of messages) {
|
|
219
|
+
* await processJob(msg.body)
|
|
220
|
+
* msg.ack()
|
|
221
|
+
* }
|
|
222
|
+
* })
|
|
223
|
+
* // Pass emailQueue as c.env.EMAIL_QUEUE
|
|
224
|
+
*/
|
|
225
|
+
interface QueueMessage<T = unknown> {
|
|
226
|
+
id: string;
|
|
227
|
+
body: T;
|
|
228
|
+
timestamp: Date;
|
|
229
|
+
ack(): void;
|
|
230
|
+
retry(): void;
|
|
231
|
+
}
|
|
232
|
+
interface MessageBatch<T = unknown> {
|
|
233
|
+
readonly queue: string;
|
|
234
|
+
readonly messages: QueueMessage<T>[];
|
|
235
|
+
ackAll(): void;
|
|
236
|
+
retryAll(): void;
|
|
237
|
+
}
|
|
238
|
+
interface SendOptions {
|
|
239
|
+
delaySeconds?: number;
|
|
240
|
+
contentType?: string;
|
|
241
|
+
}
|
|
242
|
+
type QueueHandler<T> = (batch: MessageBatch<T>) => Promise<void>;
|
|
243
|
+
interface QueueDriver<T = unknown> {
|
|
244
|
+
/**
|
|
245
|
+
* Send a message. The registered handler is invoked synchronously (after
|
|
246
|
+
* `delaySeconds` have elapsed when non-zero, via a non-blocking `setTimeout`).
|
|
247
|
+
*/
|
|
248
|
+
send(body: T, options?: SendOptions): Promise<void>;
|
|
249
|
+
/**
|
|
250
|
+
* Send many messages in a single batch.
|
|
251
|
+
*/
|
|
252
|
+
sendBatch(messages: Array<{
|
|
253
|
+
body: T;
|
|
254
|
+
options?: SendOptions;
|
|
255
|
+
}>): Promise<void>;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Create a synchronous Queue driver.
|
|
259
|
+
*
|
|
260
|
+
* @param handler Called for each message (or batch). When `handler` is omitted,
|
|
261
|
+
* messages are logged and discarded — useful when EMAIL_QUEUE is
|
|
262
|
+
* optional and no handler is wired yet.
|
|
263
|
+
* @param queueName Optional queue name shown in MessageBatch (cosmetic).
|
|
264
|
+
*/
|
|
265
|
+
declare function createSyncQueueDriver<T = unknown>(handler?: QueueHandler<T>, queueName?: string): QueueDriver<T>;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Node / Bun server adapter for self-hosted SonicJS deployments.
|
|
269
|
+
*
|
|
270
|
+
* Wires the local SQLite DB driver, filesystem storage, in-memory KV, and
|
|
271
|
+
* optional sync queue into a SonicJS app so it can run outside Cloudflare Workers.
|
|
272
|
+
*
|
|
273
|
+
* ## Quick start (Node.js)
|
|
274
|
+
*
|
|
275
|
+
* ```ts
|
|
276
|
+
* import { serve } from '@hono/node-server'
|
|
277
|
+
* import { createSonicJSApp } from '@sonicjs-cms/core'
|
|
278
|
+
* import { createNodeSonicApp } from '@sonicjs-cms/core/adapters'
|
|
279
|
+
*
|
|
280
|
+
* const app = createSonicJSApp({ ... })
|
|
281
|
+
* const adapted = await createNodeSonicApp(app, {
|
|
282
|
+
* dbPath: './data/sonicjs.db',
|
|
283
|
+
* storagePath: './data/media',
|
|
284
|
+
* })
|
|
285
|
+
* serve({ fetch: adapted.fetch, port: 3000 })
|
|
286
|
+
* ```
|
|
287
|
+
*
|
|
288
|
+
* ## Quick start (Bun)
|
|
289
|
+
*
|
|
290
|
+
* ```ts
|
|
291
|
+
* import { createSonicJSApp } from '@sonicjs-cms/core'
|
|
292
|
+
* import { createNodeSonicApp } from '@sonicjs-cms/core/adapters'
|
|
293
|
+
*
|
|
294
|
+
* const app = createSonicJSApp({ ... })
|
|
295
|
+
* const adapted = await createNodeSonicApp(app, {
|
|
296
|
+
* dbPath: './data/sonicjs.db',
|
|
297
|
+
* storagePath: './data/media',
|
|
298
|
+
* })
|
|
299
|
+
* Bun.serve({ fetch: adapted.fetch, port: 3000 })
|
|
300
|
+
* ```
|
|
301
|
+
*/
|
|
302
|
+
|
|
303
|
+
interface NodeAdapterOptions {
|
|
304
|
+
/**
|
|
305
|
+
* Path to the SQLite database file.
|
|
306
|
+
* Defaults to `./data/sonicjs.db`.
|
|
307
|
+
*/
|
|
308
|
+
dbPath?: string;
|
|
309
|
+
/**
|
|
310
|
+
* Root directory for uploaded media files (replaces R2).
|
|
311
|
+
* Defaults to `./data/media`.
|
|
312
|
+
*/
|
|
313
|
+
storagePath?: string;
|
|
314
|
+
/**
|
|
315
|
+
* Path to a JSON file used to persist KV entries across restarts.
|
|
316
|
+
* Omit for ephemeral in-memory KV (TTL-based caching only).
|
|
317
|
+
*/
|
|
318
|
+
kvPersistPath?: string;
|
|
319
|
+
/**
|
|
320
|
+
* Environment variables injected as `c.env.*` (JWT_SECRET, CORS_ORIGINS, etc.).
|
|
321
|
+
* Values are merged with process.env so you can also use dotenv.
|
|
322
|
+
*/
|
|
323
|
+
env?: Record<string, string | undefined>;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Wrap a SonicJS Hono app with local adapter bindings injected before any
|
|
327
|
+
* SonicJS middleware (bootstrap, auth, routes) executes.
|
|
328
|
+
*
|
|
329
|
+
* The injection MUST run first — SonicJS's bootstrap middleware accesses
|
|
330
|
+
* `c.env.DB` on the very first request. If we append the middleware after
|
|
331
|
+
* `createSonicJSApp()` we'd lose the ordering race. Instead we wrap the
|
|
332
|
+
* sonicApp in a fresh Hono instance that injects env first, then routes
|
|
333
|
+
* all requests through the sonicApp.
|
|
334
|
+
*
|
|
335
|
+
* @param sonicApp The SonicJS Hono app created by `createSonicJSApp()`.
|
|
336
|
+
* @param options Adapter configuration.
|
|
337
|
+
*/
|
|
338
|
+
declare function createNodeSonicApp<E extends {
|
|
339
|
+
Bindings: Record<string, unknown>;
|
|
340
|
+
}>(sonicApp: Hono<E>, options?: NodeAdapterOptions): Promise<Hono<E>>;
|
|
341
|
+
|
|
342
|
+
export { type KVDriver, type KVGetWithMetadata, type KVListOptions, type KVListResult, type KVPutOptions, type MemoryKVOptions, type MessageBatch, type NodeAdapterOptions, type PutOptions, type QueueDriver, type QueueHandler, type QueueMessage, type R2HttpMetadata, type R2ObjectBody, type R2ObjectInfo, type R2StoredMeta, type SendOptions, type SqliteDriver, type SqliteDriverOptions, type StorageDriver, createFilesystemDriver, createMemoryKVDriver, createNodeSonicApp, createSqliteDriver, createSyncQueueDriver };
|