@spacefast/common 0.2.1 → 0.4.1

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 (226) hide show
  1. package/dist/agents/marketplace-prompts.d.ts +1 -0
  2. package/dist/agents/marketplace-prompts.js +6 -0
  3. package/dist/agents/registry/core.d.ts +4 -12
  4. package/dist/agents/registry/core.js +7 -28
  5. package/dist/config/domains.d.ts +10 -8
  6. package/dist/config/domains.js +4 -7
  7. package/dist/contracts/access-core.d.ts +135 -0
  8. package/dist/contracts/access-core.js +242 -0
  9. package/dist/contracts/access-profiles.d.ts +8 -8
  10. package/dist/contracts/access-profiles.js +8 -8
  11. package/dist/contracts/access.d.ts +14 -103
  12. package/dist/contracts/access.js +10 -210
  13. package/dist/contracts/activity.d.ts +54 -55
  14. package/dist/contracts/activity.js +38 -9
  15. package/dist/contracts/api-keys.d.ts +69 -26
  16. package/dist/contracts/api-keys.js +29 -2
  17. package/dist/contracts/application-journal.d.ts +197 -4
  18. package/dist/contracts/application-journal.js +97 -16
  19. package/dist/contracts/auth.d.ts +20 -0
  20. package/dist/contracts/auth.js +18 -0
  21. package/dist/contracts/beta.d.ts +13 -0
  22. package/dist/contracts/beta.js +28 -0
  23. package/dist/contracts/billing.d.ts +2 -2
  24. package/dist/contracts/bootstrap.d.ts +23 -6
  25. package/dist/contracts/builds.d.ts +24 -4
  26. package/dist/contracts/builds.js +16 -4
  27. package/dist/contracts/channels.d.ts +43 -0
  28. package/dist/contracts/channels.js +22 -6
  29. package/dist/contracts/comments.d.ts +49 -0
  30. package/dist/contracts/comments.js +61 -2
  31. package/dist/contracts/commerce.d.ts +19 -13
  32. package/dist/contracts/connector-access.d.ts +28 -0
  33. package/dist/contracts/connector-access.js +31 -0
  34. package/dist/contracts/connectors.d.ts +2382 -0
  35. package/dist/contracts/connectors.js +941 -0
  36. package/dist/contracts/content-contract-verification.d.ts +112 -58
  37. package/dist/contracts/content-contract-verification.js +42 -54
  38. package/dist/contracts/content-model-emit.d.ts +97 -0
  39. package/dist/contracts/content-model-emit.js +306 -0
  40. package/dist/contracts/content-model-sdk.d.ts +101 -0
  41. package/dist/contracts/content-model-sdk.js +232 -0
  42. package/dist/contracts/{content-program.d.ts → content-model.d.ts} +338 -82
  43. package/dist/contracts/{content-program.js → content-model.js} +291 -63
  44. package/dist/contracts/content-platform-fixture.d.ts +380 -110
  45. package/dist/contracts/content-platform-fixture.js +8 -8
  46. package/dist/contracts/content-sync.d.ts +197 -74
  47. package/dist/contracts/content-sync.js +98 -37
  48. package/dist/contracts/content.d.ts +30 -422
  49. package/dist/contracts/content.js +34 -294
  50. package/dist/contracts/dashboard-prefs.d.ts +10 -3
  51. package/dist/contracts/dashboard-prefs.js +6 -3
  52. package/dist/contracts/data-location.d.ts +9 -0
  53. package/dist/contracts/data-location.js +7 -0
  54. package/dist/contracts/deployment-comparison.d.ts +67 -0
  55. package/dist/contracts/deployment-comparison.js +68 -0
  56. package/dist/contracts/device-auth.d.ts +55 -11
  57. package/dist/contracts/domains.d.ts +40 -16
  58. package/dist/contracts/domains.js +23 -0
  59. package/dist/contracts/enums.d.ts +1 -10
  60. package/dist/contracts/enums.js +0 -15
  61. package/dist/contracts/error-code-meta.d.ts +388 -20
  62. package/dist/contracts/error-code-meta.js +201 -5
  63. package/dist/contracts/error-codes.d.ts +17 -4
  64. package/dist/contracts/error-codes.js +109 -10
  65. package/dist/contracts/events.d.ts +5 -0
  66. package/dist/contracts/events.js +12 -3
  67. package/dist/contracts/execution.d.ts +5 -1
  68. package/dist/contracts/execution.js +4 -0
  69. package/dist/contracts/feature-lifecycle.d.ts +53 -20
  70. package/dist/contracts/feature-lifecycle.js +65 -23
  71. package/dist/contracts/features.d.ts +57 -31
  72. package/dist/contracts/features.js +0 -1
  73. package/dist/contracts/feedback.d.ts +124 -0
  74. package/dist/contracts/feedback.js +64 -0
  75. package/dist/contracts/fixtures/content-model-sdk-v1.generated.d.ts +129 -0
  76. package/dist/contracts/fixtures/content-model-sdk-v1.generated.js +48 -0
  77. package/dist/contracts/fixtures/content-platform-v1.json +101 -49
  78. package/dist/contracts/frame-session.d.ts +60 -10
  79. package/dist/contracts/frame-session.js +52 -12
  80. package/dist/contracts/functions.d.ts +28 -2
  81. package/dist/contracts/functions.js +25 -2
  82. package/dist/contracts/grant-copy.d.ts +2 -0
  83. package/dist/contracts/grant-copy.js +2 -0
  84. package/dist/contracts/grants-core.d.ts +95 -0
  85. package/dist/contracts/grants-core.js +435 -0
  86. package/dist/contracts/grants.d.ts +13 -84
  87. package/dist/contracts/grants.js +21 -369
  88. package/dist/contracts/ids-core.d.ts +11 -0
  89. package/dist/contracts/ids-core.js +14 -0
  90. package/dist/contracts/ids.d.ts +7 -4
  91. package/dist/contracts/ids.js +12 -4
  92. package/dist/contracts/internal.d.ts +18 -0
  93. package/dist/contracts/internal.js +19 -0
  94. package/dist/contracts/mcp.d.ts +8 -18
  95. package/dist/contracts/mcp.js +2 -23
  96. package/dist/contracts/notifications.d.ts +4 -1
  97. package/dist/contracts/notifications.js +1 -0
  98. package/dist/contracts/oauth-resources.d.ts +55 -9
  99. package/dist/contracts/oauth-resources.js +64 -1
  100. package/dist/contracts/oauth-scope-actions.d.ts +27 -3
  101. package/dist/contracts/oauth-scope-actions.js +18 -8
  102. package/dist/contracts/operations.d.ts +1 -1
  103. package/dist/contracts/partner-account.d.ts +629 -0
  104. package/dist/contracts/partner-account.js +18 -0
  105. package/dist/contracts/principal-assertion.d.ts +74 -1
  106. package/dist/contracts/principal-assertion.js +67 -3
  107. package/dist/contracts/publish-archive.d.ts +22 -1
  108. package/dist/contracts/push-new.d.ts +3 -3
  109. package/dist/contracts/push-new.js +1 -1
  110. package/dist/contracts/quotas.js +2 -1
  111. package/dist/contracts/repository-connections.d.ts +10 -6
  112. package/dist/contracts/repository-connections.js +15 -0
  113. package/dist/contracts/resources.d.ts +6 -5
  114. package/dist/contracts/resources.js +7 -0
  115. package/dist/contracts/route-inventory.d.ts +74 -19
  116. package/dist/contracts/route-inventory.js +20 -6
  117. package/dist/contracts/runtime-api.d.ts +415 -4
  118. package/dist/contracts/runtime-api.js +60 -15
  119. package/dist/contracts/runtime-app.d.ts +6 -1
  120. package/dist/contracts/runtime-components.d.ts +646 -18
  121. package/dist/contracts/runtime-components.js +304 -188
  122. package/dist/contracts/runtime-services.d.ts +15 -0
  123. package/dist/contracts/runtime-services.js +25 -0
  124. package/dist/contracts/runtime-storage.d.ts +32 -0
  125. package/dist/contracts/runtime-storage.js +37 -0
  126. package/dist/contracts/source-workspaces.d.ts +511 -0
  127. package/dist/contracts/source-workspaces.js +276 -0
  128. package/dist/contracts/space-config-spacefast.d.ts +394 -0
  129. package/dist/contracts/space-config-spacefast.js +70 -0
  130. package/dist/contracts/space-config.d.ts +177 -90
  131. package/dist/contracts/space-config.js +77 -92
  132. package/dist/contracts/spaces.d.ts +132 -24
  133. package/dist/contracts/spaces.js +77 -13
  134. package/dist/contracts/superadmin-connectors.d.ts +371 -0
  135. package/dist/contracts/superadmin-connectors.js +61 -0
  136. package/dist/contracts/superadmin-emails.d.ts +1 -1
  137. package/dist/contracts/superadmin-emails.js +1 -1
  138. package/dist/contracts/superadmin-entities.d.ts +105 -0
  139. package/dist/contracts/superadmin-entities.js +64 -0
  140. package/dist/contracts/superadmin-runtime.d.ts +35 -116
  141. package/dist/contracts/superadmin-runtime.js +13 -69
  142. package/dist/contracts/superadmin-search.d.ts +12 -14
  143. package/dist/contracts/superadmin-search.js +2 -2
  144. package/dist/contracts/superadmin-spaces.d.ts +15 -72
  145. package/dist/contracts/superadmin-spaces.js +2 -7
  146. package/dist/contracts/superadmin-teams.d.ts +12 -0
  147. package/dist/contracts/superadmin-teams.js +7 -1
  148. package/dist/contracts/superadmin-tenants.d.ts +44 -16
  149. package/dist/contracts/superadmin.d.ts +2 -2
  150. package/dist/contracts/tags.d.ts +12 -12
  151. package/dist/contracts/teams.d.ts +29 -1
  152. package/dist/contracts/teams.js +24 -2
  153. package/dist/contracts/tenants.d.ts +98 -0
  154. package/dist/contracts/tenants.js +20 -0
  155. package/dist/contracts/transfers.d.ts +92 -0
  156. package/dist/contracts/transfers.js +37 -0
  157. package/dist/contracts/usage.d.ts +9 -1
  158. package/dist/contracts/usage.js +2 -0
  159. package/dist/contracts/visual-review.d.ts +448 -0
  160. package/dist/contracts/visual-review.js +156 -0
  161. package/dist/contracts/wordpress-php-toolkit.json +9 -0
  162. package/dist/contracts/work-mode.d.ts +24 -0
  163. package/dist/contracts/work-mode.js +17 -0
  164. package/dist/contracts/wp-cli.d.ts +66 -0
  165. package/dist/contracts/wp-cli.js +68 -0
  166. package/dist/contracts/zero-catalog.d.ts +29 -0
  167. package/dist/contracts/zero-catalog.js +55 -0
  168. package/dist/contracts/zero-platform-abilities.generated.d.ts +31 -0
  169. package/dist/contracts/zero-platform-abilities.generated.js +373 -0
  170. package/dist/contracts/zero-spacefast.d.ts +444 -0
  171. package/dist/contracts/zero-spacefast.js +149 -0
  172. package/dist/contracts/zero-types.d.ts +47 -0
  173. package/dist/contracts/zero-types.js +263 -0
  174. package/dist/contracts/zero.d.ts +781 -14
  175. package/dist/contracts/zero.js +204 -10
  176. package/dist/dashboard-paths/index.d.ts +2 -2
  177. package/dist/dashboard-paths/route-reservations.generated.d.ts +2 -2
  178. package/dist/dashboard-paths/route-reservations.generated.js +5 -0
  179. package/dist/docs/agent-prose.d.ts +1 -1
  180. package/dist/docs/agent-prose.js +15 -18
  181. package/dist/docs/agent-setup.d.ts +1 -1
  182. package/dist/docs/agent-setup.js +5 -13
  183. package/dist/docs/catalog.d.ts +64 -28
  184. package/dist/docs/catalog.js +155 -28
  185. package/dist/docs/error-docs.d.ts +365 -17
  186. package/dist/docs/error-docs.js +369 -21
  187. package/dist/docs/reference-urls.js +1 -1
  188. package/dist/docs/skills.d.ts +84 -84
  189. package/dist/docs/skills.js +66 -47
  190. package/dist/docs/start-prompts.d.ts +14 -0
  191. package/dist/docs/start-prompts.js +20 -0
  192. package/dist/utils/account-labels.d.ts +7 -0
  193. package/dist/utils/account-labels.js +15 -0
  194. package/dist/utils/auth.d.ts +1 -0
  195. package/dist/utils/auth.js +1 -0
  196. package/dist/utils/credential-policy.d.ts +22 -1
  197. package/dist/utils/credential-policy.js +55 -6
  198. package/dist/utils/local-space-state.d.ts +7 -0
  199. package/dist/utils/local-space-state.js +7 -0
  200. package/dist/utils/logger.d.ts +34 -0
  201. package/dist/utils/logger.js +144 -0
  202. package/dist/utils/page-routes.d.ts +9 -0
  203. package/dist/utils/page-routes.js +82 -0
  204. package/dist/utils/pages.d.ts +2 -0
  205. package/dist/utils/pages.js +7 -0
  206. package/dist/utils/publish-form-data.d.ts +2 -0
  207. package/dist/utils/publish-form-data.js +2 -0
  208. package/dist/utils/publish-policy-spacefast.d.ts +13 -0
  209. package/dist/utils/publish-policy-spacefast.js +117 -0
  210. package/dist/utils/publish-policy.d.ts +22 -16
  211. package/dist/utils/publish-policy.fixtures.json +15 -0
  212. package/dist/utils/publish-policy.js +62 -139
  213. package/dist/utils/query-client.js +9 -1
  214. package/dist/utils/runtime-upload.d.ts +4 -0
  215. package/dist/utils/runtime-upload.js +5 -0
  216. package/dist/utils/space-config.d.ts +2 -3
  217. package/dist/utils/space-config.js +1 -1
  218. package/dist/utils/uploaded-source.d.ts +4 -0
  219. package/dist/utils/uploaded-source.js +12 -0
  220. package/dist/visual-review.d.ts +4 -0
  221. package/dist/visual-review.js +208 -0
  222. package/dist/vocabulary.d.ts +38 -0
  223. package/dist/vocabulary.js +55 -0
  224. package/package.json +7 -2
  225. package/dist/agents/registry/deeplink-probes.d.ts +0 -23
  226. package/dist/agents/registry/deeplink-probes.js +0 -18
