@spacefast/common 0.0.13 → 0.0.18

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 (154) hide show
  1. package/dist/agents/client-registry.d.ts +22 -0
  2. package/dist/agents/client-registry.js +41 -0
  3. package/dist/agents/connect-targets.d.ts +51 -6
  4. package/dist/agents/connect-targets.js +93 -28
  5. package/dist/brand.d.ts +0 -11
  6. package/dist/brand.js +0 -20
  7. package/dist/config/domains.d.ts +15 -6
  8. package/dist/config/domains.js +24 -10
  9. package/dist/config/postgres-budget.d.ts +3 -0
  10. package/dist/config/postgres-budget.js +6 -1
  11. package/dist/config/postgres-budget.json +2 -1
  12. package/dist/contracts/abuse.js +1 -1
  13. package/dist/contracts/access.d.ts +149 -44
  14. package/dist/contracts/access.js +94 -8
  15. package/dist/contracts/activity.js +5 -1
  16. package/dist/contracts/api-keys.js +9 -9
  17. package/dist/contracts/archives.js +11 -11
  18. package/dist/contracts/beta.js +1 -1
  19. package/dist/contracts/builds.js +14 -14
  20. package/dist/contracts/channels.js +2 -2
  21. package/dist/contracts/comments.d.ts +86 -15
  22. package/dist/contracts/comments.js +116 -20
  23. package/dist/contracts/common.d.ts +12 -12
  24. package/dist/contracts/common.js +33 -30
  25. package/dist/contracts/continuation.js +5 -5
  26. package/dist/contracts/data-source-routes.js +1 -1
  27. package/dist/contracts/device-auth.d.ts +6 -6
  28. package/dist/contracts/device-auth.js +12 -12
  29. package/dist/contracts/docs.d.ts +10 -10
  30. package/dist/contracts/domains.js +39 -33
  31. package/dist/contracts/enums.d.ts +2 -10
  32. package/dist/contracts/enums.js +0 -2
  33. package/dist/contracts/error-code-meta.d.ts +116 -47
  34. package/dist/contracts/error-code-meta.js +37 -16
  35. package/dist/contracts/error-codes.d.ts +7 -4
  36. package/dist/contracts/error-codes.js +46 -21
  37. package/dist/contracts/events.d.ts +10 -3
  38. package/dist/contracts/events.js +33 -5
  39. package/dist/contracts/execution.d.ts +26 -0
  40. package/dist/contracts/execution.js +21 -0
  41. package/dist/contracts/feature-lifecycle.d.ts +21 -24
  42. package/dist/contracts/feature-lifecycle.js +28 -26
  43. package/dist/contracts/features.js +4 -4
  44. package/dist/contracts/functions.d.ts +8 -140
  45. package/dist/contracts/functions.js +9 -19
  46. package/dist/contracts/git.d.ts +1 -1
  47. package/dist/contracts/grants.d.ts +96 -79
  48. package/dist/contracts/grants.js +130 -43
  49. package/dist/contracts/ids.d.ts +2 -2
  50. package/dist/contracts/ids.js +2 -4
  51. package/dist/contracts/internal.js +2 -2
  52. package/dist/contracts/mcp.d.ts +17 -556
  53. package/dist/contracts/mcp.js +9 -103
  54. package/dist/contracts/me.js +3 -4
  55. package/dist/contracts/notifications.d.ts +108 -0
  56. package/dist/contracts/notifications.js +54 -0
  57. package/dist/contracts/oauth-resources.d.ts +18 -1
  58. package/dist/contracts/oauth-resources.js +22 -2
  59. package/dist/contracts/operations.d.ts +2 -50
  60. package/dist/contracts/operations.js +0 -19
  61. package/dist/contracts/pages.d.ts +8 -4
  62. package/dist/contracts/pages.js +16 -3
  63. package/dist/contracts/platform.js +10 -8
  64. package/dist/contracts/problem-document.d.ts +14 -0
  65. package/dist/contracts/problem-document.js +35 -0
  66. package/dist/contracts/push-new.d.ts +21 -6
  67. package/dist/contracts/push-new.js +3 -7
  68. package/dist/contracts/resources.d.ts +8 -7
  69. package/dist/contracts/resources.js +9 -6
  70. package/dist/contracts/runtime-api.d.ts +162 -92
  71. package/dist/contracts/runtime-api.js +99 -27
  72. package/dist/contracts/runtime-app.d.ts +41 -11
  73. package/dist/contracts/runtime-app.js +22 -1
  74. package/dist/contracts/runtime-db.d.ts +424 -0
  75. package/dist/contracts/runtime-db.js +108 -0
  76. package/dist/contracts/runtime-storage.d.ts +120 -0
  77. package/dist/contracts/runtime-storage.js +40 -0
  78. package/dist/contracts/sf-config-v1.d.ts +49 -1
  79. package/dist/contracts/sf-config-v1.js +2 -2
  80. package/dist/contracts/sites.d.ts +1 -0
  81. package/dist/contracts/sites.js +20 -12
  82. package/dist/contracts/space-config.d.ts +62 -22
  83. package/dist/contracts/space-config.js +173 -47
  84. package/dist/contracts/spaces.d.ts +79 -45
  85. package/dist/contracts/spaces.js +66 -50
  86. package/dist/contracts/superadmin-spaces.d.ts +13 -2
  87. package/dist/contracts/superadmin-spaces.js +15 -0
  88. package/dist/contracts/superadmin-tenants.js +4 -4
  89. package/dist/contracts/superadmin.d.ts +4 -0
  90. package/dist/contracts/superadmin.js +14 -1
  91. package/dist/contracts/tags.d.ts +17 -17
  92. package/dist/contracts/tags.js +1 -1
  93. package/dist/contracts/teams.js +25 -16
  94. package/dist/contracts/theme-json.js +1 -1
  95. package/dist/contracts/transfers.js +4 -4
  96. package/dist/contracts/variables.js +3 -3
  97. package/dist/contracts/zero.d.ts +204 -338
  98. package/dist/contracts/zero.js +94 -98
  99. package/dist/dashboard-paths/index.d.ts +16 -1
  100. package/dist/dashboard-paths/index.js +18 -0
  101. package/dist/docs/agent-handoff-document.d.ts +2 -2
  102. package/dist/docs/agent-handoff-document.js +8 -7
  103. package/dist/docs/agent-prose.d.ts +22 -3
  104. package/dist/docs/agent-prose.js +221 -51
  105. package/dist/docs/agent-setup.d.ts +13 -10
  106. package/dist/docs/agent-setup.js +279 -53
  107. package/dist/docs/agent-solutions.d.ts +6 -6
  108. package/dist/docs/agent-solutions.js +23 -21
  109. package/dist/docs/catalog.d.ts +29 -74
  110. package/dist/docs/catalog.js +65 -92
  111. package/dist/docs/error-docs.js +293 -221
  112. package/dist/docs/skill-distribution.d.ts +2 -0
  113. package/dist/docs/skill-distribution.js +7 -4
  114. package/dist/docs/skills.d.ts +546 -0
  115. package/dist/docs/skills.js +340 -0
  116. package/dist/utils/browser-credential.d.ts +19 -0
  117. package/dist/utils/browser-credential.js +62 -0
  118. package/dist/utils/comment-avatars.d.ts +7 -0
  119. package/dist/utils/comment-avatars.js +42 -0
  120. package/dist/utils/content-type.d.ts +3 -3
  121. package/dist/utils/content-type.js +38 -61
  122. package/dist/utils/domain-diagnostics.d.ts +5 -5
  123. package/dist/utils/domain-diagnostics.js +21 -10
  124. package/dist/utils/local-space-state.d.ts +78 -4
  125. package/dist/utils/local-space-state.js +383 -82
  126. package/dist/utils/one-shot-replay.d.ts +14 -0
  127. package/dist/utils/one-shot-replay.js +21 -0
  128. package/dist/utils/page-preview.d.ts +7 -0
  129. package/dist/utils/page-preview.js +9 -0
  130. package/dist/utils/pages.d.ts +2 -11
  131. package/dist/utils/pages.js +58 -14
  132. package/dist/utils/publish-form-data.d.ts +22 -0
  133. package/dist/utils/publish-form-data.js +26 -0
  134. package/dist/utils/publish-policy.js +20 -4
  135. package/dist/utils/query-keys.d.ts +7 -4
  136. package/dist/utils/query-keys.js +13 -4
  137. package/dist/utils/runtime-paths.d.ts +3 -0
  138. package/dist/utils/runtime-paths.js +7 -0
  139. package/dist/utils/secure-local-file.d.ts +73 -0
  140. package/dist/utils/secure-local-file.js +1373 -0
  141. package/dist/utils/space-config.d.ts +13 -4
  142. package/dist/utils/space-config.js +123 -15
  143. package/dist/utils/storage-policy.d.ts +20 -0
  144. package/dist/utils/storage-policy.fixtures.json +174 -0
  145. package/dist/utils/storage-policy.js +193 -0
  146. package/dist/utils/upload-session.d.ts +12 -1
  147. package/dist/utils/upload-session.js +37 -2
  148. package/dist/utils/version-file-browser.d.ts +13 -0
  149. package/dist/utils/version-file-browser.js +59 -0
  150. package/package.json +19 -1
  151. package/dist/contracts/mounts.d.ts +0 -142
  152. package/dist/contracts/mounts.js +0 -56
  153. package/dist/utils/asset-fingerprint.d.ts +0 -64
  154. package/dist/utils/asset-fingerprint.js +0 -620
