@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.
Files changed (108) hide show
  1. package/dist/adapters.cjs +512 -0
  2. package/dist/adapters.cjs.map +1 -0
  3. package/dist/adapters.d.cts +342 -0
  4. package/dist/adapters.d.ts +342 -0
  5. package/dist/adapters.js +501 -0
  6. package/dist/adapters.js.map +1 -0
  7. package/dist/admin-documents-form.template-55V2ZGWT.js +6 -0
  8. package/dist/{admin-documents-form.template-DDSH6ROU.js.map → admin-documents-form.template-55V2ZGWT.js.map} +1 -1
  9. package/dist/admin-documents-form.template-YNAVMJCV.cjs +19 -0
  10. package/dist/{admin-layout-catalyst.template-YQ4EMF2J.js.map → admin-documents-form.template-YNAVMJCV.cjs.map} +1 -1
  11. package/dist/admin-layout-catalyst.template-3XZXDGF7.cjs +21 -0
  12. package/dist/{admin-layout-catalyst.template-KDHKVLXR.cjs.map → admin-layout-catalyst.template-3XZXDGF7.cjs.map} +1 -1
  13. package/dist/admin-layout-catalyst.template-6UQGLCKI.js +7 -0
  14. package/dist/{admin-documents-form.template-LSZKGA5J.cjs.map → admin-layout-catalyst.template-6UQGLCKI.js.map} +1 -1
  15. package/dist/{chunk-FTXGZ3BF.cjs → chunk-37V3RET3.cjs} +21 -37
  16. package/dist/chunk-37V3RET3.cjs.map +1 -0
  17. package/dist/{chunk-3CT3SUR4.cjs → chunk-A2SN3RVL.cjs} +3 -3
  18. package/dist/chunk-A2SN3RVL.cjs.map +1 -0
  19. package/dist/{chunk-CRGUD4KC.cjs → chunk-BBEKHLTW.cjs} +9 -9
  20. package/dist/{chunk-CRGUD4KC.cjs.map → chunk-BBEKHLTW.cjs.map} +1 -1
  21. package/dist/{chunk-RMRJGMDE.js → chunk-BHND5PEV.js} +3 -3
  22. package/dist/{chunk-RMRJGMDE.js.map → chunk-BHND5PEV.js.map} +1 -1
  23. package/dist/{chunk-OBA2RYZN.js → chunk-BJROXF4N.js} +3 -3
  24. package/dist/{chunk-OBA2RYZN.js.map → chunk-BJROXF4N.js.map} +1 -1
  25. package/dist/{chunk-EUJAEB6W.cjs → chunk-BP52BFIV.cjs} +53 -4
  26. package/dist/chunk-BP52BFIV.cjs.map +1 -0
  27. package/dist/{chunk-PXNTCCPE.cjs → chunk-E66UVONV.cjs} +8 -8
  28. package/dist/{chunk-PXNTCCPE.cjs.map → chunk-E66UVONV.cjs.map} +1 -1
  29. package/dist/{chunk-CHWT5KBK.js → chunk-FHPEM77Y.js} +9 -25
  30. package/dist/chunk-FHPEM77Y.js.map +1 -0
  31. package/dist/{chunk-JFLZHOQP.cjs → chunk-I767LVN7.cjs} +531 -419
  32. package/dist/chunk-I767LVN7.cjs.map +1 -0
  33. package/dist/{chunk-N32OWET6.cjs → chunk-JMJM7DMM.cjs} +5 -5
  34. package/dist/{chunk-N32OWET6.cjs.map → chunk-JMJM7DMM.cjs.map} +1 -1
  35. package/dist/{chunk-QWSR4THH.cjs → chunk-KKNEYIZJ.cjs} +12 -5
  36. package/dist/chunk-KKNEYIZJ.cjs.map +1 -0
  37. package/dist/{chunk-73O4GPJ6.js → chunk-LNI3FYSI.js} +12 -5
  38. package/dist/chunk-LNI3FYSI.js.map +1 -0
  39. package/dist/{chunk-PYZYU7EK.cjs → chunk-QCJHCSOK.cjs} +36 -2
  40. package/dist/chunk-QCJHCSOK.cjs.map +1 -0
  41. package/dist/{chunk-MGHHPNJN.cjs → chunk-QJUJNTIU.cjs} +11 -59
  42. package/dist/chunk-QJUJNTIU.cjs.map +1 -0
  43. package/dist/{chunk-DOMXI5PM.js → chunk-RJ2O5IIE.js} +36 -2
  44. package/dist/chunk-RJ2O5IIE.js.map +1 -0
  45. package/dist/{chunk-5V62WT6M.js → chunk-THFZSDIU.js} +4 -2
  46. package/dist/chunk-THFZSDIU.js.map +1 -0
  47. package/dist/{chunk-2FCKKOA5.js → chunk-UIHIXTIS.js} +3 -3
  48. package/dist/chunk-UIHIXTIS.js.map +1 -0
  49. package/dist/{chunk-JQISFW6U.js → chunk-URIBTX27.js} +3 -3
  50. package/dist/{chunk-JQISFW6U.js.map → chunk-URIBTX27.js.map} +1 -1
  51. package/dist/{chunk-K623Q6WD.cjs → chunk-UYJQDDEE.cjs} +4 -2
  52. package/dist/chunk-UYJQDDEE.cjs.map +1 -0
  53. package/dist/{chunk-6KNTZ67K.js → chunk-VC5XVPLM.js} +234 -126
  54. package/dist/chunk-VC5XVPLM.js.map +1 -0
  55. package/dist/{chunk-I2DM27HP.js → chunk-VGEZNFWH.js} +49 -4
  56. package/dist/chunk-VGEZNFWH.js.map +1 -0
  57. package/dist/{chunk-GB2Q2DNK.js → chunk-VYQVDL2M.js} +5 -49
  58. package/dist/chunk-VYQVDL2M.js.map +1 -0
  59. package/dist/{define-plugin-DsKiGu1q.d.cts → define-plugin-DMcvEy_C.d.cts} +33 -0
  60. package/dist/{define-plugin-BYIey6sp.d.ts → define-plugin-D_6D9vVZ.d.ts} +33 -0
  61. package/dist/index.cjs +2587 -326
  62. package/dist/index.cjs.map +1 -1
  63. package/dist/index.d.cts +30 -3
  64. package/dist/index.d.ts +30 -3
  65. package/dist/index.js +2281 -22
  66. package/dist/index.js.map +1 -1
  67. package/dist/middleware.cjs +34 -34
  68. package/dist/middleware.js +5 -5
  69. package/dist/migrations-FGXZPKDC.cjs +13 -0
  70. package/dist/{migrations-UIMA6ZL6.cjs.map → migrations-FGXZPKDC.cjs.map} +1 -1
  71. package/dist/migrations-Z6D27NAW.js +4 -0
  72. package/dist/{migrations-HLK2QF4T.js.map → migrations-Z6D27NAW.js.map} +1 -1
  73. package/dist/plugins.cjs +40 -40
  74. package/dist/plugins.d.cts +1 -1
  75. package/dist/plugins.d.ts +1 -1
  76. package/dist/plugins.js +3 -3
  77. package/dist/routes.cjs +29 -29
  78. package/dist/routes.js +9 -9
  79. package/dist/services.cjs +18 -18
  80. package/dist/services.js +2 -2
  81. package/dist/templates.cjs +17 -17
  82. package/dist/templates.js +3 -3
  83. package/dist/utils.cjs +3 -3
  84. package/dist/utils.js +1 -1
  85. package/migrations/0001_core.sql +7 -1
  86. package/package.json +10 -3
  87. package/dist/admin-documents-form.template-DDSH6ROU.js +0 -6
  88. package/dist/admin-documents-form.template-LSZKGA5J.cjs +0 -19
  89. package/dist/admin-layout-catalyst.template-KDHKVLXR.cjs +0 -21
  90. package/dist/admin-layout-catalyst.template-YQ4EMF2J.js +0 -7
  91. package/dist/chunk-2FCKKOA5.js.map +0 -1
  92. package/dist/chunk-3CT3SUR4.cjs.map +0 -1
  93. package/dist/chunk-5V62WT6M.js.map +0 -1
  94. package/dist/chunk-6KNTZ67K.js.map +0 -1
  95. package/dist/chunk-73O4GPJ6.js.map +0 -1
  96. package/dist/chunk-CHWT5KBK.js.map +0 -1
  97. package/dist/chunk-DOMXI5PM.js.map +0 -1
  98. package/dist/chunk-EUJAEB6W.cjs.map +0 -1
  99. package/dist/chunk-FTXGZ3BF.cjs.map +0 -1
  100. package/dist/chunk-GB2Q2DNK.js.map +0 -1
  101. package/dist/chunk-I2DM27HP.js.map +0 -1
  102. package/dist/chunk-JFLZHOQP.cjs.map +0 -1
  103. package/dist/chunk-K623Q6WD.cjs.map +0 -1
  104. package/dist/chunk-MGHHPNJN.cjs.map +0 -1
  105. package/dist/chunk-PYZYU7EK.cjs.map +0 -1
  106. package/dist/chunk-QWSR4THH.cjs.map +0 -1
  107. package/dist/migrations-HLK2QF4T.js +0 -4
  108. 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 };