@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
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activity Logger contract.
|
|
3
|
+
*
|
|
4
|
+
* Records side-effect events ("a user did X to entity Y") for the audit trail
|
|
5
|
+
* displayed in the dashboard's settings activity tab and the recent activity
|
|
6
|
+
* feed. The interface is intentionally framework-agnostic so it can be
|
|
7
|
+
* fulfilled in production by a D1-backed implementation, in tests by an
|
|
8
|
+
* in-memory implementation, and in the future by remote sinks.
|
|
9
|
+
*
|
|
10
|
+
* @module @beechcms/core/observability/activity-logger
|
|
11
|
+
*/
|
|
12
|
+
export type ActivityAction = 'create' | 'update' | 'delete' | 'upload';
|
|
13
|
+
export type EntityType = 'content' | 'media';
|
|
14
|
+
/**
|
|
15
|
+
* Identifies the human (or machine) actor that triggered the event.
|
|
16
|
+
*
|
|
17
|
+
* The actor MUST be assembled by the caller (typically a Hono handler that has
|
|
18
|
+
* already authenticated the request). The logger never reaches into a
|
|
19
|
+
* framework-specific context to discover who is acting — this avoids hidden
|
|
20
|
+
* coupling and lets the same logger run inside CLI scripts and background
|
|
21
|
+
* jobs.
|
|
22
|
+
*/
|
|
23
|
+
export interface ActivityActor {
|
|
24
|
+
id: string;
|
|
25
|
+
email: string;
|
|
26
|
+
name?: string | null;
|
|
27
|
+
}
|
|
28
|
+
export interface ActivityLogEntry {
|
|
29
|
+
action: ActivityAction;
|
|
30
|
+
entityType: EntityType;
|
|
31
|
+
entityId: string;
|
|
32
|
+
entitySlug?: string;
|
|
33
|
+
details?: Record<string, unknown>;
|
|
34
|
+
actor: ActivityActor;
|
|
35
|
+
}
|
|
36
|
+
export interface IActivityLogger {
|
|
37
|
+
/**
|
|
38
|
+
* Persist a single activity entry.
|
|
39
|
+
*
|
|
40
|
+
* Implementations decide whether to fire-and-forget (Cloudflare
|
|
41
|
+
* `executionCtx.waitUntil`) or to wait inline (test environments).
|
|
42
|
+
* In either case the call MUST NOT throw to the caller: logging is
|
|
43
|
+
* observability, never a hard dependency of the request being served.
|
|
44
|
+
* Internal errors must be swallowed and logged via `console.error` so the
|
|
45
|
+
* mainline response path is never disturbed by audit-trail failures.
|
|
46
|
+
*/
|
|
47
|
+
log(entry: ActivityLogEntry): Promise<void> | void;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=activity-logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activity-logger.d.ts","sourceRoot":"","sources":["../../src/observability/activity-logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;AACtE,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,OAAO,CAAA;AAE5C;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,cAAc,CAAA;IACtB,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,KAAK,EAAE,aAAa,CAAA;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B;;;;;;;;;OASG;IACH,GAAG,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CACnD"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activity Logger contract.
|
|
3
|
+
*
|
|
4
|
+
* Records side-effect events ("a user did X to entity Y") for the audit trail
|
|
5
|
+
* displayed in the dashboard's settings activity tab and the recent activity
|
|
6
|
+
* feed. The interface is intentionally framework-agnostic so it can be
|
|
7
|
+
* fulfilled in production by a D1-backed implementation, in tests by an
|
|
8
|
+
* in-memory implementation, and in the future by remote sinks.
|
|
9
|
+
*
|
|
10
|
+
* @module @beechcms/core/observability/activity-logger
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metric tracked by the analytics table. Today only request counts and
|
|
3
|
+
* unique-visitor counts are recorded; new metric names should be appended
|
|
4
|
+
* to this union and to the implementation's switch statement.
|
|
5
|
+
*/
|
|
6
|
+
export type AnalyticsMetric = 'requests' | 'visitors';
|
|
7
|
+
/**
|
|
8
|
+
* Read/write contract for the analytics counters that power the dashboard
|
|
9
|
+
* widgets (per-day request totals, sparklines, system health proxy).
|
|
10
|
+
*
|
|
11
|
+
* Implementations must use idempotent upserts so the recording middleware
|
|
12
|
+
* can be invoked once per request without producing duplicates when the
|
|
13
|
+
* same day/seed/metric tuple is touched repeatedly.
|
|
14
|
+
*/
|
|
15
|
+
export interface IAnalyticsRepository {
|
|
16
|
+
/**
|
|
17
|
+
* Upserts a request counter for the given seed at the current day bucket.
|
|
18
|
+
* The day bucket (Unix timestamp truncated to midnight UTC) is computed
|
|
19
|
+
* internally from the implementation's clock so callers never need to
|
|
20
|
+
* pass a timestamp. Implementations must use INSERT ... ON CONFLICT DO
|
|
21
|
+
* UPDATE to remain idempotent under concurrent calls within the same day.
|
|
22
|
+
*/
|
|
23
|
+
recordRequest(seedSlug: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Returns the total count for the given metric since sinceTimestamp.
|
|
26
|
+
* Used by the stats handler for total request counts and visitor counts.
|
|
27
|
+
* The aggregation sums the stored counter values, not row counts.
|
|
28
|
+
*/
|
|
29
|
+
sumByMetric(metric: AnalyticsMetric, seedSlug: string, sinceTimestamp: number): Promise<number>;
|
|
30
|
+
/**
|
|
31
|
+
* Returns a map of date strings (YYYY-MM-DD) to request counts since
|
|
32
|
+
* sinceTimestamp, suitable for chart rendering without further
|
|
33
|
+
* transformation by the caller.
|
|
34
|
+
*/
|
|
35
|
+
groupByMetric(seedSlug: string, sinceTimestamp: number): Promise<Record<string, number>>;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=analytics.repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.repository.d.ts","sourceRoot":"","sources":["../../src/observability/analytics.repository.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,UAAU,CAAA;AAErD;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE9C;;;;OAIG;IACH,WAAW,CACT,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,MAAM,CAAC,CAAA;IAElB;;;;OAIG;IACH,aAAa,CACX,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CACnC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface RateLimitResult {
|
|
2
|
+
isAllowed: boolean;
|
|
3
|
+
retryAfterSeconds?: number;
|
|
4
|
+
}
|
|
5
|
+
export interface IRateLimiter {
|
|
6
|
+
/**
|
|
7
|
+
* Checks whether the given key is within the rate limit.
|
|
8
|
+
* The key should combine the client IP address and an endpoint-specific prefix
|
|
9
|
+
* to prevent one endpoint's limit from being shared with another.
|
|
10
|
+
*/
|
|
11
|
+
checkLimit(key: string): Promise<RateLimitResult>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/rate-limit/rate-limiter.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,OAAO,CAAA;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;CAClD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Seed } from '../types.js';
|
|
2
|
+
export interface SearchQueryOptions {
|
|
3
|
+
queryText: string;
|
|
4
|
+
schemaSlug: string | null;
|
|
5
|
+
statusFilter: string | null;
|
|
6
|
+
limit: number;
|
|
7
|
+
cursor: string | null;
|
|
8
|
+
}
|
|
9
|
+
export interface SearchResultRow {
|
|
10
|
+
entryId: string;
|
|
11
|
+
schemaSlug: string;
|
|
12
|
+
slug: string | null;
|
|
13
|
+
status: string;
|
|
14
|
+
title: string | null;
|
|
15
|
+
excerpt: string;
|
|
16
|
+
rank: number;
|
|
17
|
+
}
|
|
18
|
+
export interface SearchCountResult {
|
|
19
|
+
total: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Read-only contract for the global FTS5 search route.
|
|
23
|
+
*
|
|
24
|
+
* Implementations encapsulate the UNION ALL query that fans out across all
|
|
25
|
+
* `fts_<seed>` virtual tables. The route handler stays free of D1 and only
|
|
26
|
+
* shapes the final response.
|
|
27
|
+
*/
|
|
28
|
+
export interface ISearchRepository {
|
|
29
|
+
/**
|
|
30
|
+
* Executes a UNION ALL full-text search across all FTS-enabled seed tables.
|
|
31
|
+
* Returns at most options.limit + 1 rows so the caller can detect hasMore
|
|
32
|
+
* without a separate count query for the cursor case.
|
|
33
|
+
* Implementations must propagate the EMPTY_QUERY error thrown by
|
|
34
|
+
* buildFtsQuery so the route handler can return an empty result set rather
|
|
35
|
+
* than a 500.
|
|
36
|
+
*/
|
|
37
|
+
search(options: SearchQueryOptions, seeds: Seed[]): Promise<SearchResultRow[]>;
|
|
38
|
+
/**
|
|
39
|
+
* Runs the count variant of the FTS query to support the `total` field in
|
|
40
|
+
* the search response. Called in parallel with search() by the route handler
|
|
41
|
+
* with the same filter inputs but without limit/cursor.
|
|
42
|
+
*/
|
|
43
|
+
count(options: Omit<SearchQueryOptions, 'limit' | 'cursor'>, seeds: Seed[]): Promise<SearchCountResult>;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=search.repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.repository.d.ts","sourceRoot":"","sources":["../../src/search/search.repository.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAEvC,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;;;OAOG;IACH,MAAM,CAAC,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAAA;IAE9E;;;;OAIG;IACH,KAAK,CACH,OAAO,EAAE,IAAI,CAAC,kBAAkB,EAAE,OAAO,GAAG,QAAQ,CAAC,EACrD,KAAK,EAAE,IAAI,EAAE,GACZ,OAAO,CAAC,iBAAiB,CAAC,CAAA;CAC9B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Seed } from './types.js';
|
|
2
|
+
export interface ISeedRegistry {
|
|
3
|
+
/**
|
|
4
|
+
* Returns all seeds as a flat array, preserving insertion order.
|
|
5
|
+
* Decouples callers from the internal storage shape so the registry
|
|
6
|
+
* implementation can change without touching every route handler.
|
|
7
|
+
*/
|
|
8
|
+
all(): Seed[];
|
|
9
|
+
/**
|
|
10
|
+
* Returns the seed with the given slug, or null if not found.
|
|
11
|
+
* Provides a single lookup point that can be overridden in tests
|
|
12
|
+
* without rebuilding the full registry object.
|
|
13
|
+
*/
|
|
14
|
+
get(slug: string): Seed | null;
|
|
15
|
+
/**
|
|
16
|
+
* Returns seeds that are visible in the dashboard sidebar.
|
|
17
|
+
* A seed is visible when dashboard.hidden is not explicitly true.
|
|
18
|
+
* Eliminates the seeds.filter(s => !s.dashboard?.hidden) pattern
|
|
19
|
+
* that would otherwise be duplicated across route handlers.
|
|
20
|
+
*/
|
|
21
|
+
visibleInDashboard(): Seed[];
|
|
22
|
+
/**
|
|
23
|
+
* Returns seeds that have allowPublicRead enabled.
|
|
24
|
+
* Eliminates the seeds.filter(s => s.allowPublicRead) pattern.
|
|
25
|
+
*/
|
|
26
|
+
publicReadable(): Seed[];
|
|
27
|
+
/**
|
|
28
|
+
* Returns seeds that have the draft workflow enabled.
|
|
29
|
+
* Eliminates the seeds.filter(s => s.allowDrafts) pattern.
|
|
30
|
+
*/
|
|
31
|
+
draftEnabled(): Seed[];
|
|
32
|
+
}
|
|
33
|
+
export declare class SeedRegistry implements ISeedRegistry {
|
|
34
|
+
private readonly seedMap;
|
|
35
|
+
private readonly orderedSeeds;
|
|
36
|
+
constructor(seeds: Seed[]);
|
|
37
|
+
all(): Seed[];
|
|
38
|
+
get(slug: string): Seed | null;
|
|
39
|
+
visibleInDashboard(): Seed[];
|
|
40
|
+
publicReadable(): Seed[];
|
|
41
|
+
draftEnabled(): Seed[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Named subclass of SeedRegistry for use in test suites.
|
|
45
|
+
* Gives tests a semantic name without requiring any dependency on factory.ts.
|
|
46
|
+
*/
|
|
47
|
+
export declare class InMemorySeedRegistry extends SeedRegistry {
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=seed-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seed-registry.d.ts","sourceRoot":"","sources":["../src/seed-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtC,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,GAAG,IAAI,IAAI,EAAE,CAAA;IAEb;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IAE9B;;;;;OAKG;IACH,kBAAkB,IAAI,IAAI,EAAE,CAAA;IAE5B;;;OAGG;IACH,cAAc,IAAI,IAAI,EAAE,CAAA;IAExB;;;OAGG;IACH,YAAY,IAAI,IAAI,EAAE,CAAA;CACvB;AAED,qBAAa,YAAa,YAAW,aAAa;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;gBAEzB,KAAK,EAAE,IAAI,EAAE;IAKzB,GAAG,IAAI,IAAI,EAAE;IAIb,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAI9B,kBAAkB,IAAI,IAAI,EAAE;IAI5B,cAAc,IAAI,IAAI,EAAE;IAIxB,YAAY,IAAI,IAAI,EAAE;CAGvB;AAED;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,YAAY;CAAG"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export class SeedRegistry {
|
|
2
|
+
seedMap;
|
|
3
|
+
orderedSeeds;
|
|
4
|
+
constructor(seeds) {
|
|
5
|
+
this.orderedSeeds = seeds;
|
|
6
|
+
this.seedMap = new Map(seeds.map(seed => [seed.slug, seed]));
|
|
7
|
+
}
|
|
8
|
+
all() {
|
|
9
|
+
return this.orderedSeeds;
|
|
10
|
+
}
|
|
11
|
+
get(slug) {
|
|
12
|
+
return this.seedMap.get(slug) ?? null;
|
|
13
|
+
}
|
|
14
|
+
visibleInDashboard() {
|
|
15
|
+
return this.orderedSeeds.filter(seed => seed.dashboard?.hidden !== true);
|
|
16
|
+
}
|
|
17
|
+
publicReadable() {
|
|
18
|
+
return this.orderedSeeds.filter(seed => seed.allowPublicRead === true);
|
|
19
|
+
}
|
|
20
|
+
draftEnabled() {
|
|
21
|
+
return this.orderedSeeds.filter(seed => seed.allowDrafts === true);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Named subclass of SeedRegistry for use in test suites.
|
|
26
|
+
* Gives tests a semantic name without requiring any dependency on factory.ts.
|
|
27
|
+
*/
|
|
28
|
+
export class InMemorySeedRegistry extends SeedRegistry {
|
|
29
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BeechBucket - S3-like interface for object storage in BeechCMS.
|
|
3
|
+
*
|
|
4
|
+
* This interface abstracts the underlying storage provider (R2, S3, or Local)
|
|
5
|
+
* allowing the application to scale without being tied to a specific vendor.
|
|
6
|
+
*/
|
|
7
|
+
export interface PutBucketOptions {
|
|
8
|
+
contentType?: string;
|
|
9
|
+
metadata?: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
export interface GetBucketResult {
|
|
12
|
+
body: ReadableStream | ArrayBuffer;
|
|
13
|
+
contentType?: string;
|
|
14
|
+
size: number;
|
|
15
|
+
metadata?: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
export interface BeechBucket {
|
|
18
|
+
/**
|
|
19
|
+
* Uploads an object to the bucket.
|
|
20
|
+
* @param key The unique key (path) for the object.
|
|
21
|
+
* @param body The data to store.
|
|
22
|
+
* @param options Optional metadata like content type.
|
|
23
|
+
*/
|
|
24
|
+
put(key: string, body: ArrayBuffer | Uint8Array | ReadableStream, options?: PutBucketOptions): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Retrieves an object from the bucket.
|
|
27
|
+
* @param key The unique key for the object.
|
|
28
|
+
* @returns The object data and metadata, or null if not found.
|
|
29
|
+
*/
|
|
30
|
+
get(key: string): Promise<GetBucketResult | null>;
|
|
31
|
+
/**
|
|
32
|
+
* Deletes an object from the bucket.
|
|
33
|
+
* @param key The unique key for the object.
|
|
34
|
+
*/
|
|
35
|
+
delete(key: string): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Retrieves metadata for an object without downloading its content.
|
|
38
|
+
* @param key The unique key for the object.
|
|
39
|
+
*/
|
|
40
|
+
head(key: string): Promise<{
|
|
41
|
+
size: number;
|
|
42
|
+
contentType?: string;
|
|
43
|
+
metadata?: Record<string, string>;
|
|
44
|
+
} | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Returns a publicly accessible URL for the given key.
|
|
47
|
+
* The implementation decides if this is a direct vendor URL (CDN)
|
|
48
|
+
* or a proxied URL through the Beech API.
|
|
49
|
+
* @param key The unique key for the object.
|
|
50
|
+
*/
|
|
51
|
+
getUrl(key: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* Calculates the total size of all objects in the bucket.
|
|
54
|
+
* Warning: This may be an expensive operation on some providers.
|
|
55
|
+
*/
|
|
56
|
+
getTotalSize(): Promise<number>;
|
|
57
|
+
/**
|
|
58
|
+
* Lists objects in the bucket.
|
|
59
|
+
*/
|
|
60
|
+
list(options?: {
|
|
61
|
+
prefix?: string;
|
|
62
|
+
limit?: number;
|
|
63
|
+
cursor?: string;
|
|
64
|
+
}): Promise<{
|
|
65
|
+
objects: Array<{
|
|
66
|
+
key: string;
|
|
67
|
+
size: number;
|
|
68
|
+
}>;
|
|
69
|
+
cursor?: string;
|
|
70
|
+
}>;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,cAAc,GAAG,WAAW,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,GAAG,cAAc,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7G;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IAElD;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC;;;OAGG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAE7G;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAE5B;;;OAGG;IACH,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAEhC;;OAEG;IACH,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnJ"}
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type BranchType = 'text' | 'number' | 'boolean' | 'json' | 'date' | 'richtext' | 'file';
|
|
1
|
+
export type BranchType = 'text' | 'number' | 'boolean' | 'json' | 'date' | 'richtext' | 'file' | 'tags';
|
|
2
2
|
/** Branch: definizione di una proprietà. alias = nome colonna SQL. */
|
|
3
3
|
export interface Branch {
|
|
4
4
|
/** Alias human-readable, usato nel payload API e come nome colonna SQL nella tabella dedicata */
|
|
@@ -96,8 +96,8 @@ export interface Seed {
|
|
|
96
96
|
/** Optional dashboard-specific UI config. Ignored by the Botanical Engine. */
|
|
97
97
|
dashboard?: DashboardSeedConfig;
|
|
98
98
|
}
|
|
99
|
-
export type FilterOperator = 'eq' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'is_empty' | 'is_not_empty';
|
|
100
|
-
export type FilterType = 'text' | 'number' | 'date' | 'boolean' | 'tags' | 'select' | 'system';
|
|
99
|
+
export type FilterOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'not_contains' | 'starts_with' | 'ends_with' | 'is_empty' | 'is_not_empty' | 'in' | 'not_in' | 'has_tag' | 'has_any_tag' | 'has_all_tags';
|
|
100
|
+
export type FilterType = 'text' | 'number' | 'date' | 'boolean' | 'tags' | 'select' | 'system' | 'json';
|
|
101
101
|
export interface FilterCondition {
|
|
102
102
|
op: FilterOperator;
|
|
103
103
|
value: string | number | boolean | null;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/validation.d.ts
CHANGED
|
@@ -21,18 +21,18 @@ export interface ValidateSeedPayloadResult {
|
|
|
21
21
|
hasAnyValidField: boolean;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* Common foundation for schema-driven validation and sanitization of payloads.
|
|
25
|
+
* Used by the Public API and reusable in the Botanical Engine.
|
|
26
26
|
*
|
|
27
|
-
*
|
|
27
|
+
* Mandatory execution order at the time of writing:
|
|
28
28
|
* validate raw → hash (privacy policy) → store
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* Hashing occurs AFTER validation, not before.
|
|
30
|
+
* This ensures that the validated value is the original raw value,
|
|
31
|
+
* not the digest — avoiding false failures on fields with formats (e.g., email).
|
|
32
32
|
*/
|
|
33
33
|
export declare function validateAndSanitizeSeedPayload(seed: Seed, payload: Record<string, unknown>, options?: ValidateSeedPayloadOptions): ValidateSeedPayloadResult;
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
35
|
+
* Validates content status supported by the CMS.
|
|
36
36
|
*/
|
|
37
37
|
export declare function isValidContentStatus(value: unknown): value is 'draft' | 'review' | 'published';
|
|
38
38
|
/**
|
package/dist/validation.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAU,IAAI,EAAE,MAAM,YAAY,CAAA;AAG9C,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC/B,2BAA2B,CAAC,EAAE,OAAO,CAAA;IACrC,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,gBAAgB,EAAE,OAAO,CAAA;CAC1B;
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAU,IAAI,EAAE,MAAM,YAAY,CAAA;AAG9C,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC/B,2BAA2B,CAAC,EAAE,OAAO,CAAA;IACrC,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AA6fD;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,OAAO,GAAE,0BAA+B,GACvC,yBAAyB,CA6G3B;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,GAAG,QAAQ,GAAG,WAAW,CAE9F;AAED;;;;;GAKG"}
|
package/dist/validation.js
CHANGED
|
@@ -278,7 +278,8 @@ function buildBranchSchema(branch, options) {
|
|
|
278
278
|
.refine((value) => isIsoDateString(value), { message: 'Expected date(ISO)' });
|
|
279
279
|
return nullable ? z.union([schema, nullable]) : schema;
|
|
280
280
|
}
|
|
281
|
-
case 'json':
|
|
281
|
+
case 'json':
|
|
282
|
+
case 'tags': {
|
|
282
283
|
const schema = jsonObjectOrArraySchema;
|
|
283
284
|
return nullable ? z.union([schema, nullable]) : schema;
|
|
284
285
|
}
|
|
@@ -316,6 +317,8 @@ function buildBranchSchema(branch, options) {
|
|
|
316
317
|
.pipe(z.url());
|
|
317
318
|
return nullable ? z.union([schema, nullable]) : schema;
|
|
318
319
|
}
|
|
320
|
+
default:
|
|
321
|
+
throw new Error(`Unhandled branch type: ${branch.type}`);
|
|
319
322
|
}
|
|
320
323
|
}
|
|
321
324
|
const compiledSchemaCache = new Map();
|
|
@@ -422,7 +425,8 @@ function validateBranchValue(branch, alias, rawValue, options) {
|
|
|
422
425
|
}
|
|
423
426
|
return { ok: true, value };
|
|
424
427
|
}
|
|
425
|
-
case 'json':
|
|
428
|
+
case 'json':
|
|
429
|
+
case 'tags': {
|
|
426
430
|
const isValidJsonLike = (typeof rawValue === 'object' && rawValue !== null) || Array.isArray(rawValue);
|
|
427
431
|
if (!isValidJsonLike || typeof rawValue === 'string') {
|
|
428
432
|
return { ok: false, detail: makeDetail(alias, 'object|array', rawValue) };
|
|
@@ -443,17 +447,19 @@ function validateBranchValue(branch, alias, rawValue, options) {
|
|
|
443
447
|
}
|
|
444
448
|
return { ok: true, value: singleUrl };
|
|
445
449
|
}
|
|
450
|
+
default:
|
|
451
|
+
throw new Error(`Unhandled branch type: ${branch.type}`);
|
|
446
452
|
}
|
|
447
453
|
}
|
|
448
454
|
/**
|
|
449
|
-
*
|
|
450
|
-
*
|
|
455
|
+
* Common foundation for schema-driven validation and sanitization of payloads.
|
|
456
|
+
* Used by the Public API and reusable in the Botanical Engine.
|
|
451
457
|
*
|
|
452
|
-
*
|
|
458
|
+
* Mandatory execution order at the time of writing:
|
|
453
459
|
* validate raw → hash (privacy policy) → store
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
460
|
+
* Hashing occurs AFTER validation, not before.
|
|
461
|
+
* This ensures that the validated value is the original raw value,
|
|
462
|
+
* not the digest — avoiding false failures on fields with formats (e.g., email).
|
|
457
463
|
*/
|
|
458
464
|
export function validateAndSanitizeSeedPayload(seed, payload, options = {}) {
|
|
459
465
|
const normalizedOptions = {
|
|
@@ -558,7 +564,7 @@ export function validateAndSanitizeSeedPayload(seed, payload, options = {}) {
|
|
|
558
564
|
};
|
|
559
565
|
}
|
|
560
566
|
/**
|
|
561
|
-
*
|
|
567
|
+
* Validates content status supported by the CMS.
|
|
562
568
|
*/
|
|
563
569
|
export function isValidContentStatus(value) {
|
|
564
570
|
return statusSchema.safeParse(value).success;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { Seed } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Discriminated union describing the aggregate to compute over a content table.
|
|
4
|
+
*
|
|
5
|
+
* The widget routes accept this shape from the dashboard. Implementations are
|
|
6
|
+
* responsible for translating each variant into a safe SQL expression and must
|
|
7
|
+
* never interpolate the column or value fields without prior validation.
|
|
8
|
+
*/
|
|
9
|
+
export type AggregateFormula = {
|
|
10
|
+
op: 'count';
|
|
11
|
+
} | {
|
|
12
|
+
op: 'sum';
|
|
13
|
+
column: string;
|
|
14
|
+
} | {
|
|
15
|
+
op: 'avg';
|
|
16
|
+
column: string;
|
|
17
|
+
} | {
|
|
18
|
+
op: 'min';
|
|
19
|
+
column: string;
|
|
20
|
+
} | {
|
|
21
|
+
op: 'max';
|
|
22
|
+
column: string;
|
|
23
|
+
} | {
|
|
24
|
+
op: 'countWhere';
|
|
25
|
+
column: string;
|
|
26
|
+
value: unknown;
|
|
27
|
+
} | {
|
|
28
|
+
op: 'percentageOf';
|
|
29
|
+
numeratorColumn: string;
|
|
30
|
+
denominatorColumn: string;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Time range applied as a WHERE filter for widget queries.
|
|
34
|
+
*
|
|
35
|
+
* "all" means no temporal restriction. The other values bracket the most recent
|
|
36
|
+
* 7 days, 1 month, and 1 year respectively, anchored on `created_at`.
|
|
37
|
+
*/
|
|
38
|
+
export type TimeWindow = 'week' | 'month' | 'year' | 'all';
|
|
39
|
+
export interface LeaderboardEntry {
|
|
40
|
+
id: string;
|
|
41
|
+
label: string;
|
|
42
|
+
score: number | string;
|
|
43
|
+
}
|
|
44
|
+
export interface LeaderboardOptions {
|
|
45
|
+
scoreColumn: string;
|
|
46
|
+
limit: number;
|
|
47
|
+
orderDirection: 'ASC' | 'DESC';
|
|
48
|
+
}
|
|
49
|
+
export interface TimeseriesPoint {
|
|
50
|
+
label: string;
|
|
51
|
+
value: number;
|
|
52
|
+
}
|
|
53
|
+
export interface WidgetListFilter {
|
|
54
|
+
column: string;
|
|
55
|
+
op: string;
|
|
56
|
+
value: unknown;
|
|
57
|
+
}
|
|
58
|
+
export interface WidgetListOptions {
|
|
59
|
+
limit: number;
|
|
60
|
+
offset: number;
|
|
61
|
+
search?: string;
|
|
62
|
+
filters?: WidgetListFilter[];
|
|
63
|
+
orderByColumn?: string;
|
|
64
|
+
orderDirection?: 'ASC' | 'DESC';
|
|
65
|
+
}
|
|
66
|
+
export interface WidgetListResult {
|
|
67
|
+
entries: Array<Record<string, unknown>>;
|
|
68
|
+
totalCount: number;
|
|
69
|
+
}
|
|
70
|
+
export interface GrowthResult {
|
|
71
|
+
currentValue: number;
|
|
72
|
+
previousValue: number;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Read-only data access contract for widget routes.
|
|
76
|
+
*
|
|
77
|
+
* Implementations must validate every column alias derived from user input
|
|
78
|
+
* against the seed before composing SQL, and must bind every user-supplied
|
|
79
|
+
* value via parameterised statements. SQL keywords (ORDER direction, aggregate
|
|
80
|
+
* function names) must be selected via hardcoded branches, never interpolated.
|
|
81
|
+
*/
|
|
82
|
+
export interface IWidgetRepository {
|
|
83
|
+
/**
|
|
84
|
+
* Returns the formula result for the given time window. Always returns a
|
|
85
|
+
* number; implementations must return 0 when the query produces no rows.
|
|
86
|
+
*/
|
|
87
|
+
aggregate(seed: Seed, formula: AggregateFormula, window: TimeWindow): Promise<number>;
|
|
88
|
+
/**
|
|
89
|
+
* Evaluates the formula twice — once for the current window period and once
|
|
90
|
+
* for the equivalent previous period — to support trend calculations.
|
|
91
|
+
* Implementations must return { currentValue: 0, previousValue: 0 } on
|
|
92
|
+
* empty results.
|
|
93
|
+
*/
|
|
94
|
+
growth(seed: Seed, formula: AggregateFormula, window: TimeWindow): Promise<GrowthResult>;
|
|
95
|
+
/**
|
|
96
|
+
* Returns entries sorted by scoreColumn, excluding nulls. label resolves
|
|
97
|
+
* from seed.displayNameAlias; falls back to id when not set.
|
|
98
|
+
*/
|
|
99
|
+
leaderboard(seed: Seed, options: LeaderboardOptions): Promise<LeaderboardEntry[]>;
|
|
100
|
+
/**
|
|
101
|
+
* Paginated read of content entries. Filters and search are applied
|
|
102
|
+
* server-side. The caller is responsible for deserialising branch values
|
|
103
|
+
* from the raw Record.
|
|
104
|
+
*/
|
|
105
|
+
list(seed: Seed, options: WidgetListOptions): Promise<WidgetListResult>;
|
|
106
|
+
/**
|
|
107
|
+
* Groups entries by a date bucket derived from groupColumn and aggregates
|
|
108
|
+
* the formula. Days with no entries are omitted (no zero-fill). Points are
|
|
109
|
+
* ordered ascending by label.
|
|
110
|
+
*/
|
|
111
|
+
timeseries(seed: Seed, formula: AggregateFormula, window: TimeWindow, groupColumn: string): Promise<TimeseriesPoint[]>;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=widget.repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget.repository.d.ts","sourceRoot":"","sources":["../../src/widget/widget.repository.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beechcms/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -18,18 +18,24 @@
|
|
|
18
18
|
"build": "tsc",
|
|
19
19
|
"dev": "tsc -w --preserveWatchOutput",
|
|
20
20
|
"lint": "eslint .",
|
|
21
|
-
"type-check": "tsc --noEmit"
|
|
21
|
+
"type-check": "tsc --noEmit",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:coverage": "vitest run --coverage"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
27
|
+
"vitest": "^3.2.4"
|
|
22
28
|
},
|
|
23
29
|
"dependencies": {
|
|
24
30
|
"@tiptap/core": "^3.22.3",
|
|
25
31
|
"@tiptap/extension-highlight": "^3.22.3",
|
|
26
32
|
"@tiptap/extension-image": "^3.22.3",
|
|
27
33
|
"@tiptap/extension-link": "^3.22.3",
|
|
34
|
+
"@tiptap/extension-mathematics": "^3.22.3",
|
|
28
35
|
"@tiptap/extension-subscript": "^3.22.3",
|
|
29
36
|
"@tiptap/extension-superscript": "^3.22.3",
|
|
30
37
|
"@tiptap/extension-table": "^3.22.3",
|
|
31
38
|
"@tiptap/extension-text-align": "^3.22.3",
|
|
32
|
-
"@tiptap/extension-mathematics": "^3.22.3",
|
|
33
39
|
"@tiptap/html": "^3.22.3",
|
|
34
40
|
"@tiptap/starter-kit": "^3.22.3",
|
|
35
41
|
"katex": "^0.16.11",
|