@@ -3,6 +3,11 @@ export declare const SF_CONFIG_V1_CANONICAL_FILE = "sf.jsonc";
3
3
  export declare const SF_CONFIG_V1_FILES: readonly ["sf.jsonc", "spacefast.jsonc", "spacefast.json", "sf.json", ".sf/sf.json", ".sf/config.jsonc", ".sf/config.json"];
4
4
  /** Published JSON Schema URL for the strict `sf.jsonc` v1 contract. */
5
5
  export declare const SF_CONFIG_V1_SCHEMA_URL: `${string}/schemas/sf.v1.json`;
6
+ /**
7
+ * How a redirect answers. Omitting it means 302. There is no 200: a rule that
8
+ * serves other content under the requested URL is a `rewrites` entry.
9
+ */
10
+ export type SfConfigV1RedirectStatus = 301 | 302 | 303 | 307 | 308;
6
11
  export type SfConfigV1 = {
7
12
  $schema?: typeof SF_CONFIG_V1_SCHEMA_URL;
8
13
  version: 1;
@@ -19,6 +24,38 @@ export type SfConfigV1 = {
19
24
  cleanUrls?: boolean;
20
25
  directoryListing?: boolean;
21
26
  };
27
+ /**
28
+ * Browser redirect rules, in the same matcher grammar as `_redirects`. The
29
+ * files match first; config rules add behavior where the files are silent.
30
+ */
31
+ redirects?: {
32
+ source: string;
33
+ destination: string;
34
+ status?: SfConfigV1RedirectStatus;
35
+ }[];
36
+ /**
37
+ * Rewrite and proxy rules: the URL stays, the content comes from the
38
+ * destination. A path destination rewrites, an absolute URL proxies — the
39
+ * same 200 rules `_redirects` writes as `/from /to 200`, which is why they
40
+ * carry no status of their own.
41
+ */
42
+ rewrites?: {
43
+ source: string;
44
+ destination: string;
45
+ /** Let shared caches store the proxy response. Absolute-URL destinations only. */
46
+ cache?: "shared";
47
+ }[];
48
+ /**
49
+ * Response headers to set on matching requests. Headers are a list, not a
50
+ * map, so repeated names (Set-Cookie) survive.
51
+ */
52
+ headers?: {
53
+ source: string;
54
+ headers: {
55
+ key: string;
56
+ value: string;
57
+ }[];
58
+ }[];
22
59
  metadata?: {
23
60
  title?: string;
24
61
  description?: string;
@@ -43,12 +80,23 @@ export type EffectiveConfigV1 = {
43
80
  cleanUrls: boolean;
44
81
  directoryListing: boolean;
45
82
  };
83
+ /** Every rule states its status; an omitted one resolves to 302. */
84
+ redirects?: (NonNullable<SfConfigV1["redirects"]>[number] & {
85
+ status: SfConfigV1RedirectStatus;
86
+ })[];
87
+ rewrites?: SfConfigV1["rewrites"];
88
+ headers?: SfConfigV1["headers"];
46
89
  metadata?: SfConfigV1["metadata"];
47
90
  substitute?: SfConfigV1["substitute"];
48
91
  grants?: Grant[];
49
92
  };
93
+ /**
94
+ * Config-shape problems use the `config_*` codes; a bad routing rule reports
95
+ * the same code the `_redirects` / `_headers` compiler would give it, because
96
+ * it is the same grammar and the same compiler.
97
+ */
50
98
  export type SfConfigV1Issue = {
51
- code: "config_alias" | "config_conflict" | "config_identity_moved" | "config_invalid" | "config_key_removed" | "config_unknown_key" | "serve_index_required" | "template_syntax_retired";
99
+ code: "config_alias" | "config_conflict" | "config_identity_moved" | "config_invalid" | "config_key_removed" | "config_unknown_key" | "header_basic_auth_unsupported" | "header_cache_control_platform_managed" | "header_cdn_cache_unsupported" | "header_name_invalid" | "header_name_unsupported" | "header_path_invalid" | "header_pattern_invalid" | "header_port_unsupported" | "header_value_invalid" | "redirect_cache_directive_invalid" | "redirect_cache_not_proxy" | "redirect_destination_invalid" | "redirect_destination_missing" | "redirect_host_invalid" | "redirect_loop_invalid" | "redirect_pattern_invalid" | "redirect_rewrite_destination_invalid" | "redirect_source_invalid" | "redirect_status_unsupported" | "redirect_status_use_rewrites" | "serve_index_required" | "template_syntax_retired";
52
100
  severity: "error" | "warning";
53
101
  path: string;
54
102
  line: number;
@@ -1,6 +1,6 @@
1
- import { BRAND } from "../brand.js";
1
+ import { serviceOrigin } from "../config/domains.js";
2
2
  import { SPACE_CONFIG_ACCEPTED_FILES, SPACE_CONFIG_CANONICAL_FILE } from "./space-config.js";
3
3
  export const SF_CONFIG_V1_CANONICAL_FILE = SPACE_CONFIG_CANONICAL_FILE;
4
4
  export const SF_CONFIG_V1_FILES = SPACE_CONFIG_ACCEPTED_FILES;
5
5
  /** Published JSON Schema URL for the strict `sf.jsonc` v1 contract. */
6
- export const SF_CONFIG_V1_SCHEMA_URL = `${BRAND.websiteOrigin}/schemas/sf.v1.json`;
6
+ export const SF_CONFIG_V1_SCHEMA_URL = `${serviceOrigin("www", "prod")}/schemas/sf.v1.json`;
@@ -104,6 +104,7 @@ export declare const operatorSiteProvisioningSchema: z.ZodOptional<z.ZodObject<{
104
104
  }, z.core.$strip>>;
105
105
  export declare const tenantSiteProvisioningSchema: z.ZodOptional<z.ZodObject<{}, z.core.$strict>>;
106
106
  export declare const siteCreateSchema: z.ZodObject<{
107
+ teamId: z.ZodOptional<z.ZodString>;
107
108
  kind: z.ZodDefault<z.ZodEnum<{
108
109
  static_runtime: "static_runtime";
109
110
  }>>;
@@ -11,7 +11,7 @@ export const SiteCapacitySchema = z
11
11
  .int()
12
12
  .nonnegative()
13
13
  .optional()
14
- .describe("Maximum spaces this site may host."),
14
+ .describe("Maximum number of spaces this site can host."),
15
15
  usedSpaces: z
16
16
  .number()
17
17
  .int()
@@ -56,8 +56,8 @@ export const SitePressureSchema = z
56
56
  // Provider linkage (`providerClientId`, `providerRef`) is operator-only and
57
57
  // never appears in this tenant-facing shape.
58
58
  export const SiteSchema = z.object({
59
- id: z.string().describe("Site id (`site_...`)."),
60
- tenantId: z.string().describe("Owning tenant id (`ten_...`)."),
59
+ id: z.string().describe("The site id (`site_...`)."),
60
+ tenantId: z.string().describe("The owning tenant id (`ten_...`)."),
61
61
  principal: PrincipalRefSchema.nullable().describe("Owning principal within the tenant; null for tenant-level and pool sites."),
62
62
  kind: siteKind.describe("Runtime kind. Only the Spacefast static runtime exists today."),
63
63
  isolation: siteIsolation.describe("Whether settings/access default to the site or to each space."),
@@ -67,7 +67,7 @@ export const SiteSchema = z.object({
67
67
  .string()
68
68
  .nullable()
69
69
  .describe("Hostname the control plane manages the site runtime through."),
70
- runtimeInstanceId: z.string().nullable().describe("Runtime instance identity for this site."),
70
+ runtimeInstanceId: z.string().nullable().describe("Runtime instance id for this site."),
71
71
  provider: z.literal("wpcloud").describe("Hosting provider."),
72
72
  capacity: SiteCapacitySchema.nullable().describe("Capacity accounting; null when the site has no explicit budget."),
73
73
  operation: AsyncOperationSchema.nullable(),
@@ -84,7 +84,7 @@ export const operatorSiteProvisioningSchema = z
84
84
  .trim()
85
85
  .min(1)
86
86
  .optional()
87
- .describe("Import an existing WP.Cloud site by its atomic_site_id (operator flows)."),
87
+ .describe("Import an existing WP.Cloud site by its `atomic_site_id` (operator flows)."),
88
88
  })
89
89
  .optional();
90
90
  // An explicit empty object requests fresh provider provisioning while keeping
@@ -92,9 +92,15 @@ export const operatorSiteProvisioningSchema = z
92
92
  export const tenantSiteProvisioningSchema = z.object({}).strict().optional();
93
93
  export const siteCreateSchema = z
94
94
  .object({
95
+ teamId: z
96
+ .string()
97
+ .trim()
98
+ .min(1)
99
+ .optional()
100
+ .describe("Team id or slug that will own the site. If omitted, the server infers it from the principal."),
95
101
  kind: siteKind.default("static_runtime"),
96
102
  isolation: siteIsolation.default("site"),
97
- region: z.string().trim().min(1).optional().describe('Placement region; defaults to `"auto"`.'),
103
+ region: z.string().trim().min(1).optional().describe('Placement region. Defaults to `"auto"`.'),
98
104
  provisioning: tenantSiteProvisioningSchema,
99
105
  })
100
106
  .strict();
@@ -113,14 +119,16 @@ export const SuperadminSiteSchema = SiteSchema.extend({
113
119
  .string()
114
120
  .nullable()
115
121
  .describe("WP.Cloud client identity (`pvc_...`) the site is provisioned under."),
116
- providerRef: z.string().nullable().describe("WP.Cloud atomic_site_id."),
122
+ providerRef: z.string().nullable().describe("WP.Cloud `atomic_site_id`."),
117
123
  });
118
124
  export const SuperadminSiteListSchema = z.object({
119
125
  sites: z.array(SuperadminSiteSchema),
120
126
  });
121
127
  // Superadmin site create targets an explicit tenant (the tenant-facing
122
128
  // `siteCreateSchema` derives the tenant from the principal).
123
- export const superadminSiteCreateSchema = siteCreateSchema.omit({ provisioning: true }).extend({
129
+ export const superadminSiteCreateSchema = siteCreateSchema
130
+ .omit({ provisioning: true, teamId: true })
131
+ .extend({
124
132
  tenantId: z.string().trim().min(1),
125
133
  provisioning: operatorSiteProvisioningSchema,
126
134
  });
@@ -133,12 +141,12 @@ export const siteRebalanceRequestSchema = z
133
141
  .array(z.string().trim().min(1))
134
142
  .max(100)
135
143
  .optional()
136
- .describe("Limit the rebalance to these space ids; omitted means every live space on the site."),
144
+ .describe("Limit the rebalance to these space ids. If omitted, the server rebalances every live space on the site."),
137
145
  })
138
146
  .describe("Operator-only rebalance trigger: move spaces off this site. This is the ONLY path that relocates an existing Space, and it is not free — see the reverification warning on the result.");
139
147
  export const SiteRebalanceResultSchema = z
140
148
  .object({
141
- siteId: z.string().describe("The site spaces are being moved off."),
149
+ siteId: z.string().describe("The site id that spaces are being moved off."),
142
150
  moves: z
143
151
  .array(z.object({
144
152
  spaceId: z.string().describe("Space queued to move."),
@@ -147,7 +155,7 @@ export const SiteRebalanceResultSchema = z
147
155
  .describe("One queued move operation per affected space."),
148
156
  warning: z
149
157
  .string()
150
- .describe("Consequence of relocating these Spaces: each hostname is handed to another provider site, which re-runs the provider's domain-exclusivity check and is rejected unless the domain's ownership TXT record is still live in DNS."),
158
+ .describe("Consequence of relocating these spaces. Each hostname moves to another provider site. The provider re-runs its domain-exclusivity check and rejects the move unless the ownership TXT record is still live in DNS."),
151
159
  domainsRequiringReverification: z
152
160
  .array(z.object({
153
161
  spaceId: z.string().describe("Space the hostname serves."),
@@ -155,6 +163,6 @@ export const SiteRebalanceResultSchema = z
155
163
  .string()
156
164
  .describe("Custom hostname whose provider ownership check re-runs during the move."),
157
165
  }))
158
- .describe("Live custom hostnames on the moved Spaces. Empty means nothing customer-visible is at risk."),
166
+ .describe("Live custom hostnames on the moved spaces. Empty means no customer-visible hostname is at risk."),
159
167
  })
160
168
  .describe("Queued rebalance moves; track each operation for completion.");
@@ -25,6 +25,8 @@ export declare const SPACE_CONFIG_CAPS: {
25
25
  readonly themeLogoChars: 2000;
26
26
  /** Max `theme.name` display-name length. */
27
27
  readonly themeNameChars: 120;
28
+ /** Max top-level `name` (space display name) length. Matches the stored column. */
29
+ readonly nameChars: 255;
28
30
  };
29
31
  /** Published JSON Schema URL for `sf.jsonc` ($schema autocomplete). */
30
32
  export declare const SPACE_CONFIG_SCHEMA_URL: string;
@@ -70,7 +72,7 @@ export declare const wordpressDataSourceSchema: z.ZodObject<{
70
72
  url: z.ZodString;
71
73
  }, z.core.$strict>;
72
74
  export type WordPressDataSource = z.infer<typeof wordpressDataSourceSchema>;
73
- export declare const spaceDataSourcesSchema: z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodObject<{
75
+ export declare const spaceDataSourcesSchema: z.ZodPipe<z.ZodUnknown, z.ZodRecord<z.ZodString, z.ZodObject<{
74
76
  kind: z.ZodLiteral<"wordpress">;
75
77
  url: z.ZodString;
76
78
  }, z.core.$strict>>>;
@@ -152,7 +154,7 @@ export declare const spaceConfigSchema: z.ZodObject<{
152
154
  bodyEnd: z.ZodOptional<z.ZodArray<z.ZodString>>;
153
155
  noscript: z.ZodOptional<z.ZodArray<z.ZodString>>;
154
156
  }, z.core.$strip>>;
155
- dataSources: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodObject<{
157
+ dataSources: z.ZodOptional<z.ZodPipe<z.ZodUnknown, z.ZodRecord<z.ZodString, z.ZodObject<{
156
158
  kind: z.ZodLiteral<"wordpress">;
157
159
  url: z.ZodString;
158
160
  }, z.core.$strict>>>>;
@@ -162,28 +164,61 @@ export type SpaceConfig = z.infer<typeof spaceConfigSchema>;
162
164
  export declare function compareDataSourceNames(left: string, right: string): number;
163
165
  export declare function resolveDefaultDataSourceName(config: Pick<SpaceConfig, "dataSources" | "defaultDataSource"> | null | undefined): string | null;
164
166
  export declare function spaceSuperpowersEnabled(config: SpaceConfig | null | undefined): boolean;
165
- export declare const spacePublishOptionsSchema: z.ZodObject<{
166
- fingerprint: z.ZodOptional<z.ZodBoolean>;
167
- }, z.core.$strip>;
168
- export type SpacePublishOptions = z.infer<typeof spacePublishOptionsSchema>;
169
- export declare const spaceConfigFileAccessSchema: z.ZodObject<{
167
+ export declare const spaceConfigFileAccessSchema: z.ZodUnion<readonly [z.ZodPipe<z.ZodLiteral<"public">, z.ZodTransform<{
168
+ public: string[];
169
+ }, "public">>, z.ZodObject<{
170
170
  public: z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
171
- }, z.core.$strict>;
171
+ }, z.core.$strict>]>;
172
172
  export type SpaceConfigFileAccess = z.infer<typeof spaceConfigFileAccessSchema>;
173
173
  /**
174
- * Overrides for the worker `sf publish` compiles.
174
+ * Zero: a transactional app with its own database.
175
+ *
176
+ * Both entries are required because Zero is never inferred. A runtime that owns
177
+ * a database must not spring into existence from a directory shape, so the only
178
+ * way to get one is to say so — and once said, a server or client that cannot
179
+ * be resolved fails the publish rather than degrading to static files.
180
+ */
181
+ export declare const spaceRuntimeZeroSchema: z.ZodObject<{
182
+ kind: z.ZodLiteral<"zero">;
183
+ server: z.ZodString;
184
+ client: z.ZodString;
185
+ }, z.core.$strip>;
186
+ /**
187
+ * Functions: the worker `sf publish` compiles.
175
188
  *
176
- * Every field is optional because the normal case is detection: a project with
177
- * `handler.ts` or an OpenNext build needs no configuration at all. These exist
178
- * for the layouts detection cannot guess — a worker somewhere unusual, or a
179
- * compatibility date the author wants pinned rather than defaulted.
189
+ * Every field beyond `kind` is optional because the normal case is detection: a
190
+ * project with `handler.ts` or an OpenNext build needs no configuration at all.
191
+ * These exist for the layouts detection cannot guess — a worker somewhere
192
+ * unusual, or a compatibility date the author wants pinned rather than
193
+ * defaulted.
180
194
  */
181
- export declare const spaceFunctionsOptionsSchema: z.ZodObject<{
195
+ export declare const spaceRuntimeFunctionsSchema: z.ZodObject<{
196
+ kind: z.ZodLiteral<"functions">;
182
197
  entry: z.ZodOptional<z.ZodString>;
183
198
  database: z.ZodDefault<z.ZodBoolean>;
184
199
  compatibilityDate: z.ZodOptional<z.ZodString>;
185
200
  }, z.core.$strip>;
186
- export type SpaceFunctionsOptions = z.infer<typeof spaceFunctionsOptionsSchema>;
201
+ /**
202
+ * The one runtime declaration, discriminated on `kind`.
203
+ *
204
+ * One block, one kind — a version cannot express two runtimes at once, so the
205
+ * ambiguity never has to be resolved downstream. File-only on purpose: a
206
+ * runtime is a fact about the content of a version, so it rolls forward and
207
+ * back with the version instead of being set through the API config overlay.
208
+ */
209
+ export declare const spaceRuntimeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
210
+ kind: z.ZodLiteral<"zero">;
211
+ server: z.ZodString;
212
+ client: z.ZodString;
213
+ }, z.core.$strip>, z.ZodObject<{
214
+ kind: z.ZodLiteral<"functions">;
215
+ entry: z.ZodOptional<z.ZodString>;
216
+ database: z.ZodDefault<z.ZodBoolean>;
217
+ compatibilityDate: z.ZodOptional<z.ZodString>;
218
+ }, z.core.$strip>], "kind">;
219
+ export type SpaceRuntimeZeroConfig = z.infer<typeof spaceRuntimeZeroSchema>;
220
+ export type SpaceRuntimeFunctionsConfig = z.infer<typeof spaceRuntimeFunctionsSchema>;
221
+ export type SpaceRuntimeConfig = z.infer<typeof spaceRuntimeSchema>;
187
222
  export declare const spaceConfigFileSchema: z.ZodObject<{
188
223
  index: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<false>]>>;
189
224
  fallback: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
@@ -264,17 +299,22 @@ export declare const spaceConfigFileSchema: z.ZodObject<{
264
299
  }, z.core.$strip>>;
265
300
  $schema: z.ZodOptional<z.ZodString>;
266
301
  space: z.ZodOptional<z.ZodString>;
267
- publish: z.ZodOptional<z.ZodObject<{
268
- fingerprint: z.ZodOptional<z.ZodBoolean>;
269
- }, z.core.$strip>>;
270
- access: z.ZodOptional<z.ZodObject<{
302
+ name: z.ZodOptional<z.ZodString>;
303
+ access: z.ZodOptional<z.ZodUnion<readonly [z.ZodPipe<z.ZodLiteral<"public">, z.ZodTransform<{
304
+ public: string[];
305
+ }, "public">>, z.ZodObject<{
271
306
  public: z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
272
- }, z.core.$strict>>;
273
- functions: z.ZodOptional<z.ZodObject<{
307
+ }, z.core.$strict>]>>;
308
+ runtime: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
309
+ kind: z.ZodLiteral<"zero">;
310
+ server: z.ZodString;
311
+ client: z.ZodString;
312
+ }, z.core.$strip>, z.ZodObject<{
313
+ kind: z.ZodLiteral<"functions">;
274
314
  entry: z.ZodOptional<z.ZodString>;
275
315
  database: z.ZodDefault<z.ZodBoolean>;
276
316
  compatibilityDate: z.ZodOptional<z.ZodString>;
277
- }, z.core.$strip>>;
317
+ }, z.core.$strip>], "kind">>;
278
318
  }, z.core.$strip>;
279
319
  export type SpaceConfigFile = z.infer<typeof spaceConfigFileSchema>;
280
320
  /**
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { BRAND } from "../brand.js";
2
+ import { serviceOrigin } from "../config/domains.js";
3
3
  import { normalizedSiteClass } from "./enums.js";
4
4
  import { GRANT_RESOURCE_PATTERN_LIMIT, normalizeGrantResourcePattern } from "./grants.js";
5
5
  export const SPACE_CONFIG_CANONICAL_FILE = "sf.jsonc";
@@ -43,9 +43,11 @@ export const SPACE_CONFIG_CAPS = {
43
43
  themeLogoChars: 2000,
44
44
  /** Max `theme.name` display-name length. */
45
45
  themeNameChars: 120,
46
+ /** Max top-level `name` (space display name) length. Matches the stored column. */
47
+ nameChars: 255,
46
48
  };
47
49
  /** Published JSON Schema URL for `sf.jsonc` ($schema autocomplete). */
48
- export const SPACE_CONFIG_SCHEMA_URL = `${BRAND.websiteOrigin}/schemas/sf.json`;
50
+ export const SPACE_CONFIG_SCHEMA_URL = `${serviceOrigin("www", "prod")}/schemas/sf.json`;
49
51
  // Pages theme tokens: visual values consumed by publish-rendered site pages.
50
52
  // Presentation only — never layout, behavior, or protocol.
51
53
  // Color grammar accepts hex/rgb(a)/hsl(a)/
@@ -317,17 +319,16 @@ export const wordpressDataSourceSchema = z
317
319
  })
318
320
  .strict()
319
321
  .describe("A public WordPress site exposed to repository builds as a content source.");
320
- function rejectReservedDataSourceRecordKeys(value) {
321
- if (typeof value === "object" &&
322
+ function hasNoReservedDataSourceRecordKeys(value) {
323
+ return !(typeof value === "object" &&
322
324
  value !== null &&
323
325
  !Array.isArray(value) &&
324
- Object.keys(value).some((name) => RESERVED_DATA_SOURCE_NAMES.has(name))) {
325
- return null;
326
- }
327
- return value;
326
+ Object.keys(value).some((name) => RESERVED_DATA_SOURCE_NAMES.has(name)));
328
327
  }
329
328
  export const spaceDataSourcesSchema = z
330
- .preprocess(rejectReservedDataSourceRecordKeys, z
329
+ .unknown()
330
+ .refine(hasNoReservedDataSourceRecordKeys, { message: "dataSources contains a reserved name." })
331
+ .pipe(z
331
332
  .record(dataSourceNameSchema, wordpressDataSourceSchema)
332
333
  .refine((sources) => Object.keys(sources).length <= SPACE_CONFIG_CAPS.dataSources, {
333
334
  message: `dataSources supports up to ${SPACE_CONFIG_CAPS.dataSources} entries.`,
@@ -365,11 +366,11 @@ const spaceConfigBaseSchema = z.object({
365
366
  }),
366
367
  ])
367
368
  .optional()
368
- .describe('Fallback document; "/index.html" means SPA. Default: nearest 404.html.'),
369
+ .describe('Fallback document. "/index.html" means SPA. Defaults to the nearest 404.html.'),
369
370
  cleanUrls: z
370
371
  .boolean()
371
372
  .optional()
372
- .describe("Serve flat `<name>.html` files at their extensionless URL (/about serves about.html). Default: on, unless a 200-status SPA fallback is configured."),
373
+ .describe("Serve flat `<name>.html` files at their extensionless URL (`/about` serves `about.html`). Defaults to on, unless a 200-status SPA fallback is configured."),
373
374
  listing: z.boolean().optional().describe("Enable automatic directory listings."),
374
375
  meta: z
375
376
  .object({
@@ -470,7 +471,7 @@ export const spaceConfigSchema = spaceConfigBaseSchema
470
471
  .describe(`Named public WordPress sources exposed to repository builds; at most ${SPACE_CONFIG_CAPS.dataSources} entries.`),
471
472
  defaultDataSource: dataSourceNameSchema
472
473
  .optional()
473
- .describe("Name of the configured source exposed through WORDPRESS_URL. Must match a dataSources entry; when omitted, the first source in bytewise name order is used."),
474
+ .describe("Name of the configured source exposed through `WORDPRESS_URL`. It must match a `dataSources` entry. When omitted, the first source in bytewise name order is used."),
474
475
  })
475
476
  .superRefine(refineDefaultDataSource);
476
477
  export function compareDataSourceNames(left, right) {
@@ -491,22 +492,9 @@ export function spaceSuperpowersEnabled(config) {
491
492
  // The `sf.jsonc` FILE shape (internal-docs/platform.md "Space
492
493
  // Configuration Files"): SpaceConfig plus the editor `$schema` pointer and the
493
494
  // optional `space` link (slug or `spc_` id) resolved by CLI/publish flows.
494
- // Routing never lives here — redirects/rewrites/proxies/headers stay
495
- // exclusively in `_redirects`/`_headers` (one way per concern).
496
- // Client-side publish options (caching plan §6): consumed by the CLI/agent
497
- // publish tooling before upload, never by the runtime — like `$schema` and
498
- // `space`, this key lives only in the FILE shape and never reaches the space
499
- // `config` overlay.
500
- export const spacePublishOptionsSchema = z
501
- .object({
502
- fingerprint: z
503
- .boolean()
504
- .optional()
505
- .describe("Publish-time asset fingerprinting: adds content-hashed immutable aliases " +
506
- "(name.<sha256-8>.ext) for stable assets and rewrites exact HTML/CSS references. " +
507
- "Originals keep serving unchanged. Default off."),
508
- })
509
- .describe("Client publish-time options consumed by the Spacefast CLI, not the runtime.");
495
+ // Routing rules (`redirects`/`rewrites`/`headers`) are sf.jsonc keys too, but they belong
496
+ // to the routing compiler, not to this shape: it resolves serving, they compile
497
+ // into the canonical rules and merge behind `_redirects`/`_headers`.
510
498
  const spaceConfigPublicPatternSchema = z
511
499
  .string()
512
500
  .min(1)
@@ -525,7 +513,7 @@ const spaceConfigPublicPatternSchema = z
525
513
  return z.NEVER;
526
514
  }
527
515
  });
528
- export const spaceConfigFileAccessSchema = z
516
+ const spaceConfigFileAccessObjectSchema = z
529
517
  .object({
530
518
  public: z
531
519
  .array(spaceConfigPublicPatternSchema)
@@ -555,39 +543,79 @@ export const spaceConfigFileAccessSchema = z
555
543
  message: "access.public requires at least one inclusion pattern.",
556
544
  });
557
545
  }
546
+ });
547
+ export const spaceConfigFileAccessSchema = z
548
+ .union([
549
+ z
550
+ .literal("public")
551
+ .describe('Shorthand for { "public": ["/**"] }: the whole live space is public.')
552
+ .transform(() => ({ public: ["/**"] })),
553
+ spaceConfigFileAccessObjectSchema,
554
+ ])
555
+ .describe("File-only public access declaration. It is inactive until the space is claimed.");
556
+ /**
557
+ * Zero: a transactional app with its own database.
558
+ *
559
+ * Both entries are required because Zero is never inferred. A runtime that owns
560
+ * a database must not spring into existence from a directory shape, so the only
561
+ * way to get one is to say so — and once said, a server or client that cannot
562
+ * be resolved fails the publish rather than degrading to static files.
563
+ */
564
+ export const spaceRuntimeZeroSchema = z
565
+ .object({
566
+ kind: z.literal("zero"),
567
+ server: z
568
+ .string()
569
+ .trim()
570
+ .min(1)
571
+ .describe("Path to the server entry module, relative to the published directory."),
572
+ client: z
573
+ .string()
574
+ .trim()
575
+ .min(1)
576
+ .describe("Path to the client entry module, relative to the published directory."),
558
577
  })
559
- .describe("File-only public access declaration. It is inactive until the Space is claimed.");
578
+ .describe("Zero: a transactional app with its own database, compiled at publish time.");
560
579
  /**
561
- * Overrides for the worker `sf publish` compiles.
580
+ * Functions: the worker `sf publish` compiles.
562
581
  *
563
- * Every field is optional because the normal case is detection: a project with
564
- * `handler.ts` or an OpenNext build needs no configuration at all. These exist
565
- * for the layouts detection cannot guess — a worker somewhere unusual, or a
566
- * compatibility date the author wants pinned rather than defaulted.
582
+ * Every field beyond `kind` is optional because the normal case is detection: a
583
+ * project with `handler.ts` or an OpenNext build needs no configuration at all.
584
+ * These exist for the layouts detection cannot guess — a worker somewhere
585
+ * unusual, or a compatibility date the author wants pinned rather than
586
+ * defaulted.
567
587
  */
568
- export const spaceFunctionsOptionsSchema = z
588
+ export const spaceRuntimeFunctionsSchema = z
569
589
  .object({
590
+ kind: z.literal("functions"),
570
591
  entry: z
571
592
  .string()
572
593
  .optional()
573
594
  .describe("Path to the worker entry module, relative to the published directory. " +
574
- "Detected automatically when absent; naming one that does not exist is an error " +
575
- "rather than a silent static publish."),
595
+ "When absent, the server detects it automatically. Naming an entry that does not exist is an error, not a silent static publish."),
576
596
  database: z
577
597
  .boolean()
578
598
  .default(false)
579
599
  .describe("Give the worker `env.DB`, a D1-shaped binding to this space's database. " +
580
- "Declared rather than detected: a capability an author did not ask for is " +
581
- "authority they cannot see, and reading it out of minified build output " +
582
- "would be a guess. Off by default, so a worker reaches nothing until it says so."),
600
+ "This capability is declared, not detected. A capability an author did not request is authority they cannot see. Reading it from minified build output is a guess. Defaults to off, so a worker reaches nothing until it says so."),
583
601
  compatibilityDate: z
584
602
  .string()
585
603
  .regex(/^\d{4}-\d{2}-\d{2}$/)
586
604
  .optional()
587
- .describe("Runtime compatibility date this worker was written against. Pinning it makes " +
588
- "rollback honest: promoting an old version restores the semantics it was built with."),
605
+ .describe("Runtime compatibility date this worker was written against. Pinning it makes rollback honest. Promoting an old version restores the semantics it was built with."),
589
606
  })
