@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.
Files changed (75) hide show
  1. package/dist/auth/hash-provider.d.ts +13 -0
  2. package/dist/auth/hash-provider.d.ts.map +1 -0
  3. package/dist/auth/hash-provider.js +1 -0
  4. package/dist/auth/password-reset-token.repository.d.ts +28 -0
  5. package/dist/auth/password-reset-token.repository.d.ts.map +1 -0
  6. package/dist/auth/password-reset-token.repository.js +1 -0
  7. package/dist/auth/session.repository.d.ts +47 -0
  8. package/dist/auth/session.repository.d.ts.map +1 -0
  9. package/dist/auth/session.repository.js +1 -0
  10. package/dist/auth/token-service.d.ts +23 -0
  11. package/dist/auth/token-service.d.ts.map +1 -0
  12. package/dist/auth/token-service.js +1 -0
  13. package/dist/auth/user.repository.d.ts +47 -0
  14. package/dist/auth/user.repository.d.ts.map +1 -0
  15. package/dist/auth/user.repository.js +1 -0
  16. package/dist/clock.d.ts +25 -0
  17. package/dist/clock.d.ts.map +1 -0
  18. package/dist/clock.js +9 -0
  19. package/dist/content-scan.repository.d.ts +9 -0
  20. package/dist/content-scan.repository.d.ts.map +1 -0
  21. package/dist/content-scan.repository.js +1 -0
  22. package/dist/content.repository.d.ts +95 -0
  23. package/dist/content.repository.d.ts.map +1 -0
  24. package/dist/content.repository.js +29 -0
  25. package/dist/engine.d.ts +24 -24
  26. package/dist/engine.d.ts.map +1 -1
  27. package/dist/engine.js +98 -34
  28. package/dist/id-generator.d.ts +19 -0
  29. package/dist/id-generator.d.ts.map +1 -0
  30. package/dist/id-generator.js +7 -0
  31. package/dist/idempotency.repository.d.ts +13 -0
  32. package/dist/idempotency.repository.d.ts.map +1 -0
  33. package/dist/idempotency.repository.js +1 -0
  34. package/dist/index.d.ts +24 -3
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +24 -3
  37. package/dist/media.repository.d.ts +61 -0
  38. package/dist/media.repository.d.ts.map +1 -0
  39. package/dist/media.repository.js +1 -0
  40. package/dist/notifications/notification-service.d.ts +29 -0
  41. package/dist/notifications/notification-service.d.ts.map +1 -0
  42. package/dist/notifications/notification-service.js +1 -0
  43. package/dist/notifications/notification.repository.d.ts +51 -0
  44. package/dist/notifications/notification.repository.d.ts.map +1 -0
  45. package/dist/notifications/notification.repository.js +10 -0
  46. package/dist/observability/activity-log.repository.d.ts +58 -0
  47. package/dist/observability/activity-log.repository.d.ts.map +1 -0
  48. package/dist/observability/activity-log.repository.js +1 -0
  49. package/dist/observability/activity-logger.d.ts +49 -0
  50. package/dist/observability/activity-logger.d.ts.map +1 -0
  51. package/dist/observability/activity-logger.js +12 -0
  52. package/dist/observability/analytics.repository.d.ts +37 -0
  53. package/dist/observability/analytics.repository.d.ts.map +1 -0
  54. package/dist/observability/analytics.repository.js +1 -0
  55. package/dist/rate-limit/rate-limiter.d.ts +13 -0
  56. package/dist/rate-limit/rate-limiter.d.ts.map +1 -0
  57. package/dist/rate-limit/rate-limiter.js +1 -0
  58. package/dist/search/search.repository.d.ts +45 -0
  59. package/dist/search/search.repository.d.ts.map +1 -0
  60. package/dist/search/search.repository.js +1 -0
  61. package/dist/seed-registry.d.ts +49 -0
  62. package/dist/seed-registry.d.ts.map +1 -0
  63. package/dist/seed-registry.js +29 -0
  64. package/dist/storage.d.ts +72 -0
  65. package/dist/storage.d.ts.map +1 -0
  66. package/dist/storage.js +1 -0
  67. package/dist/types.d.ts +3 -3
  68. package/dist/types.d.ts.map +1 -1
  69. package/dist/validation.d.ts +7 -7
  70. package/dist/validation.d.ts.map +1 -1
  71. package/dist/validation.js +15 -9
  72. package/dist/widget/widget.repository.d.ts +113 -0
  73. package/dist/widget/widget.repository.d.ts.map +1 -0
  74. package/dist/widget/widget.repository.js +1 -0
  75. 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
