@beechcms/core 0.4.0-preview.9 → 0.4.1
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/auth/hash-provider.d.ts +13 -0
- package/dist/auth/hash-provider.d.ts.map +1 -0
- package/dist/auth/hash-provider.js +1 -0
- package/dist/auth/password-reset-token.repository.d.ts +28 -0
- package/dist/auth/password-reset-token.repository.d.ts.map +1 -0
- package/dist/auth/password-reset-token.repository.js +1 -0
- package/dist/auth/session.repository.d.ts +47 -0
- package/dist/auth/session.repository.d.ts.map +1 -0
- package/dist/auth/session.repository.js +1 -0
- package/dist/auth/token-service.d.ts +23 -0
- package/dist/auth/token-service.d.ts.map +1 -0
- package/dist/auth/token-service.js +1 -0
- package/dist/auth/user.repository.d.ts +47 -0
- package/dist/auth/user.repository.d.ts.map +1 -0
- package/dist/auth/user.repository.js +1 -0
- package/dist/clock.d.ts +25 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +9 -0
- package/dist/content-scan.repository.d.ts +9 -0
- package/dist/content-scan.repository.d.ts.map +1 -0
- package/dist/content-scan.repository.js +1 -0
- package/dist/content.repository.d.ts +95 -0
- package/dist/content.repository.d.ts.map +1 -0
- package/dist/content.repository.js +29 -0
- package/dist/engine.d.ts +24 -24
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +98 -34
- package/dist/id-generator.d.ts +19 -0
- package/dist/id-generator.d.ts.map +1 -0
- package/dist/id-generator.js +7 -0
- package/dist/idempotency.repository.d.ts +13 -0
- package/dist/idempotency.repository.d.ts.map +1 -0
- package/dist/idempotency.repository.js +1 -0
- package/dist/index.d.ts +24 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -3
- package/dist/media.repository.d.ts +61 -0
- package/dist/media.repository.d.ts.map +1 -0
- package/dist/media.repository.js +1 -0
- package/dist/notifications/notification-service.d.ts +29 -0
- package/dist/notifications/notification-service.d.ts.map +1 -0
- package/dist/notifications/notification-service.js +1 -0
- package/dist/notifications/notification.repository.d.ts +51 -0
- package/dist/notifications/notification.repository.d.ts.map +1 -0
- package/dist/notifications/notification.repository.js +10 -0
- package/dist/observability/activity-log.repository.d.ts +58 -0
- package/dist/observability/activity-log.repository.d.ts.map +1 -0
- package/dist/observability/activity-log.repository.js +1 -0
- package/dist/observability/activity-logger.d.ts +49 -0
- package/dist/observability/activity-logger.d.ts.map +1 -0
- package/dist/observability/activity-logger.js +12 -0
- package/dist/observability/analytics.repository.d.ts +37 -0
- package/dist/observability/analytics.repository.d.ts.map +1 -0
- package/dist/observability/analytics.repository.js +1 -0
- package/dist/rate-limit/rate-limiter.d.ts +13 -0
- package/dist/rate-limit/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limit/rate-limiter.js +1 -0
- package/dist/search/search.repository.d.ts +45 -0
- package/dist/search/search.repository.d.ts.map +1 -0
- package/dist/search/search.repository.js +1 -0
- package/dist/seed-registry.d.ts +49 -0
- package/dist/seed-registry.d.ts.map +1 -0
- package/dist/seed-registry.js +29 -0
- package/dist/storage.d.ts +72 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +1 -0
- package/dist/types.d.ts +3 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts +7 -7
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +15 -9
- package/dist/widget/widget.repository.d.ts +113 -0
- package/dist/widget/widget.repository.d.ts.map +1 -0
- package/dist/widget/widget.repository.js +1 -0
- package/package.json +9 -3
package/dist/engine.js
CHANGED
|
@@ -6,6 +6,7 @@ const BRANCH_TYPE_SQL = {
|
|
|
6
6
|
json: { sqlType: 'TEXT' }, // JSON serializzato
|
|
7
7
|
richtext: { sqlType: 'TEXT' },
|
|
8
8
|
file: { sqlType: 'TEXT' }, // URL singolo o JSON array di URL
|
|
9
|
+
tags: { sqlType: 'TEXT' }, // JSON array di stringhe
|
|
9
10
|
};
|
|
10
11
|
const SYSTEM_COLUMNS = new Set(['id', 'slug', 'status', 'created_at', 'updated_at']);
|
|
11
12
|
// ---- Private helpers ----
|
|
@@ -70,8 +71,8 @@ function normalizeAssetListValue(rawValue) {
|
|
|
70
71
|
}
|
|
71
72
|
// ---- DDL Generators ----
|
|
72
73
|
/**
|
|
73
|
-
*
|
|
74
|
-
* +
|
|
74
|
+
* Generates `CREATE TABLE IF NOT EXISTS content_{slug}` with system columns
|
|
75
|
+
* + one column per Branch. Pure function: same Seed → same SQL.
|
|
75
76
|
*/
|
|
76
77
|
export function generateCreateTable(seed) {
|
|
77
78
|
const table = tableName(seed);
|
|
@@ -96,9 +97,9 @@ export function generateCreateTable(seed) {
|
|
|
96
97
|
return lines.join('\n');
|
|
97
98
|
}
|
|
98
99
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
100
|
+
* Generates the drafts table `content_{slug}_drafts` for Seeds with `allowDrafts: true`.
|
|
101
|
+
* All branch columns are nullable (drafts are partial).
|
|
102
|
+
* Returns null if the Seed does not have `allowDrafts: true`.
|
|
102
103
|
*/
|
|
103
104
|
export function generateDraftTable(seed) {
|
|
104
105
|
if (!seed.allowDrafts)
|
|
@@ -113,7 +114,7 @@ export function generateDraftTable(seed) {
|
|
|
113
114
|
for (const branch of seed.branches) {
|
|
114
115
|
const { sqlType } = BRANCH_TYPE_SQL[branch.type];
|
|
115
116
|
let col = ` ${branch.alias} ${sqlType}`;
|
|
116
|
-
// boolean CHECK: in SQLite, NULL IN (0,1) → NULL,
|
|
117
|
+
// boolean CHECK: in SQLite, NULL IN (0,1) → NULL, which passes the CHECK (only FALSE fails it)
|
|
117
118
|
if (branch.type === 'boolean')
|
|
118
119
|
col += ` CHECK (${branch.alias} IN (0, 1))`;
|
|
119
120
|
lines.push(col + ',');
|
|
@@ -123,16 +124,16 @@ export function generateDraftTable(seed) {
|
|
|
123
124
|
return lines.join('\n');
|
|
124
125
|
}
|
|
125
126
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
127
|
+
* Generates `ALTER TABLE content_{slug} ADD COLUMN {alias} {type}`.
|
|
128
|
+
* New columns are always nullable (SQLite limit on ALTER TABLE).
|
|
128
129
|
*/
|
|
129
130
|
export function generateAddColumn(seed, branch) {
|
|
130
131
|
const { sqlType } = BRANCH_TYPE_SQL[branch.type];
|
|
131
132
|
return `ALTER TABLE ${tableName(seed)} ADD COLUMN ${branch.alias} ${sqlType};`;
|
|
132
133
|
}
|
|
133
134
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
135
|
+
* Generates B-tree indexes for status, created_at and every filterable Branch
|
|
136
|
+
* with an indexable type (text, number, date, boolean).
|
|
136
137
|
*/
|
|
137
138
|
export function generateIndexes(seed) {
|
|
138
139
|
const table = tableName(seed);
|
|
@@ -151,8 +152,8 @@ export function generateIndexes(seed) {
|
|
|
151
152
|
return indexes;
|
|
152
153
|
}
|
|
153
154
|
/**
|
|
154
|
-
*
|
|
155
|
-
*
|
|
155
|
+
* Generates the FTS5 virtual table for indexable text/richtext Branches.
|
|
156
|
+
* Returns null if the Seed has no branches with search enabled.
|
|
156
157
|
*/
|
|
157
158
|
export function generateFtsTable(seed) {
|
|
158
159
|
const rtBranches = indexableSearchBranches(seed);
|
|
@@ -169,9 +170,9 @@ export function generateFtsTable(seed) {
|
|
|
169
170
|
].join('\n');
|
|
170
171
|
}
|
|
171
172
|
/**
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
173
|
+
* Generates the 3 SQLite triggers (insert/update/delete) that keep the FTS
|
|
174
|
+
* automatically synchronized — eliminates the need for manual syncFts.
|
|
175
|
+
* Returns an empty array if the Seed has no indexable branches.
|
|
175
176
|
*/
|
|
176
177
|
export function generateFtsTriggers(seed) {
|
|
177
178
|
const rtBranches = indexableSearchBranches(seed);
|
|
@@ -207,9 +208,9 @@ export function generateFtsTriggers(seed) {
|
|
|
207
208
|
}
|
|
208
209
|
// ---- Query Builder ----
|
|
209
210
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
211
|
+
* Builds a parameterized SELECT on `content_{slug}`.
|
|
212
|
+
* Never uses json_extract — every column is a real column.
|
|
213
|
+
* Unknown columns in filters/orderBy are ignored (fail-closed).
|
|
213
214
|
*/
|
|
214
215
|
export function buildSelectQuery(seed, options = {}) {
|
|
215
216
|
const table = tableName(seed);
|
|
@@ -277,6 +278,32 @@ export function buildSelectQuery(seed, options = {}) {
|
|
|
277
278
|
}
|
|
278
279
|
return { sql, bindings };
|
|
279
280
|
}
|
|
281
|
+
function normalizeFilterValue(type, value) {
|
|
282
|
+
if (value === null || value === undefined)
|
|
283
|
+
return null;
|
|
284
|
+
if (type === 'date') {
|
|
285
|
+
if (typeof value === 'number')
|
|
286
|
+
return value;
|
|
287
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
288
|
+
const d = new Date(value);
|
|
289
|
+
return isNaN(d.getTime()) ? null : Math.floor(d.getTime() / 1000);
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
if (type === 'boolean') {
|
|
294
|
+
return value ? 1 : 0;
|
|
295
|
+
}
|
|
296
|
+
if (type === 'number') {
|
|
297
|
+
if (typeof value === 'number')
|
|
298
|
+
return value;
|
|
299
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
300
|
+
const n = Number(value);
|
|
301
|
+
return isNaN(n) ? null : n;
|
|
302
|
+
}
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
return value;
|
|
306
|
+
}
|
|
280
307
|
function buildFilterCondition(col, type, cond, bindings) {
|
|
281
308
|
const { op, value } = cond;
|
|
282
309
|
if (op === 'is_empty') {
|
|
@@ -285,30 +312,65 @@ function buildFilterCondition(col, type, cond, bindings) {
|
|
|
285
312
|
if (op === 'is_not_empty') {
|
|
286
313
|
return type === 'text' ? `(${col} IS NOT NULL AND ${col} != '')` : `${col} IS NOT NULL`;
|
|
287
314
|
}
|
|
288
|
-
if (
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
315
|
+
if (op === 'in' || op === 'not_in') {
|
|
316
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
317
|
+
return null;
|
|
318
|
+
const normalizedValues = value
|
|
319
|
+
.map((v) => normalizeFilterValue(type, v))
|
|
320
|
+
.filter((v) => v !== null);
|
|
321
|
+
if (normalizedValues.length === 0)
|
|
322
|
+
return null;
|
|
323
|
+
const placeholders = normalizedValues.map(() => '?').join(', ');
|
|
324
|
+
bindings.push(...normalizedValues);
|
|
325
|
+
return `${col} ${op === 'in' ? 'IN' : 'NOT IN'} (${placeholders})`;
|
|
293
326
|
}
|
|
294
|
-
if (op === '
|
|
295
|
-
if (type
|
|
296
|
-
|
|
297
|
-
|
|
327
|
+
if (op === 'has_tag' || op === 'has_any_tag' || op === 'has_all_tags') {
|
|
328
|
+
if (type !== 'tags' && type !== 'json')
|
|
329
|
+
return null;
|
|
330
|
+
const tags = (op === 'has_tag' ? [value] : Array.isArray(value) ? value : [value])
|
|
331
|
+
.map(t => String(t));
|
|
332
|
+
if (tags.length === 0)
|
|
333
|
+
return null;
|
|
334
|
+
if (op === 'has_tag' || op === 'has_any_tag') {
|
|
335
|
+
const placeholders = tags.map(() => '?').join(', ');
|
|
336
|
+
bindings.push(...tags);
|
|
337
|
+
return `EXISTS (SELECT 1 FROM json_each(${col}) WHERE value IN (${placeholders}))`;
|
|
298
338
|
}
|
|
299
|
-
|
|
300
|
-
|
|
339
|
+
// has_all_tags: each tag must exist
|
|
340
|
+
const clauses = tags.map(() => `EXISTS (SELECT 1 FROM json_each(${col}) WHERE value = ?)`);
|
|
341
|
+
bindings.push(...tags);
|
|
342
|
+
return `(${clauses.join(' AND ')})`;
|
|
343
|
+
}
|
|
344
|
+
const normalized = normalizeFilterValue(type, value);
|
|
345
|
+
if (normalized === null)
|
|
346
|
+
return null;
|
|
347
|
+
if (op === 'eq' || op === 'neq') {
|
|
348
|
+
const sqlOp = op === 'eq' ? '=' : '!=';
|
|
349
|
+
bindings.push(normalized);
|
|
350
|
+
return `${col} ${sqlOp} ?`;
|
|
351
|
+
}
|
|
352
|
+
if (op === 'contains' || op === 'not_contains' || op === 'starts_with' || op === 'ends_with') {
|
|
353
|
+
const sqlOp = op === 'not_contains' ? 'NOT LIKE' : 'LIKE';
|
|
354
|
+
let pattern = String(value);
|
|
355
|
+
if (op === 'contains' || op === 'not_contains')
|
|
356
|
+
pattern = `%${pattern}%`;
|
|
357
|
+
else if (op === 'starts_with')
|
|
358
|
+
pattern = `${pattern}%`;
|
|
359
|
+
else if (op === 'ends_with')
|
|
360
|
+
pattern = `%${pattern}`;
|
|
361
|
+
bindings.push(pattern);
|
|
362
|
+
return `${col} ${sqlOp} ?`;
|
|
301
363
|
}
|
|
302
364
|
const mathOps = { gt: '>', gte: '>=', lt: '<', lte: '<=' };
|
|
303
365
|
if (mathOps[op]) {
|
|
304
|
-
bindings.push(
|
|
366
|
+
bindings.push(normalized);
|
|
305
367
|
return `${col} ${mathOps[op]} ?`;
|
|
306
368
|
}
|
|
307
369
|
return null;
|
|
308
370
|
}
|
|
309
371
|
/**
|
|
310
|
-
*
|
|
311
|
-
*
|
|
372
|
+
* Returns the list of expected columns for a Seed's table.
|
|
373
|
+
* Used by `beech seed:load --diff` to compare current vs expected schema.
|
|
312
374
|
*/
|
|
313
375
|
export function getExpectedColumns(seed) {
|
|
314
376
|
return [
|
|
@@ -327,7 +389,7 @@ export function getExpectedColumns(seed) {
|
|
|
327
389
|
}
|
|
328
390
|
// ---- Serialization / Deserialization ----
|
|
329
391
|
/**
|
|
330
|
-
*
|
|
392
|
+
* Serializes a value for writing to the DB.
|
|
331
393
|
* boolean → 0/1 | date → Unix timestamp | json/asset-list → JSON string
|
|
332
394
|
*/
|
|
333
395
|
export function serializeForDb(branch, value) {
|
|
@@ -337,6 +399,7 @@ export function serializeForDb(branch, value) {
|
|
|
337
399
|
case 'boolean':
|
|
338
400
|
return value ? 1 : 0;
|
|
339
401
|
case 'json':
|
|
402
|
+
case 'tags':
|
|
340
403
|
case 'richtext':
|
|
341
404
|
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
342
405
|
case 'date': {
|
|
@@ -364,7 +427,7 @@ export function serializeForDb(branch, value) {
|
|
|
364
427
|
}
|
|
365
428
|
}
|
|
366
429
|
/**
|
|
367
|
-
*
|
|
430
|
+
* Deserializes a value read from the DB for the API response.
|
|
368
431
|
* 0/1 → boolean | Unix timestamp → ISO 8601 | JSON string → object
|
|
369
432
|
*/
|
|
370
433
|
export function deserializeFromDb(branch, value) {
|
|
@@ -374,6 +437,7 @@ export function deserializeFromDb(branch, value) {
|
|
|
374
437
|
case 'boolean':
|
|
375
438
|
return value === 1 || value === true;
|
|
376
439
|
case 'json':
|
|
440
|
+
case 'tags':
|
|
377
441
|
case 'richtext': {
|
|
378
442
|
if (typeof value === 'string') {
|
|
379
443
|
try {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identifier abstraction. Hides direct calls to {@link crypto.randomUUID}
|
|
3
|
+
* so callers can swap in deterministic generators during tests for stable
|
|
4
|
+
* snapshot assertions and predictable insert ordering.
|
|
5
|
+
*/
|
|
6
|
+
export interface IIdGenerator {
|
|
7
|
+
/**
|
|
8
|
+
* Generates a new universally unique identifier.
|
|
9
|
+
* Production implementation delegates to crypto.randomUUID().
|
|
10
|
+
* Test implementations return deterministic values for snapshot assertions.
|
|
11
|
+
*/
|
|
12
|
+
uuid(): string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Production singleton. Delegates to the host runtime CSPRNG-backed UUID
|
|
16
|
+
* generator. Stateless and therefore safe to share across requests.
|
|
17
|
+
*/
|
|
18
|
+
export declare const SystemIdGenerator: IIdGenerator;
|
|
19
|
+
//# sourceMappingURL=id-generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id-generator.d.ts","sourceRoot":"","sources":["../src/id-generator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,IAAI,IAAI,MAAM,CAAA;CACf;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,YAE/B,CAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface IdempotencyRecord {
|
|
2
|
+
key: string;
|
|
3
|
+
fingerprint: string;
|
|
4
|
+
responseStatus: number;
|
|
5
|
+
responseBody: string;
|
|
6
|
+
expiresAt: number;
|
|
7
|
+
}
|
|
8
|
+
export interface IdempotencyRepository {
|
|
9
|
+
lookup(key: string): Promise<IdempotencyRecord | null>;
|
|
10
|
+
store(record: IdempotencyRecord): Promise<void>;
|
|
11
|
+
cleanup(now: number): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=idempotency.repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.repository.d.ts","sourceRoot":"","sources":["../src/idempotency.repository.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAA;IACtD,KAAK,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @beechcms/core - Botanical Engine
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* In v0.4.0
|
|
6
|
-
*
|
|
4
|
+
* Shared package of the Beech CMS monorepo.
|
|
5
|
+
* In v0.4.0 the Botanical Engine is a SQL schema compiler: it reads TypeScript
|
|
6
|
+
* Seeds and generates deterministic DDL + parameterized queries.
|
|
7
7
|
*
|
|
8
8
|
* @module @beechcms/core
|
|
9
9
|
*/
|
|
@@ -15,5 +15,26 @@ export * from './validation.js';
|
|
|
15
15
|
export * from './richtext.js';
|
|
16
16
|
export * from './richtext-render.js';
|
|
17
17
|
export * from './slug-utils.js';
|
|
18
|
+
export * from './content.repository.js';
|
|
19
|
+
export * from './idempotency.repository.js';
|
|
20
|
+
export * from './media.repository.js';
|
|
21
|
+
export * from './storage.js';
|
|
18
22
|
export * from './policies.js';
|
|
23
|
+
export * from './auth/hash-provider.js';
|
|
24
|
+
export * from './auth/token-service.js';
|
|
25
|
+
export * from './auth/user.repository.js';
|
|
26
|
+
export * from './auth/session.repository.js';
|
|
27
|
+
export * from './auth/password-reset-token.repository.js';
|
|
28
|
+
export * from './rate-limit/rate-limiter.js';
|
|
29
|
+
export * from './observability/activity-logger.js';
|
|
30
|
+
export * from './observability/activity-log.repository.js';
|
|
31
|
+
export * from './observability/analytics.repository.js';
|
|
32
|
+
export * from './notifications/notification.repository.js';
|
|
33
|
+
export * from './notifications/notification-service.js';
|
|
34
|
+
export * from './widget/widget.repository.js';
|
|
35
|
+
export * from './search/search.repository.js';
|
|
36
|
+
export * from './content-scan.repository.js';
|
|
37
|
+
export * from './clock.js';
|
|
38
|
+
export * from './id-generator.js';
|
|
39
|
+
export * from './seed-registry.js';
|
|
19
40
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,cAAc,YAAY,CAAA;AAC1B,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,cAAc,YAAY,CAAA;AAC1B,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,yBAAyB,CAAA;AACvC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,yBAAyB,CAAA;AACvC,cAAc,yBAAyB,CAAA;AACvC,cAAc,2BAA2B,CAAA;AACzC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,2CAA2C,CAAA;AACzD,cAAc,8BAA8B,CAAA;AAC5C,cAAc,oCAAoC,CAAA;AAClD,cAAc,4CAA4C,CAAA;AAC1D,cAAc,yCAAyC,CAAA;AACvD,cAAc,4CAA4C,CAAA;AAC1D,cAAc,yCAAyC,CAAA;AACvD,cAAc,+BAA+B,CAAA;AAC7C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,8BAA8B,CAAA;AAC5C,cAAc,YAAY,CAAA;AAC1B,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @beechcms/core - Botanical Engine
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* In v0.4.0
|
|
6
|
-
*
|
|
4
|
+
* Shared package of the Beech CMS monorepo.
|
|
5
|
+
* In v0.4.0 the Botanical Engine is a SQL schema compiler: it reads TypeScript
|
|
6
|
+
* Seeds and generates deterministic DDL + parameterized queries.
|
|
7
7
|
*
|
|
8
8
|
* @module @beechcms/core
|
|
9
9
|
*/
|
|
@@ -15,4 +15,25 @@ export * from './validation.js';
|
|
|
15
15
|
export * from './richtext.js';
|
|
16
16
|
export * from './richtext-render.js';
|
|
17
17
|
export * from './slug-utils.js';
|
|
18
|
+
export * from './content.repository.js';
|
|
19
|
+
export * from './idempotency.repository.js';
|
|
20
|
+
export * from './media.repository.js';
|
|
21
|
+
export * from './storage.js';
|
|
18
22
|
export * from './policies.js';
|
|
23
|
+
export * from './auth/hash-provider.js';
|
|
24
|
+
export * from './auth/token-service.js';
|
|
25
|
+
export * from './auth/user.repository.js';
|
|
26
|
+
export * from './auth/session.repository.js';
|
|
27
|
+
export * from './auth/password-reset-token.repository.js';
|
|
28
|
+
export * from './rate-limit/rate-limiter.js';
|
|
29
|
+
export * from './observability/activity-logger.js';
|
|
30
|
+
export * from './observability/activity-log.repository.js';
|
|
31
|
+
export * from './observability/analytics.repository.js';
|
|
32
|
+
export * from './notifications/notification.repository.js';
|
|
33
|
+
export * from './notifications/notification-service.js';
|
|
34
|
+
export * from './widget/widget.repository.js';
|
|
35
|
+
export * from './search/search.repository.js';
|
|
36
|
+
export * from './content-scan.repository.js';
|
|
37
|
+
export * from './clock.js';
|
|
38
|
+
export * from './id-generator.js';
|
|
39
|
+
export * from './seed-registry.js';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MediaRepository - Interface for tracking media assets in the database.
|
|
3
|
+
*/
|
|
4
|
+
export interface MediaObject {
|
|
5
|
+
key: string;
|
|
6
|
+
filename: string;
|
|
7
|
+
mime_type: string;
|
|
8
|
+
size_bytes: number;
|
|
9
|
+
uploaded_by: string;
|
|
10
|
+
created_at: number;
|
|
11
|
+
}
|
|
12
|
+
export interface MediaRepository {
|
|
13
|
+
/**
|
|
14
|
+
* Tracks a new file upload in the database.
|
|
15
|
+
*/
|
|
16
|
+
trackUpload(media: Omit<MediaObject, 'created_at'>): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Retrieves tracking info for a file by its key.
|
|
19
|
+
*/
|
|
20
|
+
getByKey(key: string): Promise<MediaObject | null>;
|
|
21
|
+
/**
|
|
22
|
+
* Removes tracking info for a file.
|
|
23
|
+
*/
|
|
24
|
+
untrack(key: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Lists all tracked media with pagination.
|
|
27
|
+
*/
|
|
28
|
+
list(options: {
|
|
29
|
+
limit: number;
|
|
30
|
+
offset: number;
|
|
31
|
+
}): Promise<{
|
|
32
|
+
items: MediaObject[];
|
|
33
|
+
total: number;
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* Gets the total number of tracked media objects.
|
|
37
|
+
*/
|
|
38
|
+
count(): Promise<number>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* SystemStatsRepository - Interface for managing global system metrics.
|
|
42
|
+
*/
|
|
43
|
+
export interface SystemStatsRepository {
|
|
44
|
+
/**
|
|
45
|
+
* Increments the total storage counter.
|
|
46
|
+
*/
|
|
47
|
+
incrementStorage(bytes: number): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Decrements the total storage counter.
|
|
50
|
+
*/
|
|
51
|
+
decrementStorage(bytes: number): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Sets a specific value for a storage stat (e.g. after a full sync).
|
|
54
|
+
*/
|
|
55
|
+
setStorage(bytes: number): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Gets the current total storage usage in bytes.
|
|
58
|
+
*/
|
|
59
|
+
getStorageUsage(): Promise<number>;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=media.repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.repository.d.ts","sourceRoot":"","sources":["../src/media.repository.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnE;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAEnD;;OAEG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpC;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,WAAW,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEnG;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/C;;OAEG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/C;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC;;OAEG;IACH,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACpC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notification service contract.
|
|
3
|
+
*
|
|
4
|
+
* High-level port that callers use to emit a notification without knowing
|
|
5
|
+
* about the underlying repository, the database, or background scheduling.
|
|
6
|
+
* Mirrors the email module pattern: handlers depend on the service, the
|
|
7
|
+
* service depends on the repository, the repository depends on D1.
|
|
8
|
+
*
|
|
9
|
+
* @module @beechcms/core/notifications/notification-service
|
|
10
|
+
*/
|
|
11
|
+
import type { NotificationType } from './notification.repository.js';
|
|
12
|
+
export interface CreateNotificationInput {
|
|
13
|
+
title: string;
|
|
14
|
+
message: string;
|
|
15
|
+
type?: NotificationType;
|
|
16
|
+
}
|
|
17
|
+
export interface INotificationService {
|
|
18
|
+
/**
|
|
19
|
+
* Emit a notification.
|
|
20
|
+
*
|
|
21
|
+
* Implementations decide whether to fire-and-forget (production: schedules
|
|
22
|
+
* the underlying repository write through `executionCtx.waitUntil`) or to
|
|
23
|
+
* wait inline (tests). Like the activity logger, this method MUST NOT
|
|
24
|
+
* throw to the caller: a failed notification must never break the public
|
|
25
|
+
* request that triggered it.
|
|
26
|
+
*/
|
|
27
|
+
notify(input: CreateNotificationInput): Promise<void> | void;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=notification-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-service.d.ts","sourceRoot":"","sources":["../../src/notifications/notification-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAEpE,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,gBAAgB,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;OAQG;IACH,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAC7D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notification repository contract.
|
|
3
|
+
*
|
|
4
|
+
* Persists in-app notifications (admin inbox) and exposes the aggregate stats
|
|
5
|
+
* the GET /notifications handler needs to build a strong ETag without reading
|
|
6
|
+
* every row.
|
|
7
|
+
*
|
|
8
|
+
* @module @beechcms/core/notifications/notification.repository
|
|
9
|
+
*/
|
|
10
|
+
export type NotificationType = 'info' | 'success' | 'warning' | 'error';
|
|
11
|
+
export interface NotificationRecord {
|
|
12
|
+
id: string;
|
|
13
|
+
title: string;
|
|
14
|
+
message: string;
|
|
15
|
+
type: NotificationType;
|
|
16
|
+
isRead: boolean;
|
|
17
|
+
createdAt: number;
|
|
18
|
+
}
|
|
19
|
+
export interface NotificationStats {
|
|
20
|
+
totalCount: number;
|
|
21
|
+
latestCreatedAt: number;
|
|
22
|
+
readCount: number;
|
|
23
|
+
}
|
|
24
|
+
export interface INotificationRepository {
|
|
25
|
+
/**
|
|
26
|
+
* Return the most recent notifications, newest first.
|
|
27
|
+
*
|
|
28
|
+
* The `limit` parameter is required to prevent unbounded reads — the inbox
|
|
29
|
+
* could grow large over time and clients only ever render a window.
|
|
30
|
+
*/
|
|
31
|
+
list(limit: number): Promise<NotificationRecord[]>;
|
|
32
|
+
/**
|
|
33
|
+
* Return aggregate counters used to build the GET /notifications ETag.
|
|
34
|
+
*
|
|
35
|
+
* Computing `totalCount`, `latestCreatedAt` and `readCount` on the database
|
|
36
|
+
* side lets the handler skip serialising the full list when nothing has
|
|
37
|
+
* changed since the client's last poll, saving bandwidth on the dashboard.
|
|
38
|
+
*/
|
|
39
|
+
stats(): Promise<NotificationStats>;
|
|
40
|
+
/**
|
|
41
|
+
* Insert a new notification and return its generated id so callers (e.g.
|
|
42
|
+
* the public form submission flow) can correlate the notification with the
|
|
43
|
+
* triggering request.
|
|
44
|
+
*/
|
|
45
|
+
create(record: Omit<NotificationRecord, 'id' | 'createdAt' | 'isRead'>): Promise<string>;
|
|
46
|
+
markRead(notificationId: string): Promise<void>;
|
|
47
|
+
markUnread(notificationId: string): Promise<void>;
|
|
48
|
+
markAllRead(): Promise<void>;
|
|
49
|
+
delete(notificationId: string): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=notification.repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification.repository.d.ts","sourceRoot":"","sources":["../../src/notifications/notification.repository.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;AAEvE,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,gBAAgB,CAAA;IACtB,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAA;IAElD;;;;;;OAMG;IACH,KAAK,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAEnC;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,GAAG,WAAW,GAAG,QAAQ,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAExF,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjD,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9C"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notification repository contract.
|
|
3
|
+
*
|
|
4
|
+
* Persists in-app notifications (admin inbox) and exposes the aggregate stats
|
|
5
|
+
* the GET /notifications handler needs to build a strong ETag without reading
|
|
6
|
+
* every row.
|
|
7
|
+
*
|
|
8
|
+
* @module @beechcms/core/notifications/notification.repository
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activity Log read-side repository contract.
|
|
3
|
+
*
|
|
4
|
+
* Used by handlers that surface the audit trail back to the user (settings
|
|
5
|
+
* activity tab, recent activity feed on the dashboard). Kept separate from
|
|
6
|
+
* {@link IActivityLogger} because writers and readers have different
|
|
7
|
+
* lifecycles: writes are fire-and-forget background tasks; reads are blocking
|
|
8
|
+
* request paths that need filters and pagination.
|
|
9
|
+
*
|
|
10
|
+
* @module @beechcms/core/observability/activity-log.repository
|
|
11
|
+
*/
|
|
12
|
+
import type { ActivityAction, EntityType } from './activity-logger.js';
|
|
13
|
+
export interface ActivityLogRecord {
|
|
14
|
+
id: string;
|
|
15
|
+
userId: string;
|
|
16
|
+
userEmail: string;
|
|
17
|
+
userName: string | null;
|
|
18
|
+
action: ActivityAction;
|
|
19
|
+
entityType: EntityType;
|
|
20
|
+
entityId: string;
|
|
21
|
+
entitySlug: string | null;
|
|
22
|
+
details: Record<string, unknown> | null;
|
|
23
|
+
createdAt: number;
|
|
24
|
+
}
|
|
25
|
+
export interface ActivityLogListOptions {
|
|
26
|
+
/** Restrict the result to a specific user. Used by the per-user activity tab. */
|
|
27
|
+
userId?: string;
|
|
28
|
+
/** Restrict the result to a specific content seed. Used by per-seed feeds. */
|
|
29
|
+
entitySlug?: string;
|
|
30
|
+
/** Hard upper bound on the number of rows returned. */
|
|
31
|
+
limit: number;
|
|
32
|
+
}
|
|
33
|
+
export interface CountSinceOptions {
|
|
34
|
+
action: ActivityAction;
|
|
35
|
+
entityType: EntityType;
|
|
36
|
+
/** Lower bound (inclusive) for `createdAt` in seconds since epoch. */
|
|
37
|
+
sinceTimestamp: number;
|
|
38
|
+
}
|
|
39
|
+
export interface IActivityLogRepository {
|
|
40
|
+
/**
|
|
41
|
+
* Return the most recent activity entries matching the given filters.
|
|
42
|
+
*
|
|
43
|
+
* The list is always ordered by `createdAt` DESC so callers get newest-first
|
|
44
|
+
* data without paying extra sort costs in TypeScript. Used by the settings
|
|
45
|
+
* activity tab (per-user) and by the stats recent-activity feed (global).
|
|
46
|
+
*/
|
|
47
|
+
list(options: ActivityLogListOptions): Promise<ActivityLogRecord[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Count entries with the given `action` and `entityType` whose `createdAt`
|
|
50
|
+
* is greater-than-or-equal to `sinceTimestamp`.
|
|
51
|
+
*
|
|
52
|
+
* Used by the dashboard `/stats/total` widget to surface today/week/month
|
|
53
|
+
* create-event counts without hardcoding SQL in the handler. Each period
|
|
54
|
+
* is one call so the repository contract stays narrow and deterministic.
|
|
55
|
+
*/
|
|
56
|
+
countSince(options: CountSinceOptions): Promise<number>;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=activity-log.repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activity-log.repository.d.ts","sourceRoot":"","sources":["../../src/observability/activity-log.repository.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAEtE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,cAAc,CAAA;IACtB,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACvC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,cAAc,CAAA;IACtB,UAAU,EAAE,UAAU,CAAA;IACtB,sEAAsE;IACtE,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;;;OAMG;IACH,IAAI,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAA;IAEnE;;;;;;;OAOG;IACH,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACxD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|