@velora-cms/api-schemas 0.9.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 (59) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +22 -0
  3. package/dist/api-error.schema.d.ts +5 -0
  4. package/dist/api-error.schema.js +6 -0
  5. package/dist/api-key-api.schema.d.ts +50 -0
  6. package/dist/api-key-api.schema.js +29 -0
  7. package/dist/auth.schema.d.ts +40 -0
  8. package/dist/auth.schema.js +30 -0
  9. package/dist/content-api.schema.d.ts +188 -0
  10. package/dist/content-api.schema.js +121 -0
  11. package/dist/content-trash-api.schema.d.ts +20 -0
  12. package/dist/content-trash-api.schema.js +23 -0
  13. package/dist/content-trash.schema.d.ts +31 -0
  14. package/dist/content-trash.schema.js +26 -0
  15. package/dist/content-version.schema.d.ts +11 -0
  16. package/dist/content-version.schema.js +10 -0
  17. package/dist/content.schema.d.ts +15 -0
  18. package/dist/content.schema.js +14 -0
  19. package/dist/data-type-api.schema.d.ts +36 -0
  20. package/dist/data-type-api.schema.js +32 -0
  21. package/dist/data-type-config.schema.d.ts +9 -0
  22. package/dist/data-type-config.schema.js +32 -0
  23. package/dist/document-type-api.schema.d.ts +202 -0
  24. package/dist/document-type-api.schema.js +86 -0
  25. package/dist/document-type.schema.d.ts +90 -0
  26. package/dist/document-type.schema.js +103 -0
  27. package/dist/health.schema.d.ts +6 -0
  28. package/dist/health.schema.js +5 -0
  29. package/dist/index.d.ts +54 -0
  30. package/dist/index.js +27 -0
  31. package/dist/marketplace-listing-api.schema.d.ts +205 -0
  32. package/dist/marketplace-listing-api.schema.js +115 -0
  33. package/dist/media-api.schema.d.ts +36 -0
  34. package/dist/media-api.schema.js +23 -0
  35. package/dist/media.schema.d.ts +11 -0
  36. package/dist/media.schema.js +10 -0
  37. package/dist/plugin-management-api.schema.d.ts +145 -0
  38. package/dist/plugin-management-api.schema.js +186 -0
  39. package/dist/plugin-registry-api.schema.d.ts +31 -0
  40. package/dist/plugin-registry-api.schema.js +25 -0
  41. package/dist/plugin-storage-api.schema.d.ts +15 -0
  42. package/dist/plugin-storage-api.schema.js +24 -0
  43. package/dist/public-api.schema.d.ts +211 -0
  44. package/dist/public-api.schema.js +185 -0
  45. package/dist/setup-api.schema.d.ts +78 -0
  46. package/dist/setup-api.schema.js +88 -0
  47. package/dist/snapshot-api.schema.d.ts +43 -0
  48. package/dist/snapshot-api.schema.js +25 -0
  49. package/dist/snapshot.schema.d.ts +28 -0
  50. package/dist/snapshot.schema.js +17 -0
  51. package/dist/template-api.schema.d.ts +33 -0
  52. package/dist/template-api.schema.js +28 -0
  53. package/dist/template.schema.d.ts +7 -0
  54. package/dist/template.schema.js +13 -0
  55. package/dist/theme-api.schema.d.ts +21 -0
  56. package/dist/theme-api.schema.js +14 -0
  57. package/dist/user-api.schema.d.ts +79 -0
  58. package/dist/user-api.schema.js +36 -0
  59. package/package.json +26 -0
