@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,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin trust & capability model — ADR 0029 (refines ADR 0027 §6's `DEFERRED`
|
|
3
|
+
* capability-scoping sub-decision; ADR 0027 stays, ADR 0029 supersedes only that
|
|
4
|
+
* row).
|
|
5
|
+
*
|
|
6
|
+
* v1 trust reality (restated from ADR 0027 §6, honestly): plugins are FULLY
|
|
7
|
+
* TRUSTED. `definePlugin` packages run in-process with the runtime; the npm
|
|
8
|
+
* install step already runs arbitrary code. There is no sandbox here and this
|
|
9
|
+
* module does NOT add one. Capability *enforcement* is therefore NOT a v1
|
|
10
|
+
* security boundary — it stays `DEFERRED` (the v2 Effect-services capability
|
|
11
|
+
* redesign ADR 0027 names).
|
|
12
|
+
*
|
|
13
|
+
* What this module DOES add is the cheap, in-process, convenient→secure move
|
|
14
|
+
* (ADR 0026 Load-Bearing Rule; SRS §Ch.4 "make the insecure path noisier",
|
|
15
|
+
* §Ch.5 least privilege): a plugin may *declare* the capabilities it intends to
|
|
16
|
+
* use, and `assertPluginCapabilities` *detects* — purely, deterministically, no
|
|
17
|
+
* I/O — when a plugin's actual contributed surface exceeds its declaration. This
|
|
18
|
+
* is the exact discipline of `assertTenantIsolation` (ADR 0026 §O9 / ADR 0027
|
|
19
|
+
* §8): an additive, opt-in declare+assert affordance that makes the trap loud
|
|
20
|
+
* and cheap to avoid without changing any existing plugin/runtime behaviour.
|
|
21
|
+
*
|
|
22
|
+
* Detection ≠ enforcement. A declared-but-exceeded capability is *reported*, not
|
|
23
|
+
* *blocked*. The value is provenance/intent transparency, a supply-chain review
|
|
24
|
+
* surface, and the on-ramp to v2 enforcement — not a runtime sandbox.
|
|
25
|
+
*
|
|
26
|
+
* `capabilities` is an OPTIONAL field on `PluginDef`; an existing plugin with no
|
|
27
|
+
* declaration keeps byte-identical behaviour and is merely *listed* as
|
|
28
|
+
* undeclared (CI chooses strictness — see `strict`).
|
|
29
|
+
*/
|
|
30
|
+
import type { PluginDef, SaacmsConfig } from "./index.ts";
|
|
31
|
+
/**
|
|
32
|
+
* The closed set of capability tags a plugin may declare. Each tag is justified
|
|
33
|
+
* by a real `PluginDef` contribution surface (ADR 0029 §Capability Vocabulary):
|
|
34
|
+
*
|
|
35
|
+
* - `collections` — registers Collections (schema + generated CRUD surface).
|
|
36
|
+
* - `blocks` — registers Blocks (editor/render surface).
|
|
37
|
+
* - `pages` — registers Pages and/or Page Templates (route surface).
|
|
38
|
+
* - `routes` — mounts custom HTTP routes under the plugin namespace.
|
|
39
|
+
* - `hooks` — registers lifecycle hooks on Collection moments.
|
|
40
|
+
* - `services` — publishes Effect service factories into the runtime.
|
|
41
|
+
* - `storage:read` — reads stored rows (implied by contributing Collections).
|
|
42
|
+
* - `storage:write` — writes stored rows (implied by Collections and/or an
|
|
43
|
+
* opaque one-shot `init` install effect).
|
|
44
|
+
* - `admin-pages` — registers admin UI pages / menu entries.
|
|
45
|
+
* - `migrations` — ships db and/or content migrations.
|
|
46
|
+
*
|
|
47
|
+
* Closed by construction so CI exhaustiveness checks and v2 enforcement have a
|
|
48
|
+
* finite, auditable surface.
|
|
49
|
+
*/
|
|
50
|
+
export type PluginCapability = "collections" | "blocks" | "pages" | "routes" | "hooks" | "services" | "storage:read" | "storage:write" | "admin-pages" | "migrations";
|
|
51
|
+
/**
|
|
52
|
+
* The closed vocabulary as a value, ordered. Exported so CI / tests can assert
|
|
53
|
+
* exhaustiveness against the `PluginCapability` union.
|
|
54
|
+
*/
|
|
55
|
+
export declare const PLUGIN_CAPABILITIES: ReadonlyArray<PluginCapability>;
|
|
56
|
+
/**
|
|
57
|
+
* Derive the capabilities a plugin ACTUALLY contributes from its `PluginDef`
|
|
58
|
+
* shape alone — pure, structural, no runtime introspection, no I/O.
|
|
59
|
+
*
|
|
60
|
+
* Derivation is *presence-based*: we read which `PluginDef` categories the
|
|
61
|
+
* plugin populates, not what the code inside them does (the bodies of
|
|
62
|
+
* `routes` / `hooks` / `services` / `init` are opaque `unknown` and cannot be
|
|
63
|
+
* inspected without the v2 runtime redesign — see ADR 0029 §Honest Limits).
|
|
64
|
+
* This boundary is exactly what is derivable WITHOUT a contract change, so the
|
|
65
|
+
* detector is well-defined and not BLOCKED.
|
|
66
|
+
*
|
|
67
|
+
* Mapping (ADR 0029 §Surface Derivation):
|
|
68
|
+
* - non-empty `collections` → `collections`, `storage:read`, `storage:write`
|
|
69
|
+
* - non-empty `blocks` → `blocks`
|
|
70
|
+
* - non-empty `pages` → `pages`
|
|
71
|
+
* - non-empty `pageTemplates`→ `pages`
|
|
72
|
+
* - non-empty `routes` → `routes`
|
|
73
|
+
* - non-empty `hooks` → `hooks`
|
|
74
|
+
* - non-empty `services` → `services`
|
|
75
|
+
* - `admin.pages`/`admin.menu` present → `admin-pages`
|
|
76
|
+
* - `migrations.db`/`.content` present → `migrations`
|
|
77
|
+
* - `init` present (opaque effect) → `storage:write`
|
|
78
|
+
* (conservative/fail-closed: an opaque install effect cannot be proven inert,
|
|
79
|
+
* so it is attributed the broadest data-affecting tag; declaring it is the
|
|
80
|
+
* cheap secure move — ADR 0026 Load-Bearing Rule.)
|
|
81
|
+
*/
|
|
82
|
+
export declare function deriveContributedCapabilities(plugin: PluginDef): ReadonlyArray<PluginCapability>;
|
|
83
|
+
export interface AssertPluginCapabilitiesOpts {
|
|
84
|
+
/**
|
|
85
|
+
* Strict mode (fail-closed): when `true`, a plugin that contributes any
|
|
86
|
+
* surface but declares NO `capabilities` at all also fails the check
|
|
87
|
+
* (`ok: false`).
|
|
88
|
+
*
|
|
89
|
+
* Default `false` for additive back-compat: existing plugins/configs with no
|
|
90
|
+
* `capabilities` declaration keep byte-identical behaviour — they are merely
|
|
91
|
+
* *listed* in `undeclared` and `ok` is unaffected by them. CI opts into the
|
|
92
|
+
* convenient→secure posture with `{ strict: true }`.
|
|
93
|
+
*/
|
|
94
|
+
readonly strict?: boolean;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* A single declared-capability escalation: `plugin` contributes `used` but did
|
|
98
|
+
* not list it among its declared `capabilities`. `declared` is always `false`
|
|
99
|
+
* for a finding (a finding *is* the absence) — it is kept on the shape so the
|
|
100
|
+
* record is self-describing for CI logs and future severity tiers.
|
|
101
|
+
*/
|
|
102
|
+
export interface PluginCapabilityFinding {
|
|
103
|
+
readonly plugin: string;
|
|
104
|
+
readonly used: PluginCapability;
|
|
105
|
+
readonly declared: boolean;
|
|
106
|
+
}
|
|
107
|
+
export interface PluginCapabilityReport {
|
|
108
|
+
/**
|
|
109
|
+
* `true` when no plugin's contributed surface exceeds its declared
|
|
110
|
+
* `capabilities`. In `strict` mode, also requires every surface-contributing
|
|
111
|
+
* plugin to have declared a `capabilities` array.
|
|
112
|
+
*/
|
|
113
|
+
readonly ok: boolean;
|
|
114
|
+
/** Declared-but-exceeded escalations. Empty when no declared plugin over-reaches. */
|
|
115
|
+
readonly violations: ReadonlyArray<PluginCapabilityFinding>;
|
|
116
|
+
/**
|
|
117
|
+
* Names of plugins that contributed surface but declared NO `capabilities`.
|
|
118
|
+
* Reported regardless of `strict` so CI can choose its own policy; only
|
|
119
|
+
* affects `ok` when `strict` is `true`.
|
|
120
|
+
*/
|
|
121
|
+
readonly undeclared: ReadonlyArray<string>;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Pure, deterministic detector. For each plugin in `config.plugins`, derive the
|
|
125
|
+
* actual contributed surface from its `PluginDef` and flag any contributed
|
|
126
|
+
* capability not present in its declared `capabilities`.
|
|
127
|
+
*
|
|
128
|
+
* - Plugin declares `capabilities`: each contributed capability NOT in the
|
|
129
|
+
* declared set → a `violation` (`ok: false`). Declaring exactly (or a
|
|
130
|
+
* superset of) its surface → passes.
|
|
131
|
+
* - Plugin declares NO `capabilities` (field absent): listed in `undeclared`.
|
|
132
|
+
* Non-strict (default) ⇒ does not affect `ok` (additive back-compat). Strict
|
|
133
|
+
* ⇒ contributes to `ok: false`.
|
|
134
|
+
*
|
|
135
|
+
* No I/O, no mutation of inputs, constant-ish time over the plugin set.
|
|
136
|
+
* Integrate into CI exactly like `assertTenantIsolation`:
|
|
137
|
+
*
|
|
138
|
+
* ```typescript
|
|
139
|
+
* const r = assertPluginCapabilities(config, { strict: true })
|
|
140
|
+
* if (!r.ok) throw new Error(`Plugin capability escalation: ${JSON.stringify(r)}`)
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export declare function assertPluginCapabilities(config: Pick<SaacmsConfig, "plugins">, opts?: AssertPluginCapabilitiesOpts): PluginCapabilityReport;
|
|
144
|
+
//# sourceMappingURL=plugin-trust.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-trust.d.ts","sourceRoot":"","sources":["../../src/schema/plugin-trust.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAMzD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,gBAAgB,GACxB,aAAa,GACb,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,OAAO,GACP,UAAU,GACV,cAAc,GACd,eAAe,GACf,aAAa,GACb,YAAY,CAAA;AAEhB;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAAa,CAAC,gBAAgB,CAWtD,CAAA;AAMV;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,SAAS,GAChB,aAAa,CAAC,gBAAgB,CAAC,CAgCjC;AAMD,MAAM,WAAW,4BAA4B;IAC3C;;;;;;;;;OASG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;IAC/B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;CAC3B;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAA;IACpB,qFAAqF;IACrF,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,uBAAuB,CAAC,CAAA;IAC3D;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAC3C;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,EACrC,IAAI,GAAE,4BAAiC,GACtC,sBAAsB,CAyBxB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-framework reactive primitive per ADR 0023 Part A.
|
|
3
|
+
*
|
|
4
|
+
* `@preact/signals-core` is the v1 polyfill for the TC39 Signals proposal.
|
|
5
|
+
* Block authors importing `signal` / `computed` / `effect` from `@saacms/core`
|
|
6
|
+
* get a primitive that works inside Web Component Blocks today and will swap to
|
|
7
|
+
* native `globalThis.Signal` once browsers ship it without any Block code change.
|
|
8
|
+
*/
|
|
9
|
+
export { signal, computed, effect } from "@preact/signals-core";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/signals/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage adapter interface per ADR 0009 (Media as a Collection kind).
|
|
3
|
+
*
|
|
4
|
+
* Implementations (`@saacms/storage-r2`, `@saacms/storage-vercel-blob`,
|
|
5
|
+
* `@saacms/storage-s3`) provide the binary-bucket primitives used by Media
|
|
6
|
+
* Collections in `bucket` storage mode. `repo`-mode collections do not go
|
|
7
|
+
* through a StorageAdapter — they're committed to the host's static-asset folder
|
|
8
|
+
* on Publish.
|
|
9
|
+
*
|
|
10
|
+
* The interface uses Web standard `Request` / `Response` shapes so the same
|
|
11
|
+
* adapter runs unchanged on Vercel Edge, Cloudflare Workers, Bun, and Node.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Per-`put` options surfaced to the adapter.
|
|
15
|
+
* `contentType` and `cacheControl` map directly to the corresponding upstream
|
|
16
|
+
* object metadata (R2 / Blob / S3 all expose them).
|
|
17
|
+
*/
|
|
18
|
+
export interface StoragePutOptions {
|
|
19
|
+
readonly contentType?: string;
|
|
20
|
+
readonly cacheControl?: string;
|
|
21
|
+
readonly contentDisposition?: string;
|
|
22
|
+
readonly metadata?: Readonly<Record<string, string>>;
|
|
23
|
+
}
|
|
24
|
+
export interface StoragePutResult {
|
|
25
|
+
readonly key: string;
|
|
26
|
+
readonly etag?: string;
|
|
27
|
+
readonly size?: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Body shapes accepted by `put`. Matches the Web `BodyInit` set so callers can
|
|
31
|
+
* stream a `ReadableStream`, hand off a `Blob`, or pass a buffer.
|
|
32
|
+
*/
|
|
33
|
+
export type StoragePutBody = ReadableStream<Uint8Array> | Blob | ArrayBuffer | ArrayBufferView | string;
|
|
34
|
+
/**
|
|
35
|
+
* Options for generating a signed URL — adapters that don't support a given
|
|
36
|
+
* verb may return a non-functional URL or throw at `signedUrl` time.
|
|
37
|
+
*/
|
|
38
|
+
export interface SignedUrlOptions {
|
|
39
|
+
readonly method?: "GET" | "PUT" | "DELETE";
|
|
40
|
+
/** Seconds until the signed URL expires. */
|
|
41
|
+
readonly expiresIn?: number;
|
|
42
|
+
readonly contentType?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface StorageAdapter {
|
|
45
|
+
readonly name: string;
|
|
46
|
+
/** Upload an object. Returns the canonical key the adapter chose to store it under. */
|
|
47
|
+
put(key: string, body: StoragePutBody, opts?: StoragePutOptions): Promise<StoragePutResult>;
|
|
48
|
+
/**
|
|
49
|
+
* Fetch an object as a Web `Response`. Caller may stream the body, read it
|
|
50
|
+
* as an `ArrayBuffer`, or proxy it directly back to the user.
|
|
51
|
+
*/
|
|
52
|
+
get(key: string): Promise<Response>;
|
|
53
|
+
/** Remove an object. Idempotent — removing a missing key is not an error. */
|
|
54
|
+
delete(key: string): Promise<void>;
|
|
55
|
+
/** Pre-signed URL for direct client → bucket transfer. */
|
|
56
|
+
signedUrl(key: string, opts?: SignedUrlOptions): Promise<string>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Cursor-based pagination + filter options for `list`.
|
|
60
|
+
* `where` is a column→value AND-conjoined predicate set; richer expressions
|
|
61
|
+
* land in v1.x. `cursor` is opaque to the caller and adapter-defined.
|
|
62
|
+
*/
|
|
63
|
+
export interface ListOpts {
|
|
64
|
+
readonly limit?: number;
|
|
65
|
+
readonly cursor?: string;
|
|
66
|
+
readonly where?: Readonly<Record<string, unknown>>;
|
|
67
|
+
readonly orderBy?: ReadonlyArray<{
|
|
68
|
+
readonly col: string;
|
|
69
|
+
readonly dir: "asc" | "desc";
|
|
70
|
+
}>;
|
|
71
|
+
}
|
|
72
|
+
export interface ListResult<T> {
|
|
73
|
+
readonly rows: ReadonlyArray<T>;
|
|
74
|
+
readonly nextCursor: string | null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Tagged error for row-storage operations. Bubbles up to the runtime which
|
|
78
|
+
* converts to RFC 9457 Problem Details (ADR 0018).
|
|
79
|
+
*/
|
|
80
|
+
export declare class RowStorageError extends Error {
|
|
81
|
+
readonly code: "NOT_FOUND" | "INSERT_FAILED" | "UPDATE_FAILED" | "DELETE_FAILED" | "QUERY_FAILED" | "NOT_IMPLEMENTED" | "MISCONFIGURED";
|
|
82
|
+
readonly cause?: unknown | undefined;
|
|
83
|
+
readonly name = "RowStorageError";
|
|
84
|
+
constructor(code: "NOT_FOUND" | "INSERT_FAILED" | "UPDATE_FAILED" | "DELETE_FAILED" | "QUERY_FAILED" | "NOT_IMPLEMENTED" | "MISCONFIGURED", message: string, cause?: unknown | undefined);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Per ADR 0030 + ADR 0020 — thrown by the storage adapter (the anti-corruption
|
|
88
|
+
* boundary) when a DB unique-constraint violation is detected. Routes check
|
|
89
|
+
* `instanceof UniqueConstraintError` to map it to a 409 response; no route
|
|
90
|
+
* ever inspects raw DB error messages.
|
|
91
|
+
*/
|
|
92
|
+
export declare class UniqueConstraintError extends Error {
|
|
93
|
+
readonly table: string;
|
|
94
|
+
readonly name = "UniqueConstraintError";
|
|
95
|
+
constructor(table: string, cause?: unknown);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Row-storage adapter — the Collection-record equivalent of `StorageAdapter`.
|
|
99
|
+
* Implementations: `@saacms/storage-d1` (v1.0); postgres / sqlite-local follow.
|
|
100
|
+
*
|
|
101
|
+
* Per ADR 0020 — the Storage bounded context fronts both binary (`StorageAdapter`)
|
|
102
|
+
* and tabular (`RowStorageAdapter`) backing stores.
|
|
103
|
+
*/
|
|
104
|
+
export interface RowStorageAdapter {
|
|
105
|
+
getById<T>(table: string, id: string): Promise<T | null>;
|
|
106
|
+
list<T>(table: string, opts?: ListOpts): Promise<ListResult<T>>;
|
|
107
|
+
insert<T>(table: string, row: T): Promise<{
|
|
108
|
+
id: string;
|
|
109
|
+
}>;
|
|
110
|
+
update<T>(table: string, id: string, patch: Partial<T>): Promise<void>;
|
|
111
|
+
delete(table: string, id: string): Promise<void>;
|
|
112
|
+
}
|
|
113
|
+
/** The shape `SaacmsConfig.storage` accepts. */
|
|
114
|
+
export interface SaacmsStorageConfig {
|
|
115
|
+
/** Row store for Collection records (D1 in v1.0). */
|
|
116
|
+
readonly rows?: RowStorageAdapter;
|
|
117
|
+
/** Binary blob store for Media Collections (R2 in v1.0). */
|
|
118
|
+
readonly media?: StorageAdapter;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage adapter interface per ADR 0009 (Media as a Collection kind).
|
|
3
|
+
*
|
|
4
|
+
* Implementations (`@saacms/storage-r2`, `@saacms/storage-vercel-blob`,
|
|
5
|
+
* `@saacms/storage-s3`) provide the binary-bucket primitives used by Media
|
|
6
|
+
* Collections in `bucket` storage mode. `repo`-mode collections do not go
|
|
7
|
+
* through a StorageAdapter — they're committed to the host's static-asset folder
|
|
8
|
+
* on Publish.
|
|
9
|
+
*
|
|
10
|
+
* The interface uses Web standard `Request` / `Response` shapes so the same
|
|
11
|
+
* adapter runs unchanged on Vercel Edge, Cloudflare Workers, Bun, and Node.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App multi-tenancy sugar — ADR 0015 + ADR 0026 §O9.
|
|
3
|
+
*
|
|
4
|
+
* ADR 0015 defers app multi-tenancy to user code via two ADR-0006 primitives:
|
|
5
|
+
* 1. A `read` predicate returning `{ where: { [recordField]: user.claims[userField] } }`
|
|
6
|
+
* scopes every list/read to the caller's tenant.
|
|
7
|
+
* 2. A `beforeChange` hook injecting `recordField = user.claims[userField]` on
|
|
8
|
+
* every create/update ensures tenant ownership is set at write time.
|
|
9
|
+
*
|
|
10
|
+
* This module provides ADR-0015-consistent SUGAR — NOT a new access grain (ADR
|
|
11
|
+
* 0006 is unchanged). `tenantScoped` produces the exact shape that
|
|
12
|
+
* `defineCollection({ access, hooks })` expects. `assertTenantIsolation` is a
|
|
13
|
+
* pure CI detector that surfaces Collections whose schema has a tenant field but
|
|
14
|
+
* lacks an `access.read` predicate, making the dangerous path loud and cheap to
|
|
15
|
+
* catch (SRS §Ch.4 "Design Tradeoffs": make the insecure path noisier).
|
|
16
|
+
*
|
|
17
|
+
* If `tenantScoped` or `assertTenantIsolation` cannot be added without modifying
|
|
18
|
+
* existing access/runtime/storage behaviour, that would be a BLOCKED finding.
|
|
19
|
+
* Neither touches existing route handlers, access evaluators, or storage adapters.
|
|
20
|
+
*/
|
|
21
|
+
import type { AccessPredicate } from "../access/index.ts";
|
|
22
|
+
import type { HookFn } from "../hooks/index.ts";
|
|
23
|
+
import type { AnySchema, SaacmsConfig } from "../schema/index.ts";
|
|
24
|
+
export interface TenantScopedOpts {
|
|
25
|
+
/**
|
|
26
|
+
* Key in `user.claims` that holds the tenant identifier.
|
|
27
|
+
* E.g. `"tenantId"` reads `user.claims["tenantId"]`.
|
|
28
|
+
*/
|
|
29
|
+
readonly userField: string;
|
|
30
|
+
/**
|
|
31
|
+
* Record field to scope on. Defaults to `userField` when omitted.
|
|
32
|
+
* E.g. `recordField: "tenantId"` produces `{ where: { tenantId: claimValue } }`.
|
|
33
|
+
*/
|
|
34
|
+
readonly recordField?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface TenantScopedResult {
|
|
37
|
+
/**
|
|
38
|
+
* Drop into `defineCollection({ access: { read: ts.read } })`.
|
|
39
|
+
*
|
|
40
|
+
* Returns `{ where: { [recordField]: user.claims[userField] } }` for
|
|
41
|
+
* authenticated callers (scopes list + per-record read to the caller's tenant).
|
|
42
|
+
* Returns `false` (deny) for anonymous callers — anonymous cannot read
|
|
43
|
+
* tenant-scoped data.
|
|
44
|
+
*/
|
|
45
|
+
readonly read: AccessPredicate;
|
|
46
|
+
readonly hooks: {
|
|
47
|
+
/**
|
|
48
|
+
* Drop into `defineCollection({ hooks: { beforeChange: ts.hooks.beforeChange } })`.
|
|
49
|
+
*
|
|
50
|
+
* Injects `recordField = user.claims[userField]` into `ctx.data` before
|
|
51
|
+
* every create/update, ensuring tenant ownership is set at write time without
|
|
52
|
+
* requiring the caller to supply it.
|
|
53
|
+
*/
|
|
54
|
+
readonly beforeChange: HookFn<AnySchema>;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* ADR-0015-consistent sugar for app multi-tenancy. Returns the `{ read, hooks }`
|
|
59
|
+
* pair that drops directly into a `defineCollection`. No new access grain.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* const ts = tenantScoped({ userField: "tenantId" })
|
|
64
|
+
* export const Orders = defineCollection({
|
|
65
|
+
* slug: "orders",
|
|
66
|
+
* schema: OrderSchema,
|
|
67
|
+
* access: { read: ts.read, create: ts.read, update: ts.read, delete: ts.read },
|
|
68
|
+
* hooks: { beforeChange: ts.hooks.beforeChange },
|
|
69
|
+
* })
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export declare function tenantScoped(opts: TenantScopedOpts): TenantScopedResult;
|
|
73
|
+
export interface AssertTenantIsolationOpts {
|
|
74
|
+
/**
|
|
75
|
+
* The schema field name that carries the tenant identifier. Collections that
|
|
76
|
+
* have this field in their Effect Schema but lack an `access.read` predicate
|
|
77
|
+
* are reported as "unscoped" — they would expose data across tenants.
|
|
78
|
+
*/
|
|
79
|
+
readonly tenantField: string;
|
|
80
|
+
/**
|
|
81
|
+
* Collection slugs exempt from the check (e.g. shared/public data).
|
|
82
|
+
*/
|
|
83
|
+
readonly exempt?: ReadonlyArray<string>;
|
|
84
|
+
}
|
|
85
|
+
export interface TenantIsolationResult {
|
|
86
|
+
/** `true` when all tenant-capable collections have an `access.read` predicate. */
|
|
87
|
+
readonly ok: boolean;
|
|
88
|
+
/** Slugs of collections flagged as unscoped. Empty when `ok` is `true`. */
|
|
89
|
+
readonly unscoped: ReadonlyArray<string>;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Pure detector: inspects `config.collections` and returns any collection whose
|
|
93
|
+
* Effect Schema has a property named `opts.tenantField` but lacks an
|
|
94
|
+
* `access.read` predicate. Runs in constant time with no I/O.
|
|
95
|
+
*
|
|
96
|
+
* Integrate into CI:
|
|
97
|
+
* ```typescript
|
|
98
|
+
* const check = assertTenantIsolation(config, { tenantField: "tenantId" })
|
|
99
|
+
* if (!check.ok) {
|
|
100
|
+
* throw new Error(`Missing tenant predicate on: ${check.unscoped.join(", ")}`)
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export declare function assertTenantIsolation(config: SaacmsConfig, opts: AssertTenantIsolationOpts): TenantIsolationResult;
|
|
105
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tenant/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAMjE,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;;;;OAOG;IACH,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAA;IAC9B,QAAQ,CAAC,KAAK,EAAE;QACd;;;;;;WAMG;QACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,SAAS,CAAC,CAAA;KACzC,CAAA;CACF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,kBAAkB,CA0BvE;AAMD,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B;;OAEG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CACxC;AAED,MAAM,WAAW,qBAAqB;IACpC,kFAAkF;IAClF,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAA;IACpB,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CACzC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,yBAAyB,GAC9B,qBAAqB,CAevB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme & Scheme primitive — ADR 0033.
|
|
3
|
+
*
|
|
4
|
+
* Theme = Developer-defined token contract (Structure / Zone-2).
|
|
5
|
+
* Scheme = Owner-operated named value set (Instance / Zone-1).
|
|
6
|
+
*
|
|
7
|
+
* `defineTheme` is the additive sibling of `defineCollection` / `defineBlock` /
|
|
8
|
+
* `definePlugin`. When absent from `SaacmsConfig` ⇒ zero behaviour change.
|
|
9
|
+
*
|
|
10
|
+
* `themeFingerprint` is the pure, deterministic cache-key contribution that
|
|
11
|
+
* composes with the access fingerprint at the host adapter's cache site,
|
|
12
|
+
* preventing the saastarter cross-tenant/cross-scheme CDN bleed (ADR 0033 §5).
|
|
13
|
+
*/
|
|
14
|
+
/** One of the four presentation token groups. */
|
|
15
|
+
export type ThemeTokenGroup = "colour" | "typography" | "radius" | "motion";
|
|
16
|
+
/** Declaration of a single CSS custom-property slot in the theme contract. */
|
|
17
|
+
export interface ThemeTokenDef {
|
|
18
|
+
/** CSS custom-property name WITHOUT the leading `--`. E.g. `"color-primary"`. */
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly group: ThemeTokenGroup;
|
|
21
|
+
/** When true, a Scheme may supply a `darkTokens[name]` override. */
|
|
22
|
+
readonly dark?: boolean;
|
|
23
|
+
readonly description?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The Developer-authored token contract. Additive optional: when absent from
|
|
27
|
+
* `SaacmsConfig` no new behaviour is introduced and all existing routes and
|
|
28
|
+
* cache keys are byte-identical (ADR 0033 §8).
|
|
29
|
+
*/
|
|
30
|
+
export interface ThemeDef {
|
|
31
|
+
readonly tokens: ReadonlyArray<ThemeTokenDef>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Identity-function declaration — lets downstream code hold a typed reference
|
|
35
|
+
* and the codegen walk a canonical shape. Mirror of `defineCollection` et al.
|
|
36
|
+
*/
|
|
37
|
+
export declare function defineTheme(opts: ThemeDef): ThemeDef;
|
|
38
|
+
/**
|
|
39
|
+
* Pure, deterministic cache-key contribution from the active theme context.
|
|
40
|
+
*
|
|
41
|
+
* Returns a stable opaque string that MUST be combined with the access
|
|
42
|
+
* fingerprint (e.g. `role:${role}`) to form a complete themed cache key:
|
|
43
|
+
*
|
|
44
|
+
* `const key = `role:${role}:${themeFingerprint(schemeId, darkMode, tenant)}`
|
|
45
|
+
*
|
|
46
|
+
* Properties guaranteed (invariant 18 in security-invariants.test.ts):
|
|
47
|
+
* - Deterministic: same inputs → same output, always.
|
|
48
|
+
* - Stable: no random or time-based components.
|
|
49
|
+
* - Composable: append to the access fingerprint, do not replace it.
|
|
50
|
+
* - Discriminating: distinct across schemes, dark/light, and tenants.
|
|
51
|
+
*
|
|
52
|
+
* When `schemeId` is null (no active scheme) the fingerprint is still defined
|
|
53
|
+
* and stable — it discriminates "no scheme" from any named scheme.
|
|
54
|
+
*/
|
|
55
|
+
export declare function themeFingerprint(schemeId: string | null, darkMode: boolean, tenantScope?: string): string;
|
|
56
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,iDAAiD;AACjD,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,YAAY,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAE3E,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,iFAAiF;IACjF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAA;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B;AAED;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,aAAa,CAAC,CAAA;CAC9C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAEpD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,QAAQ,EAAE,OAAO,EACjB,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAMR"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branded ID types so the type system catches "I passed a UserID where a SessionID was expected".
|
|
3
|
+
* Per ADR 0020 (each aggregate has its own identity).
|
|
4
|
+
*/
|
|
5
|
+
declare const __brand: unique symbol;
|
|
6
|
+
export type UserID = string & {
|
|
7
|
+
readonly [__brand]: "UserID";
|
|
8
|
+
};
|
|
9
|
+
export type SessionID = string & {
|
|
10
|
+
readonly [__brand]: "SessionID";
|
|
11
|
+
};
|
|
12
|
+
export type CollectionSlug = string & {
|
|
13
|
+
readonly [__brand]: "CollectionSlug";
|
|
14
|
+
};
|
|
15
|
+
export type RecordID = string & {
|
|
16
|
+
readonly [__brand]: "RecordID";
|
|
17
|
+
};
|
|
18
|
+
export type PageID = string & {
|
|
19
|
+
readonly [__brand]: "PageID";
|
|
20
|
+
};
|
|
21
|
+
export type BlockSlug = string & {
|
|
22
|
+
readonly [__brand]: "BlockSlug";
|
|
23
|
+
};
|
|
24
|
+
export type DraftID = string & {
|
|
25
|
+
readonly [__brand]: "DraftID";
|
|
26
|
+
};
|
|
27
|
+
export type PublicationID = string & {
|
|
28
|
+
readonly [__brand]: "PublicationID";
|
|
29
|
+
};
|
|
30
|
+
export type MigrationID = string & {
|
|
31
|
+
readonly [__brand]: "MigrationID";
|
|
32
|
+
};
|
|
33
|
+
export declare const id: {
|
|
34
|
+
user: (s: string) => UserID;
|
|
35
|
+
session: (s: string) => SessionID;
|
|
36
|
+
collection: (s: string) => CollectionSlug;
|
|
37
|
+
record: (s: string) => RecordID;
|
|
38
|
+
page: (s: string) => PageID;
|
|
39
|
+
block: (s: string) => BlockSlug;
|
|
40
|
+
draft: (s: string) => DraftID;
|
|
41
|
+
publication: (s: string) => PublicationID;
|
|
42
|
+
migration: (s: string) => MigrationID;
|
|
43
|
+
};
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=ids.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ids.d.ts","sourceRoot":"","sources":["../../src/types/ids.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,CAAC,MAAM,OAAO,EAAE,OAAO,MAAM,CAAA;AAEpC,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAA;CAAE,CAAA;AAC9D,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,CAAA;CAAE,CAAA;AACpE,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,gBAAgB,CAAA;CAAE,CAAA;AAC9E,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,UAAU,CAAA;CAAE,CAAA;AAClE,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAA;CAAE,CAAA;AAC9D,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,WAAW,CAAA;CAAE,CAAA;AACpE,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,SAAS,CAAA;CAAE,CAAA;AAChE,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,eAAe,CAAA;CAAE,CAAA;AAC5E,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,aAAa,CAAA;CAAE,CAAA;AAExE,eAAO,MAAM,EAAE;cACH,MAAM,KAAU,MAAM;iBACnB,MAAM,KAAU,SAAS;oBACtB,MAAM,KAAU,cAAc;gBAClC,MAAM,KAAU,QAAQ;cAC1B,MAAM,KAAU,MAAM;eACrB,MAAM,KAAU,SAAS;eACzB,MAAM,KAAU,OAAO;qBACjB,MAAM,KAAU,aAAa;mBAC/B,MAAM,KAAU,WAAW;CAC3C,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branded ID types so the type system catches "I passed a UserID where a SessionID was expected".
|
|
3
|
+
* Per ADR 0020 (each aggregate has its own identity).
|
|
4
|
+
*/
|
|
5
|
+
export const id = {
|
|
6
|
+
user: (s) => s,
|
|
7
|
+
session: (s) => s,
|
|
8
|
+
collection: (s) => s,
|
|
9
|
+
record: (s) => s,
|
|
10
|
+
page: (s) => s,
|
|
11
|
+
block: (s) => s,
|
|
12
|
+
draft: (s) => s,
|
|
13
|
+
publication: (s) => s,
|
|
14
|
+
migration: (s) => s,
|
|
15
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,WAAW,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { UserID } from "./ids.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The user projection passed to access predicates and hooks.
|
|
4
|
+
* Per ADR 0020 — this is NOT the Better Auth row; it's the Identity-context
|
|
5
|
+
* projection of it that the Content/Authoring/Publishing contexts may see.
|
|
6
|
+
*/
|
|
7
|
+
export interface User {
|
|
8
|
+
readonly id: UserID;
|
|
9
|
+
readonly email: string | null;
|
|
10
|
+
readonly role: Role;
|
|
11
|
+
readonly claims: Readonly<Record<string, unknown>>;
|
|
12
|
+
}
|
|
13
|
+
export type Role = "admin" | "editor" | "viewer" | "user" | (string & {});
|
|
14
|
+
//# sourceMappingURL=user.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/types/user.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAEtC;;;;GAIG;AACH,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CACnD;AAED,MAAM,MAAM,IAAI,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|