590
- .describe("Overrides for the worker compiled at publish time.");
607
+ .describe("Functions: a worker compiled at publish time.");
608
+ /**
609
+ * The one runtime declaration, discriminated on `kind`.
610
+ *
611
+ * One block, one kind — a version cannot express two runtimes at once, so the
612
+ * ambiguity never has to be resolved downstream. File-only on purpose: a
613
+ * runtime is a fact about the content of a version, so it rolls forward and
614
+ * back with the version instead of being set through the API config overlay.
615
+ */
616
+ export const spaceRuntimeSchema = z
617
+ .discriminatedUnion("kind", [spaceRuntimeZeroSchema, spaceRuntimeFunctionsSchema])
618
+ .describe("Runtime this version's code runs on. Omit it for a static space. Functions is also detected from the published directory when it is not stated.");
591
619
  export const spaceConfigFileSchema = spaceConfigBaseSchema.extend({
592
620
  $schema: z
593
621
  .string()
@@ -598,9 +626,17 @@ export const spaceConfigFileSchema = spaceConfigBaseSchema.extend({
598
626
  .min(1)
599
627
  .optional()
600
628
  .describe("Optional space link: a space slug or spc_ id this project publishes to."),
601
- publish: spacePublishOptionsSchema.optional(),
629
+ // File-owned naming: the live version's config decides the display name, so
630
+ // it rolls forward and back with the version like every other content fact.
631
+ name: z
632
+ .string()
633
+ .trim()
634
+ .min(1)
635
+ .max(SPACE_CONFIG_CAPS.nameChars)
636
+ .optional()
637
+ .describe("Display name for this space. While set, the API refuses to rename the space."),
602
638
  access: spaceConfigFileAccessSchema.optional(),
603
- functions: spaceFunctionsOptionsSchema.optional(),
639
+ runtime: spaceRuntimeSchema.optional(),
604
640
  });
605
641
  /**
606
642
  * The published JSON Schema artifact for `sf.jsonc`, generated from the
@@ -608,12 +644,102 @@ export const spaceConfigFileSchema = spaceConfigBaseSchema.extend({
608
644
  * autocomplete and validation.
609
645
  */
610
646
  export function spaceConfigJsonSchema() {
611
- return stripHiddenSpaceConfigKeys({
647
+ const schema = stripHiddenSpaceConfigKeys({
612
648
  ...z.toJSONSchema(spaceConfigFileSchema, { target: "draft-7", io: "input" }),
613
649
  $id: SPACE_CONFIG_SCHEMA_URL,
614
650
  title: "Spacefast space configuration (sf.jsonc)",
615
651
  });
652
+ // `redirects`, `rewrites` and `headers` are real sf.jsonc keys, owned by the routing
653
+ // compiler rather than by the serving shape above — which is why they are
654
+ // described here instead of in the zod schema that resolves serving. An editor
655
+ // reading this file still has to know they exist, or the documented routing
656
+ // examples read as unknown keys.
657
+ return {
658
+ ...schema,
659
+ properties: {
660
+ ...schema.properties,
661
+ ...SPACE_CONFIG_ROUTING_SCHEMA_PROPERTIES,
662
+ },
663
+ };
616
664
  }
665
+ const SPACE_CONFIG_ROUTING_SCHEMA_PROPERTIES = {
666
+ redirects: {
667
+ type: "array",
668
+ description: "Browser redirect rules. Same matcher grammar as _redirects, and _redirects rules match first.",
669
+ items: {
670
+ type: "object",
671
+ properties: {
672
+ source: {
673
+ type: "string",
674
+ minLength: 1,
675
+ description: "Request path to match. * is a splat, :name captures a segment.",
676
+ },
677
+ destination: {
678
+ type: "string",
679
+ minLength: 1,
680
+ description: "Path or absolute URL. :splat and :name from the source are substituted.",
681
+ },
682
+ status: {
683
+ enum: [301, 302, 303, 307, 308],
684
+ default: 302,
685
+ description: "Redirect status. To serve other content under this URL, use rewrites.",
686
+ },
687
+ },
688
+ required: ["source", "destination"],
689
+ additionalProperties: false,
690
+ },
691
+ },
692
+ rewrites: {
693
+ type: "array",
694
+ description: "Rewrite and proxy rules: the URL stays, the content comes from the destination. _redirects rules match first, then redirects.",
695
+ items: {
696
+ type: "object",
697
+ properties: {
698
+ source: {
699
+ type: "string",
700
+ minLength: 1,
701
+ description: "Request path to match. * is a splat, :name captures a segment.",
702
+ },
703
+ destination: {
704
+ type: "string",
705
+ minLength: 1,
706
+ description: "Path to serve under this URL, or an absolute URL to proxy. :splat and :name from the source are substituted.",
707
+ },
708
+ cache: {
709
+ const: "shared",
710
+ description: "Let shared caches store the proxy response. Absolute-URL destinations only.",
711
+ },
712
+ },
713
+ required: ["source", "destination"],
714
+ additionalProperties: false,
715
+ },
716
+ },
717
+ headers: {
718
+ type: "array",
719
+ description: "Response headers to set on matching requests. A _headers rule wins on the same header name.",
720
+ items: {
721
+ type: "object",
722
+ properties: {
723
+ source: { type: "string", minLength: 1, description: "Request path to match." },
724
+ headers: {
725
+ type: "array",
726
+ minItems: 1,
727
+ items: {
728
+ type: "object",
729
+ properties: {
730
+ key: { type: "string", minLength: 1 },
731
+ value: { type: "string" },
732
+ },
733
+ required: ["key", "value"],
734
+ additionalProperties: false,
735
+ },
736
+ },
737
+ },
738
+ required: ["source", "headers"],
739
+ additionalProperties: false,
740
+ },
741
+ },
742
+ };
617
743
  /*
618
744
  * Hidden-feature discipline (internal-docs/build-pipeline-plan.md): the
619
745
  * `experimental_gutenberg` flag and its `inject` companion are ACCEPTED by the