@saacms/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/access/index.d.ts +37 -0
- package/dist/access/index.d.ts.map +1 -0
- package/dist/access/index.js +6 -0
- package/dist/auth/index.d.ts +30 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/codegen/content-migration.d.ts +167 -0
- package/dist/codegen/content-migration.d.ts.map +1 -0
- package/dist/codegen/filter-openapi-for-user.d.ts +100 -0
- package/dist/codegen/filter-openapi-for-user.d.ts.map +1 -0
- package/dist/codegen/index.d.ts +19 -0
- package/dist/codegen/index.d.ts.map +1 -0
- package/dist/codegen/index.js +43 -0
- package/dist/codegen/openapi-types.d.ts +125 -0
- package/dist/codegen/openapi-types.d.ts.map +1 -0
- package/dist/codegen/to-d1-migration.d.ts +88 -0
- package/dist/codegen/to-d1-migration.d.ts.map +1 -0
- package/dist/codegen/to-drizzle.d.ts +131 -0
- package/dist/codegen/to-drizzle.d.ts.map +1 -0
- package/dist/codegen/to-openapi.d.ts +80 -0
- package/dist/codegen/to-openapi.d.ts.map +1 -0
- package/dist/codegen/to-puck-fields.d.ts +109 -0
- package/dist/codegen/to-puck-fields.d.ts.map +1 -0
- package/dist/codegen/to-ts-types.d.ts +59 -0
- package/dist/codegen/to-ts-types.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +94 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +8 -0
- package/dist/host/index.d.ts +109 -0
- package/dist/host/index.d.ts.map +1 -0
- package/dist/host/index.js +16 -0
- package/dist/index-172n82sz.js +4 -0
- package/dist/index-8g8ymd37.js +275 -0
- package/dist/index-a3pnt8yz.js +1494 -0
- package/dist/index-b59hfany.js +3078 -0
- package/dist/index-b7z43xwp.js +6 -0
- package/dist/index-r0at8zaw.js +13 -0
- package/dist/index-zgbq60fy.js +74 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +261 -0
- package/dist/observability/audit.d.ts +65 -0
- package/dist/observability/audit.d.ts.map +1 -0
- package/dist/observability/index.d.ts +2 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/publish/compile.d.ts +76 -0
- package/dist/publish/compile.d.ts.map +1 -0
- package/dist/runtime/auth-middleware.d.ts +34 -0
- package/dist/runtime/auth-middleware.d.ts.map +1 -0
- package/dist/runtime/boolean-columns.d.ts +65 -0
- package/dist/runtime/boolean-columns.d.ts.map +1 -0
- package/dist/runtime/cache.d.ts +62 -0
- package/dist/runtime/cache.d.ts.map +1 -0
- package/dist/runtime/create-route.d.ts +26 -0
- package/dist/runtime/create-route.d.ts.map +1 -0
- package/dist/runtime/delete-route.d.ts +15 -0
- package/dist/runtime/delete-route.d.ts.map +1 -0
- package/dist/runtime/drafts-route.d.ts +65 -0
- package/dist/runtime/drafts-route.d.ts.map +1 -0
- package/dist/runtime/health-route.d.ts +23 -0
- package/dist/runtime/health-route.d.ts.map +1 -0
- package/dist/runtime/index.d.ts +24 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +17 -0
- package/dist/runtime/json-columns.d.ts +50 -0
- package/dist/runtime/json-columns.d.ts.map +1 -0
- package/dist/runtime/list-route.d.ts +20 -0
- package/dist/runtime/list-route.d.ts.map +1 -0
- package/dist/runtime/openapi-route.d.ts +30 -0
- package/dist/runtime/openapi-route.d.ts.map +1 -0
- package/dist/runtime/pattern-route.d.ts +48 -0
- package/dist/runtime/pattern-route.d.ts.map +1 -0
- package/dist/runtime/problem-details.d.ts +32 -0
- package/dist/runtime/problem-details.d.ts.map +1 -0
- package/dist/runtime/put-route.d.ts +19 -0
- package/dist/runtime/put-route.d.ts.map +1 -0
- package/dist/runtime/read-route.d.ts +26 -0
- package/dist/runtime/read-route.d.ts.map +1 -0
- package/dist/runtime/scale-cost.d.ts +84 -0
- package/dist/runtime/scale-cost.d.ts.map +1 -0
- package/dist/runtime/scheme-route.d.ts +49 -0
- package/dist/runtime/scheme-route.d.ts.map +1 -0
- package/dist/runtime/services.d.ts +42 -0
- package/dist/runtime/services.d.ts.map +1 -0
- package/dist/runtime/update-route.d.ts +20 -0
- package/dist/runtime/update-route.d.ts.map +1 -0
- package/dist/runtime/upload-route.d.ts +46 -0
- package/dist/runtime/upload-route.d.ts.map +1 -0
- package/dist/schema/index.d.ts +185 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +40 -0
- package/dist/schema/media.d.ts +237 -0
- package/dist/schema/media.d.ts.map +1 -0
- package/dist/schema/plugin-trust.d.ts +144 -0
- package/dist/schema/plugin-trust.d.ts.map +1 -0
- package/dist/signals/index.d.ts +10 -0
- package/dist/signals/index.d.ts.map +1 -0
- package/dist/signals/index.js +10 -0
- package/dist/storage/index.d.ts +120 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +13 -0
- package/dist/tenant/index.d.ts +105 -0
- package/dist/tenant/index.d.ts.map +1 -0
- package/dist/theme/index.d.ts +56 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/types/ids.d.ts +45 -0
- package/dist/types/ids.d.ts.map +1 -0
- package/dist/types/ids.js +15 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/user.d.ts +14 -0
- package/dist/types/user.d.ts.map +1 -0
- package/dist/types/user.js +1 -0
- package/package.json +116 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L4 read-through cache resolution for the runtime read paths.
|
|
3
|
+
*
|
|
4
|
+
* Per ADR 0021 §6 (cache hierarchy) + ADR 0023 amendment 2026-05-15
|
|
5
|
+
* (no-platform-dependency principle): caching MUST work with NO plugin
|
|
6
|
+
* installed. The in-isolate `Map` fallback below IS the correctness layer —
|
|
7
|
+
* the `@saacms/plugin-cache-kv` adapter (contributed via `services.cache`) is
|
|
8
|
+
* the cross-isolate efficiency upgrade, not a prerequisite.
|
|
9
|
+
*
|
|
10
|
+
* Core must NOT import `@saacms/plugin-cache-kv`; instead `resolveCache`
|
|
11
|
+
* duck-types a structural `CacheLike` against `getServices(c).cache`. Any
|
|
12
|
+
* object exposing `get`/`put` functions (the KV adapter's shape) is used
|
|
13
|
+
* directly; anything else falls back to the module-level Map.
|
|
14
|
+
*
|
|
15
|
+
* Tag schema (ADR 0021): `saacms:record:<slug>:<id>`,
|
|
16
|
+
* `saacms:collection:<slug>`, `saacms:openapi`, `saacms:openapi:role:<role>`.
|
|
17
|
+
* Write-through invalidation (ADR 0021): a successful write calls
|
|
18
|
+
* `purgeByTag` so stale entries are dropped immediately rather than waiting
|
|
19
|
+
* for TTL. Per ADR 0023 this MUST work without the plugin, so the in-isolate
|
|
20
|
+
* fallback maintains its own `tag → keys` reverse index.
|
|
21
|
+
*/
|
|
22
|
+
import type { Context } from "hono";
|
|
23
|
+
/** What a `get` resolves to — mirrors the KV adapter's `KvCacheEntry`. */
|
|
24
|
+
export interface CacheEntry<T = unknown> {
|
|
25
|
+
readonly value: T;
|
|
26
|
+
readonly tags: ReadonlyArray<string>;
|
|
27
|
+
readonly storedAt: string;
|
|
28
|
+
}
|
|
29
|
+
export interface CachePutOptions {
|
|
30
|
+
readonly tags?: ReadonlyArray<string>;
|
|
31
|
+
/** Seconds until the entry is treated as a miss. */
|
|
32
|
+
readonly expirationTtl?: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Structural cache contract. Deliberately a subset of `KvCacheAdapter`
|
|
36
|
+
* (`get` + `put` only) so the KV plugin's adapter satisfies it without core
|
|
37
|
+
* importing the plugin package.
|
|
38
|
+
*/
|
|
39
|
+
export interface CacheLike {
|
|
40
|
+
get<T = unknown>(key: string): Promise<CacheEntry<T> | null>;
|
|
41
|
+
put<T = unknown>(key: string, value: T, opts?: CachePutOptions): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Drop every entry tagged `tag`. Mirrors `KvCacheAdapter.purgeByTag` so the
|
|
44
|
+
* KV plugin's adapter satisfies `CacheLike` without core importing it.
|
|
45
|
+
* Returns how many keys were removed (for observability/tests).
|
|
46
|
+
*/
|
|
47
|
+
purgeByTag(tag: string): Promise<{
|
|
48
|
+
purgedKeys: number;
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
/** Test-only: clear fallback state so cases don't leak into each other. */
|
|
52
|
+
export declare function __resetInIsolateCache(): void;
|
|
53
|
+
/** Test-only: override the fallback clock (pass `null` to restore real time). */
|
|
54
|
+
export declare function __setInIsolateClock(fn: (() => number) | null): void;
|
|
55
|
+
/**
|
|
56
|
+
* Return the plugin-contributed cache when it structurally satisfies
|
|
57
|
+
* `CacheLike`, else the shared in-isolate fallback. Never returns `null` — a
|
|
58
|
+
* read route can always cache. A service exposing `get`/`put` but no
|
|
59
|
+
* `purgeByTag` is wrapped so invalidation degrades safely (ADR 0023).
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveCache(c: Context): CacheLike;
|
|
62
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/runtime/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAGnC,0EAA0E;AAC1E,MAAM,WAAW,UAAU,CAAC,CAAC,GAAG,OAAO;IACrC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;IACjB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IACrC,oDAAoD;IACpD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAChC;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;IAC5D,GAAG,CAAC,CAAC,GAAG,OAAO,EACb,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,CAAC,EACR,IAAI,CAAC,EAAE,eAAe,GACrB,OAAO,CAAC,IAAI,CAAC,CAAA;IAChB;;;;OAIG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACzD;AAoDD,2EAA2E;AAC3E,wBAAgB,qBAAqB,IAAI,IAAI,CAK5C;AAED,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,IAAI,GAAG,IAAI,CAEnE;AAmGD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,SAAS,CAIlD"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `POST /api/saacms/v1/:collection` — Collection create endpoint per ADR 0018.
|
|
3
|
+
*
|
|
4
|
+
* The route resolves the Collection by slug, asserts a `RowStorageAdapter` is
|
|
5
|
+
* wired, enforces `application/json` content negotiation, parses + validates
|
|
6
|
+
* the request body against the Collection's Effect Schema, evaluates the
|
|
7
|
+
* Collection's `access.create` predicate with the validated input bound as
|
|
8
|
+
* `record` (ADR 0006 §1), inserts via `storage.rows.insert`, and returns a
|
|
9
|
+
* `201 Created` envelope with a `Location` header pointing at the new
|
|
10
|
+
* resource (RFC 9110 §10.2.2).
|
|
11
|
+
*
|
|
12
|
+
* Schema-validation failures surface as `422` with both a human-readable
|
|
13
|
+
* `detail` (Effect's `TreeFormatter`) and a structured `extensions.issues`
|
|
14
|
+
* array (`ArrayFormatter`) so callers can render per-field UI without
|
|
15
|
+
* re-parsing free-form text.
|
|
16
|
+
*
|
|
17
|
+
* Per ADR 0021 §6 the response carries `Cache-Control: no-store` — a
|
|
18
|
+
* successful write must never be served from a downstream cache.
|
|
19
|
+
*
|
|
20
|
+
* Every 4xx/5xx response is an RFC 9457 `application/problem+json` document
|
|
21
|
+
* via the shared `problemDetails` helper.
|
|
22
|
+
*/
|
|
23
|
+
import type { Env, Hono } from "hono";
|
|
24
|
+
import type { SaacmsConfig } from "../schema/index.ts";
|
|
25
|
+
export declare function mountCreateRoute<E extends Env>(app: Hono<E>, config: SaacmsConfig): void;
|
|
26
|
+
//# sourceMappingURL=create-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-route.d.ts","sourceRoot":"","sources":["../../src/runtime/create-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,OAAO,KAAK,EAAW,GAAG,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAQ9C,OAAO,KAAK,EAA4B,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAchF,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,GAAG,EAC5C,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,EACZ,MAAM,EAAE,YAAY,GACnB,IAAI,CAsNN"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `DELETE /api/saacms/v1/:collection/:id` — single-record delete endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Idempotent per RFC 9110 §9.2.2: a `DELETE` on an absent resource is a 204 as
|
|
5
|
+
* long as the caller has not asserted an `If-Match` precondition. When an
|
|
6
|
+
* `If-Match` is supplied, the route enforces optimistic concurrency against
|
|
7
|
+
* the same weak ETag the read-route emits (`W/"<slug>:<id>:<updatedAt>"`).
|
|
8
|
+
*
|
|
9
|
+
* Every 4xx/5xx response is an RFC 9457 `application/problem+json` document
|
|
10
|
+
* (ADR 0018 §3) via the shared `problemDetails` helper.
|
|
11
|
+
*/
|
|
12
|
+
import type { Env, Hono } from "hono";
|
|
13
|
+
import type { SaacmsConfig } from "../schema/index.ts";
|
|
14
|
+
export declare function mountDeleteRoute<E extends Env>(app: Hono<E>, config: SaacmsConfig): void;
|
|
15
|
+
//# sourceMappingURL=delete-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete-route.d.ts","sourceRoot":"","sources":["../../src/runtime/delete-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAW,GAAG,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAI9C,OAAO,KAAK,EAA4B,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAYhF,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,GAAG,EAC5C,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,EACZ,MAAM,EAAE,YAAY,GACnB,IAAI,CAoLN"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/api/saacms/v1/drafts/:pageId` — Page Draft persistence (CONTEXT.md
|
|
3
|
+
* "Drafts"; ADR 0025 Zone-1 self-serve; ADR 0018 §7 / ADR 0021 §6).
|
|
4
|
+
*
|
|
5
|
+
* A Page Draft is the in-flight Puck/Block `layout` tree for a Page id,
|
|
6
|
+
* owned by saacms in `config.storage.rows` against a reserved table slug
|
|
7
|
+
* `"saacms_drafts"` (the exact persistence move `pattern-route.ts` makes for
|
|
8
|
+
* Owner-saved Patterns — internal runtime state in the same `RowStorageAdapter`,
|
|
9
|
+
* NO schema/contract change). Per CONTEXT.md edits only ever *accumulate* in
|
|
10
|
+
* Drafts (free, no CI); Publish stays the single deliberate `saacms publish`.
|
|
11
|
+
*
|
|
12
|
+
* Load-bearing invariant — **no code path here can trigger Publish/commit/CI.**
|
|
13
|
+
* This module imports only storage + cache + Problem-Details + the
|
|
14
|
+
* `afterChange`/`afterDelete` event-phase hook moments (ADR 0013). It NEVER
|
|
15
|
+
* imports or reaches the publish/compile/git path, and it NEVER resolves the
|
|
16
|
+
* `beforePublish`/`afterPublish` moments. Saving a Draft is a row write and
|
|
17
|
+
* nothing else (asserted in `__tests__/drafts-route.test.ts`).
|
|
18
|
+
*
|
|
19
|
+
* Conventions mirrored from `create-route` / `pattern-route` / `read-route` /
|
|
20
|
+
* `update-route` (no machinery is re-implemented):
|
|
21
|
+
*
|
|
22
|
+
* - Shared `problemDetails` (RFC 9457) on every 4xx/5xx.
|
|
23
|
+
* - Weak-ETag `W/"saacms_drafts:<pageId>:<updatedAt>"` with
|
|
24
|
+
* `If-Match`/412 optimistic concurrency (the multi-editor strategy per
|
|
25
|
+
* ADR 0018 §7 / CONTEXT.md "Optimistic concurrency") and
|
|
26
|
+
* `If-None-Match`/304 on GET — identical to read/update-route.
|
|
27
|
+
* - `Cache-Control: no-store` on every response: a Draft is volatile,
|
|
28
|
+
* per-Owner editor state; it must never be served from a downstream cache
|
|
29
|
+
* (ADR 0026 §O5). There is deliberately NO L4 read-through here — a Draft
|
|
30
|
+
* must always reflect the latest write; correctness over a TTL cache on
|
|
31
|
+
* mutable editor state.
|
|
32
|
+
* - **Non-enumeration (ADR 0026 §O2 / OWASP API #1).** A Draft that is
|
|
33
|
+
* missing OR not accessible returns a 404 whose Problem-Details body is
|
|
34
|
+
* byte-identical to the unknown-pageId 404 — an unauthenticated caller
|
|
35
|
+
* cannot tell whether a Draft exists. Write denial (PUT/DELETE without a
|
|
36
|
+
* logged-in editor) returns 403 (mirrors `pattern-route` / create-route's
|
|
37
|
+
* Zone-1 "a logged-in editor is required" rule).
|
|
38
|
+
*
|
|
39
|
+
* Design choices specific to Drafts:
|
|
40
|
+
*
|
|
41
|
+
* - **The Page id IS the row id.** One Draft per Page (`id = pageId`), so
|
|
42
|
+
* `getById(table, pageId)` is the natural lookup and PUT is an upsert.
|
|
43
|
+
* - **`tree` is stored as a JSON string.** The Draft tree is opaque editor
|
|
44
|
+
* JSON; serialising it to a single `tree` TEXT column keeps the row a flat
|
|
45
|
+
* scalar record (so it binds through any `RowStorageAdapter` unchanged) and
|
|
46
|
+
* keeps this route the sole owner of the `saacms_drafts` contract.
|
|
47
|
+
* - **Hand-rolled shape validation, no Effect Schema.** A Draft tree is not a
|
|
48
|
+
* user-supplied Collection schema; the contract is fixed here (`{ nodes:
|
|
49
|
+
* [...] }`, optional `root`), so a tiny inline validator is clearer.
|
|
50
|
+
*/
|
|
51
|
+
import type { Env, Hono } from "hono";
|
|
52
|
+
import type { SaacmsConfig } from "../schema/index.ts";
|
|
53
|
+
/** Persisted Draft row — a flat scalar record (`tree` is serialized JSON). */
|
|
54
|
+
export interface DraftRow {
|
|
55
|
+
/** The Page id this Draft belongs to (one Draft per Page). */
|
|
56
|
+
id: string;
|
|
57
|
+
/** Serialized Draft tree JSON; opaque here. */
|
|
58
|
+
tree: string;
|
|
59
|
+
/** User id of the Owner/editor who last saved it; `null` if anon. */
|
|
60
|
+
createdBy: string | null;
|
|
61
|
+
createdAt: string;
|
|
62
|
+
updatedAt: string;
|
|
63
|
+
}
|
|
64
|
+
export declare function mountDraftsRoute<E extends Env>(app: Hono<E>, config: SaacmsConfig): void;
|
|
65
|
+
//# sourceMappingURL=drafts-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drafts-route.d.ts","sourceRoot":"","sources":["../../src/runtime/drafts-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAGH,OAAO,KAAK,EAAW,GAAG,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAG9C,OAAO,KAAK,EAAa,YAAY,EAAE,MAAM,oBAAoB,CAAA;AASjE,8EAA8E;AAC9E,MAAM,WAAW,QAAQ;IACvB,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAA;IACV,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAA;IACZ,qEAAqE;IACrE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAWD,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,GAAG,EAC5C,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,EACZ,MAAM,EAAE,YAAY,GACnB,IAAI,CAwLN"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /api/saacms/v1/health` — liveness/version endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Returns a small JSON document the host adapter (or an external probe) can
|
|
5
|
+
* hit to confirm the runtime is live and to read the deployed `@saacms/core`
|
|
6
|
+
* version. Per ADR 0021 §6 the response carries `Cache-Control: no-store`
|
|
7
|
+
* because intermediaries caching a stale "ok" defeats the point of a health
|
|
8
|
+
* probe.
|
|
9
|
+
*
|
|
10
|
+
* `version` is captured once at module init from `package.json`, and
|
|
11
|
+
* `uptimeMs` is measured from the timestamp the module is first imported —
|
|
12
|
+
* neither value re-reads any state per request.
|
|
13
|
+
*/
|
|
14
|
+
import type { Env, Hono } from "hono";
|
|
15
|
+
import type { SaacmsConfig } from "../schema/index.ts";
|
|
16
|
+
/**
|
|
17
|
+
* Register `GET /api/saacms/v1/health` on the supplied Hono app. The `config`
|
|
18
|
+
* argument is currently unused — kept for parity with the other `mountXRoute`
|
|
19
|
+
* helpers so a future iteration can surface configured probes (storage,
|
|
20
|
+
* cache, etc.) without breaking the call sites.
|
|
21
|
+
*/
|
|
22
|
+
export declare function mountHealthRoute<E extends Env>(app: Hono<E>, _config: SaacmsConfig): void;
|
|
23
|
+
//# sourceMappingURL=health-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health-route.d.ts","sourceRoot":"","sources":["../../src/runtime/health-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAErC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAOtD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,GAAG,EAC5C,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,EACZ,OAAO,EAAE,YAAY,GACpB,IAAI,CAaN"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime HTTP handler skeleton.
|
|
3
|
+
*
|
|
4
|
+
* Per CONTEXT.md, the runtime handler is the framework-agnostic Hono app the
|
|
5
|
+
* host adapter mounts. Owns: admin UI, drafts API, publish endpoint, Collection
|
|
6
|
+
* CRUD via OpenAPI (ADR 0018).
|
|
7
|
+
*
|
|
8
|
+
* Live-update channels (SSE / WebSocket) are deliberately NOT in v1 — they're
|
|
9
|
+
* shipped as opt-in plugins (`@saacms/plugin-realtime`) so the framework can
|
|
10
|
+
* deploy on the cheapest edge substrate (no sticky connections / Durable
|
|
11
|
+
* Objects required). Polling + ETag (`If-None-Match`) is the canonical path
|
|
12
|
+
* for cache coherence and admin-UI refresh; SSE is a turbocharger.
|
|
13
|
+
*/
|
|
14
|
+
import { Hono } from "hono";
|
|
15
|
+
import type { SaacmsConfig } from "../schema/index.ts";
|
|
16
|
+
export * from "./services.ts";
|
|
17
|
+
export * from "./scale-cost.ts";
|
|
18
|
+
/**
|
|
19
|
+
* Build the saacms Hono app from a resolved config. The returned `Hono` instance
|
|
20
|
+
* is mountable under any base path the host adapter chooses (defaults to
|
|
21
|
+
* `/api/saacms` — see `SaacmsConfig.mountPath`).
|
|
22
|
+
*/
|
|
23
|
+
export declare function createSaacmsRuntime(config: SaacmsConfig): Hono;
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAmBtD,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAI/B;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAkE9D"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_THRESHOLDS,
|
|
3
|
+
collectServices,
|
|
4
|
+
computeScaleCostSignal,
|
|
5
|
+
createSaacmsRuntime,
|
|
6
|
+
getServices
|
|
7
|
+
} from "../index-b59hfany.js";
|
|
8
|
+
import"../index-zgbq60fy.js";
|
|
9
|
+
import"../index-a3pnt8yz.js";
|
|
10
|
+
import"../index-8g8ymd37.js";
|
|
11
|
+
export {
|
|
12
|
+
getServices,
|
|
13
|
+
createSaacmsRuntime,
|
|
14
|
+
computeScaleCostSignal,
|
|
15
|
+
collectServices,
|
|
16
|
+
DEFAULT_THRESHOLDS
|
|
17
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema-driven JSON (de)serialisation at the runtime storage boundary.
|
|
3
|
+
*
|
|
4
|
+
* Per ADR 0005 the Effect Schema is the single source of truth; per ADR 0020
|
|
5
|
+
* the storage adapter is a schema-AGNOSTIC anti-corruption layer that only
|
|
6
|
+
* knows snake↔camel and never the column types. So the projection of an
|
|
7
|
+
* object/array domain field to its TEXT column (and back) cannot live in the
|
|
8
|
+
* adapter — it lives here, at the runtime boundary, where the collection's
|
|
9
|
+
* Effect Schema is already in hand.
|
|
10
|
+
*
|
|
11
|
+
* `to-d1-migration.ts` maps `Schema.Array(X)` / nested `Struct` /
|
|
12
|
+
* `Schema.Unknown` (and other non-primitive shapes) to a TEXT column with the
|
|
13
|
+
* note "JSON at the runtime layer". These two functions ARE that layer:
|
|
14
|
+
*
|
|
15
|
+
* - `encodeJsonColumns` — JSON-stringify the complex-typed fields of a record
|
|
16
|
+
* immediately BEFORE `rows.insert/update`.
|
|
17
|
+
* - `decodeJsonColumns` — JSON-parse them immediately AFTER
|
|
18
|
+
* `rows.getById/list`.
|
|
19
|
+
*
|
|
20
|
+
* Contract:
|
|
21
|
+
* - Which fields are "complex" is derived from the SAME AST the codegen walks
|
|
22
|
+
* (post optional/nullable/refinement stripping) — Tuple (Schema.Array),
|
|
23
|
+
* TypeLiteral (nested Struct), Unknown/Any/Object keywords. Primitives
|
|
24
|
+
* (string/number/boolean/date/literal-union) are never touched.
|
|
25
|
+
* - `null` / absent values are left untouched, composing correctly with the
|
|
26
|
+
* adapter's NULL→absent read behaviour (ADR 0020) — an absent optional
|
|
27
|
+
* complex field never becomes the literal `"null"` string.
|
|
28
|
+
* - Symmetric + idempotent for valid data: `decode∘encode = identity`;
|
|
29
|
+
* `decode` of an already-decoded (non-string) value is a no-op, so it is
|
|
30
|
+
* safe to run on a cache-hit raw record or a freshly-decoded one.
|
|
31
|
+
* - Pure: never mutates its input record; returns a shallow copy.
|
|
32
|
+
*/
|
|
33
|
+
import type { AnySchema } from "../schema/index.ts";
|
|
34
|
+
/**
|
|
35
|
+
* Return a shallow copy of `record` with every complex-typed field
|
|
36
|
+
* JSON-stringified, ready for `rows.insert/update`. `null`/absent fields are
|
|
37
|
+
* passed through unchanged so the adapter's NULL handling still applies.
|
|
38
|
+
* Already-string values are left as-is (idempotent — a re-encode is a no-op),
|
|
39
|
+
* which keeps it safe if a caller pre-serialised.
|
|
40
|
+
*/
|
|
41
|
+
export declare function encodeJsonColumns<T extends Record<string, unknown>>(schema: AnySchema, record: T): T;
|
|
42
|
+
/**
|
|
43
|
+
* Return a shallow copy of `record` with every complex-typed field JSON-parsed
|
|
44
|
+
* back into its domain object/array. Non-string values (already decoded, or
|
|
45
|
+
* `null`/absent) are left untouched, so this is idempotent and safe to run on
|
|
46
|
+
* a raw storage row OR an already-decoded one. A value that fails to parse is
|
|
47
|
+
* left as the original string (defensive — never throw on a read path).
|
|
48
|
+
*/
|
|
49
|
+
export declare function decodeJsonColumns<T extends Record<string, unknown>>(schema: AnySchema, record: T): T;
|
|
50
|
+
//# sourceMappingURL=json-columns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-columns.d.ts","sourceRoot":"","sources":["../../src/runtime/json-columns.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAmEnD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjE,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,CAAC,GACR,CAAC,CAWH;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjE,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,CAAC,GACR,CAAC,CAeH"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /api/saacms/v1/:collection` — Collection list endpoint per ADR 0018.
|
|
3
|
+
*
|
|
4
|
+
* The route resolves the URL `:collection` slug to a `CollectionDef`, runs the
|
|
5
|
+
* caller through the Collection's `access.read` predicate (ADR 0006), reads
|
|
6
|
+
* pagination/sort/filter from the query string, calls
|
|
7
|
+
* `config.storage.rows.list(slug, opts)`, and returns a HATEOAS-shaped envelope
|
|
8
|
+
* with `_links.self` + `_links.next` and a `_meta` echo of the request.
|
|
9
|
+
*
|
|
10
|
+
* Per ADR 0021 §6 the response carries `Cache-Control: private, max-age=15` and
|
|
11
|
+
* `Vary: Authorization` so per-user variants don't cross-pollinate.
|
|
12
|
+
*
|
|
13
|
+
* Errors are emitted as RFC 9457 Problem Details (ADR 0018) — `404` when the
|
|
14
|
+
* slug is unknown, `403` when the caller fails the read predicate, `503` when
|
|
15
|
+
* the host has not wired a `RowStorageAdapter`.
|
|
16
|
+
*/
|
|
17
|
+
import type { Env, Hono } from "hono";
|
|
18
|
+
import type { SaacmsConfig } from "../schema/index.ts";
|
|
19
|
+
export declare function mountListRoute<E extends Env>(app: Hono<E>, config: SaacmsConfig): void;
|
|
20
|
+
//# sourceMappingURL=list-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-route.d.ts","sourceRoot":"","sources":["../../src/runtime/list-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAW,GAAG,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAQ9C,OAAO,KAAK,EAA4B,YAAY,EAAE,MAAM,oBAAoB,CAAA;AA8GhF,wBAAgB,cAAc,CAAC,CAAC,SAAS,GAAG,EAC1C,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,EACZ,MAAM,EAAE,YAAY,GACnB,IAAI,CA2MN"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /api/saacms/v1/openapi.json` — per-user OpenAPI assembly.
|
|
3
|
+
*
|
|
4
|
+
* Composition test for the runtime → codegen → access pipeline per
|
|
5
|
+
* ADR 0006 + ADR 0018 §2. The route:
|
|
6
|
+
*
|
|
7
|
+
* 1. Reads the calling user from `c.get("user")`. A future Better Auth
|
|
8
|
+
* middleware is expected to populate that variable before this handler
|
|
9
|
+
* runs; until then it is `undefined`/anonymous.
|
|
10
|
+
* 2. Assembles the full OpenAPI document by calling
|
|
11
|
+
* `collectionToOpenApiPaths(coll)` for each registered Collection and
|
|
12
|
+
* merging the resulting `paths` + `components.schemas` via
|
|
13
|
+
* `mergeOpenApiSpecs`.
|
|
14
|
+
* 3. Passes the assembled spec through `filterOpenApiForUser` to scope
|
|
15
|
+
* routes/fields the caller cannot reach.
|
|
16
|
+
*
|
|
17
|
+
* Per ADR 0021 §6 the response carries `Cache-Control: private, max-age=60`
|
|
18
|
+
* and `Vary: Authorization` so per-user variants don't cross-pollinate.
|
|
19
|
+
*/
|
|
20
|
+
import type { Env, Hono } from "hono";
|
|
21
|
+
import type { SaacmsConfig } from "../schema/index.ts";
|
|
22
|
+
/**
|
|
23
|
+
* Register `GET /api/saacms/v1/openapi.json` on the supplied Hono app.
|
|
24
|
+
*
|
|
25
|
+
* The handler reads `c.get("user")` (set by a future auth middleware) and
|
|
26
|
+
* passes it through `filterOpenApiForUser`. When no middleware ran, the value
|
|
27
|
+
* is `undefined` and the filter sees `null` — the anonymous case.
|
|
28
|
+
*/
|
|
29
|
+
export declare function mountOpenApiRoute<E extends Env>(app: Hono<E>, config: SaacmsConfig): void;
|
|
30
|
+
//# sourceMappingURL=openapi-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi-route.d.ts","sourceRoot":"","sources":["../../src/runtime/openapi-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AASrC,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,oBAAoB,CAAA;AA+BrE;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,GAAG,EAC7C,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,EACZ,MAAM,EAAE,YAAY,GACnB,IAAI,CA0DN"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/api/saacms/v1/patterns` — Pattern CRUD (ADR 0025, Operating law Zone 1).
|
|
3
|
+
*
|
|
4
|
+
* A Pattern is a runtime-created, Owner-saved reusable arrangement of existing
|
|
5
|
+
* Block instances with preset content. It is NOT a dev-authored primitive
|
|
6
|
+
* (there is deliberately no `definePattern`); it is stored *content*, persisted
|
|
7
|
+
* through the same `RowStorageAdapter` as Collection records — here against a
|
|
8
|
+
* reserved table slug `"saacms_patterns"`.
|
|
9
|
+
*
|
|
10
|
+
* This route is structurally a constrained Collection route. It mirrors the
|
|
11
|
+
* conventions of `list-route` / `create-route` / `read-route` / `delete-route`
|
|
12
|
+
* exactly: shared `problemDetails` (RFC 9457) on every 4xx/5xx, `resolveCache`
|
|
13
|
+
* read-through + tag invalidation (ADR 0021 §6), the
|
|
14
|
+
* `resolveHooksFor` / `runHooks` event-phase dispatch (ADR 0013), the HATEOAS
|
|
15
|
+
* envelope, and the weak-ETag / `If-None-Match` / `If-Match` semantics.
|
|
16
|
+
*
|
|
17
|
+
* Design choices specific to Patterns:
|
|
18
|
+
*
|
|
19
|
+
* - **Hand-rolled validation, no Effect Schema.** The Collection routes decode
|
|
20
|
+
* against a *user-supplied* `collection.schema`; a Pattern's shape is fixed
|
|
21
|
+
* by this module (`PatternRecord`), so a tiny inline validator is clearer
|
|
22
|
+
* and avoids constructing a Schema for a 2-field contract.
|
|
23
|
+
* - **No PUT/PATCH for v1.** Editing a Pattern in the UI is delete-then-create
|
|
24
|
+
* (Patterns are recreate-not-edit), which keeps the route surface minimal.
|
|
25
|
+
* - **Access (v1, simplest correct rule).** Patterns are admin-surface
|
|
26
|
+
* content: writes (POST/DELETE) require a logged-in user (`c.get("user")`);
|
|
27
|
+
* reads are open (consistent with the rest of the runtime treating absent
|
|
28
|
+
* auth as open). Finer-grained per-Pattern access is a future Escalation.
|
|
29
|
+
*/
|
|
30
|
+
import type { Env, Hono } from "hono";
|
|
31
|
+
import type { SaacmsConfig } from "../schema/index.ts";
|
|
32
|
+
/**
|
|
33
|
+
* The persisted Pattern shape. `tree` is the serialized Puck block-subtree
|
|
34
|
+
* JSON — opaque to this route (the editor produces/consumes it).
|
|
35
|
+
*/
|
|
36
|
+
export interface PatternRecord {
|
|
37
|
+
id: string;
|
|
38
|
+
/** Human label, required, 1..120 chars. */
|
|
39
|
+
name: string;
|
|
40
|
+
/** Serialized Puck block-subtree JSON; opaque here. */
|
|
41
|
+
tree: unknown;
|
|
42
|
+
/** User id of the Owner/editor who saved it; `null` if anon-disabled. */
|
|
43
|
+
createdBy: string | null;
|
|
44
|
+
createdAt: string;
|
|
45
|
+
updatedAt: string;
|
|
46
|
+
}
|
|
47
|
+
export declare function mountPatternRoute<E extends Env>(app: Hono<E>, config: SaacmsConfig): void;
|
|
48
|
+
//# sourceMappingURL=pattern-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pattern-route.d.ts","sourceRoot":"","sources":["../../src/runtime/pattern-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,OAAO,KAAK,EAAW,GAAG,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAG9C,OAAO,KAAK,EAAa,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAoBjE;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAA;IACZ,uDAAuD;IACvD,IAAI,EAAE,OAAO,CAAA;IACb,yEAAyE;IACzE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,GAAG,EAC7C,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,EACZ,MAAM,EAAE,YAAY,GACnB,IAAI,CAmPN"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 9457 Problem Details helper — shared across runtime route handlers.
|
|
3
|
+
*
|
|
4
|
+
* Per ADR 0018 §3, every 4xx/5xx response from the saacms runtime is an
|
|
5
|
+
* `application/problem+json` document. The lifted helper centralises the
|
|
6
|
+
* `type` URI namespace + content-type so individual routes only express
|
|
7
|
+
* `{ type, title, status, detail? }`.
|
|
8
|
+
*
|
|
9
|
+
* `type` URIs are rooted at `https://saacms.dev/errors/`; the path segment
|
|
10
|
+
* after the slash is the error code. New error types are documented in
|
|
11
|
+
* `docs/errors.md` (TODO) so they're discoverable through HATEOAS hop.
|
|
12
|
+
*/
|
|
13
|
+
import type { Context } from "hono";
|
|
14
|
+
export declare const PROBLEM_TYPE_BASE = "https://saacms.dev/errors";
|
|
15
|
+
export type ProblemStatus = 400 | 401 | 403 | 404 | 405 | 409 | 410 | 412 | 415 | 422 | 500 | 503;
|
|
16
|
+
export interface ProblemDetailsOptions {
|
|
17
|
+
/** Stable error code; appended to `PROBLEM_TYPE_BASE` to form the `type` URI. */
|
|
18
|
+
readonly code: string;
|
|
19
|
+
/** Short human-readable title; per RFC 9457 §3.1 should not change between occurrences. */
|
|
20
|
+
readonly title: string;
|
|
21
|
+
/** HTTP status — must match the response status. */
|
|
22
|
+
readonly status: ProblemStatus;
|
|
23
|
+
/** Free-text human-readable explanation of THIS occurrence. */
|
|
24
|
+
readonly detail?: string;
|
|
25
|
+
/** URI identifying the specific occurrence (e.g. a request id). */
|
|
26
|
+
readonly instance?: string;
|
|
27
|
+
/** Free-form extension members; merged into the document at the top level. */
|
|
28
|
+
readonly extensions?: Readonly<Record<string, unknown>>;
|
|
29
|
+
}
|
|
30
|
+
/** Emit an RFC 9457 Problem Details response on the given Hono context. */
|
|
31
|
+
export declare function problemDetails(c: Context, options: ProblemDetailsOptions): Response;
|
|
32
|
+
//# sourceMappingURL=problem-details.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"problem-details.d.ts","sourceRoot":"","sources":["../../src/runtime/problem-details.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC,eAAO,MAAM,iBAAiB,8BAA8B,CAAA;AAE5D,MAAM,MAAM,aAAa,GACrB,GAAG,GACH,GAAG,GACH,GAAG,GACH,GAAG,GACH,GAAG,GACH,GAAG,GACH,GAAG,GACH,GAAG,GACH,GAAG,GACH,GAAG,GACH,GAAG,GACH,GAAG,CAAA;AAEP,MAAM,WAAW,qBAAqB;IACpC,iFAAiF;IACjF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,2FAA2F;IAC3F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,oDAAoD;IACpD,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CACxD;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAC5B,CAAC,EAAE,OAAO,EACV,OAAO,EAAE,qBAAqB,GAC7B,QAAQ,CAiBV"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `PUT /api/saacms/v1/:collection/:id` — full-replace endpoint per RFC 9110 §9.3.4.
|
|
3
|
+
*
|
|
4
|
+
* Unlike PATCH (RFC 7396 shallow merge), PUT replaces the record outright: the
|
|
5
|
+
* request body is validated as a complete Collection record (Effect
|
|
6
|
+
* `Schema.decodeUnknownEither`) and the full validated value is handed to the
|
|
7
|
+
* row-storage adapter — every column tracked by the Schema is overwritten with
|
|
8
|
+
* what the caller supplied, no merge against the prior row.
|
|
9
|
+
*
|
|
10
|
+
* Optimistic concurrency follows RFC 9110 §13 — when `If-Match` is supplied we
|
|
11
|
+
* compute the same weak ETag the read route emits (`W/"<slug>:<id>:<updatedAt>"`)
|
|
12
|
+
* and short-circuit to `412` on mismatch (or when the record exposes no
|
|
13
|
+
* `updatedAt`, since the precondition cannot be satisfied). All 4xx/5xx
|
|
14
|
+
* responses are RFC 9457 Problem Details via the shared helper, per ADR 0018 §3.
|
|
15
|
+
*/
|
|
16
|
+
import type { Env, Hono } from "hono";
|
|
17
|
+
import type { SaacmsConfig } from "../schema/index.ts";
|
|
18
|
+
export declare function mountPutRoute<E extends Env>(app: Hono<E>, config: SaacmsConfig): void;
|
|
19
|
+
//# sourceMappingURL=put-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"put-route.d.ts","sourceRoot":"","sources":["../../src/runtime/put-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAW,GAAG,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAE9C,OAAO,KAAK,EAA4B,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAgBhF,wBAAgB,aAAa,CAAC,CAAC,SAAS,GAAG,EACzC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,EACZ,MAAM,EAAE,YAAY,GACnB,IAAI,CA+MN"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /api/saacms/v1/:collection/:id` — single-record read endpoint.
|
|
3
|
+
*
|
|
4
|
+
* The route resolves the Collection by slug, loads the row via
|
|
5
|
+
* `config.storage.rows.getById`, evaluates the Collection's `access.read`
|
|
6
|
+
* predicate with the loaded record bound (per ADR 0006 §2 — record-level
|
|
7
|
+
* grain), and returns a HATEOAS-shaped envelope on success.
|
|
8
|
+
*
|
|
9
|
+
* Per ADR 0021 §6 the response carries `Cache-Control: private, max-age=30`
|
|
10
|
+
* and `Vary: Authorization` so per-user variants don't cross-pollinate. When
|
|
11
|
+
* the row exposes an `updatedAt` ISO timestamp we emit a weak `ETag`
|
|
12
|
+
* (RFC 9110 §8.8.3) and short-circuit to `304` on a matching `If-None-Match`.
|
|
13
|
+
*
|
|
14
|
+
* Lifecycle hooks (ADR 0013) bracket the fetch: command-phase `beforeRead`
|
|
15
|
+
* runs as a GATE before any cache/storage access (a failed gate is `403`
|
|
16
|
+
* `hook-rejected`), and event-phase `afterRead` runs best-effort after the
|
|
17
|
+
* ETag/304 decision (skipped on the 304 path) and MAY transform the outgoing
|
|
18
|
+
* representation.
|
|
19
|
+
*
|
|
20
|
+
* Every 4xx/5xx response is an RFC 9457 `application/problem+json` document
|
|
21
|
+
* per ADR 0018 §3 via the shared `problemDetails` helper.
|
|
22
|
+
*/
|
|
23
|
+
import type { Env, Hono } from "hono";
|
|
24
|
+
import type { SaacmsConfig } from "../schema/index.ts";
|
|
25
|
+
export declare function mountReadRoute<E extends Env>(app: Hono<E>, config: SaacmsConfig): void;
|
|
26
|
+
//# sourceMappingURL=read-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-route.d.ts","sourceRoot":"","sources":["../../src/runtime/read-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EAAW,GAAG,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAI9C,OAAO,KAAK,EAA4B,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAchF,wBAAgB,cAAc,CAAC,CAAC,SAAS,GAAG,EAC1C,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,EACZ,MAAM,EAAE,YAAY,GACnB,IAAI,CAuON"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scale & Cost signal engine. Per ADR 0025 + ADR 0021 §8 + CONTEXT.md
|
|
3
|
+
* "Scale & Cost panel": ships the **objective signal**, never prices — a
|
|
4
|
+
* hardcoded Cloudflare/GitHub pricing table is a rot trap, so the Developer
|
|
5
|
+
* supplies the price layer downstream. saacms emits only the structural facts
|
|
6
|
+
* + crossed-threshold markers.
|
|
7
|
+
*
|
|
8
|
+
* Two independent cost-crossover axes:
|
|
9
|
+
* - **serve** — dynamic Collection-read volume × traffic → SQL DB cost.
|
|
10
|
+
* Per-collection; mitigated by wiring a cache/KV tier.
|
|
11
|
+
* - **publish** — publishes/window × CI minutes × rebuild → Actions cost.
|
|
12
|
+
* The Draft-batches-Publish invariant already keeps CI
|
|
13
|
+
* O(publishes) not O(edits); this axis flags when even the
|
|
14
|
+
* O(publishes) volume crosses the GitHub plan allowance.
|
|
15
|
+
*
|
|
16
|
+
* This is a **pure** function in the discipline of `codegen/to-d1-migration.ts`:
|
|
17
|
+
* deterministic, no I/O, no globals, no `Date.now()`. Same snapshot in → same
|
|
18
|
+
* signal out. It does NOT collect metrics — collection is a separate plugin
|
|
19
|
+
* concern. The caller hands in a structural snapshot (and, for publish, the
|
|
20
|
+
* client's GitHub-plan free-minutes allowance, since we never hardcode it).
|
|
21
|
+
*
|
|
22
|
+
* ADR 0021 §8 names the upstream cache metrics this consumes in aggregate:
|
|
23
|
+
* `saacms.cache.hits{tier,key_kind}`, `saacms.cache.misses{...}`.
|
|
24
|
+
*/
|
|
25
|
+
export interface MetricsSnapshot {
|
|
26
|
+
/** Observation window in days (e.g. 30). Projections normalise to a 30-day month. */
|
|
27
|
+
readonly windowDays: number;
|
|
28
|
+
readonly collections: ReadonlyArray<{
|
|
29
|
+
readonly slug: string;
|
|
30
|
+
/** Dynamic (non-cached) Collection reads observed in the window. */
|
|
31
|
+
readonly dynamicReads: number;
|
|
32
|
+
readonly cacheHits: number;
|
|
33
|
+
readonly cacheMisses: number;
|
|
34
|
+
}>;
|
|
35
|
+
readonly publish: {
|
|
36
|
+
/** Publishes observed in the window. */
|
|
37
|
+
readonly publishes: number;
|
|
38
|
+
/** Mean CI minutes per publish. */
|
|
39
|
+
readonly avgCiMinutes: number;
|
|
40
|
+
/**
|
|
41
|
+
* The client's GitHub-plan free Actions minutes per month. The caller
|
|
42
|
+
* supplies this — saacms never hardcodes plan pricing/allowances.
|
|
43
|
+
*/
|
|
44
|
+
readonly githubFreeMinutesPerMonth: number;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export interface ScaleCostThresholds {
|
|
48
|
+
/** reads/day at/above which serve volume is in the superlinear region. */
|
|
49
|
+
readonly serveDynamicReadsPerDay: number;
|
|
50
|
+
/** Cache hit-rate below which a high-volume Collection counts as crossed. */
|
|
51
|
+
readonly serveMinCacheHitRate: number;
|
|
52
|
+
/** Projected Actions-minutes/mo ≥ this fraction of the free allowance = crossed. */
|
|
53
|
+
readonly publishMonthlyMinutesFraction: number;
|
|
54
|
+
}
|
|
55
|
+
export declare const DEFAULT_THRESHOLDS: ScaleCostThresholds;
|
|
56
|
+
export interface AxisSignal {
|
|
57
|
+
readonly axis: "serve" | "publish";
|
|
58
|
+
readonly crossed: boolean;
|
|
59
|
+
/** Human-readable, plain-language facts. NO prices. */
|
|
60
|
+
readonly metricLines: ReadonlyArray<string>;
|
|
61
|
+
/** The action (not a price) the Developer should take. */
|
|
62
|
+
readonly recommendedAction: string;
|
|
63
|
+
/** Collection slug for serve; omitted for publish. */
|
|
64
|
+
readonly subject?: string;
|
|
65
|
+
}
|
|
66
|
+
export interface ScaleCostSignal {
|
|
67
|
+
readonly window: {
|
|
68
|
+
readonly days: number;
|
|
69
|
+
};
|
|
70
|
+
/** One serve entry per over-threshold Collection + one publish entry when crossed. */
|
|
71
|
+
readonly axes: ReadonlyArray<AxisSignal>;
|
|
72
|
+
readonly anyCrossed: boolean;
|
|
73
|
+
/** ONE plain-language sentence for the Owner banner; "" when nothing crossed. */
|
|
74
|
+
readonly ownerSummary: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Compute the objective Scale & Cost signal from a structural snapshot.
|
|
78
|
+
*
|
|
79
|
+
* Pure: deterministic, no I/O, no globals, no clock. The output contains zero
|
|
80
|
+
* prices/currency — only structural facts + crossed markers + the action the
|
|
81
|
+
* Developer should take (the Developer attaches the price downstream).
|
|
82
|
+
*/
|
|
83
|
+
export declare function computeScaleCostSignal(snapshot: MetricsSnapshot, thresholds?: Partial<ScaleCostThresholds>): ScaleCostSignal;
|
|
84
|
+
//# sourceMappingURL=scale-cost.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scale-cost.d.ts","sourceRoot":"","sources":["../../src/runtime/scale-cost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH,MAAM,WAAW,eAAe;IAC9B,qFAAqF;IACrF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC;QAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;QACrB,oEAAoE;QACpE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;QAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;QAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;KAC7B,CAAC,CAAA;IACF,QAAQ,CAAC,OAAO,EAAE;QAChB,wCAAwC;QACxC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;QAC1B,mCAAmC;QACnC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;QAC7B;;;WAGG;QACH,QAAQ,CAAC,yBAAyB,EAAE,MAAM,CAAA;KAC3C,CAAA;CACF;AAMD,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAA;IACxC,6EAA6E;IAC7E,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAA;IACrC,oFAAoF;IACpF,QAAQ,CAAC,6BAA6B,EAAE,MAAM,CAAA;CAC/C;AAED,eAAO,MAAM,kBAAkB,EAAE,mBAIvB,CAAA;AAMV,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS,CAAA;IAClC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,uDAAuD;IACvD,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IAC3C,0DAA0D;IAC1D,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAA;IAClC,sDAAsD;IACtD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1C,sFAAsF;IACtF,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAA;IACxC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;IAC5B,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAgHD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,eAAe,EACzB,UAAU,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,GACxC,eAAe,CAyBjB"}
|