@civitai/app-sdk 0.46.0 → 0.48.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.
Files changed (44) hide show
  1. package/README.md +120 -33
  2. package/dist/blocks/appStorageLimits.d.ts +113 -0
  3. package/dist/blocks/appStorageLimits.d.ts.map +1 -0
  4. package/dist/blocks/appStorageLimits.js +113 -0
  5. package/dist/blocks/appStorageLimits.js.map +1 -0
  6. package/dist/blocks/index.d.ts +26 -7
  7. package/dist/blocks/index.d.ts.map +1 -1
  8. package/dist/blocks/index.js +25 -5
  9. package/dist/blocks/index.js.map +1 -1
  10. package/dist/blocks/manifestError.d.ts +20 -0
  11. package/dist/blocks/manifestError.d.ts.map +1 -0
  12. package/dist/blocks/manifestError.js +22 -0
  13. package/dist/blocks/manifestError.js.map +1 -0
  14. package/dist/blocks/messages.d.ts.map +1 -1
  15. package/dist/blocks/messages.js.map +1 -1
  16. package/dist/blocks/scopes.d.ts +7 -3
  17. package/dist/blocks/scopes.d.ts.map +1 -1
  18. package/dist/blocks/scopes.js +7 -3
  19. package/dist/blocks/scopes.js.map +1 -1
  20. package/dist/blocks/types.d.ts +197 -50
  21. package/dist/blocks/types.d.ts.map +1 -1
  22. package/dist/blocks/types.js +56 -0
  23. package/dist/blocks/types.js.map +1 -1
  24. package/dist/manifest/defineBlock.d.ts +145 -0
  25. package/dist/manifest/defineBlock.d.ts.map +1 -0
  26. package/dist/manifest/defineBlock.js +390 -0
  27. package/dist/manifest/defineBlock.js.map +1 -0
  28. package/dist/manifest/index.d.ts +19 -0
  29. package/dist/manifest/index.d.ts.map +1 -0
  30. package/dist/manifest/index.js +17 -0
  31. package/dist/manifest/index.js.map +1 -0
  32. package/dist/oauth/token.d.ts +34 -3
  33. package/dist/oauth/token.d.ts.map +1 -1
  34. package/dist/oauth/token.js +116 -4
  35. package/dist/oauth/token.js.map +1 -1
  36. package/dist/vite/index.d.ts +14 -0
  37. package/dist/vite/index.d.ts.map +1 -0
  38. package/dist/vite/index.js +61 -0
  39. package/dist/vite/index.js.map +1 -0
  40. package/package.json +42 -2
  41. package/dist/blocks/defineBlock.d.ts +0 -53
  42. package/dist/blocks/defineBlock.d.ts.map +0 -1
  43. package/dist/blocks/defineBlock.js +0 -338
  44. package/dist/blocks/defineBlock.js.map +0 -1
package/README.md CHANGED
@@ -19,12 +19,19 @@ pnpm add @civitai/app-sdk
19
19
  | `cookies/*` — `sealCookie`, `unsealCookie`, `buildSetCookieHeader`, `readCookie` | AES-256-GCM authenticated cookie crypto. Use to seal a session blob (refresh token, expiry, scope) into an `httpOnly` cookie with zero external session store. |
20
20
  | `orchestrator/*` — `createOrchestratorClient`, `estimateWorkflow`, `submitWorkflow`, `getWorkflow`, `pollWorkflow`, `buildTextToImageBody`, `buildImageGenBody`, `buildWorkflowBody`, `WORKFLOW_STEP_TYPES`, `IMAGE_GEN_ENGINES`, `isTerminal`, `extractImageUrls`, `OrchestratorError`, `WorkflowSnapshot`, `GenerateInput`, `ImageGenInput`, `WorkflowStepType`, `ImageGenEngine`, `DEFAULT_MODEL_AIR` | Orchestrator workflow glue — types, body builders, raw HTTP, and long-poll helper. Client + server safe (fetch-only). `estimateWorkflow` calls `?whatif=true` to preview Buzz cost without spending. `pollWorkflow` long-polls to terminal status. `WORKFLOW_STEP_TYPES` is the catalog of every step `$type` the orchestrator accepts. |
21
21
  | `orchestrator/steps` — `WorkflowStepTemplates`, `WorkflowStepTemplateFor`, `WorkflowStepInputFor`, `AnyWorkflowStepTemplate`, `TypedWorkflowTemplate` | **Type-only** subpath (0 runtime bytes) keying the orchestrator's generated workflow-step shapes from `@civitai/client` by wire `$type`, so apps compose real step bodies against types that track the spec instead of hand-maintained copies. Requires the optional peer `@civitai/client@beta`. Type surface only — it grants no submit permission; see "Typed step shapes" below. |
