@beechcms/core 0.6.0-preview.2 → 0.6.0-preview.4

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.
@@ -48,6 +48,20 @@ export declare class RelationTargetNotFoundError extends RepositoryError {
48
48
  value: string;
49
49
  });
50
50
  }
51
+ /**
52
+ * Thrown by lifecycle `before*` hooks to signal a business validation failure.
53
+ * Mapped to 422 Unprocessable Entity by the API problem-mapper.
54
+ */
55
+ export declare class HookValidationError extends RepositoryError {
56
+ readonly fields?: Array<{
57
+ field: string;
58
+ message: string;
59
+ }>;
60
+ constructor(message: string, fields?: Array<{
61
+ field: string;
62
+ message: string;
63
+ }>);
64
+ }
51
65
  export type BulkFieldUpdate = {
52
66
  kind: 'set';
53
67
  value: unknown;
@@ -61,6 +75,52 @@ export type BulkFieldUpdate = {
61
75
  kind: 'array_remove';
62
76
  value: string[];
63
77
  };
78
+ /**
79
+ * Options passed to write operations. Currently carries the acting user
80
+ * (extracted from the JWT) so lifecycle hooks can attribute changes.
81
+ */
82
+ export interface RepositoryOptions {
83
+ actor?: {
84
+ id: string;
85
+ role?: string;
86
+ email?: string;
87
+ };
88
+ }
89
+ /**
90
+ * Declarative multi-write operation translated into a single `db.batch` call.
91
+ * Used for coordinated writes across one or more seeds when D1's lack of
92
+ * interactive transactions makes a callback-based API impossible.
93
+ *
94
+ * NOTE: document-level lifecycle hooks do NOT run for operations inside a
95
+ * `runBatch` call — they would be non-atomic side-effects.
96
+ */
97
+ export type BatchWrite = {
98
+ kind: 'create';
99
+ seed: Seed;
100
+ id: string;
101
+ slug: string;
102
+ status: string;
103
+ data: Record<string, any>;
104
+ } | {
105
+ kind: 'update';
106
+ seed: Seed;
107
+ id: string;
108
+ data: Record<string, any>;
109
+ status?: string;
110
+ } | {
111
+ kind: 'mutateField';
112
+ seed: Seed;
113
+ id: string;
114
+ fieldName: string;
115
+ operation: {
116
+ type: 'increment' | 'decrement';
117
+ value: number;
118
+ };
119
+ options?: {
120
+ min?: number;
121
+ max?: number;
122
+ };
123
+ };
64
124
  /**
65
125
  * Interface defining the standard operations for content persistence.
66
126
  * This is platform-agnostic and should be implemented for specific databases (e.g., D1).
@@ -94,20 +154,41 @@ export interface ContentRepository {
94
154
  * Creates a new content entry in the live table.
95
155
  * Throws SlugConflictError if the slug is already taken.
96
156
  */
97
- create(seed: Seed, id: string, slug: string, status: string, data: Record<string, any>): Promise<void>;
157
+ create(seed: Seed, id: string, slug: string, status: string, data: Record<string, any>, options?: RepositoryOptions): Promise<void>;
98
158
  /**
99
159
  * Partially updates an existing entry in the live table.
100
160
  * Throws EntryNotFoundError if the ID does not exist.
101
161
  */
102
- update(seed: Seed, id: string, data: Record<string, any>, status?: string): Promise<void>;
162
+ update(seed: Seed, id: string, data: Record<string, any>, status?: string, options?: RepositoryOptions): Promise<void>;
103
163
  /**
104
164
  * Deletes an entry from the live table.
105
165
  * Returns the deleted row data (useful for media cleanup).
106
166
  * Throws EntryNotFoundError if the ID does not exist.
107
167
  */
108
- delete(seed: Seed, id: string): Promise<{
168
+ delete(seed: Seed, id: string, options?: RepositoryOptions): Promise<{
109
169
  row: Record<string, any>;
110
170
  }>;
171
+ /**
172
+ * Atomically increments/decrements a numeric field with optional min/max guards.
173
+ * Bypasses document-level lifecycle hooks — it's a single UPDATE statement,
174
+ * used to prevent race conditions on counters (stock, balances).
175
+ * Throws RepositoryError if `fieldName` is not a numeric branch of `seed`, or
176
+ * if the guard conditions fail / the row does not exist.
177
+ */
178
+ mutateField(seed: Seed, id: string, fieldName: string, operation: {
179
+ type: 'increment' | 'decrement';
180
+ value: number;
181
+ }, options?: {
182
+ min?: number;
183
+ max?: number;
184
+ }): Promise<{
185
+ newValue: number;
186
+ }>;
187
+ /**
188
+ * Executes a list of write operations atomically via a single `db.batch`.
189
+ * Document-level lifecycle hooks do NOT run for operations inside this call.
190
+ */
191
+ runBatch(operations: BatchWrite[]): Promise<void>;
111
192
  /**
112
193
  * Saves or updates a pending draft in the mirror table.
113
194
  */
@@ -1 +1 @@
1
- {"version":3,"file":"content.repository.d.ts","sourceRoot":"","sources":["../src/content.repository.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"content.repository.d.ts","sourceRoot":"","sources":["../src/content.repository.ts"],"names":[],"mappings":"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"}
@@ -45,3 +45,15 @@ export class RelationTargetNotFoundError extends RepositoryError {
45
45
  this.value = params.value;
46
46
  }
47
47
  }
48
+ /**
49
+ * Thrown by lifecycle `before*` hooks to signal a business validation failure.
50
+ * Mapped to 422 Unprocessable Entity by the API problem-mapper.
51
+ */
52
+ export class HookValidationError extends RepositoryError {
53
+ fields;
54
+ constructor(message, fields) {
55
+ super(message);
56
+ this.name = 'HookValidationError';
57
+ this.fields = fields;
58
+ }
59
+ }
@@ -0,0 +1,29 @@
1
+ import type { ContentRepository } from './content.repository.js';
2
+ import type { Seed } from './types.js';
3
+ export interface HookActor {
4
+ id: string;
5
+ role?: string;
6
+ email?: string;
7
+ }
8
+ export interface HookContext {
9
+ seed: Seed;
10
+ /** Canale lecito per side-effect sui contenuti dagli hook (rispetta la Botanical Engine). */
11
+ repository: ContentRepository;
12
+ /** Utente che esegue l'operazione, estratto dal JWT. Assente per operazioni di sistema/cron. */
13
+ actor?: HookActor;
14
+ /**
15
+ * Escape hatch per la connessione nativa (D1Database in prod, better-sqlite3 nei test).
16
+ * Tipizzato `unknown` per non accoppiare @beechcms/core a Cloudflare. Usare con cautela:
17
+ * scrivere qui bypassa la Botanical Engine.
18
+ */
19
+ db: unknown;
20
+ }
21
+ export interface BeechHooks {
22
+ beforeCreate?: (data: Record<string, any>, ctx: HookContext) => Promise<Record<string, any> | void> | Record<string, any> | void;
23
+ beforeUpdate?: (id: string, patches: Record<string, any>, ctx: HookContext) => Promise<Record<string, any> | void> | Record<string, any> | void;
24
+ beforeDelete?: (id: string, ctx: HookContext) => Promise<void> | void;
25
+ afterCreate?: (entry: Record<string, any>, ctx: HookContext) => Promise<void> | void;
26
+ afterUpdate?: (entry: Record<string, any>, ctx: HookContext) => Promise<void> | void;
27
+ afterDelete?: (id: string, ctx: HookContext) => Promise<void> | void;
28
+ }
29
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtC,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,IAAI,CAAA;IACV,6FAA6F;IAC7F,UAAU,EAAE,iBAAiB,CAAA;IAC7B,gGAAgG;IAChG,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB;;;;OAIG;IACH,EAAE,EAAE,OAAO,CAAA;CACZ;AAED,MAAM,WAAW,UAAU;IAGzB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAA;IAChI,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAA;IAC/I,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAGrE,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACpF,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACpF,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CACrE"}
package/dist/hooks.js ADDED
@@ -0,0 +1,3 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ export {};
package/dist/index.d.ts CHANGED
@@ -17,6 +17,7 @@ export * from './richtext.js';
17
17
  export * from './richtext-render.js';
18
18
  export * from './slug-utils.js';
19
19
  export * from './content.repository.js';
20
+ export * from './hooks.js';
20
21
  export * from './idempotency.repository.js';
21
22
  export * from './media.repository.js';
22
23
  export * from './storage.js';
@@ -44,6 +45,8 @@ export * from './automations.runner.interface.js';
44
45
  export * from './automations.repository.interface.js';
45
46
  export * from './scheduler.interface.js';
46
47
  export * from './scheduler.stub.js';
48
+ export * from './queue.interface.js';
49
+ export * from './queue.stub.js';
47
50
  export * from './webhook-validation.js';
48
51
  export * from './relations.js';
49
52
  export * from './site-settings.repository.js';
@@ -59,4 +62,6 @@ export * from './seed.repository.js';
59
62
  export * from './seed-validation.js';
60
63
  export * from './seed-ddl.js';
61
64
  export * from './schema-mutator.js';
65
+ export * from './seed-types-generator.js';
66
+ export * from './webhook-crypto.js';
62
67
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,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;AAClC,cAAc,wBAAwB,CAAA;AACtC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,mCAAmC,CAAA;AACjD,cAAc,uCAAuC,CAAA;AACrD,cAAc,0BAA0B,CAAA;AACxC,cAAc,qBAAqB,CAAA;AACnC,cAAc,yBAAyB,CAAA;AACvC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,+BAA+B,CAAA;AAC7C,cAAc,2BAA2B,CAAA;AACzC,cAAc,kBAAkB,CAAA;AAChC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,kCAAkC,CAAA;AAChD,cAAc,sBAAsB,CAAA;AACpC,cAAc,sBAAsB,CAAA;AACpC,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,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,YAAY,CAAA;AAC1B,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;AAClC,cAAc,wBAAwB,CAAA;AACtC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,mCAAmC,CAAA;AACjD,cAAc,uCAAuC,CAAA;AACrD,cAAc,0BAA0B,CAAA;AACxC,cAAc,qBAAqB,CAAA;AACnC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,yBAAyB,CAAA;AACvC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,+BAA+B,CAAA;AAC7C,cAAc,2BAA2B,CAAA;AACzC,cAAc,kBAAkB,CAAA;AAChC,cAAc,6BAA6B,CAAA;AAC3C,cAAc,yBAAyB,CAAA;AACvC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,kCAAkC,CAAA;AAChD,cAAc,sBAAsB,CAAA;AACpC,cAAc,sBAAsB,CAAA;AACpC,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,2BAA2B,CAAA;AACzC,cAAc,qBAAqB,CAAA"}
package/dist/index.js CHANGED
@@ -19,6 +19,7 @@ export * from './richtext.js';
19
19
  export * from './richtext-render.js';
20
20
  export * from './slug-utils.js';
21
21
  export * from './content.repository.js';
22
+ export * from './hooks.js';
22
23
  export * from './idempotency.repository.js';
23
24
  export * from './media.repository.js';
24
25
  export * from './storage.js';
@@ -46,6 +47,8 @@ export * from './automations.runner.interface.js';
46
47
  export * from './automations.repository.interface.js';
47
48
  export * from './scheduler.interface.js';
48
49
  export * from './scheduler.stub.js';
50
+ export * from './queue.interface.js';
51
+ export * from './queue.stub.js';
49
52
  export * from './webhook-validation.js';
50
53
  export * from './relations.js';
51
54
  export * from './site-settings.repository.js';
@@ -61,3 +64,5 @@ export * from './seed.repository.js';
61
64
  export * from './seed-validation.js';
62
65
  export * from './seed-ddl.js';
63
66
  export * from './schema-mutator.js';
67
+ export * from './seed-types-generator.js';
68
+ export * from './webhook-crypto.js';
@@ -0,0 +1,39 @@
1
+ import type { ContentRepository } from './content.repository.js';
2
+ import type { BeechBucket } from './storage.js';
3
+ import type { IClock } from './clock.js';
4
+ import type { IIdGenerator } from './id-generator.js';
5
+ /**
6
+ * The envelope put on the wire / queue. `name` selects the handler from the
7
+ * JobRegistry; `payload` is the developer-supplied, JSON-serializable body.
8
+ */
9
+ export interface QueueMessage<T = unknown> {
10
+ name: string;
11
+ payload: T;
12
+ }
13
+ /**
14
+ * Execution context handed to every job. Botanical Invariant: jobs receive the
15
+ * engine-mediated repository, NEVER a raw D1Database. `env` is a read-only,
16
+ * stringly-typed view of bindings/secrets for things like fetch targets — it
17
+ * deliberately does not expose `DB`.
18
+ */
19
+ export interface JobContext {
20
+ repository: ContentRepository;
21
+ bucket: BeechBucket;
22
+ clock: IClock;
23
+ idGenerator: IIdGenerator;
24
+ env: Record<string, string | undefined>;
25
+ }
26
+ /** A single background worker. Receives the decoded payload + context. */
27
+ export type JobHandler<T = unknown> = (payload: T, context: JobContext) => Promise<void>;
28
+ /** Name -> handler map, supplied via BeechConfig.jobs. */
29
+ export type JobRegistry = Record<string, JobHandler<any>>;
30
+ /**
31
+ * Producer port. `enqueue` schedules deferred work and returns once the message
32
+ * is accepted by the transport. Like INotificationService, implementations MUST
33
+ * decide their own fire-and-forget vs inline semantics and MUST NOT let a
34
+ * transport failure crash the request that called enqueue.
35
+ */
36
+ export interface IQueueService {
37
+ enqueue<T>(name: string, payload: T): Promise<void>;
38
+ }
39
+ //# sourceMappingURL=queue.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.interface.d.ts","sourceRoot":"","sources":["../src/queue.interface.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,CAAC,CAAA;CACX;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,iBAAiB,CAAA;IAC7B,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,YAAY,CAAA;IACzB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CACxC;AAED,0EAA0E;AAC1E,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,CACpC,OAAO,EAAE,CAAC,EACV,OAAO,EAAE,UAAU,KAChB,OAAO,CAAC,IAAI,CAAC,CAAA;AAElB,0DAA0D;AAC1D,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAA;AAEzD;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACpD"}
@@ -0,0 +1,3 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { IQueueService } from './queue.interface.js';
2
+ /** Safe no-op producer (e.g. unit tests that don't assert on enqueue). */
3
+ export declare class NoOpQueueService implements IQueueService {
4
+ enqueue<T>(_name: string, _payload: T): Promise<void>;
5
+ }
6
+ //# sourceMappingURL=queue.stub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.stub.d.ts","sourceRoot":"","sources":["../src/queue.stub.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEzD,0EAA0E;AAC1E,qBAAa,gBAAiB,YAAW,aAAa;IAC9C,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAG5D"}
@@ -0,0 +1,8 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ /** Safe no-op producer (e.g. unit tests that don't assert on enqueue). */
4
+ export class NoOpQueueService {
5
+ async enqueue(_name, _payload) {
6
+ // intentionally empty
7
+ }
8
+ }
@@ -0,0 +1,10 @@
1
+ import type { Seed, Branch } from './types.js';
2
+ /** slug/alias → PascalCase identifier. 'blog-posts' → 'BlogPosts'. */
3
+ export declare function pascalCase(input: string): string;
4
+ /** Maps a single Branch to its TypeScript type expression (no optional marker). */
5
+ export declare function tsTypeForBranch(branch: Branch): string;
6
+ /** Emits one exported interface for a single Seed. */
7
+ export declare function interfaceForSeed(seed: Seed): string;
8
+ /** Pure entry point. Deterministic: sorts seeds by slug for stable diffs. */
9
+ export declare function generateSeedTypes(seeds: Seed[]): string;
10
+ //# sourceMappingURL=seed-types-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-types-generator.d.ts","sourceRoot":"","sources":["../src/seed-types-generator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAe9C,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMhD;AAMD,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAoCtD;AAWD,sDAAsD;AACtD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAYnD;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CASvD"}
@@ -0,0 +1,86 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ const HEADER = '// Questo file è generato automaticamente da BeechCMS CLI. Non modificarlo direttamente.\n';
4
+ /** System columns emitted by generateCreateTable — always present, never optional. */
5
+ const SYSTEM_FIELDS = ` id: string\n` +
6
+ ` slug: string\n` +
7
+ ` status: 'draft' | 'review' | 'published' | 'archived'\n`;
8
+ const SYSTEM_TIMESTAMPS = ` created_at: number\n` +
9
+ ` updated_at: number\n`;
10
+ /** slug/alias → PascalCase identifier. 'blog-posts' → 'BlogPosts'. */
11
+ export function pascalCase(input) {
12
+ return input
13
+ .split(/[^A-Za-z0-9]+/)
14
+ .filter(Boolean)
15
+ .map(w => w.charAt(0).toUpperCase() + w.slice(1))
16
+ .join('');
17
+ }
18
+ function literalUnion(options) {
19
+ return options.map(o => `'${o.replace(/'/g, "\\'")}'`).join(' | ');
20
+ }
21
+ /** Maps a single Branch to its TypeScript type expression (no optional marker). */
22
+ export function tsTypeForBranch(branch) {
23
+ switch (branch.type) {
24
+ case 'number':
25
+ case 'date':
26
+ return 'number';
27
+ case 'boolean':
28
+ return 'boolean';
29
+ case 'json':
30
+ return 'unknown';
31
+ case 'text':
32
+ case 'richtext':
33
+ case 'file': {
34
+ const base = branch.type === 'text' && branch.options?.length
35
+ ? literalUnion(branch.options)
36
+ : 'string';
37
+ return branch.type === 'file' && branch.multiple ? `${base}[]` : base;
38
+ }
39
+ case 'tags': {
40
+ const base = branch.options?.length ? `(${literalUnion(branch.options)})` : 'string';
41
+ return `${base}[]`;
42
+ }
43
+ case 'relation':
44
+ return branch.multiple ? 'string[]' : 'string';
45
+ case 'repeater': {
46
+ const inner = (branch.fields ?? [])
47
+ .map(f => `${propName(f)}${optional(f)}: ${tsTypeForBranch(f)}`)
48
+ .join('; ');
49
+ return `Array<{ ${inner} }>`;
50
+ }
51
+ default: {
52
+ // Exhaustiveness guard — fails the build if BranchType gains a member.
53
+ const _never = branch.type;
54
+ return _never;
55
+ }
56
+ }
57
+ }
58
+ function optional(branch) {
59
+ return branch.requiredOnCreate ? '' : '?';
60
+ }
61
+ function propName(branch) {
62
+ // alias is a valid SQL column; quote only if not a plain identifier.
63
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(branch.alias) ? branch.alias : `'${branch.alias}'`;
64
+ }
65
+ /** Emits one exported interface for a single Seed. */
66
+ export function interfaceForSeed(seed) {
67
+ const name = pascalCase(seed.slug);
68
+ const props = seed.branches
69
+ .map(b => ` ${propName(b)}${optional(b)}: ${tsTypeForBranch(b)}`)
70
+ .join('\n');
71
+ return (`export interface ${name} {\n` +
72
+ SYSTEM_FIELDS +
73
+ (props ? props + '\n' : '') +
74
+ SYSTEM_TIMESTAMPS +
75
+ `}\n`);
76
+ }
77
+ /** Pure entry point. Deterministic: sorts seeds by slug for stable diffs. */
78
+ export function generateSeedTypes(seeds) {
79
+ const sorted = [...seeds].sort((a, b) => a.slug.localeCompare(b.slug));
80
+ const interfaces = sorted.map(interfaceForSeed).join('\n');
81
+ const registry = `export interface SeedRegistryTypes {\n` +
82
+ sorted.map(s => ` ${propName({ alias: s.slug })}: ${pascalCase(s.slug)}`).join('\n') +
83
+ (sorted.length ? '\n' : '') +
84
+ `}\n`;
85
+ return `${HEADER}\n${interfaces}\n${registry}`;
86
+ }
@@ -0,0 +1,8 @@
1
+ /** Signs a raw request body. Returns `sha256=<hex>`. */
2
+ export declare function signWebhookBody(body: string, secret: string): Promise<string>;
3
+ /**
4
+ * Verifies an inbound signature against the recomputed HMAC.
5
+ * Accepts the `sha256=` prefix on the provided signature (optional).
6
+ */
7
+ export declare function verifyWebhookSignature(body: string, signature: string | null | undefined, secret: string): Promise<boolean>;
8
+ //# sourceMappingURL=webhook-crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-crypto.d.ts","sourceRoot":"","sources":["../src/webhook-crypto.ts"],"names":[],"mappings":"AAqBA,wDAAwD;AACxD,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAInF;AAUD;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAKlB"}
@@ -0,0 +1,37 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ const SIG_PREFIX = 'sha256=';
4
+ function toHex(buf) {
5
+ return Array.from(new Uint8Array(buf))
6
+ .map((b) => b.toString(16).padStart(2, '0'))
7
+ .join('');
8
+ }
9
+ async function hmacKey(secret) {
10
+ return crypto.subtle.importKey('raw', new TextEncoder().encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
11
+ }
12
+ /** Signs a raw request body. Returns `sha256=<hex>`. */
13
+ export async function signWebhookBody(body, secret) {
14
+ const key = await hmacKey(secret);
15
+ const sig = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(body));
16
+ return SIG_PREFIX + toHex(sig);
17
+ }
18
+ /** Constant-time comparison of two equal-or-unequal-length hex strings. */
19
+ function timingSafeEqual(a, b) {
20
+ if (a.length !== b.length)
21
+ return false;
22
+ let mismatch = 0;
23
+ for (let i = 0; i < a.length; i++)
24
+ mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
25
+ return mismatch === 0;
26
+ }
27
+ /**
28
+ * Verifies an inbound signature against the recomputed HMAC.
29
+ * Accepts the `sha256=` prefix on the provided signature (optional).
30
+ */
31
+ export async function verifyWebhookSignature(body, signature, secret) {
32
+ if (!signature || !secret)
33
+ return false;
34
+ const expected = await signWebhookBody(body, secret);
35
+ const provided = signature.startsWith(SIG_PREFIX) ? signature : SIG_PREFIX + signature;
36
+ return timingSafeEqual(expected, provided);
37
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beechcms/core",
3
- "version": "0.6.0-preview.2",
3
+ "version": "0.6.0-preview.4",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -12,16 +12,12 @@
12
12
  ".": {
13
13
  "import": "./dist/index.js",
14
14
  "types": "./dist/index.d.ts"
15
+ },
16
+ "./webhook-crypto": {
17
+ "import": "./dist/webhook-crypto.js",
18
+ "types": "./dist/webhook-crypto.d.ts"
15
19
  }
16
20
  },
17
- "scripts": {
18
- "build": "tsc",
19
- "dev": "tsc -w --preserveWatchOutput",
20
- "lint": "eslint .",
21
- "type-check": "tsc --noEmit",
22
- "test": "vitest run",
23
- "test:coverage": "vitest run --coverage"
24
- },
25
21
  "devDependencies": {
26
22
  "@vitest/coverage-v8": "^4.1.0",
27
23
  "vitest": "^4.1.0"
@@ -41,5 +37,13 @@
41
37
  "katex": "^0.16.11",
42
38
  "zod": "^4.3.6"
43
39
  },
44
- "license": "MIT"
45
- }
40
+ "license": "MIT",
41
+ "scripts": {
42
+ "build": "tsc",
43
+ "dev": "tsc -w --preserveWatchOutput",
44
+ "lint": "eslint .",
45
+ "type-check": "tsc --noEmit",
46
+ "test": "vitest run",
47
+ "test:coverage": "vitest run --coverage"
48
+ }
49
+ }