@@ -0,0 +1,115 @@
1
+ import { z } from "zod";
2
+ export const MarketplaceListingVersionSchema = z.object({
3
+ version: z.string(),
4
+ supportedDialects: z.array(z.string()),
5
+ permissions: z.record(z.string(), z.boolean()),
6
+ license: z.string(),
7
+ cmsVersion: z.string(),
8
+ verified: z.boolean(),
9
+ createdAt: z.string(),
10
+ });
11
+ export const MarketplaceListingSchema = z.object({
12
+ pluginId: z.string(),
13
+ name: z.string(),
14
+ type: z.string(),
15
+ description: z.string(),
16
+ author: z.string(),
17
+ priceCents: z.number().int(),
18
+ coverImagePath: z.string().nullable(),
19
+ latestVersion: MarketplaceListingVersionSchema.nullable(),
20
+ // Computed SERVER-SIDE against this CMS's active dialect — the admin
21
+ // client never guesses compatibility from raw dialect lists.
22
+ compatible: z.boolean(),
23
+ });
24
+ // S148: a starter frontend the marketplace hosts as a versioned archive
25
+ // (Global Constraints — a bundledTemplate WITHOUT an archive stays legal,
26
+ // hence hasArchive rather than always assuming a download exists) and the
27
+ // document types a template/section listing provisions on install. Both
28
+ // are projected server-side by the marketplace (starter-projection.ts) and
29
+ // only ever appear on the DETAIL surface, never on browse — see
30
+ // MarketplaceListingDetailVersionSchema below.
31
+ export const MarketplaceStarterSchema = z.object({
32
+ name: z.string(),
33
+ description: z.string().nullable(),
34
+ repository: z.string().nullable(),
35
+ hasArchive: z.boolean(),
36
+ });
37
+ export const MarketplaceDocumentTypeSummarySchema = z.object({
38
+ name: z.string(),
39
+ fields: z.array(z.string()),
40
+ });
41
+ // S153: a bundle's enriched member projection — snapshotted at ingest by
42
+ // the marketplace (bundle-members.ts), detail-only like the S148 starter
43
+ // fields (same extend-don't-widen browse protection below).
44
+ export const MarketplaceBundleMemberSchema = z.object({
45
+ pluginId: z.string(),
46
+ name: z.string(),
47
+ description: z.string().nullable(),
48
+ type: z.string(),
49
+ priceCents: z.number().int(),
50
+ });
51
+ // Extends the base version shape rather than widening it: browse
52
+ // (MarketplaceListingSchema.latestVersion, MarketplaceListResponseSchema)
53
+ // keeps using MarketplaceListingVersionSchema untouched, so starter/
54
+ // documentTypesSummary can never leak onto the browse screen even though
55
+ // the upstream marketplace's JSON carries them on every version row.
56
+ export const MarketplaceListingDetailVersionSchema = MarketplaceListingVersionSchema.extend({
57
+ starter: MarketplaceStarterSchema.nullable().optional(),
58
+ documentTypesSummary: z.array(MarketplaceDocumentTypeSummarySchema).nullable().optional(),
59
+ bundledMembers: z.array(MarketplaceBundleMemberSchema).nullable().optional(),
60
+ });
61
+ export const MarketplaceListingDetailSchema = MarketplaceListingSchema.extend({
62
+ latestVersion: MarketplaceListingDetailVersionSchema.nullable(),
63
+ versions: z.array(MarketplaceListingDetailVersionSchema),
64
+ activeDialect: z.string(),
65
+ marketplaceBaseUrl: z.string(),
66
+ });
67
+ export const MarketplaceListQuerySchema = z.object({
68
+ q: z.string().optional(),
69
+ type: z.string().optional(),
70
+ db: z.string().optional(),
71
+ cursor: z.string().uuid().optional(),
72
+ limit: z.coerce.number().int().min(1).max(50).default(24),
73
+ });
74
+ export const MarketplaceListResponseSchema = z.object({
75
+ items: z.array(MarketplaceListingSchema),
76
+ nextCursor: z.string().nullable(),
77
+ activeDialect: z.string(),
78
+ marketplaceBaseUrl: z.string(),
79
+ });
80
+ export const MarketplacePluginIdParamsSchema = z.object({ pluginId: z.string().min(1) });
81
+ // Path-safety gate, mirroring install-built-package.ts's own reverse-domain
82
+ // id regex (its manifest.json id check, run before that id becomes a
83
+ // managed-store directory name): pluginId reaches removePluginPackages,
84
+ // which builds a path from it and does a recursive, forced rmSync. Without
85
+ // this, a bare `z.string().min(1)` would let a traversal-shaped id
86
+ // (`"../../etc"`) all the way to that filesystem call.
87
+ const PLUGIN_ID_PATTERN = /^[a-z0-9-]+(\.[a-z0-9-]+)+$/i;
88
+ export const MarketplaceInstallBodySchema = z.object({
89
+ pluginId: z
90
+ .string()
91
+ .min(1)
92
+ .regex(PLUGIN_ID_PATTERN, "pluginId must be a reverse-domain identifier (e.g. com.example.plugin)"),
93
+ version: z.string().min(1),
94
+ licenseKey: z.string().min(1).optional(),
95
+ });
96
+ // S152: a bundle install reports what its fan-out did per member —
97
+ // "installed" (this operation installed it, ownership recorded) or
98
+ // "already-installed" (pre-existing, skipped, never claimed). Optional
99
+ // and absent for every non-bundle install, so single-plugin clients are
100
+ // untouched.
101
+ export const MarketplaceInstallMemberSchema = z.object({
102
+ pluginId: z.string(),
103
+ version: z.string(),
104
+ outcome: z.enum(["installed", "already-installed"]),
105
+ });
106
+ // trustTier is a literal, not the broader TrustTier union — this door
107
+ // (S138) only ever answers 201 for a "verified" result; a non-"verified"
108
+ // verdict is a 422 failure response (ApiErrorSchema), never this shape.
109
+ export const MarketplaceInstallResponseSchema = z.object({
110
+ pluginId: z.string(),
111
+ version: z.string(),
112
+ trustTier: z.literal("verified"),
113
+ licensed: z.boolean(),
114
+ members: z.array(MarketplaceInstallMemberSchema).optional(),
115
+ });
@@ -0,0 +1,36 @@
1
+ import { z } from "zod";
2
+ export declare const MediaFileResponseSchema: z.ZodObject<{
3
+ id: z.ZodString;
4
+ filename: z.ZodString;
5
+ mimeType: z.ZodString;
6
+ size: z.ZodNumber;
7
+ url: z.ZodString;
8
+ uploadedBy: z.ZodString;
9
+ uploadedAt: z.ZodString;
10
+ downloadUrl: z.ZodString;
11
+ }, z.core.$strip>;
12
+ export type MediaFileResponse = z.infer<typeof MediaFileResponseSchema>;
13
+ export declare const MediaDeleteResponseSchema: z.ZodObject<{
14
+ success: z.ZodLiteral<true>;
15
+ }, z.core.$strip>;
16
+ export type MediaDeleteResponse = z.infer<typeof MediaDeleteResponseSchema>;
17
+ export declare const MediaListQuerySchema: z.ZodObject<{
18
+ cursor: z.ZodOptional<z.ZodString>;
19
+ limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
20
+ search: z.ZodOptional<z.ZodString>;
21
+ }, z.core.$strip>;
22
+ export type MediaListQuery = z.infer<typeof MediaListQuerySchema>;
23
+ export declare const MediaListResponseSchema: z.ZodObject<{
24
+ items: z.ZodArray<z.ZodObject<{
25
+ id: z.ZodString;
26
+ filename: z.ZodString;
27
+ mimeType: z.ZodString;
28
+ size: z.ZodNumber;
29
+ url: z.ZodString;
30
+ uploadedBy: z.ZodString;
31
+ uploadedAt: z.ZodString;
32
+ downloadUrl: z.ZodString;
33
+ }, z.core.$strip>>;
34
+ nextCursor: z.ZodNullable<z.ZodString>;
35
+ }, z.core.$strip>;
36
+ export type MediaListResponse = z.infer<typeof MediaListResponseSchema>;
@@ -0,0 +1,23 @@
1
+ import { z } from "zod";
2
+ import { MediaFileSchema } from "./media.schema.js";
3
+ // downloadUrl is generated per response (time-limited presigned GET) —
4
+ // it is never the persisted canonical `url` and must not be stored.
5
+ export const MediaFileResponseSchema = MediaFileSchema.extend({
6
+ downloadUrl: z.string().url(),
7
+ });
8
+ export const MediaDeleteResponseSchema = z.object({
9
+ success: z.literal(true),
10
+ });
11
+ // GET /api/media — newest-first pages; cursor = id of the previous
12
+ // page's last item. `search` filters by filename substring,
13
+ // case-insensitive.
14
+ export const MediaListQuerySchema = z.object({
15
+ cursor: z.string().uuid().optional(),
16
+ limit: z.coerce.number().int().min(1).max(100).default(24),
17
+ search: z.string().min(1).optional(),
18
+ });
19
+ export const MediaListResponseSchema = z.object({
20
+ items: z.array(MediaFileResponseSchema),
21
+ // null = no more pages.
22
+ nextCursor: z.string().uuid().nullable(),
23
+ });
@@ -0,0 +1,11 @@
1
+ import { z } from "zod";
2
+ export declare const MediaFileSchema: z.ZodObject<{
3
+ id: z.ZodString;
4
+ filename: z.ZodString;
5
+ mimeType: z.ZodString;
6
+ size: z.ZodNumber;
7
+ url: z.ZodString;
8
+ uploadedBy: z.ZodString;
9
+ uploadedAt: z.ZodString;
10
+ }, z.core.$strip>;
11
+ export type MediaFile = z.infer<typeof MediaFileSchema>;
@@ -0,0 +1,10 @@
1
+ import { z } from "zod";
2
+ export const MediaFileSchema = z.object({
3
+ id: z.string().uuid(),
4
+ filename: z.string().min(1),
5
+ mimeType: z.string().min(1),
6
+ size: z.number().int().nonnegative(),
7
+ url: z.string().url(),
8
+ uploadedBy: z.string().uuid(),
9
+ uploadedAt: z.string().datetime(),
10
+ });
@@ -0,0 +1,145 @@
1
+ import { z } from "zod";
2
+ export declare const ManagedPluginSchema: z.ZodObject<{
3
+ pluginId: z.ZodString;
4
+ name: z.ZodString;
5
+ type: z.ZodString;
6
+ version: z.ZodString;
7
+ trustTier: z.ZodEnum<{
8
+ verified: "verified";
9
+ unverified: "unverified";
10
+ }>;
11
+ storageType: z.ZodEnum<{
12
+ simple: "simple";
13
+ structured: "structured";
14
+ }>;
15
+ enabled: z.ZodBoolean;
16
+ licenseStatus: z.ZodNullable<z.ZodString>;
17
+ licenseReason: z.ZodNullable<z.ZodString>;
18
+ licenseLastValidatedAt: z.ZodNullable<z.ZodString>;
19
+ licenseLastAttemptError: z.ZodNullable<z.ZodString>;
20
+ boundDomain: z.ZodNullable<z.ZodString>;
21
+ }, z.core.$strip>;
22
+ export type ManagedPlugin = z.infer<typeof ManagedPluginSchema>;
23
+ export declare const ManagedPluginListQuerySchema: z.ZodObject<{
24
+ cursor: z.ZodOptional<z.ZodString>;
25
+ limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
26
+ }, z.core.$strip>;
27
+ export declare const ManagedPluginListResponseSchema: z.ZodObject<{
28
+ items: z.ZodArray<z.ZodObject<{
29
+ pluginId: z.ZodString;
30
+ name: z.ZodString;
31
+ type: z.ZodString;
32
+ version: z.ZodString;
33
+ trustTier: z.ZodEnum<{
34
+ verified: "verified";
35
+ unverified: "unverified";
36
+ }>;
37
+ storageType: z.ZodEnum<{
38
+ simple: "simple";
39
+ structured: "structured";
40
+ }>;
41
+ enabled: z.ZodBoolean;
42
+ licenseStatus: z.ZodNullable<z.ZodString>;
43
+ licenseReason: z.ZodNullable<z.ZodString>;
44
+ licenseLastValidatedAt: z.ZodNullable<z.ZodString>;
45
+ licenseLastAttemptError: z.ZodNullable<z.ZodString>;
46
+ boundDomain: z.ZodNullable<z.ZodString>;
47
+ }, z.core.$strip>>;
48
+ nextCursor: z.ZodNullable<z.ZodString>;
49
+ }, z.core.$strip>;
50
+ export declare const PluginIdParamsSchema: z.ZodObject<{
51
+ pluginId: z.ZodString;
52
+ }, z.core.$strip>;
53
+ export declare const UninstallPluginBodySchema: z.ZodOptional<z.ZodNullable<z.ZodObject<{
54
+ confirmStructuredDataCleanup: z.ZodOptional<z.ZodBoolean>;
55
+ }, z.core.$strip>>>;
56
+ export declare const UninstallConfirmationRequiredSchema: z.ZodObject<{
57
+ requiresConfirmation: z.ZodLiteral<true>;
58
+ snapshotGroupId: z.ZodString;
59
+ message: z.ZodString;
60
+ }, z.core.$strip>;
61
+ export declare const BundleUninstallConfirmationRequiredSchema: z.ZodObject<{
62
+ requiresConfirmation: z.ZodLiteral<true>;
63
+ memberSnapshots: z.ZodArray<z.ZodObject<{
64
+ pluginId: z.ZodString;
65
+ snapshotGroupId: z.ZodString;
66
+ }, z.core.$strip>>;
67
+ message: z.ZodString;
68
+ }, z.core.$strip>;
69
+ export declare const UninstallSucceededSchema: z.ZodObject<{
70
+ snapshotGroupId: z.ZodNullable<z.ZodString>;
71
+ }, z.core.$strip>;
72
+ export type UninstallSucceeded = z.infer<typeof UninstallSucceededSchema>;
73
+ export declare const BundleUninstallSucceededSchema: z.ZodObject<{
74
+ memberSnapshots: z.ZodArray<z.ZodObject<{
75
+ pluginId: z.ZodString;
76
+ snapshotGroupId: z.ZodString;
77
+ }, z.core.$strip>>;
78
+ }, z.core.$strip>;
79
+ export type BundleUninstallSucceeded = z.infer<typeof BundleUninstallSucceededSchema>;
80
+ export declare const LicenseRecheckResponseSchema: z.ZodObject<{
81
+ pluginId: z.ZodString;
82
+ checked: z.ZodBoolean;
83
+ status: z.ZodNullable<z.ZodString>;
84
+ reason: z.ZodNullable<z.ZodString>;
85
+ lastValidatedAt: z.ZodNullable<z.ZodString>;
86
+ }, z.core.$strip>;
87
+ export type LicenseRecheckResponse = z.infer<typeof LicenseRecheckResponseSchema>;
88
+ export declare const SnapshotGroupSchema: z.ZodObject<{
89
+ snapshotGroupId: z.ZodString;
90
+ takenAt: z.ZodString;
91
+ trigger: z.ZodNullable<z.ZodString>;
92
+ tableNames: z.ZodArray<z.ZodString>;
93
+ totalTableCount: z.ZodNumber;
94
+ status: z.ZodEnum<{
95
+ complete: "complete";
96
+ partial: "partial";
97
+ }>;
98
+ }, z.core.$strip>;
99
+ export type SnapshotGroup = z.infer<typeof SnapshotGroupSchema>;
100
+ export declare const SnapshotGroupListQuerySchema: z.ZodObject<{
101
+ cursor: z.ZodOptional<z.ZodString>;
102
+ limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
103
+ }, z.core.$strip>;
104
+ export declare const SnapshotGroupListResponseSchema: z.ZodObject<{
105
+ pluginId: z.ZodString;
106
+ items: z.ZodArray<z.ZodObject<{
107
+ snapshotGroupId: z.ZodString;
108
+ takenAt: z.ZodString;
109
+ trigger: z.ZodNullable<z.ZodString>;
110
+ tableNames: z.ZodArray<z.ZodString>;
111
+ totalTableCount: z.ZodNumber;
112
+ status: z.ZodEnum<{
113
+ complete: "complete";
114
+ partial: "partial";
115
+ }>;
116
+ }, z.core.$strip>>;
117
+ nextCursor: z.ZodNullable<z.ZodString>;
118
+ }, z.core.$strip>;
119
+ export type SnapshotGroupListResponse = z.infer<typeof SnapshotGroupListResponseSchema>;
120
+ export declare const RestorePlanEntrySchema: z.ZodObject<{
121
+ tableName: z.ZodString;
122
+ snapshotGroupId: z.ZodString;
123
+ groupTakenAt: z.ZodString;
124
+ }, z.core.$strip>;
125
+ export type RestorePlanEntry = z.infer<typeof RestorePlanEntrySchema>;
126
+ export declare const RestoreConfirmationRequiredSchema: z.ZodObject<{
127
+ requiresConfirmation: z.ZodLiteral<true>;
128
+ plan: z.ZodArray<z.ZodObject<{
129
+ tableName: z.ZodString;
130
+ snapshotGroupId: z.ZodString;
131
+ groupTakenAt: z.ZodString;
132
+ }, z.core.$strip>>;
133
+ planHash: z.ZodString;
134
+ message: z.ZodString;
135
+ }, z.core.$strip>;
136
+ export type RestoreConfirmationRequired = z.infer<typeof RestoreConfirmationRequiredSchema>;
137
+ export declare const RestoreSucceededSchema: z.ZodObject<{
138
+ preRestoreSnapshotGroupId: z.ZodNullable<z.ZodString>;
139
+ restoredTables: z.ZodArray<z.ZodString>;
140
+ }, z.core.$strip>;
141
+ export type RestoreSucceeded = z.infer<typeof RestoreSucceededSchema>;
142
+ export declare const RestoreBodySchema: z.ZodOptional<z.ZodNullable<z.ZodObject<{
143
+ confirmRestore: z.ZodOptional<z.ZodBoolean>;
144
+ planHash: z.ZodOptional<z.ZodString>;
145
+ }, z.core.$strip>>>;
@@ -0,0 +1,186 @@
1
+ import { z } from "zod";
2
+ // The ADMIN management view of the registry (S135): unlike the public
3
+ // GET /api/plugins (enabled-only, boot-loader metadata), this surface
4
+ // lists every installed plugin, disabled included. licenseStatus reflects
5
+ // the PluginLicense row's last verdict ("valid" | "revoked" |
6
+ // "domain_mismatch" | "unknown"), or null when the plugin has no license
7
+ // row at all (free plugins, or a paid plugin never validated yet,
8
+ // S138/S139). The three fields below (S139 Task 5) ride along so the
9
+ // admin's license cell can render an honest state without a second
10
+ // request per row: licenseReason mirrors PluginLicense.lastReason (the
11
+ // server's explanation for a non-valid verdict), licenseLastValidatedAt
12
+ // mirrors lastValidatedAt (the last SUCCESSFUL verdict), and
13
+ // licenseLastAttemptError mirrors lastAttemptError (why the most recent
14
+ // ATTEMPT — successful or not — failed to reach a verdict; null when it
15
+ // didn't). Together the last two let the cell compute "unreachable since"
16
+ // itself. boundDomain mirrors PluginLicense.boundDomain (the domain this
17
+ // site bound the license to at S138 install; never mutated by later
18
+ // revalidation — see revalidate-license.ts) so a domain_mismatch warning
19
+ // can name the actual bound domain instead of the raw upstream reason
20
+ // token (S139 fix wave, spec section 6). Never add `key` here — see the
21
+ // route's comment on the same prisma select.
22
+ export const ManagedPluginSchema = z.object({
23
+ pluginId: z.string(),
24
+ name: z.string(),
25
+ type: z.string(),
26
+ version: z.string(),
27
+ trustTier: z.enum(["verified", "unverified"]),
28
+ storageType: z.enum(["simple", "structured"]),
29
+ enabled: z.boolean(),
30
+ licenseStatus: z.string().nullable(),
31
+ licenseReason: z.string().nullable(),
32
+ licenseLastValidatedAt: z.string().nullable(),
33
+ licenseLastAttemptError: z.string().nullable(),
34
+ boundDomain: z.string().nullable(),
35
+ });
36
+ export const ManagedPluginListQuerySchema = z.object({
37
+ cursor: z.string().uuid().optional(),
38
+ limit: z.coerce.number().int().min(1).max(100).default(50),
39
+ });
40
+ export const ManagedPluginListResponseSchema = z.object({
41
+ items: z.array(ManagedPluginSchema),
42
+ nextCursor: z.string().uuid().nullable(),
43
+ });
44
+ export const PluginIdParamsSchema = z.object({
45
+ pluginId: z.string().min(1),
46
+ });
47
+ // Fastify substitutes `null` for a genuinely absent body before handing it
48
+ // to the validator (lib/validation.js's validateParam: `isUndefined ? null
49
+ // : request[paramName]`) — a DELETE sent with no payload at all lands here
50
+ // as `null`, not `undefined`. `.nullish()` (not `.optional()`) is required
51
+ // so an absent body validates; `request.body?.confirmStructuredDataCleanup`
52
+ // in the route handler already tolerates both null and undefined.
53
+ export const UninstallPluginBodySchema = z
54
+ .object({
55
+ confirmStructuredDataCleanup: z.boolean().optional(),
56
+ })
57
+ .nullish();
58
+ // 409 payload when a structured plugin's uninstall needs confirmation —
59
+ // the snapshot has ALREADY been taken (S87's safe-to-retry design).
60
+ export const UninstallConfirmationRequiredSchema = z.object({
61
+ requiresConfirmation: z.literal(true),
62
+ snapshotGroupId: z.string(),
63
+ message: z.string(),
64
+ });
65
+ // S152: the BUNDLE uninstall's aggregated 409 — one snapshot group per
66
+ // structured plugin in the removal set (owned members, and the bundle
67
+ // itself if structured). A separate schema rather than widening
68
+ // UninstallConfirmationRequiredSchema: the single-plugin contract
69
+ // (snapshotGroupId, required) stays byte-identical for existing clients.
70
+ // Every snapshot is ALREADY taken when this is sent (S87's safe-to-retry
71
+ // property, kept cross-plugin).
72
+ export const BundleUninstallConfirmationRequiredSchema = z.object({
73
+ requiresConfirmation: z.literal(true),
74
+ memberSnapshots: z.array(z.object({
75
+ pluginId: z.string(),
76
+ snapshotGroupId: z.string(),
77
+ })),
78
+ message: z.string(),
79
+ });
80
+ // Task 6: the 200 success body for a single-plugin DELETE — the CONFIRMED
81
+ // pass's freshly-minted snapshotGroupId (the gate site already took it;
82
+ // this just stops discarding it), null for a simple-storage plugin (never
83
+ // snapshotted) or for the earlier PluginRegistry.uninstall() callers that
84
+ // route around the HTTP layer entirely.
85
+ export const UninstallSucceededSchema = z.object({
86
+ snapshotGroupId: z.string().nullable(),
87
+ });
88
+ // Task 6: the 200 success body for a BUNDLE DELETE — one entry per
89
+ // structured plugin in the cascade (owned members, and the bundle itself
90
+ // if structured) that got a fresh snapshot on this confirmed pass. Simple-
91
+ // storage members never appear here, same as the 409 aggregate above.
92
+ export const BundleUninstallSucceededSchema = z.object({
93
+ memberSnapshots: z.array(z.object({
94
+ pluginId: z.string(),
95
+ snapshotGroupId: z.string(),
96
+ })),
97
+ });
98
+ // The manual re-check route's response (S139): mirrors revalidateLicense's
99
+ // RevalidationOutcome, flattened for the wire. `checked: false` covers
100
+ // every fail-open case that still has a license row to report on
101
+ // (unreachable license server, no VELORA_SITE_DOMAIN, no marketplace
102
+ // configured) — `status` and `lastValidatedAt` stay null in that shape
103
+ // since nothing was verified. A plugin with no license row at all gets a
104
+ // 404 instead of this body (ApiErrorSchema) — there's nothing to recheck.
105
+ export const LicenseRecheckResponseSchema = z.object({
106
+ pluginId: z.string(),
107
+ checked: z.boolean(),
108
+ status: z.string().nullable(),
109
+ reason: z.string().nullable(),
110
+ lastValidatedAt: z.string().nullable(),
111
+ });
112
+ // S154 Task 4: a plugin's structured-data snapshot GROUPS, derived from
113
+ // PluginStructuredSnapshot (GROUP BY snapshotGroupId) rather than tracked
114
+ // by their own table — see apps/server/src/plugins/snapshot-groups.ts for
115
+ // the derivation. takenAt is the group's earliest row's createdAt (every
116
+ // row in a group is written in one snapshotPluginTables() call, so in
117
+ // practice they're all within the same transaction-ish burst — min is a
118
+ // defensive, not load-bearing, choice). trigger rides along from Task 3's
119
+ // column ("uninstall", "restore", or null for pre-Task-3 rows). status
120
+ // compares the group's own tableNames against totalTableCount — the
121
+ // UNION of distinct tableNames across ALL of the plugin's snapshot rows,
122
+ // the only denominator that survives an uninstall (which deletes the
123
+ // PluginStructuredTable tracking rows, never the snapshots themselves).
124
+ export const SnapshotGroupSchema = z.object({
125
+ snapshotGroupId: z.string(),
126
+ takenAt: z.string(),
127
+ trigger: z.string().nullable(),
128
+ tableNames: z.array(z.string()),
129
+ totalTableCount: z.number().int(),
130
+ status: z.enum(["complete", "partial"]),
131
+ });
132
+ export const SnapshotGroupListQuerySchema = z.object({
133
+ cursor: z.string().uuid().optional(),
134
+ limit: z.coerce.number().int().min(1).max(100).default(50),
135
+ });
136
+ export const SnapshotGroupListResponseSchema = z.object({
137
+ pluginId: z.string(),
138
+ items: z.array(SnapshotGroupSchema), // newest first
139
+ nextCursor: z.string().nullable(),
140
+ });
141
+ // S154 Task 5: the UNION restore plan — the newest available snapshot copy
142
+ // of each DISTINCT table this plugin has ever snapshotted, chosen
143
+ // independently per table across every group (not "pick one group and use
144
+ // only its tables"). This is what makes restore recover from the S87
145
+ // retry wedge: no single group need be complete for a full restore to be
146
+ // possible, as long as every table appears in SOME group.
147
+ export const RestorePlanEntrySchema = z.object({
148
+ tableName: z.string(),
149
+ snapshotGroupId: z.string(),
150
+ groupTakenAt: z.string(),
151
+ });
152
+ // 409 payload for an unconfirmed restore — nothing has been touched yet,
153
+ // the plan is offered for the admin to review before confirming. planHash
154
+ // (S161, S154 rider) is a stable digest of this exact plan: the confirm
155
+ // request must echo it back, and the handler refuses to proceed if a fresh
156
+ // re-plan hashes differently (a snapshot landed between the operator
157
+ // seeing this plan and confirming it) — closing the requestRestorePlan ->
158
+ // confirmRestore TOCTOU. A mismatch response reuses this same schema, with
159
+ // a fresh plan/planHash and a message explaining the plan changed.
160
+ export const RestoreConfirmationRequiredSchema = z.object({
161
+ requiresConfirmation: z.literal(true),
162
+ plan: z.array(RestorePlanEntrySchema),
163
+ planHash: z.string(),
164
+ message: z.string(),
165
+ });
166
+ export const RestoreSucceededSchema = z.object({
167
+ // The pre-restore safety snapshot's group id — null only when the
168
+ // plugin had no live tracked tables to protect (restoring onto empty).
169
+ preRestoreSnapshotGroupId: z.string().nullable(),
170
+ restoredTables: z.array(z.string()),
171
+ });
172
+ // Same DELETE-body-null convention as UninstallPluginBodySchema above: an
173
+ // absent POST body lands as `null` (not `undefined`) once Fastify's
174
+ // validator substitutes for a genuinely missing payload, so `.nullish()`
175
+ // (not `.optional()`) is required for an absent body to validate at all.
176
+ // planHash (S161, S154 rider) is optional at the schema level only
177
+ // because the SAME body shape covers the unconfirmed call (`{}`, no plan
178
+ // to hash yet) — the route handler enforces it as effectively required
179
+ // whenever confirmRestore is true: a confirm with a missing or stale
180
+ // planHash is refused exactly the same way (409, fresh plan reissued).
181
+ export const RestoreBodySchema = z
182
+ .object({
183
+ confirmRestore: z.boolean().optional(),
184
+ planHash: z.string().optional(),
185
+ })
186
+ .nullish();
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+ export declare const PluginSchema: z.ZodObject<{
3
+ pluginId: z.ZodString;
4
+ name: z.ZodString;
5
+ type: z.ZodString;
6
+ version: z.ZodString;
7
+ trustTier: z.ZodString;
8
+ storageType: z.ZodString;
9
+ enabled: z.ZodBoolean;
10
+ remoteUrl: z.ZodNullable<z.ZodString>;
11
+ }, z.core.$strip>;
12
+ export type Plugin = z.infer<typeof PluginSchema>;
13
+ export declare const PluginListQuerySchema: z.ZodObject<{
14
+ cursor: z.ZodOptional<z.ZodString>;
15
+ limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
16
+ }, z.core.$strip>;
17
+ export type PluginListQuery = z.infer<typeof PluginListQuerySchema>;
18
+ export declare const PluginListResponseSchema: z.ZodObject<{
19
+ items: z.ZodArray<z.ZodObject<{
20
+ pluginId: z.ZodString;
21
+ name: z.ZodString;
22
+ type: z.ZodString;
23
+ version: z.ZodString;
24
+ trustTier: z.ZodString;
25
+ storageType: z.ZodString;
26
+ enabled: z.ZodBoolean;
27
+ remoteUrl: z.ZodNullable<z.ZodString>;
28
+ }, z.core.$strip>>;
29
+ nextCursor: z.ZodNullable<z.ZodString>;
30
+ }, z.core.$strip>;
31
+ export type PluginListResponse = z.infer<typeof PluginListResponseSchema>;
@@ -0,0 +1,25 @@
1
+ import { z } from "zod";
2
+ // Read-side shape of a plugin_registry row — the ONLY consumer today is
3
+ // the admin's boot-time remote loader (velora-dev, Session 79), but this
4
+ // is real, permanent infrastructure: any dynamically-installed plugin
5
+ // needs a way for the admin to discover it at boot, sandbox or not.
6
+ export const PluginSchema = z.object({
7
+ pluginId: z.string(),
8
+ name: z.string(),
9
+ type: z.string(),
10
+ version: z.string(),
11
+ trustTier: z.string(),
12
+ storageType: z.string(),
13
+ enabled: z.boolean(),
14
+ remoteUrl: z.string().nullable(),
15
+ });
16
+ // Cursor-paginated per the non-negotiable list-endpoint rule, mirroring
17
+ // DataTypeListResponseSchema's exact shape (data-type-api.schema.ts).
18
+ export const PluginListQuerySchema = z.object({
19
+ cursor: z.string().uuid().optional(),
20
+ limit: z.coerce.number().int().min(1).max(100).default(50),
21
+ });
22
+ export const PluginListResponseSchema = z.object({
23
+ items: z.array(PluginSchema),
24
+ nextCursor: z.string().uuid().nullable(),
25
+ });
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+ export declare const PluginStorageParamsSchema: z.ZodObject<{
3
+ pluginId: z.ZodString;
4
+ contentId: z.ZodString;
5
+ fieldId: z.ZodString;
6
+ }, z.core.$strip>;
7
+ export type PluginStorageParams = z.infer<typeof PluginStorageParamsSchema>;
8
+ export declare const SetPluginFieldValueRequestSchema: z.ZodObject<{
9
+ value: z.ZodUnknown;
10
+ }, z.core.$strict>;
11
+ export type SetPluginFieldValueRequest = z.infer<typeof SetPluginFieldValueRequestSchema>;
12
+ export declare const PluginFieldValueResponseSchema: z.ZodObject<{
13
+ value: z.ZodUnknown;
14
+ }, z.core.$strip>;
15
+ export type PluginFieldValueResponse = z.infer<typeof PluginFieldValueResponseSchema>;
@@ -0,0 +1,24 @@
1
+ import { z } from "zod";
2
+ // SimpleStorageAPI's HTTP surface (Session 68) — mirrors PROJECT_CONTEXT.md's
3
+ // SimpleStorageAPI interface (get(contentId, fieldId) / set(contentId,
4
+ // fieldId, value)) with pluginId made explicit as a required 3rd param:
5
+ // this is the lower-level adapter/HTTP surface, not the plugin-facing SDK
6
+ // ergonomics (usePluginValue(), Session 70, resolves pluginId from React
7
+ // context and never asks a plugin author to pass it by hand).
8
+ // Matches FieldDefinition.id's shape (document-type.schema.ts) — same
9
+ // stable key, just scoped to one plugin's stored value for that field.
10
+ const FieldIdSchema = z.string().regex(/^[a-zA-Z][a-zA-Z0-9_]*$/);
11
+ // Deliberately NOT plugin-sdk's stricter reverse-DNS pluginIdSchema:
12
+ // plugin-sdk depends on api-schemas, not the reverse, so it can't be
13
+ // imported here without a cycle.
14
+ const PluginIdSchema = z.string().min(1);
15
+ export const PluginStorageParamsSchema = z.object({
16
+ pluginId: PluginIdSchema,
17
+ contentId: z.string().uuid(),
18
+ fieldId: FieldIdSchema,
19
+ });
20
+ // `value` is z.unknown(), not a record — PROJECT_CONTEXT.md types
21
+ // SimpleStorageAPI's value as `any`; a plugin can store a string, number,
22
+ // array, or object, not only key-value maps.
23
+ export const SetPluginFieldValueRequestSchema = z.object({ value: z.unknown() }).strict();
24
+ export const PluginFieldValueResponseSchema = z.object({ value: z.unknown() });