@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.
- package/dist/agents/marketplace-prompts.d.ts +1 -0
- package/dist/agents/marketplace-prompts.js +6 -0
- package/dist/agents/registry/core.d.ts +4 -12
- package/dist/agents/registry/core.js +7 -28
- package/dist/config/domains.d.ts +10 -8
- package/dist/config/domains.js +4 -7
- package/dist/contracts/access-core.d.ts +135 -0
- package/dist/contracts/access-core.js +242 -0
- package/dist/contracts/access-profiles.d.ts +8 -8
- package/dist/contracts/access-profiles.js +8 -8
- package/dist/contracts/access.d.ts +14 -103
- package/dist/contracts/access.js +10 -210
- package/dist/contracts/activity.d.ts +54 -55
- package/dist/contracts/activity.js +38 -9
- package/dist/contracts/api-keys.d.ts +69 -26
- package/dist/contracts/api-keys.js +29 -2
- package/dist/contracts/application-journal.d.ts +197 -4
- package/dist/contracts/application-journal.js +97 -16
- package/dist/contracts/auth.d.ts +20 -0
- package/dist/contracts/auth.js +18 -0
- package/dist/contracts/beta.d.ts +13 -0
- package/dist/contracts/beta.js +28 -0
- package/dist/contracts/billing.d.ts +2 -2
- package/dist/contracts/bootstrap.d.ts +23 -6
- package/dist/contracts/builds.d.ts +24 -4
- package/dist/contracts/builds.js +16 -4
- package/dist/contracts/channels.d.ts +43 -0
- package/dist/contracts/channels.js +22 -6
- package/dist/contracts/comments.d.ts +49 -0
- package/dist/contracts/comments.js +61 -2
- package/dist/contracts/commerce.d.ts +19 -13
- package/dist/contracts/connector-access.d.ts +28 -0
- package/dist/contracts/connector-access.js +31 -0
- package/dist/contracts/connectors.d.ts +2382 -0
- package/dist/contracts/connectors.js +941 -0
- package/dist/contracts/content-contract-verification.d.ts +112 -58
- package/dist/contracts/content-contract-verification.js +42 -54
- package/dist/contracts/content-model-emit.d.ts +97 -0
- package/dist/contracts/content-model-emit.js +306 -0
- package/dist/contracts/content-model-sdk.d.ts +101 -0
- package/dist/contracts/content-model-sdk.js +232 -0
- package/dist/contracts/{content-program.d.ts → content-model.d.ts} +338 -82
- package/dist/contracts/{content-program.js → content-model.js} +291 -63
- package/dist/contracts/content-platform-fixture.d.ts +380 -110
- package/dist/contracts/content-platform-fixture.js +8 -8
- package/dist/contracts/content-sync.d.ts +197 -74
- package/dist/contracts/content-sync.js +98 -37
- package/dist/contracts/content.d.ts +30 -422
- package/dist/contracts/content.js +34 -294
- package/dist/contracts/dashboard-prefs.d.ts +10 -3
- package/dist/contracts/dashboard-prefs.js +6 -3
- package/dist/contracts/data-location.d.ts +9 -0
- package/dist/contracts/data-location.js +7 -0
- package/dist/contracts/deployment-comparison.d.ts +67 -0
- package/dist/contracts/deployment-comparison.js +68 -0
- package/dist/contracts/device-auth.d.ts +55 -11
- package/dist/contracts/domains.d.ts +40 -16
- package/dist/contracts/domains.js +23 -0
- package/dist/contracts/enums.d.ts +1 -10
- package/dist/contracts/enums.js +0 -15
- package/dist/contracts/error-code-meta.d.ts +388 -20
- package/dist/contracts/error-code-meta.js +201 -5
- package/dist/contracts/error-codes.d.ts +17 -4
- package/dist/contracts/error-codes.js +109 -10
- package/dist/contracts/events.d.ts +5 -0
- package/dist/contracts/events.js +12 -3
- package/dist/contracts/execution.d.ts +5 -1
- package/dist/contracts/execution.js +4 -0
- package/dist/contracts/feature-lifecycle.d.ts +53 -20
- package/dist/contracts/feature-lifecycle.js +65 -23
- package/dist/contracts/features.d.ts +57 -31
- package/dist/contracts/features.js +0 -1
- package/dist/contracts/feedback.d.ts +124 -0
- package/dist/contracts/feedback.js +64 -0
- package/dist/contracts/fixtures/content-model-sdk-v1.generated.d.ts +129 -0
- package/dist/contracts/fixtures/content-model-sdk-v1.generated.js +48 -0
- package/dist/contracts/fixtures/content-platform-v1.json +101 -49
- package/dist/contracts/frame-session.d.ts +60 -10
- package/dist/contracts/frame-session.js +52 -12
- package/dist/contracts/functions.d.ts +28 -2
- package/dist/contracts/functions.js +25 -2
- package/dist/contracts/grant-copy.d.ts +2 -0
- package/dist/contracts/grant-copy.js +2 -0
- package/dist/contracts/grants-core.d.ts +95 -0
- package/dist/contracts/grants-core.js +435 -0
- package/dist/contracts/grants.d.ts +13 -84
- package/dist/contracts/grants.js +21 -369
- package/dist/contracts/ids-core.d.ts +11 -0
- package/dist/contracts/ids-core.js +14 -0
- package/dist/contracts/ids.d.ts +7 -4
- package/dist/contracts/ids.js +12 -4
- package/dist/contracts/internal.d.ts +18 -0
- package/dist/contracts/internal.js +19 -0
- package/dist/contracts/mcp.d.ts +8 -18
- package/dist/contracts/mcp.js +2 -23
- package/dist/contracts/notifications.d.ts +4 -1
- package/dist/contracts/notifications.js +1 -0
- package/dist/contracts/oauth-resources.d.ts +55 -9
- package/dist/contracts/oauth-resources.js +64 -1
- package/dist/contracts/oauth-scope-actions.d.ts +27 -3
- package/dist/contracts/oauth-scope-actions.js +18 -8
- package/dist/contracts/operations.d.ts +1 -1
- package/dist/contracts/partner-account.d.ts +629 -0
- package/dist/contracts/partner-account.js +18 -0
- package/dist/contracts/principal-assertion.d.ts +74 -1
- package/dist/contracts/principal-assertion.js +67 -3
- package/dist/contracts/publish-archive.d.ts +22 -1
- package/dist/contracts/push-new.d.ts +3 -3
- package/dist/contracts/push-new.js +1 -1
- package/dist/contracts/quotas.js +2 -1
- package/dist/contracts/repository-connections.d.ts +10 -6
- package/dist/contracts/repository-connections.js +15 -0
- package/dist/contracts/resources.d.ts +6 -5
- package/dist/contracts/resources.js +7 -0
- package/dist/contracts/route-inventory.d.ts +74 -19
- package/dist/contracts/route-inventory.js +20 -6
- package/dist/contracts/runtime-api.d.ts +415 -4
- package/dist/contracts/runtime-api.js +60 -15
- package/dist/contracts/runtime-app.d.ts +6 -1
- package/dist/contracts/runtime-components.d.ts +646 -18
- package/dist/contracts/runtime-components.js +304 -188
- package/dist/contracts/runtime-services.d.ts +15 -0
- package/dist/contracts/runtime-services.js +25 -0
- package/dist/contracts/runtime-storage.d.ts +32 -0
- package/dist/contracts/runtime-storage.js +37 -0
- package/dist/contracts/source-workspaces.d.ts +511 -0
- package/dist/contracts/source-workspaces.js +276 -0
- package/dist/contracts/space-config-spacefast.d.ts +394 -0
- package/dist/contracts/space-config-spacefast.js +70 -0
- package/dist/contracts/space-config.d.ts +177 -90
- package/dist/contracts/space-config.js +77 -92
- package/dist/contracts/spaces.d.ts +132 -24
- package/dist/contracts/spaces.js +77 -13
- package/dist/contracts/superadmin-connectors.d.ts +371 -0
- package/dist/contracts/superadmin-connectors.js +61 -0
- package/dist/contracts/superadmin-emails.d.ts +1 -1
- package/dist/contracts/superadmin-emails.js +1 -1
- package/dist/contracts/superadmin-entities.d.ts +105 -0
- package/dist/contracts/superadmin-entities.js +64 -0
- package/dist/contracts/superadmin-runtime.d.ts +35 -116
- package/dist/contracts/superadmin-runtime.js +13 -69
- package/dist/contracts/superadmin-search.d.ts +12 -14
- package/dist/contracts/superadmin-search.js +2 -2
- package/dist/contracts/superadmin-spaces.d.ts +15 -72
- package/dist/contracts/superadmin-spaces.js +2 -7
- package/dist/contracts/superadmin-teams.d.ts +12 -0
- package/dist/contracts/superadmin-teams.js +7 -1
- package/dist/contracts/superadmin-tenants.d.ts +44 -16
- package/dist/contracts/superadmin.d.ts +2 -2
- package/dist/contracts/tags.d.ts +12 -12
- package/dist/contracts/teams.d.ts +29 -1
- package/dist/contracts/teams.js +24 -2
- package/dist/contracts/tenants.d.ts +98 -0
- package/dist/contracts/tenants.js +20 -0
- package/dist/contracts/transfers.d.ts +92 -0
- package/dist/contracts/transfers.js +37 -0
- package/dist/contracts/usage.d.ts +9 -1
- package/dist/contracts/usage.js +2 -0
- package/dist/contracts/visual-review.d.ts +448 -0
- package/dist/contracts/visual-review.js +156 -0
- package/dist/contracts/wordpress-php-toolkit.json +9 -0
- package/dist/contracts/work-mode.d.ts +24 -0
- package/dist/contracts/work-mode.js +17 -0
- package/dist/contracts/wp-cli.d.ts +66 -0
- package/dist/contracts/wp-cli.js +68 -0
- package/dist/contracts/zero-catalog.d.ts +29 -0
- package/dist/contracts/zero-catalog.js +55 -0
- package/dist/contracts/zero-platform-abilities.generated.d.ts +31 -0
- package/dist/contracts/zero-platform-abilities.generated.js +373 -0
- package/dist/contracts/zero-spacefast.d.ts +444 -0
- package/dist/contracts/zero-spacefast.js +149 -0
- package/dist/contracts/zero-types.d.ts +47 -0
- package/dist/contracts/zero-types.js +263 -0
- package/dist/contracts/zero.d.ts +781 -14
- package/dist/contracts/zero.js +204 -10
- package/dist/dashboard-paths/index.d.ts +2 -2
- package/dist/dashboard-paths/route-reservations.generated.d.ts +2 -2
- package/dist/dashboard-paths/route-reservations.generated.js +5 -0
- package/dist/docs/agent-prose.d.ts +1 -1
- package/dist/docs/agent-prose.js +15 -18
- package/dist/docs/agent-setup.d.ts +1 -1
- package/dist/docs/agent-setup.js +5 -13
- package/dist/docs/catalog.d.ts +64 -28
- package/dist/docs/catalog.js +155 -28
- package/dist/docs/error-docs.d.ts +365 -17
- package/dist/docs/error-docs.js +369 -21
- package/dist/docs/reference-urls.js +1 -1
- package/dist/docs/skills.d.ts +84 -84
- package/dist/docs/skills.js +66 -47
- package/dist/docs/start-prompts.d.ts +14 -0
- package/dist/docs/start-prompts.js +20 -0
- package/dist/utils/account-labels.d.ts +7 -0
- package/dist/utils/account-labels.js +15 -0
- package/dist/utils/auth.d.ts +1 -0
- package/dist/utils/auth.js +1 -0
- package/dist/utils/credential-policy.d.ts +22 -1
- package/dist/utils/credential-policy.js +55 -6
- package/dist/utils/local-space-state.d.ts +7 -0
- package/dist/utils/local-space-state.js +7 -0
- package/dist/utils/logger.d.ts +34 -0
- package/dist/utils/logger.js +144 -0
- package/dist/utils/page-routes.d.ts +9 -0
- package/dist/utils/page-routes.js +82 -0
- package/dist/utils/pages.d.ts +2 -0
- package/dist/utils/pages.js +7 -0
- package/dist/utils/publish-form-data.d.ts +2 -0
- package/dist/utils/publish-form-data.js +2 -0
- package/dist/utils/publish-policy-spacefast.d.ts +13 -0
- package/dist/utils/publish-policy-spacefast.js +117 -0
- package/dist/utils/publish-policy.d.ts +22 -16
- package/dist/utils/publish-policy.fixtures.json +15 -0
- package/dist/utils/publish-policy.js +62 -139
- package/dist/utils/query-client.js +9 -1
- package/dist/utils/runtime-upload.d.ts +4 -0
- package/dist/utils/runtime-upload.js +5 -0
- package/dist/utils/space-config.d.ts +2 -3
- package/dist/utils/space-config.js +1 -1
- package/dist/utils/uploaded-source.d.ts +4 -0
- package/dist/utils/uploaded-source.js +12 -0
- package/dist/visual-review.d.ts +4 -0
- package/dist/visual-review.js +208 -0
- package/dist/vocabulary.d.ts +38 -0
- package/dist/vocabulary.js +55 -0
- package/package.json +7 -2
- package/dist/agents/registry/deeplink-probes.d.ts +0 -23
- 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
|
+
}
|