kernelcms 0.1.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/sqlite.js ADDED
@@ -0,0 +1,393 @@
1
+ // ../db-sqlite/src/index.ts
2
+ import { createRequire } from "module";
3
+ var nodeRequire = createRequire(import.meta.url);
4
+ var { DatabaseSync } = nodeRequire("node:sqlite");
5
+ var SQL_TYPE = {
6
+ text: "TEXT",
7
+ integer: "INTEGER",
8
+ real: "REAL",
9
+ boolean: "INTEGER",
10
+ timestamp: "TEXT",
11
+ json: "TEXT"
12
+ };
13
+ function quote(ident) {
14
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(ident)) {
15
+ throw new Error(`Invalid SQL identifier: ${ident}`);
16
+ }
17
+ return `"${ident}"`;
18
+ }
19
+ function resolveLocation(url) {
20
+ if (!url) return ":memory:";
21
+ if (url === ":memory:") return url;
22
+ if (url.startsWith("file:")) return url.slice("file:".length);
23
+ return url;
24
+ }
25
+ var SQLiteAdapter = class {
26
+ kind = "db";
27
+ name = "sqlite";
28
+ contractVersion = "1.0";
29
+ capabilities = {
30
+ transactions: true,
31
+ joins: "application",
32
+ jsonQuery: false,
33
+ fullTextSearch: false,
34
+ returning: false
35
+ };
36
+ db = null;
37
+ logger = null;
38
+ options;
39
+ tables = /* @__PURE__ */ new Map();
40
+ allowedCols = /* @__PURE__ */ new Map();
41
+ // Serializes transactions. There is one shared synchronous connection, so two
42
+ // overlapping `BEGIN…COMMIT` blocks would interleave their statements at the
43
+ // `await` points inside the callback and corrupt each other. Chaining every
44
+ // transaction onto this tail guarantees at most one is open at a time.
45
+ txTail = Promise.resolve();
46
+ constructor(options) {
47
+ this.options = options;
48
+ }
49
+ async init(ctx) {
50
+ this.logger = ctx.logger;
51
+ const location = resolveLocation(this.options.url);
52
+ this.db = new DatabaseSync(location);
53
+ this.db.exec(`PRAGMA busy_timeout = ${Number(this.options.busyTimeout ?? 5e3)};`);
54
+ if (location !== ":memory:" && (this.options.wal ?? true)) {
55
+ this.db.exec("PRAGMA journal_mode = WAL;");
56
+ }
57
+ this.logger.info(`sqlite connected (${location})`);
58
+ }
59
+ conn() {
60
+ if (!this.db) throw new Error("SQLite adapter not initialized. Did you call initKernel()?");
61
+ return this.db;
62
+ }
63
+ table(name) {
64
+ const t = this.tables.get(name);
65
+ if (!t) throw new Error(`Unknown table "${name}". Run migrations first.`);
66
+ return t;
67
+ }
68
+ async migrate(schema) {
69
+ const db = this.conn();
70
+ const report = { createdTables: [], addedColumns: [], statements: [] };
71
+ for (const table of schema.tables) {
72
+ this.tables.set(table.table, table);
73
+ const allowed = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt"]);
74
+ for (const c of table.columns) allowed.add(c.name);
75
+ this.allowedCols.set(table.table, allowed);
76
+ const existing = db.prepare(`PRAGMA table_info(${quote(table.table)})`).all();
77
+ if (existing.length === 0) {
78
+ const sql = this.createTableSql(table);
79
+ db.exec(sql);
80
+ report.createdTables.push(table.table);
81
+ report.statements.push(sql);
82
+ } else {
83
+ const have = new Set(existing.map((c) => c.name));
84
+ for (const col of table.columns) {
85
+ if (!have.has(col.name)) {
86
+ const sql = `ALTER TABLE ${quote(table.table)} ADD COLUMN ${this.columnSql(col)};`;
87
+ db.exec(sql);
88
+ report.addedColumns.push(`${table.table}.${col.name}`);
89
+ report.statements.push(sql);
90
+ }
91
+ }
92
+ }
93
+ for (const col of table.columns) {
94
+ if (col.indexed && !col.unique) {
95
+ const idx = `idx_${table.table}_${col.name}`;
96
+ const sql = `CREATE INDEX IF NOT EXISTS ${quote(idx)} ON ${quote(table.table)} (${quote(col.name)});`;
97
+ db.exec(sql);
98
+ }
99
+ }
100
+ }
101
+ this.logger?.info(
102
+ `migrated: ${report.createdTables.length} table(s) created, ${report.addedColumns.length} column(s) added`
103
+ );
104
+ return report;
105
+ }
106
+ columnSql(col) {
107
+ const parts = [quote(col.name), SQL_TYPE[col.type]];
108
+ if (col.unique) parts.push("UNIQUE");
109
+ return parts.join(" ");
110
+ }
111
+ createTableSql(table) {
112
+ const cols = [`${quote("id")} TEXT PRIMARY KEY`];
113
+ for (const col of table.columns) cols.push(this.columnSql(col));
114
+ if (table.timestamps) {
115
+ cols.push(`${quote("createdAt")} TEXT`);
116
+ cols.push(`${quote("updatedAt")} TEXT`);
117
+ }
118
+ return `CREATE TABLE IF NOT EXISTS ${quote(table.table)} (
119
+ ${cols.join(",\n ")}
120
+ );`;
121
+ }
122
+ // -- value codecs ---------------------------------------------------------
123
+ encode(col, value) {
124
+ if (value === void 0 || value === null) return null;
125
+ switch (col.type) {
126
+ case "boolean":
127
+ return value ? 1 : 0;
128
+ case "json":
129
+ return JSON.stringify(value);
130
+ case "integer":
131
+ return Math.trunc(Number(value));
132
+ case "real":
133
+ return Number(value);
134
+ case "timestamp":
135
+ return value instanceof Date ? value.toISOString() : String(value);
136
+ default:
137
+ return String(value);
138
+ }
139
+ }
140
+ decode(col, value) {
141
+ if (value === void 0 || value === null) return null;
142
+ switch (col.type) {
143
+ case "boolean":
144
+ return Boolean(value);
145
+ case "json":
146
+ try {
147
+ return typeof value === "string" ? JSON.parse(value) : value;
148
+ } catch {
149
+ return null;
150
+ }
151
+ default:
152
+ return value;
153
+ }
154
+ }
155
+ decodeRow(table, raw) {
156
+ const out = { id: raw.id };
157
+ if ("createdAt" in raw) out.createdAt = raw.createdAt;
158
+ if ("updatedAt" in raw) out.updatedAt = raw.updatedAt;
159
+ for (const col of table.columns) out[col.name] = this.decode(col, raw[col.name]);
160
+ return out;
161
+ }
162
+ colByName(table) {
163
+ const m = /* @__PURE__ */ new Map();
164
+ for (const c of table.columns) m.set(c.name, c);
165
+ return m;
166
+ }
167
+ // -- where compilation ----------------------------------------------------
168
+ buildWhere(where, table, cols, allowed, params) {
169
+ const clauses = [];
170
+ if (where.and?.length) {
171
+ const sub = where.and.map((w) => this.buildWhere(w, table, cols, allowed, params)).filter(Boolean);
172
+ if (sub.length) clauses.push(`(${sub.join(" AND ")})`);
173
+ }
174
+ if (where.or?.length) {
175
+ const sub = where.or.map((w) => this.buildWhere(w, table, cols, allowed, params)).filter(Boolean);
176
+ if (sub.length) clauses.push(`(${sub.join(" OR ")})`);
177
+ }
178
+ for (const [field, cond] of Object.entries(where)) {
179
+ if (field === "and" || field === "or" || cond === void 0) continue;
180
+ if (!allowed.has(field)) throw new Error(`Cannot filter on unknown field "${field}" of "${table.table}".`);
181
+ clauses.push(this.fieldClause(field, cond, cols.get(field), params));
182
+ }
183
+ return clauses.length ? clauses.join(" AND ") : "";
184
+ }
185
+ encodeOperand(col, operand) {
186
+ if (col) return this.encode(col, operand);
187
+ if (typeof operand === "boolean") return operand ? 1 : 0;
188
+ return operand;
189
+ }
190
+ fieldClause(field, cond, col, params) {
191
+ const id = quote(field);
192
+ const parts = [];
193
+ for (const [op, operand] of Object.entries(cond)) {
194
+ switch (op) {
195
+ case "equals":
196
+ if (operand === null) parts.push(`${id} IS NULL`);
197
+ else {
198
+ params.push(this.encodeOperand(col, operand));
199
+ parts.push(`${id} = ?`);
200
+ }
201
+ break;
202
+ case "not_equals":
203
+ if (operand === null) parts.push(`${id} IS NOT NULL`);
204
+ else {
205
+ params.push(this.encodeOperand(col, operand));
206
+ parts.push(`(${id} <> ? OR ${id} IS NULL)`);
207
+ }
208
+ break;
209
+ case "in":
210
+ case "not_in": {
211
+ const arr = Array.isArray(operand) ? operand : [operand];
212
+ if (arr.length === 0) {
213
+ parts.push(op === "in" ? "0" : "1");
214
+ break;
215
+ }
216
+ for (const v of arr) params.push(this.encodeOperand(col, v));
217
+ const ph = arr.map(() => "?").join(", ");
218
+ parts.push(`${id} ${op === "in" ? "IN" : "NOT IN"} (${ph})`);
219
+ break;
220
+ }
221
+ case "greater_than":
222
+ params.push(this.encodeOperand(col, operand));
223
+ parts.push(`${id} > ?`);
224
+ break;
225
+ case "greater_than_equal":
226
+ params.push(this.encodeOperand(col, operand));
227
+ parts.push(`${id} >= ?`);
228
+ break;
229
+ case "less_than":
230
+ params.push(this.encodeOperand(col, operand));
231
+ parts.push(`${id} < ?`);
232
+ break;
233
+ case "less_than_equal":
234
+ params.push(this.encodeOperand(col, operand));
235
+ parts.push(`${id} <= ?`);
236
+ break;
237
+ case "like":
238
+ case "contains": {
239
+ const literal = String(operand).replace(/[\\%_]/g, (ch) => `\\${ch}`);
240
+ params.push(`%${literal}%`);
241
+ parts.push(`${id} LIKE ? ESCAPE '\\'`);
242
+ break;
243
+ }
244
+ case "exists":
245
+ parts.push(operand ? `${id} IS NOT NULL` : `${id} IS NULL`);
246
+ break;
247
+ default:
248
+ throw new Error(`Unsupported operator "${op}".`);
249
+ }
250
+ }
251
+ return parts.length ? `(${parts.join(" AND ")})` : "";
252
+ }
253
+ whereSql(table, where) {
254
+ if (!where) return { sql: "", params: [] };
255
+ const params = [];
256
+ const allowed = this.allowedCols.get(table.table) ?? /* @__PURE__ */ new Set(["id"]);
257
+ const clause = this.buildWhere(where, table, this.colByName(table), allowed, params);
258
+ return { sql: clause ? ` WHERE ${clause}` : "", params };
259
+ }
260
+ // -- CRUD -----------------------------------------------------------------
261
+ rawGet(table, id) {
262
+ const row = this.conn().prepare(`SELECT * FROM ${quote(table.table)} WHERE ${quote("id")} = ?`).get(id);
263
+ return row ? this.decodeRow(table, row) : null;
264
+ }
265
+ async findByID(args) {
266
+ return this.rawGet(this.table(args.collection), args.id);
267
+ }
268
+ async find(args) {
269
+ const table = this.table(args.collection);
270
+ const db = this.conn();
271
+ const { sql: whereSql, params } = this.whereSql(table, args.where);
272
+ const allowed = this.allowedCols.get(table.table) ?? /* @__PURE__ */ new Set(["id"]);
273
+ const totalRow = db.prepare(`SELECT COUNT(*) AS c FROM ${quote(table.table)}${whereSql}`).get(...params);
274
+ const totalDocs = Number(totalRow.c);
275
+ const sortParts = (args.sort ?? []).filter((s) => allowed.has(s.field)).map((s) => `${quote(s.field)} ${s.direction === "desc" ? "DESC" : "ASC"}`);
276
+ const orderBy = sortParts.length ? ` ORDER BY ${sortParts.join(", ")}` : ` ORDER BY ${quote("id")} ASC`;
277
+ const limit = args.limit;
278
+ const offset = (args.page - 1) * limit;
279
+ const rows = db.prepare(`SELECT * FROM ${quote(table.table)}${whereSql}${orderBy} LIMIT ? OFFSET ?`).all(...params, limit, offset);
280
+ const docs = rows.map((r) => this.decodeRow(table, r));
281
+ const totalPages = limit > 0 ? Math.max(Math.ceil(totalDocs / limit), 1) : 1;
282
+ const page = args.page;
283
+ return {
284
+ docs,
285
+ totalDocs,
286
+ limit,
287
+ page,
288
+ totalPages,
289
+ hasPrevPage: page > 1,
290
+ hasNextPage: page < totalPages,
291
+ prevPage: page > 1 ? page - 1 : null,
292
+ nextPage: page < totalPages ? page + 1 : null,
293
+ pagingCounter: offset + 1
294
+ };
295
+ }
296
+ async create(args) {
297
+ const table = this.table(args.collection);
298
+ const id = String(args.data.id);
299
+ const now = (/* @__PURE__ */ new Date()).toISOString();
300
+ const fields = [quote("id")];
301
+ const values = [id];
302
+ for (const col of table.columns) {
303
+ if (Object.prototype.hasOwnProperty.call(args.data, col.name)) {
304
+ fields.push(quote(col.name));
305
+ values.push(this.encode(col, args.data[col.name]));
306
+ }
307
+ }
308
+ if (table.timestamps) {
309
+ fields.push(quote("createdAt"), quote("updatedAt"));
310
+ values.push(now, now);
311
+ }
312
+ const placeholders = fields.map(() => "?").join(", ");
313
+ this.conn().prepare(`INSERT INTO ${quote(table.table)} (${fields.join(", ")}) VALUES (${placeholders})`).run(...values);
314
+ const created = this.rawGet(table, id);
315
+ if (!created) throw new Error("Insert succeeded but row could not be read back.");
316
+ return created;
317
+ }
318
+ async update(args) {
319
+ const table = this.table(args.collection);
320
+ const sets = [];
321
+ const values = [];
322
+ for (const col of table.columns) {
323
+ if (Object.prototype.hasOwnProperty.call(args.data, col.name)) {
324
+ sets.push(`${quote(col.name)} = ?`);
325
+ values.push(this.encode(col, args.data[col.name]));
326
+ }
327
+ }
328
+ if (table.timestamps) {
329
+ sets.push(`${quote("updatedAt")} = ?`);
330
+ values.push((/* @__PURE__ */ new Date()).toISOString());
331
+ }
332
+ if (sets.length > 0) {
333
+ values.push(args.id);
334
+ const res = this.conn().prepare(`UPDATE ${quote(table.table)} SET ${sets.join(", ")} WHERE ${quote("id")} = ?`).run(...values);
335
+ if (Number(res.changes) === 0 && !this.rawGet(table, args.id)) return null;
336
+ }
337
+ return this.rawGet(table, args.id);
338
+ }
339
+ async delete(args) {
340
+ const table = this.table(args.collection);
341
+ const existing = this.rawGet(table, args.id);
342
+ if (!existing) return null;
343
+ this.conn().prepare(`DELETE FROM ${quote(table.table)} WHERE ${quote("id")} = ?`).run(args.id);
344
+ return existing;
345
+ }
346
+ async count(args) {
347
+ const table = this.table(args.collection);
348
+ const { sql, params } = this.whereSql(table, args.where);
349
+ const row = this.conn().prepare(`SELECT COUNT(*) AS c FROM ${quote(table.table)}${sql}`).get(...params);
350
+ return Number(row.c);
351
+ }
352
+ async transaction(fn) {
353
+ const run = async () => {
354
+ const db = this.conn();
355
+ db.exec("BEGIN IMMEDIATE");
356
+ try {
357
+ const result2 = await fn(this);
358
+ db.exec("COMMIT");
359
+ return result2;
360
+ } catch (err) {
361
+ try {
362
+ db.exec("ROLLBACK");
363
+ } catch {
364
+ }
365
+ throw err;
366
+ }
367
+ };
368
+ const result = this.txTail.then(run, run);
369
+ this.txTail = result.then(
370
+ () => void 0,
371
+ () => void 0
372
+ );
373
+ return result;
374
+ }
375
+ async health() {
376
+ try {
377
+ this.conn().prepare("SELECT 1").get();
378
+ return { status: "ok" };
379
+ } catch (err) {
380
+ return { status: "down", detail: err instanceof Error ? err.message : String(err) };
381
+ }
382
+ }
383
+ async destroy() {
384
+ this.db?.close();
385
+ this.db = null;
386
+ }
387
+ };
388
+ function sqliteAdapter(options = {}) {
389
+ return new SQLiteAdapter(options);
390
+ }
391
+ export {
392
+ sqliteAdapter
393
+ };
@@ -0,0 +1,310 @@
1
+ import { R as Row, W as Where, D as DatabaseAdapter, K as KernelSchema, P as PaginatedResult } from './index-BxvPeUO2.js';
2
+
3
+ interface AuthUser {
4
+ id: string;
5
+ email?: string;
6
+ roles?: string[];
7
+ collection?: string;
8
+ [key: string]: unknown;
9
+ }
10
+ interface RequestContext<TUser extends AuthUser = AuthUser> {
11
+ /** The authenticated user, or null for anonymous requests. */
12
+ user: TUser | null;
13
+ /** Active locale for reads/writes. */
14
+ locale: string;
15
+ /** Locale to fall back to when a value is missing, or false to disable. */
16
+ fallbackLocale: string | false;
17
+ /** Arbitrary per-request data plugins can attach. */
18
+ context: Record<string, unknown>;
19
+ }
20
+ interface AccessArgs<TUser extends AuthUser = AuthUser> {
21
+ req: RequestContext<TUser>;
22
+ /** Present for update/delete/read-by-id. */
23
+ id?: string;
24
+ /** Present for create/update. */
25
+ data?: Row;
26
+ }
27
+ /** `true`/`false` to allow/deny, or a `Where` to constrain which rows are visible. */
28
+ type AccessResult = boolean | Where;
29
+ type AccessFn<TUser extends AuthUser = AuthUser> = (args: AccessArgs<TUser>) => AccessResult | Promise<AccessResult>;
30
+ type Operation = 'create' | 'read' | 'update' | 'delete';
31
+ interface HookArgs {
32
+ req: RequestContext;
33
+ operation: Operation;
34
+ /** Incoming/outgoing document data. */
35
+ data?: Row;
36
+ /** The existing document for update/delete. */
37
+ originalDoc?: Row;
38
+ doc?: Row;
39
+ }
40
+ type CollectionBeforeChangeHook = (args: HookArgs & {
41
+ data: Row;
42
+ }) => Row | Promise<Row>;
43
+ type CollectionAfterChangeHook = (args: HookArgs & {
44
+ doc: Row;
45
+ }) => Row | Promise<Row>;
46
+ type CollectionAfterReadHook = (args: HookArgs & {
47
+ doc: Row;
48
+ }) => Row | Promise<Row>;
49
+ type CollectionBeforeDeleteHook = (args: {
50
+ req: RequestContext;
51
+ id: string;
52
+ }) => void | Promise<void>;
53
+ type CollectionAfterDeleteHook = (args: {
54
+ req: RequestContext;
55
+ id: string;
56
+ doc: Row;
57
+ }) => void | Promise<void>;
58
+ interface CollectionHooks {
59
+ beforeChange?: CollectionBeforeChangeHook[];
60
+ afterChange?: CollectionAfterChangeHook[];
61
+ afterRead?: CollectionAfterReadHook[];
62
+ beforeDelete?: CollectionBeforeDeleteHook[];
63
+ afterDelete?: CollectionAfterDeleteHook[];
64
+ }
65
+ interface FieldValidateArgs {
66
+ value: unknown;
67
+ data: Row;
68
+ req: RequestContext;
69
+ operation: 'create' | 'update';
70
+ siblingData: Row;
71
+ }
72
+ type ValidateFn = (args: FieldValidateArgs) => true | string | Promise<true | string>;
73
+ interface FieldAdmin {
74
+ description?: string;
75
+ placeholder?: string;
76
+ readOnly?: boolean;
77
+ hidden?: boolean;
78
+ position?: 'main' | 'sidebar';
79
+ width?: number;
80
+ /** Show this field only when the predicate over the current form data is true. */
81
+ condition?: (data: Row, siblingData: Row) => boolean;
82
+ }
83
+ interface FieldAccess {
84
+ read?: AccessFn;
85
+ create?: AccessFn;
86
+ update?: AccessFn;
87
+ }
88
+ interface FieldBase {
89
+ name: string;
90
+ label?: string;
91
+ required?: boolean;
92
+ unique?: boolean;
93
+ localized?: boolean;
94
+ index?: boolean;
95
+ defaultValue?: unknown;
96
+ admin?: FieldAdmin;
97
+ access?: FieldAccess;
98
+ validate?: ValidateFn;
99
+ }
100
+ interface TextField extends FieldBase {
101
+ type: 'text' | 'textarea' | 'email' | 'code' | 'slug';
102
+ minLength?: number;
103
+ maxLength?: number;
104
+ pattern?: string;
105
+ }
106
+ interface NumberField extends FieldBase {
107
+ type: 'number';
108
+ min?: number;
109
+ max?: number;
110
+ integer?: boolean;
111
+ }
112
+ interface BooleanField extends FieldBase {
113
+ type: 'boolean' | 'checkbox';
114
+ }
115
+ interface DateField extends FieldBase {
116
+ type: 'date';
117
+ }
118
+ interface JSONField extends FieldBase {
119
+ type: 'json';
120
+ }
121
+ interface RichTextField extends FieldBase {
122
+ type: 'richText';
123
+ }
124
+ interface PointField extends FieldBase {
125
+ type: 'point';
126
+ }
127
+ type SelectOption = string | {
128
+ label: string;
129
+ value: string;
130
+ };
131
+ interface SelectField extends FieldBase {
132
+ type: 'select' | 'radio';
133
+ options: SelectOption[];
134
+ hasMany?: boolean;
135
+ }
136
+ interface RelationshipField extends FieldBase {
137
+ type: 'relationship' | 'upload';
138
+ relationTo: string;
139
+ hasMany?: boolean;
140
+ }
141
+ interface ArrayField extends FieldBase {
142
+ type: 'array';
143
+ fields: AnyField[];
144
+ minRows?: number;
145
+ maxRows?: number;
146
+ }
147
+ interface GroupField extends FieldBase {
148
+ type: 'group';
149
+ fields: AnyField[];
150
+ }
151
+ type AnyField = TextField | NumberField | BooleanField | DateField | JSONField | RichTextField | PointField | SelectField | RelationshipField | ArrayField | GroupField;
152
+ type FieldType = AnyField['type'];
153
+ interface CollectionAccess {
154
+ read?: AccessFn;
155
+ create?: AccessFn;
156
+ update?: AccessFn;
157
+ delete?: AccessFn;
158
+ }
159
+ interface AuthOptions {
160
+ /** Token lifetime in seconds. */
161
+ tokenExpiration?: number;
162
+ /** Field used as the login identifier. Defaults to "email". */
163
+ loginField?: string;
164
+ }
165
+ interface CollectionConfig {
166
+ slug: string;
167
+ labels?: {
168
+ singular?: string;
169
+ plural?: string;
170
+ };
171
+ fields: AnyField[];
172
+ timestamps?: boolean;
173
+ admin?: {
174
+ useAsTitle?: string;
175
+ defaultColumns?: string[];
176
+ group?: string;
177
+ description?: string;
178
+ hidden?: boolean;
179
+ };
180
+ access?: CollectionAccess;
181
+ hooks?: CollectionHooks;
182
+ /** Mark this collection as an auth collection (adds email + password handling). */
183
+ auth?: boolean | AuthOptions;
184
+ }
185
+ interface GlobalConfig {
186
+ slug: string;
187
+ label?: string;
188
+ fields: AnyField[];
189
+ access?: {
190
+ read?: AccessFn;
191
+ update?: AccessFn;
192
+ };
193
+ hooks?: Pick<CollectionHooks, 'beforeChange' | 'afterChange' | 'afterRead'>;
194
+ }
195
+ interface LocalizationConfig {
196
+ locales: string[];
197
+ defaultLocale: string;
198
+ fallback?: boolean;
199
+ }
200
+ interface KernelConfig {
201
+ serverURL?: string;
202
+ db: DatabaseAdapter;
203
+ collections: CollectionConfig[];
204
+ globals?: GlobalConfig[];
205
+ localization?: LocalizationConfig;
206
+ routes?: {
207
+ api?: string;
208
+ };
209
+ admin?: {
210
+ user?: string;
211
+ meta?: {
212
+ titleSuffix?: string;
213
+ };
214
+ };
215
+ /** Secret used to sign auth tokens. Never hardcode; read from env. */
216
+ secret?: string;
217
+ }
218
+ interface SanitizedLocalization {
219
+ locales: string[];
220
+ defaultLocale: string;
221
+ fallback: boolean;
222
+ }
223
+ interface SanitizedConfig {
224
+ serverURL: string;
225
+ db: DatabaseAdapter;
226
+ collections: CollectionConfig[];
227
+ globals: GlobalConfig[];
228
+ localization: SanitizedLocalization | false;
229
+ routes: {
230
+ api: string;
231
+ };
232
+ admin: {
233
+ user: string;
234
+ };
235
+ secret: string;
236
+ collectionsBySlug: Record<string, CollectionConfig>;
237
+ globalsBySlug: Record<string, GlobalConfig>;
238
+ }
239
+ type Doc = Row & {
240
+ id: string;
241
+ };
242
+ interface OperationBase {
243
+ req?: Partial<RequestContext>;
244
+ overrideAccess?: boolean;
245
+ depth?: number;
246
+ }
247
+ interface FindOptions extends OperationBase {
248
+ collection: string;
249
+ where?: Where;
250
+ sort?: string | string[];
251
+ limit?: number;
252
+ page?: number;
253
+ }
254
+ interface FindByIDOptions extends OperationBase {
255
+ collection: string;
256
+ id: string;
257
+ }
258
+ interface CreateOptions extends OperationBase {
259
+ collection: string;
260
+ data: Row;
261
+ }
262
+ interface UpdateOptions extends OperationBase {
263
+ collection: string;
264
+ id: string;
265
+ data: Row;
266
+ }
267
+ interface DeleteOptions extends OperationBase {
268
+ collection: string;
269
+ id: string;
270
+ }
271
+ interface CountOptions extends OperationBase {
272
+ collection: string;
273
+ where?: Where;
274
+ }
275
+ interface FindGlobalOptions extends OperationBase {
276
+ slug: string;
277
+ }
278
+ interface UpdateGlobalOptions extends OperationBase {
279
+ slug: string;
280
+ data: Row;
281
+ }
282
+ interface LoginOptions {
283
+ collection: string;
284
+ email: string;
285
+ password: string;
286
+ }
287
+ interface AuthResult {
288
+ user: AuthUser;
289
+ token: string;
290
+ exp: number;
291
+ }
292
+ interface Kernel {
293
+ readonly config: SanitizedConfig;
294
+ readonly db: DatabaseAdapter;
295
+ readonly schema: KernelSchema;
296
+ find<T extends Doc = Doc>(opts: FindOptions): Promise<PaginatedResult<T>>;
297
+ findByID<T extends Doc = Doc>(opts: FindByIDOptions): Promise<T | null>;
298
+ create<T extends Doc = Doc>(opts: CreateOptions): Promise<T>;
299
+ update<T extends Doc = Doc>(opts: UpdateOptions): Promise<T | null>;
300
+ delete<T extends Doc = Doc>(opts: DeleteOptions): Promise<T | null>;
301
+ count(opts: CountOptions): Promise<number>;
302
+ login(opts: LoginOptions): Promise<AuthResult>;
303
+ authenticate(token: string): Promise<AuthUser | null>;
304
+ findGlobal<T extends Row = Row>(opts: FindGlobalOptions): Promise<T>;
305
+ updateGlobal<T extends Row = Row>(opts: UpdateGlobalOptions): Promise<T>;
306
+ migrate(): Promise<void>;
307
+ destroy(): Promise<void>;
308
+ }
309
+
310
+ export type { AccessArgs as A, BooleanField as B, CollectionAccess as C, DateField as D, KernelConfig as E, FieldAccess as F, GlobalConfig as G, HookArgs as H, LoginOptions as I, JSONField as J, Kernel as K, LocalizationConfig as L, OperationBase as M, NumberField as N, Operation as O, PointField as P, RequestContext as Q, RelationshipField as R, RichTextField as S, SanitizedConfig as T, SanitizedLocalization as U, SelectField as V, SelectOption as W, TextField as X, UpdateGlobalOptions as Y, UpdateOptions as Z, ValidateFn as _, AccessFn as a, AccessResult as b, AnyField as c, ArrayField as d, AuthOptions as e, AuthResult as f, AuthUser as g, CollectionAfterChangeHook as h, CollectionAfterDeleteHook as i, CollectionAfterReadHook as j, CollectionBeforeChangeHook as k, CollectionBeforeDeleteHook as l, CollectionConfig as m, CollectionHooks as n, CountOptions as o, CreateOptions as p, DeleteOptions as q, Doc as r, FieldAdmin as s, FieldBase as t, FieldType as u, FieldValidateArgs as v, FindByIDOptions as w, FindGlobalOptions as x, FindOptions as y, GroupField as z };