- * Genera `CREATE TABLE IF NOT EXISTS content_{slug}` con colonne di sistema
74
- * + una colonna per ogni Branch. Funzione pura: stesso Seed → stesso SQL.
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
- * Genera la tabella bozze `content_{slug}_drafts` per i Seed con `allowDrafts: true`.
100
- * Tutte le colonne branch sono nullable (le bozze sono parziali).
101
- * Ritorna null se il Seed non ha `allowDrafts: true`.
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, che passa il CHECK (solo FALSE lo fallisce)
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
- * Genera `ALTER TABLE content_{slug} ADD COLUMN {alias} {type}`.
127
- * Nuove colonne sono sempre nullable (limite SQLite su ALTER TABLE).
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
- * Genera indici B-tree per status, created_at e ogni Branch filtrabile
135
- * con tipo indicizzabile (text, number, date, boolean).
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
- * Genera la virtual table FTS5 per i Branch text/richtext indicizzabili.
155
- * Ritorna null se il Seed non ha branch con search abilitato.
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
- * Genera i 3 trigger SQLite (insert/update/delete) che mantengono la FTS
173
- * sincronizzata automaticamenteelimina la necessità di syncFts manuale.
174
- * Ritorna array vuoto se il Seed non ha branch indicizzabili.
173
+ * Generates the 3 SQLite triggers (insert/update/delete) that keep the FTS
174
+ * automatically synchronizedeliminates 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
- * Costruisce una SELECT parametrizzata su `content_{slug}`.
211
- * Non usa mai json_extract — ogni colonna è una colonna reale.
212
- * Colonne sconosciute nei filtri/orderBy vengono ignorate (fail-closed).
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 (value === null || value === undefined)
289
- return null;
290
- if (op === 'eq') {
291
- bindings.push(type === 'boolean' ? (value ? 1 : 0) : value);
292
- return `${col} = ?`;
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 === 'contains') {
295
- if (type === 'tags') {
296
- bindings.push(String(value));
297
- return `EXISTS (SELECT 1 FROM json_each(${col}) WHERE value = ?)`;
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
- bindings.push(`%${String(value)}%`);
300
- return `${col} LIKE ?`;
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(value);
366
+ bindings.push(normalized);
305
367
  return `${col} ${mathOps[op]} ?`;
306
368
  }
307
369
  return null;
308
370
  }
309
371
  /**
310
- * Ritorna la lista di colonne attese per la tabella di un Seed.
311
- * Usato da `beech seed:load --diff` per confrontare schema attuale vs atteso.
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
- * Serializza un valore per la scrittura nel DB.
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
- * Deserializza un valore letto dal DB per la risposta API.
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,7 @@
1
+ /**
2
+ * Production singleton. Delegates to the host runtime CSPRNG-backed UUID
3
+ * generator. Stateless and therefore safe to share across requests.
4
+ */
5
+ export const SystemIdGenerator = {
6
+ uuid: () => crypto.randomUUID(),
7
+ };
@@ -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
- * Pacchetto condiviso del monorepo Beech CMS.
5
- * In v0.4.0 il Botanical Engine è un compilatore di schema SQL: legge i Seed
6
- * TypeScript e genera DDL deterministico + query parametrizzate.
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
@@ -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
- * Pacchetto condiviso del monorepo Beech CMS.
5
- * In v0.4.0 il Botanical Engine è un compilatore di schema SQL: legge i Seed
6
- * TypeScript e genera DDL deterministico + query parametrizzate.
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 {};