@civitai/app-sdk 0.61.0 → 0.63.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.
@@ -109,7 +109,7 @@ export { isModelSlotContext, isPageSlotContext } from './types.js';
109
109
  * in `types.ts` for which of the two it uses and why.
110
110
  */
111
111
  export { isSignedIn } from './types.js';
112
- export type { BlockContext, KnownSlotId, ModelSlotId, PageSlotContext, PageSlotId, UnknownSlotContext, BlockManifest, BlockManifestGood, 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, BlockNavigateScope, BlockTextToImageParams, BlockWorkflowSnapshot, BlockModelSubstitution, ModelSubstitutionReason, BlockStepToolCall, BlockTrainedEpoch, BlockPublishedModel, 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, WorkflowBodyTraining, AiToolkitTrainingParams, AiToolkitTrainingParamsBase, AiToolkitPlainEcosystem, AiToolkitLrScheduler, AiToolkitOptimizerType, BlockTrainingQuote, BlockTrainingDatasetItem, BlockTrainingDatasetResult, BlockTrainingRejectionReason, BlockPrepareTrainingDatasetHostError, BlockRunTrainingHostError, } from './types.js';
112
+ export type { BlockContext, KnownSlotId, ModelSlotId, PageSlotContext, PageSlotId, UnknownSlotContext, BlockAnalyticsDeclaration, BlockAnalyticsEventDeclaration, BlockAnalyticsPropertyDeclaration, BlockManifest, BlockManifestGood, 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, BlockNavigateScope, BlockTextToImageParams, BlockWorkflowSnapshot, BlockModelSubstitution, ModelSubstitutionReason, BlockStepToolCall, BlockTrainedEpoch, BlockPublishedModel, 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, WorkflowBodyTraining, AiToolkitTrainingParams, AiToolkitTrainingParamsBase, AiToolkitPlainEcosystem, AiToolkitLrScheduler, AiToolkitOptimizerType, BlockTrainingQuote, BlockTrainingDatasetItem, BlockTrainingDatasetResult, BlockTrainingRejectionReason, BlockPrepareTrainingDatasetHostError, BlockRunTrainingHostError, } from './types.js';
113
113
  /**
114
114
  * Host bounds for the App Blocks `kind: 'training'` flow, mirrored from
115
115
  * civitai/civitai's zod — see `WorkflowBodyTraining`.
@@ -521,6 +521,8 @@ export type ParentToBlockMessage = {
521
521
  payload: {
522
522
  requestId: string;
523
523
  selected?: BlockUploadedImageInfo | BlockGenerationSourceImageInfo | BlockPendingImageInfo;
524
+ /** A `bytes` upload's refusal or failure: a host string or a forwarded server message. */
525
+ error?: string;
524
526
  };