@@ -0,0 +1,306 @@
1
+ import { canonicalJson } from "../utils/canonical-json.js";
2
+ import { contentContractSha256, } from "./content-contract-verification.js";
3
+ import { contentModelSdkDescriptors } from "./content-model-sdk.js";
4
+ import { CONTENT_MODEL_ARTIFACT_FORMATS, CONTENT_MODEL_FORMAT, CONTENT_MODEL_VERSION, compiledContentModelV1Schema, contentExecutableSetV1Schema, contentModelFieldSchema, contentModelReleaseV1Schema, contentRouteContributionSetV1Schema, } from "./content-model.js";
5
+ import { compiledContentModelArtifactsV1Schema, contentModelRequirementsV1Schema, } from "./runtime-components.js";
6
+ /**
7
+ * The fields WordPress gives every site whether an author mentions them or not.
8
+ * A `posts` collection that never declared a title still has one, so the model
9
+ * declares it too rather than shipping an editor with no title box. The
10
+ * author's own field of the same name wins.
11
+ */
12
+ const NATIVE_POST_FIELD_NAMES = new Set(["title", "content", "excerpt", "featured_media"]);
13
+ const NATIVE_FIELDS = {
14
+ posts: [
15
+ {
16
+ id: "posts-title",
17
+ name: "title",
18
+ label: "Title",
19
+ required: true,
20
+ value: { kind: "text", multiline: false, maxLength: 500 },
21
+ },
22
+ {
23
+ id: "posts-content",
24
+ name: "content",
25
+ label: "Content",
26
+ required: false,
27
+ value: { kind: "blocks", allowed: "any" },
28
+ },
29
+ ],
30
+ pages: [
31
+ {
32
+ id: "pages-title",
33
+ name: "title",
34
+ label: "Title",
35
+ required: true,
36
+ value: { kind: "text", multiline: false, maxLength: 500 },
37
+ },
38
+ {
39
+ id: "pages-content",
40
+ name: "content",
41
+ label: "Content",
42
+ required: false,
43
+ value: { kind: "blocks", allowed: "any" },
44
+ },
45
+ ],
46
+ media: [
47
+ {
48
+ id: "media-alt",
49
+ name: "alt",
50
+ label: "Alternative text",
51
+ required: false,
52
+ value: { kind: "text", multiline: false, maxLength: 2_000 },
53
+ },
54
+ ],
55
+ };
56
+ const NATIVE_LABELS = {
57
+ posts: "Posts",
58
+ pages: "Pages",
59
+ media: "Media",
60
+ };
61
+ /**
62
+ * The JSON Schema for one field, deliberately the same shape the control
63
+ * plane's PHP generator gives `register_post_meta`'s `show_in_rest`. A model
64
+ * whose declared contract disagreed with what WordPress serves would be a
65
+ * contract in name only.
66
+ */
67
+ function fieldJsonSchema(field) {
68
+ const value = field.value;
69
+ switch (value.kind) {
70
+ case "number":
71
+ return { type: "number" };
72
+ case "boolean":
73
+ return { type: "boolean" };
74
+ case "media":
75
+ case "reference":
76
+ return value.multiple ? { type: "array", items: { type: "integer" } } : { type: "integer" };
77
+ case "enum":
78
+ return value.multiple
79
+ ? { type: "array", items: { type: "string", enum: [...value.values] } }
80
+ : { type: "string", enum: [...value.values] };
81
+ case "text":
82
+ return value.maxLength === null
83
+ ? { type: "string" }
84
+ : { type: "string", maxLength: value.maxLength };
85
+ case "datetime":
86
+ return { type: "string", format: "date-time" };
87
+ case "blocks":
88
+ // HTML, not block markup. A `blocks` field is stored as WordPress blocks
89
+ // and exposed as HTML in both directions, so this is what the contract
90
+ // has to say a customer will receive and may send. Declaring a bare
91
+ // string here is what let `<!-- wp: -->` look like a conforming value.
92
+ return {
93
+ type: "string",
94
+ contentMediaType: "text/html",
95
+ description: "HTML. WordPress block markup is never exposed or accepted here.",
96
+ };
97
+ }
98
+ }
99
+ function resourceJsonSchema(resource) {
100
+ const properties = {};
101
+ for (const field of resource.fields) {
102
+ properties[field.name] = fieldJsonSchema(field);
103
+ }
104
+ return {
105
+ $schema: "https://json-schema.org/draft/2020-12/schema",
106
+ type: "object",
107
+ title: resource.label,
108
+ properties,
109
+ required: resource.fields.filter((field) => field.required).map((field) => field.name),
110
+ additionalProperties: false,
111
+ };
112
+ }
113
+ const EMPTY_PROPS_SCHEMA = {
114
+ $schema: "https://json-schema.org/draft/2020-12/schema",
115
+ type: "object",
116
+ properties: {},
117
+ required: [],
118
+ additionalProperties: false,
119
+ };
120
+ /**
121
+ * Merges an adopted native's declared fields over the ones WordPress always
122
+ * has. A native nobody adopted keeps WordPress's own default of public reads;
123
+ * an adopted one takes the author's read policy.
124
+ */
125
+ function nativeResource(kind, adopted) {
126
+ const declared = adopted?.fields ?? [];
127
+ const declaredNames = new Set(declared.map((field) => field.name));
128
+ const inherited = NATIVE_FIELDS[kind].filter((field) => !declaredNames.has(field.name));
129
+ return {
130
+ id: kind,
131
+ name: kind,
132
+ label: adopted?.label ?? NATIVE_LABELS[kind],
133
+ kind,
134
+ publicRead: adopted?.publicRead ?? true,
135
+ fields: [...inherited, ...declared],
136
+ };
137
+ }
138
+ /**
139
+ * The runtime components this model needs, derived from what it actually
140
+ * declares. A model that never asks for a custom field must not demand the
141
+ * plugin that renders one.
142
+ */
143
+ function requiredCapabilities(resources, syncBindings) {
144
+ const capabilities = [
145
+ "wordpress.core",
146
+ "wordpress.blocks",
147
+ "wordpress.media",
148
+ "wordpress.content-kernel",
149
+ ];
150
+ // Exactly the condition the PHP generator builds an SCF field group for: a
151
+ // collection resource carrying at least one field WordPress does not already
152
+ // store on the post row.
153
+ const usesCustomFields = resources.some((resource) => resource.kind === "collection" &&
154
+ resource.fields.some((field) => !NATIVE_POST_FIELD_NAMES.has(field.name)));
155
+ if (usesCustomFields)
156
+ capabilities.push("wordpress.scf");
157
+ if (syncBindings.length > 0)
158
+ capabilities.push("source.sync");
159
+ return capabilities;
160
+ }
161
+ function artifactRef(format, inputDigest, sha256,
162
+ // The same input `contentContractSha256` digests: a parsed release carries
163
+ // optional properties, so the artifact type has to admit an absent one.
164
+ artifact) {
165
+ return {
166
+ format,
167
+ version: 1,
168
+ inputDigest,
169
+ sha256,
170
+ bytes: new TextEncoder().encode(canonicalJson(artifact)).byteLength,
171
+ mediaType: "application/json",
172
+ };
173
+ }
174
+ const PLACEHOLDER_REVISION = `sha256:${"0".repeat(64)}`;
175
+ /**
176
+ * Lowers declared content into the compiled model.
177
+ *
178
+ * The revision is digested from the *parsed* release with its own revision
179
+ * removed, never from the draft that went in. The schema trims labels and
180
+ * applies defaults, so digesting the draft would produce a revision that
181
+ * `verifyContentModelReleaseV1` recomputes differently and rejects.
182
+ *
183
+ * No Abilities are emitted. This model declares content and nothing that runs,
184
+ * so there are no executables, no routes, and an SDK whose operations are
185
+ * empty — inventing a query here would mean inventing the QuickJS body behind
186
+ * it. The content is still reachable: the kernel registers the post types,
187
+ * meta, and taxonomy terms, and WordPress's own REST surface serves them.
188
+ */
189
+ export async function emitContentModelArtifacts(input) {
190
+ const syncBindings = input.syncBindings ?? [];
191
+ const materializations = input.materializations ?? [];
192
+ const pages = input.pages ?? [];
193
+ const adopted = new Map(input.resources
194
+ .filter((resource) => resource.kind !== "collection")
195
+ .map((resource) => [resource.kind, resource]));
196
+ const resources = [
197
+ nativeResource("posts", adopted.get("posts")),
198
+ nativeResource("pages", adopted.get("pages")),
199
+ nativeResource("media", adopted.get("media")),
200
+ ...input.resources.filter((resource) => resource.kind === "collection"),
201
+ ];
202
+ const resourceSchemas = await Promise.all(resources.map(async (resource) => {
203
+ const schema = resourceJsonSchema(resource);
204
+ return {
205
+ id: `content.${resource.id}.item`,
206
+ sha256: await contentContractSha256(schema),
207
+ schema,
208
+ };
209
+ }));
210
+ // Every page shares one empty props schema: a page compiled from a component
211
+ // path declares no props yet, and emitting a distinct identical schema per
212
+ // page would bloat the release for nothing.
213
+ const pageSchemas = pages.length === 0
214
+ ? []
215
+ : [
216
+ {
217
+ id: "content.page.props",
218
+ sha256: await contentContractSha256(EMPTY_PROPS_SCHEMA),
219
+ schema: EMPTY_PROPS_SCHEMA,
220
+ },
221
+ ];
222
+ const pagePropsRef = pageSchemas[0] ?? null;
223
+ if (pages.length > 0 && pagePropsRef === null) {
224
+ throw new TypeError("A content model with pages must carry the shared page props schema.");
225
+ }
226
+ const draftInput = {
227
+ format: CONTENT_MODEL_FORMAT,
228
+ version: CONTENT_MODEL_VERSION,
229
+ revision: PLACEHOLDER_REVISION,
230
+ schemas: [...resourceSchemas, ...pageSchemas],
231
+ wordpress: {
232
+ format: "spacefast.wordpress-content-model",
233
+ version: 1,
234
+ resources: resources.map((resource) => ({
235
+ id: resource.id,
236
+ name: resource.name,
237
+ label: resource.label,
238
+ kind: resource.kind,
239
+ publicRead: resource.publicRead,
240
+ fields: resource.fields.map((field) => ({ ...field })),
241
+ })),
242
+ },
243
+ tables: [],
244
+ abilities: [],
245
+ pages: pages.map((page) => ({
246
+ id: page.id,
247
+ sourceKey: page.sourceKey,
248
+ path: page.path,
249
+ title: page.title,
250
+ blockName: "zero/component",
251
+ componentId: page.componentId,
252
+ props: { id: pagePropsRef?.id, sha256: pagePropsRef?.sha256 },
253
+ })),
254
+ hooks: [],
255
+ syncBindings: syncBindings.map((binding) => ({ ...binding })),
256
+ materializations: materializations.map((materialization) => ({ ...materialization })),
257
+ };
258
+ const draft = contentModelReleaseV1Schema.parse(draftInput);
259
+ const { revision: _placeholder, ...meaning } = draft;
260
+ const revision = await contentContractSha256(meaning);
261
+ const contentModel = contentModelReleaseV1Schema.parse({ ...meaning, revision });
262
+ const executables = contentExecutableSetV1Schema.parse({
263
+ format: CONTENT_MODEL_ARTIFACT_FORMATS.executables,
264
+ version: 1,
265
+ inputDigest: revision,
266
+ executables: [],
267
+ });
268
+ const sdk = contentModelSdkDescriptors(contentModel);
269
+ const routes = contentRouteContributionSetV1Schema.parse({
270
+ format: CONTENT_MODEL_ARTIFACT_FORMATS.routes,
271
+ version: 1,
272
+ inputDigest: revision,
273
+ routes: [],
274
+ });
275
+ const requirements = contentModelRequirementsV1Schema.parse({
276
+ format: CONTENT_MODEL_ARTIFACT_FORMATS.requirements,
277
+ version: 1,
278
+ inputDigest: revision,
279
+ wordpressAbi: 1,
280
+ capabilities: requiredCapabilities(resources, syncBindings),
281
+ });
282
+ const [contentModelSha, executablesSha, sdkSha, routesSha, requirementsSha] = await Promise.all([
283
+ contentContractSha256(contentModel),
284
+ contentContractSha256(executables),
285
+ contentContractSha256(sdk),
286
+ contentContractSha256(routes),
287
+ contentContractSha256(requirements),
288
+ ]);
289
+ const compiled = compiledContentModelV1Schema.parse({
290
+ format: "spacefast.compiled-content-model",
291
+ version: 1,
292
+ model: artifactRef(CONTENT_MODEL_ARTIFACT_FORMATS.model, input.sourceDigest, contentModelSha, contentModel),
293
+ executables: artifactRef(CONTENT_MODEL_ARTIFACT_FORMATS.executables, revision, executablesSha, executables),
294
+ sdk: artifactRef(CONTENT_MODEL_ARTIFACT_FORMATS.sdk, revision, sdkSha, sdk),
295
+ routes: artifactRef(CONTENT_MODEL_ARTIFACT_FORMATS.routes, revision, routesSha, routes),
296
+ requirements: artifactRef(CONTENT_MODEL_ARTIFACT_FORMATS.requirements, revision, requirementsSha, requirements),
297
+ });
298
+ return compiledContentModelArtifactsV1Schema.parse({
299
+ compiled,
300
+ model: contentModel,
301
+ executables,
302
+ sdk,
303
+ routes,
304
+ requirements,
305
+ });
306
+ }
@@ -0,0 +1,101 @@
1
+ import { type ContentCacheProfileV1, type ContentModelJsonValue, type ContentModelReleaseV1, type ContentSdkDescriptorSetV1 } from "./content-model.js";
2
+ /**
3
+ * The content model SDK: the typed surface a compiled ContentModelRelease offers its
4
+ * callers.
5
+ *
6
+ * Two derivations, one input. `contentModelSdkDescriptors` lowers a
7
+ * ContentModelRelease into the SDK descriptor artifact the compiled bundle carries,
8
+ * and `contentModelSdkModule` lowers the same release into a self-contained
9
+ * TypeScript module an app or an agent can drop into its own project. Both are
10
+ * pure functions of the release, so the SDK is never a separate thing to keep
11
+ * in step with the content model — it is the content model, spelled for a caller.
12
+ *
13
+ * Dispatch is not here and never will be: a compiled Ability is published
14
+ * through the WordPress Abilities API under `contentAbilityWordPressName`, and
15
+ * that registry is the one dispatcher. `createContentModelClient` is the thin
16
+ * typed seam over whichever transport reaches it — REST, MCP, or an in-process
17
+ * call.
18
+ */
19
+ /**
20
+ * Derives the SDK descriptor artifact from a ContentModelRelease.
21
+ *
22
+ * `invalidates` is the whole reason this is a derivation rather than a
23
+ * declaration. A write Ability invalidates exactly the read Abilities that read
24
+ * a resource it writes, so a cached read cannot outlive the write that changed
25
+ * it. Nothing upstream is trusted to compute that graph: the release names the
26
+ * reads and the writes, and the edges follow.
27
+ */
28
+ export declare function contentModelSdkDescriptors(contentModel: ContentModelReleaseV1): ContentSdkDescriptorSetV1;
29
+ /**
30
+ * What a caller must hold to run an Ability, in the vocabulary the catalog uses.
31
+ *
32
+ * Structurally the same as `SpaceZeroAbilityRequirement`; spelled here rather
33
+ * than imported so this module stays the self-contained one it prints from.
34
+ */
35
+ export type ContentModelAbilityRequirement = Readonly<{
36
+ kind: "wordpress";
37
+ capability: string;
38
+ }> | Readonly<{
39
+ kind: "grant";
40
+ capabilities: readonly string[];
41
+ }>;
42
+ export type ContentModelAbilityDescriptor = Readonly<{
43
+ /** The name the WordPress Abilities API publishes this Ability under. */
44
+ name: string;
45
+ kind: "query" | "mutation" | "endpoint";
46
+ mode: "read" | "write";
47
+ cache: ContentCacheProfileV1;
48
+ invalidates: readonly string[];
49
+ /**
50
+ * What a caller must hold, when the descriptor's source knows.
51
+ *
52
+ * A transport that cannot supply a credential — a browser on a public content
53
+ * page — refuses a call that needs one instead of shipping it to a 403. Absent
54
+ * means the descriptor's source did not state a requirement, which is not the
55
+ * same as "requires nothing" and is deliberately not read as a grant.
56
+ */
57
+ requires?: ContentModelAbilityRequirement;
58
+ }>;
59
+ export type ContentModelAbilityType = Readonly<{
60
+ input: ContentModelJsonValue;
61
+ output: ContentModelJsonValue;
62
+ }>;
63
+ export type ContentModelAbilityTypes = Readonly<Record<string, ContentModelAbilityType>>;
64
+ export type ContentModelAbilityCall = Readonly<{
65
+ abilityId: string;
66
+ ability: ContentModelAbilityDescriptor;
67
+ input: ContentModelJsonValue;
68
+ }>;
69
+ /** Whatever reaches the Abilities API — REST, MCP, or an in-process call. */
70
+ export type ContentModelAbilityDispatch = (call: ContentModelAbilityCall) => Promise<ContentModelJsonValue>;
71
+ export type ContentModelClient<TAbilities extends ContentModelAbilityTypes> = Readonly<{
72
+ abilities: Readonly<Record<keyof TAbilities & string, ContentModelAbilityDescriptor>>;
73
+ call<TId extends keyof TAbilities & string>(id: TId, input: TAbilities[TId]["input"]): Promise<TAbilities[TId]["output"]>;
74
+ }>;
75
+ /**
76
+ * Binds a generated module's ability table to a dispatcher. The client resolves
77
+ * a content model id to the WordPress Ability name and hands the call on; it decides
78
+ * nothing about admission, caching, or execution.
79
+ */
80
+ export declare function createContentModelClient<TAbilities extends ContentModelAbilityTypes>(options: {
81
+ abilities: Readonly<Record<keyof TAbilities & string, ContentModelAbilityDescriptor>>;
82
+ dispatch: ContentModelAbilityDispatch;
83
+ }): ContentModelClient<TAbilities>;
84
+ /**
85
+ * Prints one JSON Schema as a TypeScript type.
86
+ *
87
+ * Exported for the Ability catalog printer, which prints the same dialect for
88
+ * schemas that never came from a content model: the platform Ability sets
89
+ * declare theirs inline in the engine's PHP. One printer for both keeps a
90
+ * `zero-types` module from spelling the same shape two ways.
91
+ */
92
+ export declare function printContentModelSchema(schema: ContentModelJsonValue): string;
93
+ /**
94
+ * Emits the self-contained TypeScript module for a ContentModelRelease.
95
+ *
96
+ * No imports, by design: the output is meant to be written into someone else's
97
+ * project, where a dependency on `@spacefast/common` would be an imposition. Its
98
+ * banner names `createContentModelClient` without importing it — pair the two
99
+ * yourself when you want the typed call seam.
100
+ */
101
+ export declare function contentModelSdkModule(contentModel: ContentModelReleaseV1): string;
@@ -0,0 +1,232 @@
1
+ import { z } from "zod";
2
+ import { CONTENT_MODEL_ARTIFACT_FORMATS, contentAbilityTypeName, contentAbilityWordPressName, contentSdkDescriptorSetV1Schema, } from "./content-model.js";
3
+ /**
4
+ * The content model SDK: the typed surface a compiled ContentModelRelease offers its
5
+ * callers.
6
+ *
7
+ * Two derivations, one input. `contentModelSdkDescriptors` lowers a
8
+ * ContentModelRelease into the SDK descriptor artifact the compiled bundle carries,
9
+ * and `contentModelSdkModule` lowers the same release into a self-contained
10
+ * TypeScript module an app or an agent can drop into its own project. Both are
11
+ * pure functions of the release, so the SDK is never a separate thing to keep
12
+ * in step with the content model — it is the content model, spelled for a caller.
13
+ *
14
+ * Dispatch is not here and never will be: a compiled Ability is published
15
+ * through the WordPress Abilities API under `contentAbilityWordPressName`, and
16
+ * that registry is the one dispatcher. `createContentModelClient` is the thin
17
+ * typed seam over whichever transport reaches it — REST, MCP, or an in-process
18
+ * call.
19
+ */
20
+ /**
21
+ * Derives the SDK descriptor artifact from a ContentModelRelease.
22
+ *
23
+ * `invalidates` is the whole reason this is a derivation rather than a
24
+ * declaration. A write Ability invalidates exactly the read Abilities that read
25
+ * a resource it writes, so a cached read cannot outlive the write that changed
26
+ * it. Nothing upstream is trusted to compute that graph: the release names the
27
+ * reads and the writes, and the edges follow.
28
+ */
29
+ export function contentModelSdkDescriptors(contentModel) {
30
+ const readers = contentModel.abilities.filter((ability) => ability.mode === "read");
31
+ return contentSdkDescriptorSetV1Schema.parse({
32
+ format: CONTENT_MODEL_ARTIFACT_FORMATS.sdk,
33
+ version: 1,
34
+ inputDigest: contentModel.revision,
35
+ operations: contentModel.abilities.map((ability) => ({
36
+ id: ability.id,
37
+ kind: ability.kind,
38
+ mode: ability.mode,
39
+ input: ability.input,
40
+ output: ability.output,
41
+ cache: ability.cache,
42
+ invalidates: ability.mode === "write"
43
+ ? readers
44
+ .filter((reader) => reader.reads.some((resource) => ability.writes.includes(resource)))
45
+ .map((reader) => reader.id)
46
+ : [],
47
+ })),
48
+ sync: contentModel.syncBindings.map((binding) => ({
49
+ id: binding.id,
50
+ resourceId: binding.resourceId,
51
+ source: binding.source,
52
+ })),
53
+ });
54
+ }
55
+ /**
56
+ * Binds a generated module's ability table to a dispatcher. The client resolves
57
+ * a content model id to the WordPress Ability name and hands the call on; it decides
58
+ * nothing about admission, caching, or execution.
59
+ */
60
+ export function createContentModelClient(options) {
61
+ return {
62
+ abilities: options.abilities,
63
+ async call(id, input) {
64
+ const ability = options.abilities[id];
65
+ if (ability === undefined) {
66
+ throw new TypeError(`The generated content model SDK has no Ability named ${id}.`);
67
+ }
68
+ const output = await options.dispatch({ abilityId: id, ability, input });
69
+ // SAFETY: the Abilities API answers under the Ability's declared output
70
+ // schema, and the generated type is that schema's TypeScript spelling.
71
+ return output;
72
+ },
73
+ };
74
+ }
75
+ const contentModelLiteralSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
76
+ // The JSON Schema dialect a compiled content model actually writes. Anything outside
77
+ // it parses as `undefined` and prints as `unknown` rather than as a guess: a
78
+ // wrong type is worse than an honest absence of one.
79
+ const contentModelJsonSchemaSchema = z.lazy(() => z
80
+ .object({
81
+ $ref: z.string().optional(),
82
+ $defs: z.record(z.string(), contentModelJsonSchemaSchema).optional(),
83
+ type: z.union([z.string(), z.array(z.string())]).optional(),
84
+ enum: z.array(contentModelLiteralSchema).optional(),
85
+ const: contentModelLiteralSchema.optional(),
86
+ properties: z.record(z.string(), contentModelJsonSchemaSchema).optional(),
87
+ required: z.array(z.string()).optional(),
88
+ additionalProperties: z.union([z.boolean(), contentModelJsonSchemaSchema]).optional(),
89
+ items: contentModelJsonSchemaSchema.optional(),
90
+ anyOf: z.array(contentModelJsonSchemaSchema).optional(),
91
+ oneOf: z.array(contentModelJsonSchemaSchema).optional(),
92
+ })
93
+ .loose());
94
+ const DEFINITION_POINTER_PREFIX = "#/$defs/";
95
+ function propertyKey(name) {
96
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
97
+ }
98
+ function printObject(schema, definitions, visiting) {
99
+ const properties = Object.entries(schema.properties ?? {});
100
+ if (properties.length === 0) {
101
+ const additional = schema.additionalProperties;
102
+ if (additional === false) {
103
+ return "Readonly<Record<string, never>>";
104
+ }
105
+ const value = additional === undefined || additional === true
106
+ ? "unknown"
107
+ : printSchema(additional, definitions, visiting);
108
+ return `Readonly<Record<string, ${value}>>`;
109
+ }
110
+ const required = new Set(schema.required ?? []);
111
+ const members = properties.map(([name, property]) => {
112
+ const optional = required.has(name) ? "" : "?";
113
+ return `readonly ${propertyKey(name)}${optional}: ${printSchema(property, definitions, visiting)}`;
114
+ });
115
+ return `{ ${members.join("; ")} }`;
116
+ }
117
+ function printKeyword(keyword, schema, definitions, visiting) {
118
+ switch (keyword) {
119
+ case "object":
120
+ return printObject(schema, definitions, visiting);
121
+ case "array":
122
+ return schema.items === undefined
123
+ ? "unknown[]"
124
+ : `Array<${printSchema(schema.items, definitions, visiting)}>`;
125
+ case "string":
126
+ return "string";
127
+ case "integer":
128
+ case "number":
129
+ return "number";
130
+ case "boolean":
131
+ return "boolean";
132
+ case "null":
133
+ return "null";
134
+ default:
135
+ return "unknown";
136
+ }
137
+ }
138
+ function printSchema(schema, definitions, visiting) {
139
+ if (schema.$ref !== undefined) {
140
+ const name = schema.$ref.startsWith(DEFINITION_POINTER_PREFIX)
141
+ ? schema.$ref.slice(DEFINITION_POINTER_PREFIX.length)
142
+ : null;
143
+ const target = name === null ? undefined : definitions.get(name);
144
+ // A self-referential definition has no finite spelling here, and inventing
145
+ // a named alias for it would leak the content model's internal $defs into the
146
+ // SDK's surface. `unknown` is the honest stopping point.
147
+ if (name === null || target === undefined || visiting.has(name)) {
148
+ return "unknown";
149
+ }
150
+ return printSchema(target, definitions, new Set([...visiting, name]));
151
+ }
152
+ if (schema.const !== undefined) {
153
+ return JSON.stringify(schema.const);
154
+ }
155
+ if (schema.enum !== undefined && schema.enum.length > 0) {
156
+ return schema.enum.map((value) => JSON.stringify(value)).join(" | ");
157
+ }
158
+ const alternatives = schema.anyOf ?? schema.oneOf;
159
+ if (alternatives !== undefined && alternatives.length > 0) {
160
+ return alternatives
161
+ .map((alternative) => printSchema(alternative, definitions, visiting))
162
+ .join(" | ");
163
+ }
164
+ if (schema.type === undefined) {
165
+ return "unknown";
166
+ }
167
+ const keywords = Array.isArray(schema.type) ? schema.type : [schema.type];
168
+ return keywords
169
+ .map((keyword) => printKeyword(keyword, schema, definitions, visiting))
170
+ .join(" | ");
171
+ }
172
+ /**
173
+ * Prints one JSON Schema as a TypeScript type.
174
+ *
175
+ * Exported for the Ability catalog printer, which prints the same dialect for
176
+ * schemas that never came from a content model: the platform Ability sets
177
+ * declare theirs inline in the engine's PHP. One printer for both keeps a
178
+ * `zero-types` module from spelling the same shape two ways.
179
+ */
180
+ export function printContentModelSchema(schema) {
181
+ const parsed = contentModelJsonSchemaSchema.safeParse(schema);
182
+ if (!parsed.success) {
183
+ return "unknown";
184
+ }
185
+ const definitions = new Map(Object.entries(parsed.data.$defs ?? {}));
186
+ return printSchema(parsed.data, definitions, new Set());
187
+ }
188
+ /**
189
+ * Emits the self-contained TypeScript module for a ContentModelRelease.
190
+ *
191
+ * No imports, by design: the output is meant to be written into someone else's
192
+ * project, where a dependency on `@spacefast/common` would be an imposition. Its
193
+ * banner names `createContentModelClient` without importing it — pair the two
194
+ * yourself when you want the typed call seam.
195
+ */
196
+ export function contentModelSdkModule(contentModel) {
197
+ const descriptors = contentModelSdkDescriptors(contentModel);
198
+ const schemas = new Map(contentModel.schemas.map((schema) => [schema.id, schema.schema]));
199
+ const printRef = (id) => {
200
+ const schema = schemas.get(id);
201
+ return schema === undefined ? "unknown" : printContentModelSchema(schema);
202
+ };
203
+ const entries = descriptors.operations.map((operation) => ({
204
+ id: operation.id,
205
+ typeName: contentAbilityTypeName(operation.id),
206
+ input: printRef(operation.input.id),
207
+ output: printRef(operation.output.id),
208
+ descriptor: {
209
+ name: contentAbilityWordPressName(operation.id),
210
+ kind: operation.kind,
211
+ mode: operation.mode,
212
+ cache: operation.cache,
213
+ invalidates: operation.invalidates,
214
+ },
215
+ }));
216
+ const lines = [
217
+ "// Generated from a Spacefast ContentModelRelease. Do not edit.",
218
+ "//",
219
+ "// Every Ability below is published by the WordPress Abilities API under the",
220
+ "// `name` in `contentModelAbilities`. Hand this table to",
221
+ "// `createContentModelClient` from `@spacefast/common/contracts/content-model-sdk`",
222
+ "// for a typed `call`, or address the names directly over REST or MCP.",
223
+ "",
224
+ `export const contentModelRevision = ${JSON.stringify(contentModel.revision)};`,
225
+ "",
226
+ ];
227
+ for (const entry of entries) {
228
+ lines.push(`/** Input for the \`${entry.id}\` Ability. */`, `export type ${entry.typeName}Input = ${entry.input};`, "", `/** Output of the \`${entry.id}\` Ability. */`, `export type ${entry.typeName}Output = ${entry.output};`, "");
229
+ }
230
+ lines.push("export type ContentModelAbilityTypes = {", ...entries.map((entry) => ` ${JSON.stringify(entry.id)}: { input: ${entry.typeName}Input; output: ${entry.typeName}Output };`), "};", "", "export const contentModelAbilities = {", ...entries.map((entry) => ` ${JSON.stringify(entry.id)}: ${JSON.stringify(entry.descriptor)},`), "} as const;", "", `export const contentModelSyncBindings = ${JSON.stringify(descriptors.sync)} as const;`, "");
231
+ return lines.join("\n");
232
+ }