22
- | `blocks/*` — `defineBlock`, `BlockManifestError`, `BLOCK_SCOPES`, `BLOCK_SCOPE_PATTERN`, `isMessage`, types (`BlockManifestV1`, `BlockContext`, `BlockToken`, `BlockSettings`, `ViewerInfo`, `ThemeInfo`, `BlockWorkflowSnapshot`, `BlockInitPayload`, `ParentToBlockMessage`, `BlockToParentMessage`, …) | Framework-agnostic contract for [Civitai Apps](https://github.com/civitai/civitai-app-starters/blob/main/docs/build-your-first-app-block.md). `defineBlock(config)` validates a `BlockManifestV1` at startup so authoring mistakes surface in `pnpm dev` instead of at `civitai app validate`/submit. Ships a byte-identical copy of the server-published canonical JSON Schema (draft 2020-12, https://civitai.com/schemas/app-block/v1.json) at the `./schemas/app-block/v1.json` subpath for offline validation; a CI drift-check keeps it in sync. Runtime-agnostic — no React or DOM types. Hooks and the iframe transport live in a separate package. |
22
+ | `blocks/*` — `BlockManifestError`, `BLOCK_SCOPES`, `BLOCK_SCOPE_PATTERN`, `isMessage`, types (`BlockManifestV1`, `BlockContext`, `BlockToken`, `BlockSettings`, `ViewerInfo`, `ThemeInfo`, `BlockWorkflowSnapshot`, `BlockInitPayload`, `ParentToBlockMessage`, `BlockToParentMessage`, …) | Framework-agnostic contract for [Civitai Apps](https://github.com/civitai/civitai-app-starters/blob/main/docs/build-your-first-app-block.md). Ships a byte-identical copy of the server-published canonical JSON Schema (draft 2020-12, https://civitai.com/schemas/app-block/v1.json) at the `./schemas/app-block/v1.json` subpath for offline validation; a CI drift-check keeps it in sync. Runtime-agnostic and **zero runtime dependencies** — no React, no DOM types, nothing in your install graph. Hooks and the iframe transport live in a separate package. |
23
+ | `manifest/*` — `defineBlock`, `SCHEMA_DIVERGENCES`, `KNOWN_GAPS` (**Node only**) | Build-time manifest validation. `defineBlock(config)` compiles the vendored canonical schema with [Ajv](https://ajv.js.org) and validates a `BlockManifestV1` against it, so authoring mistakes surface in `pnpm dev` instead of at `civitai app validate`/submit. It is **derived from the schema, not a hand-written mirror of it** — that mirror is what produced [#330](https://github.com/civitai/civitai-app-starters/issues/330). Needs `node:fs` and the optional peer `ajv`, which is why it is not on the browser-facing `./blocks` surface. |
24
+ | `vite/*` — `blockManifestPlugin` (**Node only**) | A Vite plugin wrapping `defineBlock`, firing from `configResolved` — the one hook Vite calls on both the dev-server and the build path. Every block scaffold registers it, so `pnpm dev`, `pnpm dev:harness` and `pnpm build` all fail on a bad manifest with the offending field path. Optional peers: `ajv` (runtime) and `vite` (types only). |
23
25
 
24
26
  ## Subpath imports
25
27
 
26
28
  ```ts
27
- import { defineBlock, BLOCK_SCOPES } from '@civitai/app-sdk/blocks';
29
+ import { BLOCK_SCOPES, isSignedIn } from '@civitai/app-sdk/blocks';
30
+ // Build-time manifest validation (NODE ONLY — needs the optional peer `ajv`).
31
+ // Most projects want the Vite plugin below rather than calling this directly:
32
+ import { defineBlock } from '@civitai/app-sdk/manifest';
33
+ // The same gate as a Vite plugin (NODE ONLY — optional peers `ajv` + `vite`):
34
+ import { blockManifestPlugin } from '@civitai/app-sdk/vite';
28
35
  // Type-only: the orchestrator's generated per-step shapes. Needs the optional
29
36
  // peer `@civitai/client@beta` — see "Typed step shapes" below:
30
37
  import type { TypedWorkflowTemplate } from '@civitai/app-sdk/orchestrator/steps';
@@ -37,9 +44,11 @@ import manifestSchema from '@civitai/app-sdk/schemas/app-block/v1.json' with { t
37
44
  ## Civitai Apps contract (`@civitai/app-sdk/blocks`)
38
45
 
39
46
  > Building a **Civitai App** (an iframe-embedded UI on a civitai.com page)? This
40
- > subpath is the framework-agnostic contract — manifest types, scope strings,
41
- > the `postMessage` protocol, and the `defineBlock` validator. The React hooks +
42
- > transport that consume it live in
47
+ > subpath is the framework-agnostic contract — manifest types, scope strings and
48
+ > the `postMessage` protocol. (The `defineBlock` validator moved to the node-only
49
+ > [`@civitai/app-sdk/manifest`](#defineblock-validator-rules) subpath; `./blocks`
50
+ > keeps zero runtime dependencies.) The React hooks + transport that consume it
51
+ > live in
43
52
  > [`@civitai/blocks-react`](https://www.npmjs.com/package/@civitai/blocks-react).
44
53
  > Start from the runnable [examples](https://github.com/civitai/civitai-app-starters/tree/main/starters/examples).
45
54
  >
@@ -89,7 +98,7 @@ interface BlockInitPayload {
89
98
  token: WrappedToken; // { raw, scopes[], expiresAt (ISO), buzzBudget? }
90
99
  context: BlockContext; // { slotId, … } — narrow to ModelSlotContext
91
100
  settings: BlockSettings; // { publisherSettings, userSettings }
92
- viewer: ViewerInfo | null; // null = anonymous
101
+ viewer: ViewerInfo | null; // null = anonymous — gate with isSignedIn(viewer)
93
102
  theme: 'light' | 'dark';
94
103
  renderMode: 'iframe' | 'inline';
95
104
  }
@@ -103,9 +112,10 @@ interface BlockInitPayload {
103
112
 
104
113
  | Export | What |
105
114
  |---|---|
106
- | `defineBlock({ manifest })` | Validates a `BlockManifestV1` (subset of the server checks) and returns it. Call at module scope so authoring mistakes throw before mount. Throws `BlockManifestError` (has a `.field` dot-path). |
107
- | `BLOCK_SCOPES` / `BLOCK_SCOPE_PATTERN` | The 15 known block scope strings (the authoritative enum `defineBlock` validates against) + the `domain:verb:target` format-helper regex. A scope is valid only if it's a member of `BLOCK_SCOPES`, matching the [canonical schema](https://civitai.com/schemas/app-block/v1.json). |
115
+ | `BLOCK_SCOPES` / `BLOCK_SCOPE_PATTERN` | The 15 known block scope strings (the authoritative enum the canonical schema validates `scopes` against) + the `domain:verb:target` format-helper regex. A scope is valid only if it's a member of `BLOCK_SCOPES`, matching the [canonical schema](https://civitai.com/schemas/app-block/v1.json). |
108
116
  | `isMessage(data, type)` | Discriminator-only message narrowing (see above). |
117
+ | `isModelSlotContext(ctx)` / `isPageSlotContext(ctx)` | Runtime narrowing for the `slotId`-discriminated `BlockContext` union. Real checks on a value that crossed a `postMessage` boundary — they verify every field they assert, not just `slotId`. |
118
+ | `isSignedIn(viewer)` | **The sign-in gate.** `isSignedIn(useBlockContext().viewer)` — do not open-code it as `viewer !== null` or `viewer?.signedIn === true`. Which of those is correct has already changed once with the host contract, and this is the one place it is decided. It reads neither `viewer.id` nor `viewer.username` (both `@deprecated`), so nothing written through it changes when those are removed. Need the identity rather than the presence? `useViewer()` — scope-gated and audited per call. |
109
119
  | types | `BlockManifestV1`, `ManifestSettings` (+ field types), `BlockContext`, `ModelSlotContext`, `BlockCheckpointInfo`, `ShowcaseImage`, `BlockToken`, `WrappedToken`, `BlockSettings`, `ViewerInfo`, `Theme`, `WorkflowBody`, `BlockTextToImageParams`, `WorkflowBodyCustomComfy` (+ its two arms `WorkflowBodyCustomComfyRecipe` / `WorkflowBodyCustomComfyInline`, and `InlineComfyNode`), `WorkflowBodyStep` / `WorkflowBodyPassThroughStep` (the two arms of `kind: 'step'`), `BlockWorkflowSnapshot`, `WorkflowStatus`, `BlockInitPayload`, `ParentToBlockMessage`, `BlockToParentMessage`. |
110
120
 
111
121
  `WorkflowBody`'s `customComfy` member is a discriminated union on `mode`, mirroring
@@ -152,31 +162,67 @@ const body: WorkflowBody = {
152
162
 
153
163
  ### `defineBlock` validator rules
154
164
 
155
- Mirrors a strict subset of the civitai/civitai server gate. It throws on:
156
-
157
- - A missing **required** field: `$schema`, `appId`, `blockId`, `version`, `name`,
158
- `type`, `targets`, `scopes`, `iframe`, `contentRating`, `minApiVersion`.
159
- - `$schema` ≠ `https://civitai.com/schemas/app-block/v1.json`.
160
- - `blockId` not matching the canonical `/^[a-z][a-z0-9-]*[a-z0-9]$/` (DNS-subdomain-safe:
161
- lowercase, starts with a letter, ends alphanumeric) or outside 3–40 chars —
162
- the blockId becomes `<blockId>.civit.ai`; `version` not semver; `name` > 80 chars.
163
- - `type` not `block` | `embed`; `contentRating` not `g|pg|pg13|r|x`.
164
- - **Empty `scopes`** (must be a non-empty array) or any scope that isn't one of
165
- the 15 known block scopes (`BLOCK_SCOPES`). The [canonical schema](https://civitai.com/schemas/app-block/v1.json)
166
- validates `scopes` by **enum membership**, so a well-formed but unknown scope
167
- (e.g. `models:read:all`) is rejected; PascalCase like `ModelsReadSelf` gets a
168
- pointed error.
169
- - **Empty `targets`**, or a target with a non-string `slotId` / non-integer `priority`.
170
- - `iframe.src` not https (http only for `localhost`/`127.0.0.1`/`[::1]`/`*.localhost`);
171
- a banned sandbox token (`allow-same-origin`, any `allow-top-navigation*`);
172
- non-positive integer `minHeight`; bad `maxHeight`; non-boolean `resizable`.
173
- - `settings` with a bad key (must be `snake_case`), > 32 fields, or a field
174
- missing/mis-typed `scope` / `type` / `label` / `description`.
175
-
176
- > The validator does **not** check that `iframe.src` hostname equals
177
- > `<blockId>.<APPS_DOMAIN>` or that the path is root — those are enforced
178
- > **server-side** at submit time (gotcha #33). Keep `iframe.src` =
179
- > `https://<blockId>.civit.ai/` (root, no path prefix) and your Vite `base: '/'`.
165
+ **There is no rule list here, and that is the point.** `defineBlock` does not
166
+ maintain one: it compiles the vendored copy of the
167
+ [canonical schema](https://civitai.com/schemas/app-block/v1.json) with
168
+ [Ajv](https://ajv.js.org) and validates against **that**. Every `required`,
169
+ `enum`, `pattern`, bound, `additionalProperties` and `allOf` the canonical
170
+ expresses is enforced, and moves when the canonical moves. The schema is the
171
+ rule list; read it, or read the errors.
172
+
173
+ That is [#330](https://github.com/civitai/civitai-app-starters/issues/330)'s own
174
+ proposed fix. The previous implementation hand-mirrored the schema, and the
175
+ mirror diverged exactly as the issue predicted: it required 11 fields where the
176
+ canonical requires **five** (`blockId`, `version`, `name`, `contentRating`,
177
+ `scopes`), required `appId` — not a canonical property at all — and *required*
178
+ the SERVER-OWNED `iframe.src` that `civitai app submit` refuses, so it rejected
179
+ every `block.manifest.json` this repo ships.
180
+
181
+ On top of the schema, `defineBlock` applies exactly five extra rules, each
182
+ mirroring a server rejection the canonical states only in **prose**:
183
+
184
+ | Rule | The canonical prose it mirrors |
185
+ |---|---|
186
+ | a dev-set **`iframe.src`** is rejected | *"SERVER-OWNED. Do NOT set `iframe.src` — the platform assigns it."* The JSON-Schema `not` is deliberately absent; the top-level `allOf` `$comment` says the platform validator and the Go CLI reject it instead. |
187
+ | a dev-set **`trustTier`** is rejected | *"SERVER-OWNED … the platform assigns the trust tier during review."* Same shape. |
188
+ | **`iframe.sandbox`** rejects `allow-same-origin` and every `allow-top-navigation*` token | *"Never combine allow-same-origin with allow-scripts."* Top-navigation is refused because a block must route navigation through the `NAVIGATE` postMessage. |
189
+ | every **`scopeJustifications`** key must be a scope in `scopes` | *"The requirement is enforced imperatively by the manifest validator (not expressed as JSON-Schema conditionals here)."* |
190
+ | **`settings`** is validated against the W3 settings meta-schema | Not in the app-block schema at all — settings are validated server-side by `manifest-settings.meta.schema.ts`. |
191
+
192
+ Each is **strictly additive**: it can only reject a manifest Ajv accepted, never
193
+ relax a canonical rule. `test/manifest/divergences.test.ts` asserts exactly that
194
+ per entry — the fixture must be schema-VALID and `defineBlock`-REJECTED — and the
195
+ table and the fixture set must be the same set, so a sixth hand-written rule with
196
+ no entry fails the suite.
197
+
198
+ > **Passing is necessary, not sufficient**, and it is **not** a replacement for
199
+ > `civitai app validate` (the Go CLI's local pre-check against the same
200
+ > canonical). Run that before `civitai app submit`. `KNOWN_GAPS` in
201
+ > `src/manifest/defineBlock.ts` names each thing only the server can check —
202
+ > including one that cuts the *other* way: the canonical's sandbox description is
203
+ > an **allowlist** (*"Unverified tier allows only: allow-scripts, allow-forms"*)
204
+ > while the rule above is a denylist, so `allow-popups`, `allow-modals` and
205
+ > `allow-downloads` **pass here and may be refused at review**. The tier is
206
+ > assigned server-side, so it cannot be enforced locally.
207
+
208
+ **Where it runs.** Every scaffold that ships a `block.manifest.json`
209
+ (`starters/civitai-block-starter` and all six `starters/examples/*`) registers
210
+ `blockManifestPlugin` from `@civitai/app-sdk/vite` in its `vite.config.ts`. It
211
+ fires from Vite's `configResolved`, the one hook called on both the dev-server
212
+ and the build path — so `pnpm dev`, `pnpm dev:harness` and `pnpm build` all fail
213
+ loudly on a bad manifest, with the offending field path in the message. Add
214
+ `ajv` to your devDependencies (it is an optional peer):
215
+
216
+ ```bash
217
+ pnpm add -D ajv
218
+ ```
219
+
220
+ ```ts
221
+ // vite.config.ts — drop this into your existing `defineConfig({ plugins: [...] })`.
222
+ import { blockManifestPlugin } from '@civitai/app-sdk/vite';
223
+
224
+ const plugins = [blockManifestPlugin()];
225
+ ```
180
226
 
181
227
  ### Web storage in a block (`@civitai/app-sdk/safe-storage`)
182
228
 
@@ -295,6 +341,7 @@ const tokens = await exchangeCode({
295
341
  redirectUri: 'https://your-app.com/api/auth/callback/civitai',
296
342
  code: codeFromQuery,
297
343
  codeVerifier: verifierFromSealedCookie,
344
+ fallbackScope: scope, // used if the response's `scope` is absent or unreadable
298
345
  });
299
346
 
300
347
  // 3. Store tokens in an encrypted httpOnly cookie
@@ -316,6 +363,46 @@ console.log(`Hi ${me.username}`);
316
363
  > explicit `baseUrl` to each call only when targeting a local / self-hosted
317
364
  > instance (e.g. a dev auth hub vs a dev main app).
318
365
 
366
+ > **`fallbackScope`, and what happens to a `scope` we cannot read.** Civitai's
367
+ > token endpoint returns `scope` as a decimal bitmask in a JSON *string*
368
+ > (`"scope": "114689"` — see the
369
+ > [endpoint reference](https://developer.civitai.com/site/oauth/endpoints)),
370
+ > matching the decimal `scope` `buildAuthorizeUrl` puts on the authorize URL,
371
+ > and that is what `exchangeCode` / `refreshToken` read. Anything that is not a
372
+ > whole number in `[0, 2**31-1]` is **not** used: `Number()` of
373
+ > [RFC 6749 §5.1](https://datatracker.ietf.org/doc/html/rfc6749#section-5.1)'s
374
+ > space-delimited form is `NaN`, and `NaN & anything` is `0`, so `hasScope()`
375
+ > would answer `false` for every scope and tell a user who just consented that
376
+ > they granted nothing. A wrong *type* is rejected on the same grounds:
377
+ > `Number(['65537'])` is `65537` and `Number(true)` is `1` (i.e.
378
+ > `TokenScope.UserRead`), so an un-guarded coercion would invent a
379
+ > valid-looking grant rather than fail. Such a value is replaced by
380
+ > `fallbackScope` and a warning naming the value received — not an exception,
381
+ > which on the token path would turn a degraded-but-working session into a hard
382
+ > login failure.
383
+ >
384
+ > Pass `fallbackScope: REQUESTED_SCOPES` on exchange and
385
+ > `fallbackScope: tokens.scope` on refresh — without it, either case resolves to
386
+ > `0`, and a caller that persists the whole refreshed token blob would lock the
387
+ > user out of features their token still grants. `fallbackScope` must itself be
388
+ > a whole number in `[0, 2**31-1]`; `NaN` (what `Number(stored.scope)` gives you
389
+ > on a half-populated store — and `??` does not catch it), a negative, a
390
+ > fraction or an over-ceiling value is **discarded in favour of `0`** with its
391
+ > own warning, rather than being handed back as the scope.
392
+ >
393
+ > **The two fallback paths are not equally sound.** An **omitted** `scope` is
394
+ > not a fault at all: RFC 6749 §5.1/§6 make it optional *precisely when the
395
+ > grant matches the request*, so the requested scope is the granted scope and
396
+ > `fallbackScope` is exactly right — that path is silent. A **present but
397
+ > unreadable** `scope` carries no such guarantee: the server is describing the
398
+ > grant in terms this SDK cannot read, and it may be a *reduced* grant, so
399
+ > falling back to the requested scope can **over-state** what the user granted.
400
+ > All four starters render `scopesFromBitmask(tokens.scope)` to the user as
401
+ > "Granted scopes", so the over-statement is user-visible. It is a deliberate
402
+ > trade (`0` and a thrown error are both worse here), which is why this path
403
+ > always warns — if you gate anything security-relevant on `tokens.scope`,
404
+ > treat that warning as "re-authenticate", not as noise.
405
+
319
406
  ```ts
320
407
 
321
408
  // 5. Estimate cost, then submit
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The App Storage ceilings the host enforces — **the only RUNTIME or
3
+ * DOCUMENTATION site in this repository that spells these numbers**.
4
+ *
5
+ * Every doc site, the mock host, the live dev host and the starter harnesses
6
+ * reference these constants. That is the point of the module: seven
7
+ * hand-copied literals is how the old figures came to agree with each other
8
+ * and disagree with the host by 25x on bytes and 1000x on rows, with nothing
9
+ * in any build, test or type-check able to notice.
10
+ *
11
+ * 🔴 **"Only site" is a claim about what is ENFORCED, and the enforcement is
12
+ * much narrower than the repo.** `tests/guards/app-storage-quota-literals.test.mjs`
13
+ * bans the OLD figures, and pins the derivation at each runtime site, over
14
+ * exactly this surface:
15
+ *
16
+ * - this file and `messages.ts`, here in `src/blocks/`;
17
+ * - `useAppStorage.ts`, `internal/mockHost.ts` and `internal/liveHost.ts` in
18
+ * `packages/civitai-blocks-react` — NAMED FILES, not the package: the
19
+ * banned numbers are the TRUE ceilings of neighbouring features there
20
+ * (the shared-storage app-wide quota, the iframe frame cap, Buzz figures),
21
+ * and a value ban over the package failed the build for saying so;
22
+ * - all of `starters/examples/kv-storage`;
23
+ * - and, in `packages/civitai-blocks-react/README.md`,
24
+ * `packages/civitai-app-sdk/README.md` and `starters/examples/README.md`,
25
+ * ONLY the App-Storage REGION — sections whose heading names App Storage,
26
+ * plus lines that name it themselves. `### useSharedStorage()` in the same
27
+ * file is not read.
28
+ *
29
+ * It does NOT scan, and the rule does not apply to: the guard itself (it spells
30
+ * every figure, as test data), `CHANGELOG.md` and `.changeset/*.md` (history,
31
+ * which must not be rewritten to satisfy a guard), `scripts/`, `tests/`,
32
+ * `claudedocs/`, the root README, other sections of the READMEs above, or any
33
+ * other file in any package. If you are re-deriving after a host move, do not
34
+ * assume those paths are empty — grep them.
35
+ *
36
+ * ## Provenance — measured, not assumed
37
+ *
38
+ * Read from `civitai/civitai` `main` on **2026-09-19** via `gh api` (not a
39
+ * local checkout, which can lag), file
40
+ * `src/server/routers/apps.router.ts`, blob `654f2d6`:
41
+ *
42
+ * ```ts
43
+ * const PER_VALUE_BYTE_CAP = 64 * 1024; // :172
44
+ * const USER_QUOTA_BYTES = 2 * 1024 * 1024; // :204
45
+ * const USER_ROW_LIMIT = 1_000; // :205
46
+ * ```
47
+ *
48
+ * and the `getQuota` procedure returns `limitBytes: USER_QUOTA_BYTES,
49
+ * limitRows: USER_ROW_LIMIT` — so these are exactly the numbers a block reads
50
+ * back from {@link https://github.com/civitai/civitai-app-starters | `useAppStorage().getQuota()`}.
51
+ *
52
+ * Re-derive with:
53
+ *
54
+ * ```sh
55
+ * gh api repos/civitai/civitai/contents/src/server/routers/apps.router.ts \
56
+ * --jq '.content' | base64 -d | grep -n 'USER_QUOTA_BYTES\|USER_ROW_LIMIT\|PER_VALUE_BYTE_CAP'
57
+ * ```
58
+ *
59
+ * 🔴 **DO NOT re-derive the numbers from this comment** — re-read the host.
60
+ * The per-user clamp was sized against a measured distribution (the host's own
61
+ * comment records: 2026-09-09, largest observed per-user footprint 0.65 MiB
62
+ * across 49 rows, `2 MiB` chosen for ~3.1x headroom) and is expected to move
63
+ * again when that distribution does.
64
+ *
65
+ * ## Which scope each number describes
66
+ *
67
+ * 🔴 **THE NAMESPACE AND THE BUDGET HAVE DIFFERENT SCOPES, and the docs used
68
+ * to quote one while naming the other.**
69
+ *
70
+ * - **Namespace** — rows are keyed `(block_instance_id, user_id, key)`. A key
71
+ * written by one block instance is invisible to another. "Per (block
72
+ * instance, viewer)" is correct, and unchanged.
73
+ * - **Budget** — {@link APP_STORAGE_MAX_BYTES} and
74
+ * {@link APP_STORAGE_MAX_ROWS} are enforced per **(app, viewer)**: the host
75
+ * counter is keyed `(app_block_id, user_id)`, so every block instance of the
76
+ * same app draws on ONE budget for that viewer. An app with three instances
77
+ * on a viewer's page shares 1,000 rows between them, not 3,000.
78
+ *
79
+ * A far larger app-wide umbrella also exists above both. It is deliberately
80
+ * NOT exported and its value is deliberately not written here: nothing reports
81
+ * an app's usage against it, so no block can render a meaningful "x of y" for
82
+ * it, and a constant nobody can act on is an invitation to build a UI that
83
+ * lies. The per-viewer clamp below is what a block will actually hit — it is
84
+ * orders of magnitude tighter.
85
+ */
86
+ /**
87
+ * Largest single value one `set()` may send, in **wire** bytes
88
+ * (`JSON.stringify(value)` measured as UTF-8).
89
+ *
90
+ * 🔴 This bounds what one call SENDS, not what it STORES. The host checks it
91
+ * in the wire unit while the byte budget below is enforced in the stored unit,
92
+ * and they diverge: the host records that the largest value this cap admits
93
+ * (65,535 wire bytes) stores 2,911,582 bytes — 44x, and on its own past
94
+ * {@link APP_STORAGE_MAX_BYTES}. So passing this check is not evidence the
95
+ * write will land.
96
+ */
97
+ export declare const APP_STORAGE_MAX_VALUE_BYTES: number;
98
+ /**
99
+ * Total stored bytes one viewer may hold across **all instances of one app**.
100
+ *
101
+ * This is the value `getQuota().limitBytes` reports.
102
+ */
103
+ export declare const APP_STORAGE_MAX_BYTES: number;
104
+ /**
105
+ * Total rows one viewer may hold across **all instances of one app**.
106
+ *
107
+ * This is the value `getQuota().limitRows` reports. It is the ceiling most
108
+ * likely to surprise: a block caching one small row per item a viewer looks at
109
+ * reaches it after a thousand items while using ~0.3 MB — well under
110
+ * {@link APP_STORAGE_MAX_BYTES} — so a byte-only estimate will not predict it.
111
+ */
112
+ export declare const APP_STORAGE_MAX_ROWS = 1000;
113
+ //# sourceMappingURL=appStorageLimits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appStorageLimits.d.ts","sourceRoot":"","sources":["../../src/blocks/appStorageLimits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoFG;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,2BAA2B,QAAY,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,QAAkB,CAAC;AAErD;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,OAAQ,CAAC"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The App Storage ceilings the host enforces — **the only RUNTIME or
3
+ * DOCUMENTATION site in this repository that spells these numbers**.
4
+ *
5
+ * Every doc site, the mock host, the live dev host and the starter harnesses
6
+ * reference these constants. That is the point of the module: seven
7
+ * hand-copied literals is how the old figures came to agree with each other
8
+ * and disagree with the host by 25x on bytes and 1000x on rows, with nothing
9
+ * in any build, test or type-check able to notice.
10
+ *
11
+ * 🔴 **"Only site" is a claim about what is ENFORCED, and the enforcement is
12
+ * much narrower than the repo.** `tests/guards/app-storage-quota-literals.test.mjs`
13
+ * bans the OLD figures, and pins the derivation at each runtime site, over
14
+ * exactly this surface:
15
+ *
16
+ * - this file and `messages.ts`, here in `src/blocks/`;
17
+ * - `useAppStorage.ts`, `internal/mockHost.ts` and `internal/liveHost.ts` in
18
+ * `packages/civitai-blocks-react` — NAMED FILES, not the package: the
19
+ * banned numbers are the TRUE ceilings of neighbouring features there
20
+ * (the shared-storage app-wide quota, the iframe frame cap, Buzz figures),
21
+ * and a value ban over the package failed the build for saying so;
22
+ * - all of `starters/examples/kv-storage`;
23
+ * - and, in `packages/civitai-blocks-react/README.md`,
24
+ * `packages/civitai-app-sdk/README.md` and `starters/examples/README.md`,
25
+ * ONLY the App-Storage REGION — sections whose heading names App Storage,
26
+ * plus lines that name it themselves. `### useSharedStorage()` in the same
27
+ * file is not read.
28
+ *
29
+ * It does NOT scan, and the rule does not apply to: the guard itself (it spells
30
+ * every figure, as test data), `CHANGELOG.md` and `.changeset/*.md` (history,
31
+ * which must not be rewritten to satisfy a guard), `scripts/`, `tests/`,
32
+ * `claudedocs/`, the root README, other sections of the READMEs above, or any
33
+ * other file in any package. If you are re-deriving after a host move, do not
34
+ * assume those paths are empty — grep them.
35
+ *
36
+ * ## Provenance — measured, not assumed
37
+ *
38
+ * Read from `civitai/civitai` `main` on **2026-09-19** via `gh api` (not a
39
+ * local checkout, which can lag), file
40
+ * `src/server/routers/apps.router.ts`, blob `654f2d6`:
41
+ *
42
+ * ```ts
43
+ * const PER_VALUE_BYTE_CAP = 64 * 1024; // :172
44
+ * const USER_QUOTA_BYTES = 2 * 1024 * 1024; // :204
45
+ * const USER_ROW_LIMIT = 1_000; // :205
46
+ * ```
47
+ *
48
+ * and the `getQuota` procedure returns `limitBytes: USER_QUOTA_BYTES,
49
+ * limitRows: USER_ROW_LIMIT` — so these are exactly the numbers a block reads
50
+ * back from {@link https://github.com/civitai/civitai-app-starters | `useAppStorage().getQuota()`}.
51
+ *
52
+ * Re-derive with:
53
+ *
54
+ * ```sh
55
+ * gh api repos/civitai/civitai/contents/src/server/routers/apps.router.ts \
56
+ * --jq '.content' | base64 -d | grep -n 'USER_QUOTA_BYTES\|USER_ROW_LIMIT\|PER_VALUE_BYTE_CAP'
57
+ * ```
58
+ *
59
+ * 🔴 **DO NOT re-derive the numbers from this comment** — re-read the host.
60
+ * The per-user clamp was sized against a measured distribution (the host's own
61
+ * comment records: 2026-09-09, largest observed per-user footprint 0.65 MiB
62
+ * across 49 rows, `2 MiB` chosen for ~3.1x headroom) and is expected to move
63
+ * again when that distribution does.
64
+ *
65
+ * ## Which scope each number describes
66
+ *
67
+ * 🔴 **THE NAMESPACE AND THE BUDGET HAVE DIFFERENT SCOPES, and the docs used
68
+ * to quote one while naming the other.**
69
+ *
70
+ * - **Namespace** — rows are keyed `(block_instance_id, user_id, key)`. A key
71
+ * written by one block instance is invisible to another. "Per (block
72
+ * instance, viewer)" is correct, and unchanged.
73
+ * - **Budget** — {@link APP_STORAGE_MAX_BYTES} and
74
+ * {@link APP_STORAGE_MAX_ROWS} are enforced per **(app, viewer)**: the host
75
+ * counter is keyed `(app_block_id, user_id)`, so every block instance of the
76
+ * same app draws on ONE budget for that viewer. An app with three instances
77
+ * on a viewer's page shares 1,000 rows between them, not 3,000.
78
+ *
79
+ * A far larger app-wide umbrella also exists above both. It is deliberately
80
+ * NOT exported and its value is deliberately not written here: nothing reports
81
+ * an app's usage against it, so no block can render a meaningful "x of y" for
82
+ * it, and a constant nobody can act on is an invitation to build a UI that
83
+ * lies. The per-viewer clamp below is what a block will actually hit — it is
84
+ * orders of magnitude tighter.
85
+ */
86
+ /**
87
+ * Largest single value one `set()` may send, in **wire** bytes
88
+ * (`JSON.stringify(value)` measured as UTF-8).
89
+ *
90
+ * 🔴 This bounds what one call SENDS, not what it STORES. The host checks it
91
+ * in the wire unit while the byte budget below is enforced in the stored unit,
92
+ * and they diverge: the host records that the largest value this cap admits
93
+ * (65,535 wire bytes) stores 2,911,582 bytes — 44x, and on its own past
94
+ * {@link APP_STORAGE_MAX_BYTES}. So passing this check is not evidence the
95
+ * write will land.
96
+ */
97
+ export const APP_STORAGE_MAX_VALUE_BYTES = 64 * 1024;
98
+ /**
99
+ * Total stored bytes one viewer may hold across **all instances of one app**.
100
+ *
101
+ * This is the value `getQuota().limitBytes` reports.
102
+ */
103
+ export const APP_STORAGE_MAX_BYTES = 2 * 1024 * 1024;
104
+ /**
105
+ * Total rows one viewer may hold across **all instances of one app**.
106
+ *
107
+ * This is the value `getQuota().limitRows` reports. It is the ceiling most
108
+ * likely to surprise: a block caching one small row per item a viewer looks at
109
+ * reaches it after a thousand items while using ~0.3 MB — well under
110
+ * {@link APP_STORAGE_MAX_BYTES} — so a byte-only estimate will not predict it.
111
+ */
112
+ export const APP_STORAGE_MAX_ROWS = 1_000;
113
+ //# sourceMappingURL=appStorageLimits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appStorageLimits.js","sourceRoot":"","sources":["../../src/blocks/appStorageLimits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoFG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,EAAE,GAAG,IAAI,CAAC;AAErD;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAErD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,CAAC"}
@@ -1,20 +1,33 @@
1
1
  /**
2
2
  * `@civitai/app-sdk/blocks` — framework-agnostic contract for Civitai Apps.
3
3
  *
4
- * This subpath exports the manifest type, scope strings, postMessage protocol,
5
- * and the `defineBlock` validator. Hooks and transport implementations live in
6
- * a separate package (see `@civitai/blocks-react`) so this module stays usable
7
- * from any runtime — Node, browsers, workers — with no React dependency.
4
+ * This subpath exports the manifest type, scope strings and postMessage
5
+ * protocol. Hooks and transport implementations live in a separate package (see
6
+ * `@civitai/blocks-react`) so this module stays usable from any runtime — Node,
7
+ * browsers, workers — with no React dependency and no runtime dependencies at
8
+ * all. Build-time manifest validation lives at `@civitai/app-sdk/manifest`
9
+ * (node-only); see the note on `BlockManifestError` below.
8
10
  */
9
11
  import '../safe-storage/index.js';
10
12
  export { installSafeStorage, createMemoryStorage } from '../safe-storage/index.js';
11
13
  export type { SafeStorageInstallResult, SafeStorageName } from '../safe-storage/index.js';
12
- export { defineBlock, BlockManifestError } from './defineBlock.js';
13
- export type { DefineBlockConfig } from './defineBlock.js';
14
+ /**
15
+ * `defineBlock` MOVED to `@civitai/app-sdk/manifest` (a NODE-ONLY subpath) in
16
+ * the release that closed #330. It now validates by compiling the vendored
17
+ * canonical schema with Ajv instead of maintaining a hand-written mirror of it,
18
+ * which needs `node:fs` and a runtime dependency — neither of which belongs on
19
+ * this browser-facing, zero-dependency surface. Most callers want the Vite
20
+ * plugin at `@civitai/app-sdk/vite` rather than the function.
21
+ *
22
+ * `BlockManifestError` stays exported here, from its own module, so
23
+ * `instanceof` means the same thing on both subpaths.
24
+ */
25
+ export { BlockManifestError } from './manifestError.js';
14
26
  export { BLOCK_SCOPES, BLOCK_SCOPE_PATTERN, BLOCK_CATEGORIES, BLOCK_TAGLINE_MAX_LENGTH, } from './scopes.js';
15
27
  export type { BlockScope, BlockScopeKey, BlockCategory } from './scopes.js';
16
28
  export { BrowsingLevel, SFW_LEVELS, NSFW_LEVELS, isSfwCeiling, isLevelAllowed, effectiveBrowsingCeiling, } from './browsingLevel.js';
17
29
  export type { BrowsingLevelKey, BrowsingLevelBit, ColorDomain } from './browsingLevel.js';
30
+ export { APP_STORAGE_MAX_VALUE_BYTES, APP_STORAGE_MAX_BYTES, APP_STORAGE_MAX_ROWS, } from './appStorageLimits.js';
18
31
  export { BLOCK_INIT_FRAGMENT_MARKER_KEY, BLOCK_INIT_FRAGMENT_VERSION, BLOCK_INIT_FRAGMENT_KEYS, encodeBlockInitFragment, parseBlockInitFragment, stripBlockInitFragment, } from './initFragment.js';
19
32
  export type { BlockInitFragment } from './initFragment.js';
20
33
  export { isMessage, BLOCK_TO_PARENT_MESSAGE_TYPES, OTHER_MESSAGE_TYPE_LABEL, boundBlockToParentMessageType, } from './messages.js';
@@ -25,5 +38,11 @@ export type { BlockInitPayload, BlockToParentMessage, BlockToParentMessageType,
25
38
  * static shape is a claim the guard is what actually checks.
26
39
  */
27
40
  export { isModelSlotContext, isPageSlotContext } from './types.js';
28
- export type { BlockContext, KnownSlotId, ModelSlotId, PageSlotContext, PageSlotId, UnknownSlotContext, BlockManifest, BlockManifestV1, BlockSettings, BlockToken, ContentRating, ManifestAsset, ManifestBooleanField, ManifestIframe, ManifestNumberField, ManifestPreview, ManifestSettingField, ManifestSettings, ManifestStringField, ManifestTarget, ModelSlotContext, SettingScope, SettingWidget, Theme, ViewerInfo, BlockCheckpointInfo, BlockResourceInfo, BlockResourcePickerType, BlockSourceImage, BlockUploadedImageInfo, BlockGenerationSourceImageInfo, BlockPendingImageInfo, BlockImageScanResult, BlockUploadPurpose, BlockTextToImageParams, BlockWorkflowSnapshot, BuzzAccountType, ShowcaseImage, WorkflowBody, WorkflowBodyTextToImage, WorkflowBodyCustomComfy, WorkflowBodyCustomComfyRecipe, WorkflowBodyCustomComfyInline, InlineComfyNode, WorkflowBodyStep, WorkflowBodyPassThroughStep, WorkflowStatus, BlockBuzzTransaction, BlockBuzzAccount, BlockDailyCompensationResource, BlockViewer, BlockWildcardPack, BlockWildcardPackErrorCode, AppWorkflow, AppWorkflowImage, BlockGatedImage, BlockCollectionFollowErrorCode, BlockCollectionFollowResult, BlockPostSource, BlockCreatePostRequest, BlockCreatePostResult, BlockCreatePostHostError, } from './types.js';
41
+ /**
42
+ * The sign-in gate, spelled once. Blocks, docs and starters call this instead
43
+ * of open-coding `viewer !== null` or `viewer?.signedIn === true`; see its doc
44
+ * in `types.ts` for which of the two it uses and why.
45
+ */
46
+ export { isSignedIn } from './types.js';
47
+ export type { BlockContext, KnownSlotId, ModelSlotId, PageSlotContext, PageSlotId, UnknownSlotContext, BlockManifest, BlockManifestV1, BlockSettings, BlockToken, ContentRating, ManifestBooleanField, ManifestIframe, ManifestNumberField, ManifestPage, ManifestPreview, ManifestSettingField, ManifestSettings, ManifestStringField, ManifestTarget, ModelSlotContext, SettingScope, SettingWidget, Theme, ViewerInfo, BlockCheckpointInfo, BlockResourceInfo, BlockResourcePickerType, BlockSourceImage, BlockUploadedImageInfo, BlockGenerationSourceImageInfo, BlockPendingImageInfo, BlockImageScanResult, BlockUploadPurpose, BlockTextToImageParams, BlockWorkflowSnapshot, BuzzAccountType, ShowcaseImage, WorkflowBody, WorkflowBodyTextToImage, WorkflowBodyCustomComfy, WorkflowBodyCustomComfyRecipe, WorkflowBodyCustomComfyInline, InlineComfyNode, WorkflowBodyStep, WorkflowBodyPassThroughStep, WorkflowStatus, BlockBuzzTransaction, BlockBuzzAccount, BlockDailyCompensationResource, BlockViewer, BlockWildcardPack, BlockWildcardPackErrorCode, AppWorkflow, AppWorkflowImage, BlockGatedImage, BlockCollectionFollowErrorCode, BlockCollectionFollowResult, BlockPostSource, BlockCreatePostRequest, BlockCreatePostResult, BlockCreatePostHostError, } from './types.js';
29
48
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/blocks/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,OAAO,0BAA0B,CAAC;AAElC,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACnF,YAAY,EAAE,wBAAwB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE1F,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACnE,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5E,OAAO,EACL,aAAa,EACb,UAAU,EACV,WAAW,EACX,YAAY,EACZ,cAAc,EACd,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAE1F,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,EAC3B,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EACL,SAAS,EACT,6BAA6B,EAC7B,wBAAwB,EACxB,6BAA6B,GAC9B,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,gBAAgB,EAChB,oBAAoB,EACpB,wBAAwB,EACxB,yBAAyB,EACzB,wBAAwB,EACxB,oBAAoB,EACpB,wBAAwB,EACxB,qBAAqB,EACrB,kBAAkB,EAClB,YAAY,EACZ,2BAA2B,EAC3B,4BAA4B,EAC5B,kBAAkB,GACnB,MAAM,eAAe,CAAC;AAEvB;;;;GAIG;AACH,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEnE,YAAY,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,aAAa,EACb,UAAU,EACV,aAAa,EACb,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,KAAK,EACL,UAAU,EACV,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,EACvB,gBAAgB,EAChB,sBAAsB,EACtB,8BAA8B,EAC9B,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,eAAe,EACf,aAAa,EACb,YAAY,EACZ,uBAAuB,EACvB,uBAAuB,EACvB,6BAA6B,EAC7B,6BAA6B,EAC7B,eAAe,EACf,gBAAgB,EAChB,2BAA2B,EAC3B,cAAc,EACd,oBAAoB,EACpB,gBAAgB,EAChB,8BAA8B,EAC9B,WAAW,EACX,iBAAiB,EACjB,0BAA0B,EAC1B,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,8BAA8B,EAC9B,2BAA2B,EAC3B,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/blocks/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH,OAAO,0BAA0B,CAAC;AAElC,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACnF,YAAY,EAAE,wBAAwB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE1F;;;;;;;;;;GAUG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5E,OAAO,EACL,aAAa,EACb,UAAU,EACV,WAAW,EACX,YAAY,EACZ,cAAc,EACd,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAE1F,OAAO,EACL,2BAA2B,EAC3B,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,EAC3B,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EACL,SAAS,EACT,6BAA6B,EAC7B,wBAAwB,EACxB,6BAA6B,GAC9B,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,gBAAgB,EAChB,oBAAoB,EACpB,wBAAwB,EACxB,yBAAyB,EACzB,wBAAwB,EACxB,oBAAoB,EACpB,wBAAwB,EACxB,qBAAqB,EACrB,kBAAkB,EAClB,YAAY,EACZ,2BAA2B,EAC3B,4BAA4B,EAC5B,kBAAkB,GACnB,MAAM,eAAe,CAAC;AAEvB;;;;GAIG;AACH,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEnE;;;;GAIG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,YAAY,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EACX,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,aAAa,EACb,UAAU,EACV,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,mBAAmB,EACnB,YAAY,EACZ,eAAe,EACf,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,KAAK,EACL,UAAU,EACV,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,EACvB,gBAAgB,EAChB,sBAAsB,EACtB,8BAA8B,EAC9B,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,eAAe,EACf,aAAa,EACb,YAAY,EACZ,uBAAuB,EACvB,uBAAuB,EACvB,6BAA6B,EAC7B,6BAA6B,EAC7B,eAAe,EACf,gBAAgB,EAChB,2BAA2B,EAC3B,cAAc,EACd,oBAAoB,EACpB,gBAAgB,EAChB,8BAA8B,EAC9B,WAAW,EACX,iBAAiB,EACjB,0BAA0B,EAC1B,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,8BAA8B,EAC9B,2BAA2B,EAC3B,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,YAAY,CAAC"}
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * `@civitai/app-sdk/blocks` — framework-agnostic contract for Civitai Apps.
3
3
  *
4
- * This subpath exports the manifest type, scope strings, postMessage protocol,
5
- * and the `defineBlock` validator. Hooks and transport implementations live in
6
- * a separate package (see `@civitai/blocks-react`) so this module stays usable
7
- * from any runtime — Node, browsers, workers — with no React dependency.
4
+ * This subpath exports the manifest type, scope strings and postMessage
5
+ * protocol. Hooks and transport implementations live in a separate package (see
6
+ * `@civitai/blocks-react`) so this module stays usable from any runtime — Node,
7
+ * browsers, workers — with no React dependency and no runtime dependencies at
8
+ * all. Build-time manifest validation lives at `@civitai/app-sdk/manifest`
9
+ * (node-only); see the note on `BlockManifestError` below.
8
10
  */
9
11
  // FIRST import, on purpose. Blocks are framed at an opaque origin (sandbox
10
12
  // without `allow-same-origin`), where touching `localStorage`/`sessionStorage`
@@ -14,9 +16,21 @@
14
16
  // or is absent (Node/SSR/workers); see `../safe-storage/index.ts`.
15
17
  import '../safe-storage/index.js';
16
18
  export { installSafeStorage, createMemoryStorage } from '../safe-storage/index.js';
17
- export { defineBlock, BlockManifestError } from './defineBlock.js';
19
+ /**
20
+ * `defineBlock` MOVED to `@civitai/app-sdk/manifest` (a NODE-ONLY subpath) in
21
+ * the release that closed #330. It now validates by compiling the vendored
22
+ * canonical schema with Ajv instead of maintaining a hand-written mirror of it,
23
+ * which needs `node:fs` and a runtime dependency — neither of which belongs on
24
+ * this browser-facing, zero-dependency surface. Most callers want the Vite
25
+ * plugin at `@civitai/app-sdk/vite` rather than the function.
26
+ *
27
+ * `BlockManifestError` stays exported here, from its own module, so
28
+ * `instanceof` means the same thing on both subpaths.
29
+ */
30
+ export { BlockManifestError } from './manifestError.js';
18
31
  export { BLOCK_SCOPES, BLOCK_SCOPE_PATTERN, BLOCK_CATEGORIES, BLOCK_TAGLINE_MAX_LENGTH, } from './scopes.js';
19
32
  export { BrowsingLevel, SFW_LEVELS, NSFW_LEVELS, isSfwCeiling, isLevelAllowed, effectiveBrowsingCeiling, } from './browsingLevel.js';
33
+ export { APP_STORAGE_MAX_VALUE_BYTES, APP_STORAGE_MAX_BYTES, APP_STORAGE_MAX_ROWS, } from './appStorageLimits.js';
20
34
  export { BLOCK_INIT_FRAGMENT_MARKER_KEY, BLOCK_INIT_FRAGMENT_VERSION, BLOCK_INIT_FRAGMENT_KEYS, encodeBlockInitFragment, parseBlockInitFragment, stripBlockInitFragment, } from './initFragment.js';
21
35
  export { isMessage, BLOCK_TO_PARENT_MESSAGE_TYPES, OTHER_MESSAGE_TYPE_LABEL, boundBlockToParentMessageType, } from './messages.js';
22
36
  /**
@@ -25,4 +39,10 @@ export { isMessage, BLOCK_TO_PARENT_MESSAGE_TYPES, OTHER_MESSAGE_TYPE_LABEL, bou
25
39
  * static shape is a claim the guard is what actually checks.
26
40
  */
27
41
  export { isModelSlotContext, isPageSlotContext } from './types.js';
42
+ /**
43
+ * The sign-in gate, spelled once. Blocks, docs and starters call this instead
44
+ * of open-coding `viewer !== null` or `viewer?.signedIn === true`; see its doc
45
+ * in `types.ts` for which of the two it uses and why.
46
+ */
47
+ export { isSignedIn } from './types.js';
28
48
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/blocks/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,2EAA2E;AAC3E,+EAA+E;AAC/E,6EAA6E;AAC7E,4EAA4E;AAC5E,8EAA8E;AAC9E,mEAAmE;AACnE,OAAO,0BAA0B,CAAC;AAElC,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAGnF,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAGnE,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,aAAa,EACb,UAAU,EACV,WAAW,EACX,YAAY,EACZ,cAAc,EACd,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,EAC3B,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,SAAS,EACT,6BAA6B,EAC7B,wBAAwB,EACxB,6BAA6B,GAC9B,MAAM,eAAe,CAAC;AAiBvB;;;;GAIG;AACH,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/blocks/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,2EAA2E;AAC3E,+EAA+E;AAC/E,6EAA6E;AAC7E,4EAA4E;AAC5E,8EAA8E;AAC9E,mEAAmE;AACnE,OAAO,0BAA0B,CAAC;AAElC,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAGnF;;;;;;;;;;GAUG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,aAAa,EACb,UAAU,EACV,WAAW,EACX,YAAY,EACZ,cAAc,EACd,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,2BAA2B,EAC3B,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,EAC3B,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,SAAS,EACT,6BAA6B,EAC7B,wBAAwB,EACxB,6BAA6B,GAC9B,MAAM,eAAe,CAAC;AAiBvB;;;;GAIG;AACH,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEnE;;;;GAIG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The manifest-validation error type, in its own zero-dependency module.
3
+ *
4
+ * It lives here rather than beside `defineBlock` because `defineBlock` now
5
+ * lives at the node-only `@civitai/app-sdk/manifest` subpath (it reads the
6
+ * vendored canonical schema off disk and compiles it with Ajv). The error class
7
+ * has to stay importable from the browser-safe `./blocks` surface so that
8
+ * `instanceof BlockManifestError` means the same thing on both sides — one
9
+ * class, one module, no duplicate identity.
10
+ */
11
+ /** Thrown by `defineBlock` for any manifest violation. */
12
+ export declare class BlockManifestError extends Error {
13
+ /** Dot-path to the offending field, e.g. `iframe.sandbox`. */
14
+ readonly field?: string | undefined;
15
+ readonly name = "BlockManifestError";
16
+ constructor(message: string,
17
+ /** Dot-path to the offending field, e.g. `iframe.sandbox`. */
18
+ field?: string | undefined);
19
+ }
20
+ //# sourceMappingURL=manifestError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifestError.d.ts","sourceRoot":"","sources":["../../src/blocks/manifestError.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,0DAA0D;AAC1D,qBAAa,kBAAmB,SAAQ,KAAK;IAIzC,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM;IAJzB,SAAkB,IAAI,wBAAwB;gBAE5C,OAAO,EAAE,MAAM;IACf,8DAA8D;IACrD,KAAK,CAAC,EAAE,MAAM,YAAA;CAI1B"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The manifest-validation error type, in its own zero-dependency module.
3
+ *
4
+ * It lives here rather than beside `defineBlock` because `defineBlock` now
5
+ * lives at the node-only `@civitai/app-sdk/manifest` subpath (it reads the
6
+ * vendored canonical schema off disk and compiles it with Ajv). The error class
7
+ * has to stay importable from the browser-safe `./blocks` surface so that
8
+ * `instanceof BlockManifestError` means the same thing on both sides — one
9
+ * class, one module, no duplicate identity.
10
+ */
11
+ /** Thrown by `defineBlock` for any manifest violation. */
12
+ export class BlockManifestError extends Error {
13
+ field;
14
+ name = 'BlockManifestError';
15
+ constructor(message,
16
+ /** Dot-path to the offending field, e.g. `iframe.sandbox`. */
17
+ field) {
18
+ super(message);
19
+ this.field = field;
20
+ }
21
+ }
22
+ //# sourceMappingURL=manifestError.js.map