525
527
  } | {
526
528
  type: 'IMAGE_SCAN_RESOLVED';
@@ -836,6 +838,10 @@ export type BlockToParentMessage = {
836
838
  requestId: string;
837
839
  purpose?: BlockUploadPurpose;
838
840
  asyncScan?: boolean;
841
+ /** In-tab image bytes; no picker. Page apps only. See the comment above. */
842
+ bytes?: ArrayBuffer;
843
+ /** Optional name for a `bytes` upload (host-sanitised). */
844
+ filename?: string;
839
845
  };
840
846
  } | {
841
847
  type: 'SET_USER_CHECKPOINT';
@@ -1859,6 +1859,57 @@ export interface ManifestPreview {
1859
1859
  description: string;
1860
1860
  screenshots?: string[];
1861
1861
  }
1862
+ /**
1863
+ * One declared property of a custom event — see {@link BlockManifestV1.analytics}.
1864
+ *
1865
+ * Exactly three types, and deliberately NO free-text string:
1866
+ *
1867
+ * - `enum` — a string drawn from `values`: 1 to 50 distinct, non-empty strings of
1868
+ * at most 64 characters (Unicode code points) each. A string that is not one of
1869
+ * the declared values will never be recorded.
1870
+ * - `number` — not range-limited, so do not use one to carry an identifier.
1871
+ * - `boolean`.
1872
+ *
1873
+ * No other key is allowed on a declaration (`values` only on an `enum`).
1874
+ * Mirrors the canonical schema's `analytics.events.*.properties.*`.
1875
+ */
1876
+ export type BlockAnalyticsPropertyDeclaration = {
1877
+ type: 'enum';
1878
+ values: string[];
1879
+ } | {
1880
+ type: 'number';
1881
+ } | {
1882
+ type: 'boolean';
1883
+ };
1884
+ /**
1885
+ * One declared custom event — see {@link BlockManifestV1.analytics}. Mirrors the
1886
+ * canonical schema's `analytics.events.*`.
1887
+ */
1888
+ export interface BlockAnalyticsEventDeclaration {
1889
+ /**
1890
+ * Optional human-readable meaning of the event, shown to reviewers and in
1891
+ * your analytics. At most 200 characters (Unicode code points).
1892
+ */
1893
+ description?: string;
1894
+ /**
1895
+ * Property name → declaration. At most 10 properties per event. A property
1896
+ * name follows the same rule as an event name: lowercase snake_case, a letter
1897
+ * then up to 63 of `a-z`, `0-9` or `_` (`^[a-z][a-z0-9_]{0,63}$`).
1898
+ */
1899
+ properties?: Record<string, BlockAnalyticsPropertyDeclaration>;
1900
+ }
1901
+ /**
1902
+ * The manifest's `analytics` object — see {@link BlockManifestV1.analytics}.
1903
+ * Mirrors the canonical schema's `analytics`; no other key is allowed.
1904
+ */
1905
+ export interface BlockAnalyticsDeclaration {
1906
+ /**
1907
+ * Event name → declaration. At most 50 events. An event name is lowercase
1908
+ * snake_case: a letter, then up to 63 of `a-z`, `0-9` or `_`
1909
+ * (`^[a-z][a-z0-9_]{0,63}$`).
1910
+ */
1911
+ events?: Record<string, BlockAnalyticsEventDeclaration>;
1912
+ }
1862
1913
  /**
1863
1914
  * One entry of a manifest's `goods[]`. Mirrors the canonical schema exactly;
1864
1915
  * `id`, `title` and `priceBuzz` are required there and so are they here.
@@ -1978,6 +2029,26 @@ export interface BlockManifestV1 {
1978
2029
  * Kept in lockstep with the canonical schema's `goods`.
1979
2030
  */
1980
2031
  goods?: BlockManifestGood[];
2032
+ /**
2033
+ * Optional CUSTOM EVENTS declaration. `events` names every event the app may
2034
+ * send and the properties each event may carry. Manifest-governed and
2035
+ * REVIEW-GATED: the declaration a moderator approves is the only one the
2036
+ * platform accepts events against, so a reviewer can see what the app tracks.
2037
+ *
2038
+ * Properties are typed `enum` (a declared list of string values), `number` or
2039
+ * `boolean` ONLY — free-text strings are deliberately not supported, so a
2040
+ * string that is not one of an enum's declared values will never be recorded.
2041
+ * That is what keeps prompts, emails and usernames out of string properties.
2042
+ * Owners see aggregates only, never individual events.
2043
+ *
2044
+ * Limits (the canonical schema's, which this type cannot express): event and
2045
+ * property names match `^[a-z][a-z0-9_]{0,63}$`; at most 50 events and 10
2046
+ * properties per event; an enum declares 1 to 50 distinct non-empty values of
2047
+ * at most 64 code points; a `description` is at most 200 code points. The
2048
+ * platform's manifest validator is authoritative. Kept in lockstep with the
2049
+ * canonical schema's `analytics`.
2050
+ */
2051
+ analytics?: BlockAnalyticsDeclaration;
1981
2052
  /** Optional; the canonical declares no required sub-field. */
1982
2053
  iframe?: ManifestIframe;
1983
2054
  /** Full-page surface descriptor (W10). */
@@ -2429,7 +2500,11 @@ export type BlockPostSource = {
2429
2500
  imageIndexes?: number[];
2430
2501
  } | {
2431
2502
  kind: 'published';
2432
- /** Ids returned by a previous `usePublishGenerationOutputs()` publish. */
2503
+ /**
2504
+ * Ids returned by a previous `usePublishGenerationOutputs()` publish, or
2505
+ * by an in-tab `bytes` upload (`useUploadImageBytes()` in
2506
+ * `@civitai/blocks-react`; page apps only).
2507
+ */
2433
2508
  imageIds: number[];
2434
2509
  };
2435
2510
  /**
package/dist/index.d.ts CHANGED
@@ -25,11 +25,11 @@
25
25
  *
26
26
  * - **`./blocks`** — importing it RUNS `./safe-storage` for its side effect
27
27
  * (repairing `localStorage`/`sessionStorage` at an opaque origin). Folding
28
- * 154 symbols and that side effect into the root would make every OAuth app
28
+ * 157 symbols and that side effect into the root would make every OAuth app
29
29
  * that writes `import { exchangeCode } from '@civitai/app-sdk'` pay for the
30
30
  * Civitai-Apps contract it does not use. Blocks and OAuth apps are disjoint
31
31
  * audiences; see this package's README.
32
- * 🔴 That 154 is ASSERTED, not merely quoted — by
32
+ * 🔴 That 157 is ASSERTED, not merely quoted — by
33
33
  * `packages/civitai-app-sdk/test/export-surface.test.ts`, which pins
34
34
  * `surface.get('./blocks').size`. It said 119 here against a real 125 until
35
35
  * 2026-10-04, because the existing checks pin the root as an exact union and
package/dist/index.js CHANGED
@@ -25,11 +25,11 @@
25
25
  *
26
26
  * - **`./blocks`** — importing it RUNS `./safe-storage` for its side effect
27
27
  * (repairing `localStorage`/`sessionStorage` at an opaque origin). Folding
28
- * 154 symbols and that side effect into the root would make every OAuth app
28
+ * 157 symbols and that side effect into the root would make every OAuth app
29
29
  * that writes `import { exchangeCode } from '@civitai/app-sdk'` pay for the
30
30
  * Civitai-Apps contract it does not use. Blocks and OAuth apps are disjoint
31
31
  * audiences; see this package's README.
32
- * 🔴 That 154 is ASSERTED, not merely quoted — by
32
+ * 🔴 That 157 is ASSERTED, not merely quoted — by
33
33
  * `packages/civitai-app-sdk/test/export-surface.test.ts`, which pins
34
34
  * `surface.get('./blocks').size`. It said 119 here against a real 125 until
35
35
  * 2026-10-04, because the existing checks pin the root as an exact union and
@@ -45,6 +45,7 @@ import { readFileSync } from 'node:fs';
45
45
  import Ajv2020 from 'ajv/dist/2020.js';
46
46
  import { BlockManifestError } from '../blocks/manifestError.js';
47
47
  import { BLOCK_SCOPES } from '../blocks/scopes.js';
48
+ import { preferDiscriminatedBranch } from './discriminatedBranch.js';
48
49
  /**
49
50
  * Rules `defineBlock` applies that the canonical schema does NOT express in
50
51
  * machine-checkable form.
@@ -173,7 +174,11 @@ function canonicalValidator() {
173
174
  // scaffold. The gap is recorded in KNOWN_GAPS, not shouted at the author on
174
175
  // every boot. NOTE this silences Ajv's own diagnostics only; validation
175
176
  // errors are returned in `validate.errors`, never logged.
176
- const ajv = new Ajv2020({ allErrors: true, strict: false, logger: false });
177
+ //
178
+ // `verbose: true` puts the failed keyword's own `schema` and the offending
179
+ // `data` on each error. `preferDiscriminatedBranch` reads both off a failed
180
+ // `oneOf` / `anyOf`; it changes which error is REPORTED, never the verdict.
181
+ const ajv = new Ajv2020({ allErrors: true, strict: false, logger: false, verbose: true });
177
182
  compiled = ajv.compile(loadCanonicalSchema());
178
183
  return compiled;
179
184
  }
@@ -194,12 +199,14 @@ function toDotPath(instancePath) {
194
199
  }
195
200
  const PASCAL_CASE_PATTERN = /^[A-Z][A-Za-z0-9]*$/;
196
201
  /**
197
- * Turns the FIRST Ajv error into a `BlockManifestError`. Ajv's own wording is
198
- * kept (it is derived from the schema, so it cannot drift); only the field path
199
- * is reshaped, and one case gets an extra hint because it is the mistake the
202
+ * Turns ONE Ajv error (the one {@link preferDiscriminatedBranch} picked) into a
203
+ * `BlockManifestError`. Ajv's own wording is kept (it is derived from the
204
+ * schema, so it cannot drift); only the field path is reshaped, and two cases
205
+ * get extra wording: an unknown key inside a discriminated branch says which
206
+ * branch, and a PascalCase scope gets a hint because it is the mistake the
200
207
  * docs see most.
201
208
  */
202
- function toManifestError(error, manifest) {
209
+ function toManifestError(error, manifest, discriminator) {
203
210
  const base = toDotPath(error.instancePath);
204
211
  if (error.keyword === 'required') {
205
212
  const missing = error.params.missingProperty;
@@ -216,7 +223,10 @@ function toManifestError(error, manifest) {
216
223
  }
217
224
  if (error.keyword === 'additionalProperties') {
218
225
  const extra = error.params.additionalProperty;
219
- message = `${shown}.${extra} is not a known property here`;
226
+ message = discriminator
227
+ ? `${shown}.${extra} is not a known property when \`${discriminator.key}\` is ` +
228
+ JSON.stringify(discriminator.value)
229
+ : `${shown}.${extra} is not a known property here`;
220
230
  return new BlockManifestError(message, field === '' ? extra : `${field}.${extra}`);
221
231
  }
222
232
  // The one hint worth adding: block scopes are colon-separated lowercase, and
@@ -281,12 +291,11 @@ export function defineBlock(config) {
281
291
  // whatever Ajv rejects, this rejects.
282
292
  const validate = canonicalValidator();
283
293
  if (!validate(manifest)) {
284
- const errors = validate.errors ?? [];
285
- const first = errors[0];
294
+ const picked = preferDiscriminatedBranch(validate.errors ?? []);
286
295
  /* c8 ignore next */
287
- if (!first)
296
+ if (!picked)
288
297
  throw new BlockManifestError('manifest failed schema validation');
289
- throw toManifestError(first, manifest);
298
+ throw toManifestError(picked.error, manifest, picked.discriminator);
290
299
  }
291
300
  // (2) The prose-only server rejections. Strictly additive, one per
292
301
  // SCHEMA_DIVERGENCES entry, in the table's order.
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Which validation error to REPORT when a `oneOf` / `anyOf` fails.
3
+ *
4
+ * INTERNAL to `./defineBlock` — not re-exported from `./index`, and typed
5
+ * structurally (no Ajv import) so it can be exercised against small synthetic
6
+ * schemas in `test/manifest/discriminated-branch.test.ts`.
7
+ */
8
+ /** The fields of an Ajv `ErrorObject` this reads. `schema` and `data` need Ajv's `verbose: true`. */
9
+ export interface ValidationErrorLike {
10
+ keyword: string;
11
+ instancePath: string;
12
+ schemaPath: string;
13
+ schema?: unknown;
14
+ data?: unknown;
15
+ }
16
+ /** The discriminator that selected the member an error is reported from. */
17
+ export interface Discriminator {
18
+ key: string;
19
+ value: unknown;
20
+ }
21
+ /**
22
+ * Picks the error to REPORT. Ajv (with `allErrors`) validates every member of
23
+ * a failed `oneOf` / `anyOf` and lists all their errors in member order, so
24
+ * the plain first error is the FIRST MEMBER's complaint whichever member the
25
+ * author was writing: a `{ type: 'number', min: 0 }` property declaration used
26
+ * to be reported as "`values` is required", the `enum` member's rule.
27
+ *
28
+ * Where the members discriminate on a `const` (see {@link discriminatorKey})
29
+ * and the instance's value for that key equals exactly one member's, that
30
+ * member is the one the author meant: errors from the OTHER members, on that
31
+ * instance, are skipped, and the first error left is returned. (The summary
32
+ * `oneOf` / `anyOf` error needs no handling: Ajv lists it after the members'
33
+ * own errors.) `discriminator` is set when the returned error is on the
34
+ * discriminated object itself, so the message can say which member it is from.
35
+ *
36
+ * Any other failed composition — no shared `const` property, or a value no
37
+ * member names, or more than one member naming it — is left exactly as it
38
+ * was: the first error wins.
39
+ *
40
+ * 🔴 SELECTION ONLY. It is given the errors of a manifest Ajv has already
41
+ * rejected and returns one of them (the plain first if it would otherwise skip
42
+ * everything; `undefined` only for an empty list), so it cannot turn a
43
+ * rejection into an acceptance or the reverse.
44
+ *
45
+ * Members reached through `$ref` report a `schemaPath` relative to the
46
+ * referenced schema, so their errors are not recognised as a member's and are
47
+ * never skipped — that site degrades to "first error not from a recognised
48
+ * other member". The canonical uses no `$ref` today.
49
+ */
50
+ export declare function preferDiscriminatedBranch<E extends ValidationErrorLike>(errors: E[]): {
51
+ error: E;
52
+ discriminator?: Discriminator;
53
+ } | undefined;
54
+ //# sourceMappingURL=discriminatedBranch.d.ts.map
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Which validation error to REPORT when a `oneOf` / `anyOf` fails.
3
+ *
4
+ * INTERNAL to `./defineBlock` — not re-exported from `./index`, and typed
5
+ * structurally (no Ajv import) so it can be exercised against small synthetic
6
+ * schemas in `test/manifest/discriminated-branch.test.ts`.
7
+ */
8
+ const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
9
+ /** `{ value }` when `member.properties[key]` pins a primitive `const`, else `null`. */
10
+ function constOf(member, key) {
11
+ const prop = isRecord(member) && isRecord(member.properties) ? member.properties[key] : undefined;
12
+ if (!isRecord(prop) || !('const' in prop))
13
+ return null;
14
+ return typeof prop.const === 'object' && prop.const !== null ? null : { value: prop.const };
15
+ }
16
+ /**
17
+ * The property every member of a `oneOf` / `anyOf` pins to its own `const` —
18
+ * `type` in `[{ properties: { type: { const: 'enum' } } }, { … 'number' }, …]` —
19
+ * or `undefined` when the members do not discriminate that way. Read from the
20
+ * schema, so nothing here names a manifest field.
21
+ */
22
+ function discriminatorKey(members) {
23
+ const first = members[0];
24
+ if (!isRecord(first) || !isRecord(first.properties))
25
+ return undefined;
26
+ return Object.keys(first.properties).find((key) => members.every((member) => constOf(member, key) !== null));
27
+ }
28
+ /**
29
+ * Picks the error to REPORT. Ajv (with `allErrors`) validates every member of
30
+ * a failed `oneOf` / `anyOf` and lists all their errors in member order, so
31
+ * the plain first error is the FIRST MEMBER's complaint whichever member the
32
+ * author was writing: a `{ type: 'number', min: 0 }` property declaration used
33
+ * to be reported as "`values` is required", the `enum` member's rule.
34
+ *
35
+ * Where the members discriminate on a `const` (see {@link discriminatorKey})
36
+ * and the instance's value for that key equals exactly one member's, that
37
+ * member is the one the author meant: errors from the OTHER members, on that
38
+ * instance, are skipped, and the first error left is returned. (The summary
39
+ * `oneOf` / `anyOf` error needs no handling: Ajv lists it after the members'
40
+ * own errors.) `discriminator` is set when the returned error is on the
41
+ * discriminated object itself, so the message can say which member it is from.
42
+ *
43
+ * Any other failed composition — no shared `const` property, or a value no
44
+ * member names, or more than one member naming it — is left exactly as it
45
+ * was: the first error wins.
46
+ *
47
+ * 🔴 SELECTION ONLY. It is given the errors of a manifest Ajv has already
48
+ * rejected and returns one of them (the plain first if it would otherwise skip
49
+ * everything; `undefined` only for an empty list), so it cannot turn a
50
+ * rejection into an acceptance or the reverse.
51
+ *
52
+ * Members reached through `$ref` report a `schemaPath` relative to the
53
+ * referenced schema, so their errors are not recognised as a member's and are
54
+ * never skipped — that site degrades to "first error not from a recognised
55
+ * other member". The canonical uses no `$ref` today.
56
+ */
57
+ export function preferDiscriminatedBranch(errors) {
58
+ // One entry per member of each discriminated composition that failed: where
59
+ // its errors live (`prefix`), on which instance, and — for the member the
60
+ // instance's discriminator names — the discriminator (`chosen`).
61
+ const scopes = [];
62
+ for (const error of errors) {
63
+ if (error.keyword !== 'oneOf' && error.keyword !== 'anyOf')
64
+ continue;
65
+ const members = error.schema;
66
+ if (!Array.isArray(members) || !isRecord(error.data))
67
+ continue;
68
+ const key = discriminatorKey(members);
69
+ if (key === undefined)
70
+ continue;
71
+ const value = error.data[key];
72
+ const matching = members.flatMap((member, index) => constOf(member, key)?.value === value ? [index] : []);
73
+ if (matching.length !== 1)
74
+ continue;
75
+ members.forEach((_member, index) => {
76
+ scopes.push({
77
+ prefix: `${error.schemaPath}/${index}/`,
78
+ instancePath: error.instancePath,
79
+ ...(index === matching[0] ? { chosen: { key, value } } : {}),
80
+ });
81
+ });
82
+ }
83
+ for (const error of errors) {
84
+ // Compositions nest, so an error can sit inside several members at once.
85
+ const within = scopes.filter((s) => error.schemaPath.startsWith(s.prefix) &&
86
+ (error.instancePath === s.instancePath ||
87
+ error.instancePath.startsWith(`${s.instancePath}/`)));
88
+ // Inside a member, at any level, that its instance's discriminator does not name.
89
+ if (within.some((s) => !s.chosen))
90
+ continue;
91
+ // The member is named in the message only for an error ON the discriminated
92
+ // object itself (the innermost one), not for one on something nested inside it.
93
+ const innermost = within.reduce((a, b) => (a && a.prefix.length >= b.prefix.length ? a : b), undefined);
94
+ return innermost?.chosen && error.instancePath === innermost.instancePath
95
+ ? { error, discriminator: innermost.chosen }
96
+ : { error };
97
+ }
98
+ return errors[0] ? { error: errors[0] } : undefined;
99
+ }
100
+ //# sourceMappingURL=discriminatedBranch.js.map
@@ -15,5 +15,5 @@
15
15
  export { defineBlock, SCHEMA_DIVERGENCES, KNOWN_GAPS, loadCanonicalSchema } from './defineBlock.js';
16
16
  export type { DefineBlockConfig } from './defineBlock.js';
17
17
  export { BlockManifestError } from '../blocks/manifestError.js';
18
- export type { BlockManifest, BlockManifestGood, BlockManifestV1 } from '../blocks/types.js';
18
+ export type { BlockAnalyticsDeclaration, BlockAnalyticsEventDeclaration, BlockAnalyticsPropertyDeclaration, BlockManifest, BlockManifestGood, BlockManifestV1, } from '../blocks/types.js';
19
19
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@civitai/app-sdk",
3
- "version": "0.61.0",
3
+ "version": "0.63.0",
4
4
  "description": "OAuth + PKCE, encrypted-cookie sessions, scopes, and orchestrator helpers for building third-party Civitai apps.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -275,6 +275,104 @@
275
275
  }
276
276
  }
277
277
  },
278
+ "analytics": {
279
+ "type": "object",
280
+ "description": "Optional CUSTOM EVENTS declaration. `events` names every event your app may send with `useBlockAnalytics().track(name, properties)` and the properties each event may carry. Manifest-governed and REVIEW-GATED: the declaration a moderator approves is the only one the platform will accept events against, so a reviewer can see what your app tracks. Properties are typed `enum` (a declared list of string values), `number` or `boolean` ONLY — free-text strings are deliberately not supported, so a string that is not one of an enum's declared values will never be recorded. That is what keeps prompts, emails and usernames out of string properties (a `number` property is not range-limited, so do not use one to carry an identifier). Owners will see aggregates only, never individual events. The platform's manifest validator is authoritative.",
281
+ "additionalProperties": false,
282
+ "properties": {
283
+ "events": {
284
+ "type": "object",
285
+ "description": "Map of event name → declaration. At most 50 events. An event name is lowercase snake_case: a letter, then up to 63 of a-z, 0-9 or _.",
286
+ "maxProperties": 50,
287
+ "additionalProperties": false,
288
+ "patternProperties": {
289
+ "^[a-z][a-z0-9_]{0,63}$": {
290
+ "type": "object",
291
+ "additionalProperties": false,
292
+ "properties": {
293
+ "description": {
294
+ "type": "string",
295
+ "description": "Optional human-readable meaning of the event, shown to reviewers and in your analytics.",
296
+ "maxLength": 200
297
+ },
298
+ "properties": {
299
+ "type": "object",
300
+ "description": "Map of property name → declaration. At most 10 properties per event. A property name follows the same rule as an event name.",
301
+ "maxProperties": 10,
302
+ "additionalProperties": false,
303
+ "patternProperties": {
304
+ "^[a-z][a-z0-9_]{0,63}$": {
305
+ "type": "object",
306
+ "allOf": [
307
+ {
308
+ "$comment": "Checked FIRST, before the oneOf below, so a wrong type is reported as such rather than as a missing `values`.",
309
+ "required": ["type"],
310
+ "properties": {
311
+ "type": {
312
+ "description": "The property's type. There is no free-text string type: declare an enum.",
313
+ "enum": ["enum", "number", "boolean"]
314
+ }
315
+ }
316
+ },
317
+ {
318
+ "oneOf": [
319
+ {
320
+ "type": "object",
321
+ "description": "A string drawn from a declared list. A string outside `values` will not be recorded.",
322
+ "additionalProperties": false,
323
+ "required": ["type", "values"],
324
+ "properties": {
325
+ "type": {
326
+ "const": "enum"
327
+ },
328
+ "values": {
329
+ "type": "array",
330
+ "description": "The allowed values: 1 to 50 distinct non-empty strings of at most 64 characters (Unicode code points) each.",
331
+ "minItems": 1,
332
+ "maxItems": 50,
333
+ "uniqueItems": true,
334
+ "items": {
335
+ "type": "string",
336
+ "minLength": 1,
337
+ "maxLength": 64
338
+ }
339
+ }
340
+ }
341
+ },
342
+ {
343
+ "type": "object",
344
+ "description": "A number.",
345
+ "additionalProperties": false,
346
+ "required": ["type"],
347
+ "properties": {
348
+ "type": {
349
+ "const": "number"
350
+ }
351
+ }
352
+ },
353
+ {
354
+ "type": "object",
355
+ "description": "A boolean.",
356
+ "additionalProperties": false,
357
+ "required": ["type"],
358
+ "properties": {
359
+ "type": {
360
+ "const": "boolean"
361
+ }
362
+ }
363
+ }
364
+ ]
365
+ }
366
+ ]
367
+ }
368
+ }
369
+ }
370
+ }
371
+ }
372
+ }
373
+ }
374
+ }
375
+ },
278
376
  "targets": {
279
377
  "type": "array",
280
378
  "description": "Model-page slot targets. Each target's slotId must be a known registered model slot (not the page slot). Optional for page-only apps.",