@spacefast/common 0.2.2 → 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 +18 -4
  26. package/dist/contracts/builds.js +7 -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 +384 -20
  62. package/dist/contracts/error-code-meta.js +200 -5
  63. package/dist/contracts/error-codes.d.ts +17 -4
  64. package/dist/contracts/error-codes.js +108 -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 +46 -9
  99. package/dist/contracts/oauth-resources.js +50 -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 +361 -17
  186. package/dist/docs/error-docs.js +365 -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,263 @@
1
+ /**
2
+ * The module `sf zero types` writes into a Zero project.
3
+ *
4
+ * Three things a project's types depend on live in the platform rather than in
5
+ * the project's own source, and each one is only knowable by asking: the
6
+ * content model the live version compiled, the Ability catalog that version
7
+ * publishes alongside the engine's own sets, and the database schema the broker
8
+ * validates against. A capsule author can read all three from the dashboard and
9
+ * retype them by hand; this prints them instead.
10
+ *
11
+ * The output is self-contained on purpose — no imports, because it lands in
12
+ * someone else's project where a dependency on `@spacefast/common` would be an
13
+ * imposition. That is the same rule `contentModelSdkModule` follows, and its
14
+ * output is embedded here verbatim rather than re-derived: it already prints
15
+ * exactly the content-model half of this file.
16
+ */
17
+ import { createHash } from "node:crypto";
18
+ import { contentModelSdkModule, printContentModelSchema } from "./content-model-sdk.js";
19
+ import { contentAbilityTypeName } from "./content-model.js";
20
+ /** Zero manages these three on every row; a capsule cannot declare them. */
21
+ const MANAGED_ROW_COLUMNS = [
22
+ ["id", "string"],
23
+ ["createdAt", "string"],
24
+ ["updatedAt", "string"],
25
+ ];
26
+ const JSON_VALUE_TYPE = "ZeroJsonValue";
27
+ function tableTypeName(name) {
28
+ return `${contentAbilityTypeName(name)}Row`;
29
+ }
30
+ function columnType(column) {
31
+ const base = column.type === "json"
32
+ ? JSON_VALUE_TYPE
33
+ : column.type === "string"
34
+ ? "string"
35
+ : column.type === "number"
36
+ ? "number"
37
+ : "boolean";
38
+ return column.nullable ? `${base} | null` : base;
39
+ }
40
+ function propertyKey(name) {
41
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
42
+ }
43
+ /**
44
+ * The row type for one table, as a reader gets it back.
45
+ *
46
+ * The managed columns lead because they are on every row and the capsule never
47
+ * declared them, so a reader who did not write this table still knows what is
48
+ * there. The physical names the deployment assigns — the `sf_<space>_<table>_…`
49
+ * rewriting the control plane does — deliberately do not appear: they are
50
+ * plumbing, and a generated type that leaked them would break on a rename that
51
+ * changes nothing about the data.
52
+ */
53
+ function printTable(table) {
54
+ const members = [
55
+ ...MANAGED_ROW_COLUMNS.map(([name, type]) => ` readonly ${name}: ${type};`),
56
+ ...table.columns.map((column) => ` readonly ${propertyKey(column.name)}: ${columnType(column)};`),
57
+ ];
58
+ return [
59
+ `/** A row of the \`${table.name}\` table. */`,
60
+ `export type ${tableTypeName(table.name)} = {`,
61
+ ...members,
62
+ "};",
63
+ "",
64
+ ];
65
+ }
66
+ /** How the catalog spells an Ability's authorization requirement in a doc comment. */
67
+ function requirementSentence(ability) {
68
+ return ability.requires.kind === "wordpress"
69
+ ? `Requires the WordPress capability \`${ability.requires.capability}\`.`
70
+ : ability.requires.capabilities.length === 0
71
+ ? "Requires no Grant capability."
72
+ : `Requires the Grant ${ability.requires.capabilities.length === 1 ? "capability" : "capabilities"} ${ability.requires.capabilities.map((capability) => `\`${capability}\``).join(", ")}.`;
73
+ }
74
+ function printAbilityCatalog(abilities) {
75
+ if (abilities.length === 0) {
76
+ return [
77
+ "/** This Space publishes no Abilities. */",
78
+ "export type ZeroAbilityCatalog = Readonly<Record<string, never>>;",
79
+ "",
80
+ "export const zeroAbilityCatalog = {} as const;",
81
+ "",
82
+ ];
83
+ }
84
+ // The catalog keys every entry by product id, in both the type and the value.
85
+ // Two rows under one id would emit a duplicate key that fails to compile, so
86
+ // refuse to print a broken module and name the id instead. The control plane
87
+ // rejects a content Ability that collides with a platform one at compile, so
88
+ // reaching this is a malformed catalog rather than ordinary input.
89
+ const seenIds = new Set();
90
+ for (const ability of abilities) {
91
+ if (seenIds.has(ability.id)) {
92
+ throw new Error(`The Ability catalog lists ${JSON.stringify(ability.id)} twice.`);
93
+ }
94
+ seenIds.add(ability.id);
95
+ }
96
+ const lines = [
97
+ "/**",
98
+ " * Every Ability this Space publishes, keyed by product id.",
99
+ " *",
100
+ " * Two origins are merged here and both are callable the same way. The",
101
+ " * `zero-wp-users` and `zero-storage` sets come from the runtime engine and are",
102
+ " * there whatever the capsule declares; the `zero-content` set was compiled from",
103
+ " * the capsule and moves with a rollback.",
104
+ " */",
105
+ "export type ZeroAbilityCatalog = {",
106
+ ];
107
+ for (const ability of abilities) {
108
+ const requires = ability.requires.kind === "wordpress"
109
+ ? `{ readonly kind: "wordpress"; readonly capability: ${JSON.stringify(ability.requires.capability)} }`
110
+ : `{ readonly kind: "grant"; readonly capabilities: readonly [${ability.requires.capabilities.map((capability) => JSON.stringify(capability)).join(", ")}] }`;
111
+ lines.push(` /** ${requirementSentence(ability)} */`, ` ${JSON.stringify(ability.id)}: {`, ` readonly input: ${ability.inputSchema === null ? "unknown" : printContentModelSchema(ability.inputSchema)};`, ` readonly output: ${ability.outputSchema === null ? "unknown" : printContentModelSchema(ability.outputSchema)};`, ` readonly mode: ${JSON.stringify(ability.mode)};`, ` readonly requires: ${requires};`, " };");
112
+ }
113
+ lines.push("};", "");
114
+ lines.push("/** The same catalog as a value, for dispatching by name. */", "export const zeroAbilityCatalog = {", ...abilities.map((ability) => ` ${JSON.stringify(ability.id)}: ${JSON.stringify({
115
+ name: ability.name,
116
+ category: ability.category,
117
+ mode: ability.mode,
118
+ requires: ability.requires,
119
+ })},`), "} as const;", "");
120
+ return lines;
121
+ }
122
+ /**
123
+ * The WordPress REST route each resource kind answers on.
124
+ *
125
+ * A declared collection's items are `post` rows carrying a collection taxonomy
126
+ * term, which is why it reads `wp/v2/posts` and is narrowed by the term rather
127
+ * than by a post type of its own.
128
+ */
129
+ const CONTENT_READ_ROUTES = {
130
+ posts: "wp/v2/posts",
131
+ pages: "wp/v2/pages",
132
+ media: "wp/v2/media",
133
+ collection: "wp/v2/posts",
134
+ };
135
+ /** The collection taxonomy the content-model kernel registers on `post`. */
136
+ const CONTENT_COLLECTION_TAXONOMY = "zero_collection";
137
+ /**
138
+ * The term a collection's items carry, spelled exactly as
139
+ * `spacefast_content_model_collection_term_slug()` mints it. Two places compute
140
+ * this and they must agree, so the rule is restated here rather than guessed:
141
+ * `sf-<first 16 hex of sha256(spaceId)>-<resource id with . and _ as ->`.
142
+ */
143
+ function collectionTermSlug(spaceId, resourceId) {
144
+ const hash = createHash("sha256").update(spaceId).digest("hex").slice(0, 16);
145
+ return `sf-${hash}-${resourceId.replaceAll(".", "-").replaceAll("_", "-")}`;
146
+ }
147
+ /**
148
+ * The reads an island or a capsule makes against this Space's own content.
149
+ *
150
+ * Printed as plain object literals, not as calls into the SDK: this module
151
+ * lands in someone else's project and imports nothing. What each entry returns
152
+ * is structurally a `ZeroQueryOptions`, so `useQuery(content.posts.list())` is
153
+ * the same line whether it runs in an island on a content page or in a capsule
154
+ * SPA — one data layer, two backends.
155
+ */
156
+ function printContentReads(source) {
157
+ const resources = source.contentModel?.wordpress.resources ?? [];
158
+ if (resources.length === 0)
159
+ return [];
160
+ const lines = [
161
+ "/**",
162
+ " * Reads against this Space's own content, over its WordPress REST surface.",
163
+ " *",
164
+ " * Same-origin and credential-less: a `publicRead` resource answers an",
165
+ " * anonymous reader, and one that is not needs a session. Pass any parameters",
166
+ " * WordPress accepts (`per_page`, `search`, `slug`, …).",
167
+ " */",
168
+ "export const content = {",
169
+ ];
170
+ for (const resource of resources) {
171
+ const route = CONTENT_READ_ROUTES[resource.kind];
172
+ const term = resource.kind === "collection" && source.space !== null
173
+ ? collectionTermSlug(source.space.id, resource.id)
174
+ : null;
175
+ if (resource.kind === "collection" && term === null) {
176
+ // Without a Space there is no term to name, and a read that fetched every
177
+ // post while claiming to be one collection would be worse than absent.
178
+ lines.push(` // \`${resource.id}\` is a collection; its term is Space-scoped, so run`, " // `sf zero types` against a Space to generate its read.");
179
+ continue;
180
+ }
181
+ const params = term === null
182
+ ? "...params"
183
+ : `${JSON.stringify(CONTENT_COLLECTION_TAXONOMY)}: ${JSON.stringify(term)}, ...params`;
184
+ lines.push(` ${propertyKey(resource.name)}: {`, ` /** ${resource.label}. ${resource.publicRead
185
+ ? "publicRead: an anonymous reader may make this call."
186
+ : "Not publicRead: this call needs a session that may read it."} */`, " list: (params: Readonly<Record<string, string | number | boolean>> = {}) => ({", ` name: "content:content.${resource.id}.list",`, ` args: [{ route: ${JSON.stringify(route)}, params: { ${params} } }],`, " }),", " },");
187
+ }
188
+ lines.push("} as const;", "");
189
+ return lines;
190
+ }
191
+ function header(source) {
192
+ const origin = source.space === null
193
+ ? "Generated by `sf zero types` from this project's local capsule compile."
194
+ : `Generated by \`sf zero types\` from the space ${source.space.slug} (${source.space.id}).`;
195
+ const lines = ["// GENERATED FILE — DO NOT EDIT.", "//", `// ${origin}`];
196
+ if (source.versionId !== null) {
197
+ lines.push(`// Live version: ${source.versionId}`);
198
+ }
199
+ if (source.contentModel !== null) {
200
+ lines.push(`// Content model revision: ${source.contentModel.revision}`);
201
+ }
202
+ lines.push("//", "// Regenerate: sf zero types", "// Verify in CI: sf zero types --check", "");
203
+ return lines;
204
+ }
205
+ /**
206
+ * Prints the whole `zero-types` module.
207
+ *
208
+ * Section order follows what a reader reaches for: the content model first
209
+ * because it is what most capsules are written against, then the Ability
210
+ * catalog that says what may be called and by whom, then the database rows.
211
+ */
212
+ export function zeroTypesModule(source) {
213
+ const sections = [header(source).join("\n")];
214
+ sections.push(source.contentModel === null
215
+ ? [
216
+ "// ── Content model ─────────────────────────────────────────────────────────",
217
+ "//",
218
+ "// This Space's live version declares no content model, so there are no",
219
+ "// content Abilities and no content types to generate.",
220
+ "",
221
+ ].join("\n")
222
+ : [
223
+ "// ── Content model ─────────────────────────────────────────────────────────",
224
+ "",
225
+ contentModelSdkModule(source.contentModel),
226
+ "",
227
+ ...printContentReads(source),
228
+ ].join("\n"));
229
+ sections.push([
230
+ "// ── Ability catalog ───────────────────────────────────────────────────────",
231
+ "",
232
+ ...printAbilityCatalog(source.abilities),
233
+ ].join("\n"));
234
+ const dbLines = [
235
+ "// ── Database ──────────────────────────────────────────────────────────────",
236
+ "",
237
+ ];
238
+ if (source.tables.some((table) => table.columns.some((column) => column.type === "json"))) {
239
+ dbLines.push("/** What a `json` column holds. */", `export type ${JSON_VALUE_TYPE} =`, " | string", " | number", " | boolean", " | null", ` | readonly ${JSON_VALUE_TYPE}[]`, ` | { readonly [key: string]: ${JSON_VALUE_TYPE} };`, "");
240
+ }
241
+ if (source.tables.length === 0) {
242
+ dbLines.push("/** This capsule declares no tables. */", "export type ZeroTables = Readonly<Record<string, never>>;", "");
243
+ }
244
+ else {
245
+ // Distinct table names can flatten onto one row-type identifier (`user_posts`
246
+ // and `user__posts` both give `UserPostsRow`), which would emit a duplicate
247
+ // `export type` that fails to compile. Refuse to print it and name the tables.
248
+ const seenTypeNames = new Map();
249
+ for (const table of source.tables) {
250
+ const typeName = tableTypeName(table.name);
251
+ const collided = seenTypeNames.get(typeName);
252
+ if (collided !== undefined) {
253
+ throw new Error(`Tables \`${collided}\` and \`${table.name}\` both generate the row type \`${typeName}\`.`);
254
+ }
255
+ seenTypeNames.set(typeName, table.name);
256
+ }
257
+ for (const table of source.tables)
258
+ dbLines.push(...printTable(table));
259
+ dbLines.push("/** Every table this capsule declares, keyed by its capsule name. */", "export type ZeroTables = {", ...source.tables.map((table) => ` readonly ${propertyKey(table.name)}: ${tableTypeName(table.name)};`), "};", "");
260
+ }
261
+ sections.push(dbLines.join("\n"));
262
+ return sections.join("\n");
263
+ }