@spacefast/common 0.0.6 → 0.0.7

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 (65) hide show
  1. package/brand-assets/spacefast-favicon.svg +16 -0
  2. package/brand-assets/spacefast-sf-full-bleed.svg +13 -0
  3. package/brand-assets/spacefast-wordmark.svg +5 -0
  4. package/dist/agents/connect-targets.d.ts +6 -1
  5. package/dist/agents/connect-targets.js +19 -0
  6. package/dist/brand-assets-build.d.ts +26 -0
  7. package/dist/brand-assets-build.js +81 -0
  8. package/dist/brand-assets.d.ts +42 -0
  9. package/dist/brand-assets.js +30 -0
  10. package/dist/contracts/access.d.ts +143 -0
  11. package/dist/contracts/access.js +33 -0
  12. package/dist/contracts/api-keys.d.ts +123 -0
  13. package/dist/contracts/api-keys.js +65 -0
  14. package/dist/contracts/builds.d.ts +40 -0
  15. package/dist/contracts/builds.js +17 -0
  16. package/dist/contracts/enums.js +5 -4
  17. package/dist/contracts/error-code-meta.d.ts +116 -4
  18. package/dist/contracts/error-code-meta.js +29 -1
  19. package/dist/contracts/error-codes.d.ts +1 -1
  20. package/dist/contracts/error-codes.js +28 -0
  21. package/dist/contracts/feature-lifecycle-core.d.ts +36 -0
  22. package/dist/contracts/feature-lifecycle-core.js +220 -0
  23. package/dist/contracts/feature-lifecycle.d.ts +7 -0
  24. package/dist/contracts/feature-lifecycle.js +31 -0
  25. package/dist/contracts/generated-feature-launch-entries.d.ts +2 -0
  26. package/dist/contracts/generated-feature-launch-entries.js +518 -0
  27. package/dist/contracts/ids.d.ts +4 -0
  28. package/dist/contracts/ids.js +8 -0
  29. package/dist/contracts/operations.d.ts +161 -1
  30. package/dist/contracts/operations.js +49 -1
  31. package/dist/contracts/platform.d.ts +6 -0
  32. package/dist/contracts/platform.js +6 -0
  33. package/dist/contracts/publishes.d.ts +130 -0
  34. package/dist/contracts/publishes.js +145 -0
  35. package/dist/contracts/repository-connections.d.ts +3 -0
  36. package/dist/contracts/repository-connections.js +13 -0
  37. package/dist/contracts/resources.d.ts +2 -0
  38. package/dist/contracts/resources.js +10 -2
  39. package/dist/contracts/runtime-api.d.ts +2 -0
  40. package/dist/contracts/runtime-api.js +1 -0
  41. package/dist/contracts/sf-config-v1.d.ts +75 -0
  42. package/dist/contracts/sf-config-v1.js +6 -0
  43. package/dist/contracts/spaces.d.ts +12 -3
  44. package/dist/contracts/spaces.js +22 -2
  45. package/dist/contracts/superadmin-spaces.d.ts +2 -0
  46. package/dist/contracts/superadmin-tenants.d.ts +6 -6
  47. package/dist/contracts/superadmin-tenants.js +6 -11
  48. package/dist/contracts/teams.d.ts +8 -11
  49. package/dist/contracts/teams.js +11 -16
  50. package/dist/docs/agent-setup.d.ts +51 -0
  51. package/dist/docs/agent-setup.js +168 -3
  52. package/dist/docs/error-docs.js +113 -1
  53. package/dist/slug-policy/blocklist.d.ts +4 -0
  54. package/dist/slug-policy/blocklist.js +122 -0
  55. package/dist/slug-policy/index.d.ts +17 -0
  56. package/dist/slug-policy/index.js +86 -0
  57. package/dist/utils/concurrency.d.ts +1 -0
  58. package/dist/utils/concurrency.js +22 -0
  59. package/dist/utils/email.d.ts +1 -0
  60. package/dist/utils/email.js +3 -0
  61. package/dist/utils/query-keys.d.ts +1 -0
  62. package/dist/utils/query-keys.js +4 -0
  63. package/dist/utils/sf-config-v1.d.ts +2 -0
  64. package/dist/utils/sf-config-v1.js +542 -0
  65. package/package.json +20 -1
@@ -5,7 +5,7 @@
5
5
  // and pages never go missing. apps/www renders the HTML pages and writes the
6
6
  // `.md` twins from the same entries.
7
7
  import { BRAND } from "../brand.js";
8
- import { ERROR_CODES, errorDocsUrl, } from "../contracts/error-codes.js";
8
+ import { errorDocsUrl } from "../contracts/error-codes.js";
9
9
  // Restore upgrade-oriented recovery copy here when paid plan checkout is live.
10
10
  export const ERROR_DOCS = {
11
11
  abuse_report_invalid_transition: {
@@ -48,6 +48,10 @@ export const ERROR_DOCS = {
48
48
  summary: "Too many unclaimed anonymous spaces are already active for this client.",
49
49
  fix: "Log in to publish into an authenticated space, or claim/delete an existing anonymous space before creating another.",
50
50
  },
51
+ api_key_scope_underivable: {
52
+ summary: "A concrete access policy could not be derived from the requested scope and permissions.",
53
+ fix: "Point scope at a resource you can already reach (an existing space owned by a team) and request permissions your credential already has.",
54
+ },
51
55
  archive_body_required: {
52
56
  summary: "This request must carry an archive body but none was provided.",
53
57
  fix: "Send the zip bytes as the request body with the matching content-type header.",
@@ -424,6 +428,10 @@ export const ERROR_DOCS = {
424
428
  summary: "The version upload declares the same file path twice.",
425
429
  fix: "Deduplicate the file list so every path appears once.",
426
430
  },
431
+ enforcement_policy_overlap: {
432
+ summary: "Enforcement policy severity ranges overlap within a category.",
433
+ fix: "Adjust the category ranges so each severity is covered by at most one policy.",
434
+ },
427
435
  expired_token: {
428
436
  summary: "The token has expired.",
429
437
  fix: "Request a new token and retry.",
@@ -768,6 +776,10 @@ export const ERROR_DOCS = {
768
776
  summary: "The uploaded archive exceeds the maximum allowed size.",
769
777
  fix: "Reduce the archive size or use a version upload session for big publishes.",
770
778
  },
779
+ publish_base_changed: {
780
+ summary: "Someone published new space settings after the base you loaded, so saving would overwrite their change.",
781
+ fix: "Reload the latest settings, review the difference, and save again; pass force only to overwrite deliberately.",
782
+ },
771
783
  publish_config_unsupported: {
772
784
  summary: "The publish carries configuration this endpoint does not support.",
773
785
  fix: "Move the configuration into sf.jsonc or the space settings.",
@@ -928,6 +940,18 @@ export const ERROR_DOCS = {
928
940
  summary: "The device login is being polled faster than the advised interval.",
929
941
  fix: "Increase the polling interval; aggressive polling invalidates the flow.",
930
942
  },
943
+ slug_invalid: {
944
+ summary: "The slug does not meet the structural rules for managed names.",
945
+ fix: "Use lowercase letters, numbers, and single dashes within the allowed length; the error message identifies the failed rule.",
946
+ },
947
+ slug_reserved: {
948
+ summary: "The slug is reserved for infrastructure, product, or impersonation protection.",
949
+ fix: "Choose a different name that is not reserved by the shared slug policy.",
950
+ },
951
+ slug_unavailable: {
952
+ summary: "The slug is already in use within the relevant ownership scope.",
953
+ fix: "Choose another slug or use the available suggestion returned by the API.",
954
+ },
931
955
  space_already_claimed: {
932
956
  summary: "The space was already claimed; a claim can only succeed once.",
933
957
  fix: "If you are the agent that published this space, exchange your claim token once at POST /v1/anonymous-claim/exchange for a durable access token and keep publishing to the same spaceId. Otherwise ask the owner for access — do not publish a duplicate space.",
@@ -952,6 +976,10 @@ export const ERROR_DOCS = {
952
976
  summary: "The space still has an active version, which blocks this operation.",
953
977
  fix: "Delete or supersede the active version first.",
954
978
  },
979
+ space_hostname_identity_missing: {
980
+ summary: "The space is missing its persisted default hostname identity.",
981
+ fix: "Contact support. Retrying cannot safely recreate a permanent hostname identity.",
982
+ },
955
983
  space_import_archive_invalid: {
956
984
  summary: `The import archive is not a valid ${BRAND.productName} export.`,
957
985
  fix: "Export the source space again and upload the unmodified archive.",
@@ -1272,6 +1300,10 @@ export const ERROR_DOCS = {
1272
1300
  summary: "Zero endpoints are activating.",
1273
1301
  fix: "Wait for the dedicated runtime move to complete, then retry the request.",
1274
1302
  },
1303
+ zero_ai_unavailable: {
1304
+ summary: "The local Zero AI service is not configured.",
1305
+ fix: "Configure an AI handler for zero dev before calling the AI service.",
1306
+ },
1275
1307
  zero_artifact_abi_mismatch: {
1276
1308
  summary: "The Zero endpoint artifact was built for a different runner ABI.",
1277
1309
  fix: `Rebuild and republish the Zero project with the current ${BRAND.productName} CLI and runtime.`,
@@ -1284,6 +1316,10 @@ export const ERROR_DOCS = {
1284
1316
  summary: "The Zero endpoint artifact could not be parsed.",
1285
1317
  fix: "Rebuild the Zero project; do not edit generated artifact JSON by hand.",
1286
1318
  },
1319
+ zero_artifact_missing: {
1320
+ summary: "A Zero route points to an endpoint artifact that is missing.",
1321
+ fix: "Declare the endpoint in zero_endpoints, then rebuild and publish again.",
1322
+ },
1287
1323
  zero_artifact_path_invalid: {
1288
1324
  summary: "The Zero endpoint artifact path is invalid.",
1289
1325
  fix: "Rebuild the Zero routes so all artifact paths stay inside the version bundle.",
@@ -1292,6 +1328,10 @@ export const ERROR_DOCS = {
1292
1328
  summary: "The Zero endpoint artifact could not be read by the runtime.",
1293
1329
  fix: "Verify the version finalized with all generated Zero files present.",
1294
1330
  },
1331
+ zero_artifact_untrusted: {
1332
+ summary: "A Zero route points to an uploaded artifact that the runtime did not compile.",
1333
+ fix: "Declare the endpoint in zero_endpoints so finalize generates trusted bytecode.",
1334
+ },
1295
1335
  zero_auth_unavailable: {
1296
1336
  summary: "Hosted Zero auth is not configured for this deployment.",
1297
1337
  fix: "Finalize the Zero version with hosted auth URLs or use guest/local auth.",
@@ -1332,6 +1372,10 @@ export const ERROR_DOCS = {
1332
1372
  summary: "The Zero endpoint bytecode file could not be read.",
1333
1373
  fix: "Verify the version finalized with the generated bytecode files present.",
1334
1374
  },
1375
+ zero_capabilities_encode_failed: {
1376
+ summary: "The runtime could not encode the Zero endpoint capabilities.",
1377
+ fix: "Rebuild with valid capability metadata; report the failure if it persists.",
1378
+ },
1335
1379
  zero_client_bundle_not_loaded: {
1336
1380
  summary: "The local Zero dev client bundle has not been generated or loaded.",
1337
1381
  fix: "Restart zero dev or run a fresh local build before loading the client route.",
@@ -1404,6 +1448,22 @@ export const ERROR_DOCS = {
1404
1448
  summary: "The Zero database dump query parameters are invalid.",
1405
1449
  fix: "Use valid table, limit, and cursor parameters.",
1406
1450
  },
1451
+ zero_endpoint_compile_failed: {
1452
+ summary: "The runtime could not compile the Zero endpoint bytecode.",
1453
+ fix: "Inspect the compile diagnostics, fix the endpoint source, and publish again.",
1454
+ },
1455
+ zero_endpoint_conflict: {
1456
+ summary: "Two Zero routes resolve to the same runtime path.",
1457
+ fix: "Remove the duplicate generated or explicit lookup route and publish again.",
1458
+ },
1459
+ zero_endpoint_duplicate: {
1460
+ summary: "Two Zero endpoints declare the same method and route.",
1461
+ fix: "Give each endpoint a unique method and route before publishing again.",
1462
+ },
1463
+ zero_endpoint_id_duplicate: {
1464
+ summary: "Two Zero endpoints declare the same endpoint id.",
1465
+ fix: "Give every endpoint a unique endpoint id before publishing again.",
1466
+ },
1407
1467
  zero_endpoint_index_invalid: {
1408
1468
  summary: "The Zero endpoint index does not contain valid endpoint mappings.",
1409
1469
  fix: "Rebuild and republish the Zero project.",
@@ -1420,6 +1480,10 @@ export const ERROR_DOCS = {
1420
1480
  summary: "The Zero endpoint index could not be read.",
1421
1481
  fix: "Verify the version finalized with zero/endpoints-index.json present.",
1422
1482
  },
1483
+ zero_endpoint_invalid: {
1484
+ summary: "A Zero endpoint definition or its database metadata is invalid.",
1485
+ fix: "Fix the endpoint method, route, source, capabilities, or database metadata and publish again.",
1486
+ },
1423
1487
  zero_endpoint_mismatch: {
1424
1488
  summary: "The requested Zero endpoint does not match the endpoint artifact.",
1425
1489
  fix: "Rebuild routes and artifacts together and redeploy the version.",
@@ -1428,6 +1492,14 @@ export const ERROR_DOCS = {
1428
1492
  summary: "No Zero endpoint artifact exists for this endpoint id.",
1429
1493
  fix: "Check the route path and rebuild the Zero endpoint index.",
1430
1494
  },
1495
+ zero_endpoints_invalid: {
1496
+ summary: "The Zero endpoints payload is invalid.",
1497
+ fix: "Send an endpoint array within the supported count and size limits.",
1498
+ },
1499
+ zero_endpoints_too_many: {
1500
+ summary: "The Zero endpoints payload exceeds the supported endpoint count.",
1501
+ fix: "Reduce the project to at most 128 Zero endpoints, then publish again.",
1502
+ },
1431
1503
  zero_requires_dedicated_plan: {
1432
1504
  summary: "Zero endpoints require a plan with dedicated runtime isolation.",
1433
1505
  fix: "Upgrade the team to a plan that includes Zero and dedicated runtime, then publish again.",
@@ -1464,10 +1536,18 @@ export const ERROR_DOCS = {
1464
1536
  summary: "The Zero log query parameters are invalid.",
1465
1537
  fix: "Use valid cursor, limit, mutationName, and requestId parameters.",
1466
1538
  },
1539
+ zero_lookup_invalid: {
1540
+ summary: "The Zero lookup map contains an invalid path or action.",
1541
+ fix: "Use normalized relative paths and valid finalized Zero actions.",
1542
+ },
1467
1543
  zero_method_not_allowed: {
1468
1544
  summary: "The request method is not allowed for this Zero endpoint.",
1469
1545
  fix: "Call the endpoint with one of the methods declared by the Zero route.",
1470
1546
  },
1547
+ zero_migration_failed: {
1548
+ summary: "The Zero database migration command failed during finalize.",
1549
+ fix: "Inspect the migration and database logs, fix the schema issue, and publish again.",
1550
+ },
1471
1551
  zero_query_name_missing: {
1472
1552
  summary: "A Zero realtime query subscription did not include a query name.",
1473
1553
  fix: "Send a query.subscribe frame with a valid query name.",
@@ -1520,6 +1600,22 @@ export const ERROR_DOCS = {
1520
1600
  summary: "The Zero endpoint response exceeds the runtime size limit.",
1521
1601
  fix: "Return a smaller response body or paginate the data.",
1522
1602
  },
1603
+ zero_routes_invalid: {
1604
+ summary: "The Zero routes artifact is invalid.",
1605
+ fix: "Rebuild the generated routes and publish the version again.",
1606
+ },
1607
+ zero_run_compile_failed: {
1608
+ summary: "The runtime could not compile a Zero run handler.",
1609
+ fix: "Inspect the compile diagnostics, fix the run-handler source, and publish again.",
1610
+ },
1611
+ zero_run_duplicate: {
1612
+ summary: "Two Zero run handlers declare the same run id.",
1613
+ fix: "Give every run handler a unique run id before publishing again.",
1614
+ },
1615
+ zero_run_invalid: {
1616
+ summary: "A Zero run-handler definition is invalid.",
1617
+ fix: "Fix the run id, source, capabilities, or database metadata and publish again.",
1618
+ },
1523
1619
  zero_run_operation_unsupported: {
1524
1620
  summary: "The hosted Zero runtime does not support this run operation yet.",
1525
1621
  fix: "Use a generated endpoint or deploy a generated run handler for this operation.",
@@ -1564,6 +1660,22 @@ export const ERROR_DOCS = {
1564
1660
  summary: "The Zero runner process could not be started.",
1565
1661
  fix: "Verify the runner binary is installed and executable in the runtime.",
1566
1662
  },
1663
+ zero_runs_invalid: {
1664
+ summary: "The Zero run-handler payload is invalid.",
1665
+ fix: "Send a valid array of generated run handlers and publish again.",
1666
+ },
1667
+ zero_runs_require_runtime_compiler: {
1668
+ summary: "Zero run handlers require the Rust runtime compiler.",
1669
+ fix: "Install the current runtime compiler and finalize the version again.",
1670
+ },
1671
+ zero_runs_too_many: {
1672
+ summary: "The Zero run-handler payload exceeds the supported handler count.",
1673
+ fix: "Reduce the project to at most 128 Zero run handlers, then publish again.",
1674
+ },
1675
+ zero_shopify_unavailable: {
1676
+ summary: "The local Zero Shopify service is not configured.",
1677
+ fix: "Configure a Shopify handler for zero dev before calling the Shopify service.",
1678
+ },
1567
1679
  zero_source_compile_failed: {
1568
1680
  summary: "The Zero endpoint source fallback failed to compile.",
1569
1681
  fix: "Fix the endpoint source and rebuild bytecode.",
@@ -0,0 +1,4 @@
1
+ export declare const INFRA_SLUG_BLOCKLIST: readonly ["www", "www1", "www2", "www3", "web", "mail", "webmail", "smtp", "imap", "pop", "mx", "mx1", "mx2", "ns", "ns1", "ns2", "ns3", "ns4", "dns", "ftp", "ssh", "vpn", "git", "svn", "cdn", "assets", "static", "img", "images", "files", "media", "local", "localhost", "host", "server", "remote", "portal", "owa", "exchange", "gov", "nic", "whois"];
2
+ export declare const PRODUCT_PLATFORM_SLUG_BLOCKLIST: readonly ["spacefast", "stattic", "viewfast", "view-fast", "sf", "my", "api", "app", "apps", "admin", "superadmin", "dashboard", "console", "login", "logout", "auth", "oauth", "sso", "signup", "signin", "register", "claim", "billing", "checkout", "mcp", "cli", "sdk", "cast", "preview", "previews", "internal", "ops", "handbook"];
3
+ export declare const TRADEMARK_EXACT_SLUG_BLOCKLIST: readonly ["automattic", "wordpress", "woocommerce", "jetpack", "wpcloud", "google", "facebook", "instagram", "twitter", "x", "tiktok", "youtube", "amazon", "apple", "microsoft", "netflix", "paypal", "stripe", "visa", "mastercard", "coinbase", "metamask", "openai", "anthropic", "claude", "github", "gitlab", "vercel", "netlify", "cloudflare"];
4
+ export declare const SUBSTRING_SLUG_BLOCKLIST: readonly ["spacefast", "stattic", "automattic", "wordpress", "paypal"];
@@ -0,0 +1,122 @@
1
+ // Infrastructure hostnames reserved by WordPress.com managed-subdomain policy.
2
+ export const INFRA_SLUG_BLOCKLIST = [
3
+ "www",
4
+ "www1",
5
+ "www2",
6
+ "www3",
7
+ "web",
8
+ "mail",
9
+ "webmail",
10
+ "smtp",
11
+ "imap",
12
+ "pop",
13
+ "mx",
14
+ "mx1",
15
+ "mx2",
16
+ "ns",
17
+ "ns1",
18
+ "ns2",
19
+ "ns3",
20
+ "ns4",
21
+ "dns",
22
+ "ftp",
23
+ "ssh",
24
+ "vpn",
25
+ "git",
26
+ "svn",
27
+ "cdn",
28
+ "assets",
29
+ "static",
30
+ "img",
31
+ "images",
32
+ "files",
33
+ "media",
34
+ "local",
35
+ "localhost",
36
+ "host",
37
+ "server",
38
+ "remote",
39
+ "portal",
40
+ "owa",
41
+ "exchange",
42
+ "gov",
43
+ "nic",
44
+ "whois",
45
+ ];
46
+ // Spacefast product, platform, and public surface names.
47
+ export const PRODUCT_PLATFORM_SLUG_BLOCKLIST = [
48
+ "spacefast",
49
+ "stattic",
50
+ "viewfast",
51
+ "view-fast",
52
+ "sf",
53
+ "my",
54
+ "api",
55
+ "app",
56
+ "apps",
57
+ "admin",
58
+ "superadmin",
59
+ "dashboard",
60
+ "console",
61
+ "login",
62
+ "logout",
63
+ "auth",
64
+ "oauth",
65
+ "sso",
66
+ "signup",
67
+ "signin",
68
+ "register",
69
+ "claim",
70
+ "billing",
71
+ "checkout",
72
+ "mcp",
73
+ "cli",
74
+ "sdk",
75
+ "cast",
76
+ "preview",
77
+ "previews",
78
+ "internal",
79
+ "ops",
80
+ "handbook",
81
+ ];
82
+ // Exact trademark and impersonation names reserved in every managed namespace.
83
+ export const TRADEMARK_EXACT_SLUG_BLOCKLIST = [
84
+ "automattic",
85
+ "wordpress",
86
+ "woocommerce",
87
+ "jetpack",
88
+ "wpcloud",
89
+ "google",
90
+ "facebook",
91
+ "instagram",
92
+ "twitter",
93
+ "x",
94
+ "tiktok",
95
+ "youtube",
96
+ "amazon",
97
+ "apple",
98
+ "microsoft",
99
+ "netflix",
100
+ "paypal",
101
+ "stripe",
102
+ "visa",
103
+ "mastercard",
104
+ "coinbase",
105
+ "metamask",
106
+ "openai",
107
+ "anthropic",
108
+ "claude",
109
+ "github",
110
+ "gitlab",
111
+ "vercel",
112
+ "netlify",
113
+ "cloudflare",
114
+ ];
115
+ // Minimal substring rules for high-risk product and impersonation strings.
116
+ export const SUBSTRING_SLUG_BLOCKLIST = [
117
+ "spacefast",
118
+ "stattic",
119
+ "automattic",
120
+ "wordpress",
121
+ "paypal",
122
+ ];
@@ -0,0 +1,17 @@
1
+ export { INFRA_SLUG_BLOCKLIST, PRODUCT_PLATFORM_SLUG_BLOCKLIST, SUBSTRING_SLUG_BLOCKLIST, TRADEMARK_EXACT_SLUG_BLOCKLIST, } from "./blocklist.js";
2
+ export type SlugPolicyOptions = {
3
+ maxLength?: number;
4
+ };
5
+ export type SlugPolicyVerdict = {
6
+ ok: true;
7
+ normalized: string;
8
+ } | {
9
+ ok: false;
10
+ reason: "slug_invalid" | "slug_reserved";
11
+ detail: string;
12
+ };
13
+ export declare const SLUG_MIN_LENGTH = 3;
14
+ export declare const TEAM_SLUG_MAX_LENGTH = 63;
15
+ export declare const SPACE_SLUG_MAX_LENGTH = 55;
16
+ export declare function evaluateSlugCandidate(input: string, options?: SlugPolicyOptions): SlugPolicyVerdict;
17
+ export declare function slugPolicyFailureSurvivesSuffix(normalized: string): boolean;
@@ -0,0 +1,86 @@
1
+ import { MANAGEMENT_LABEL_PREFIX } from "../config/domains.js";
2
+ import { INFRA_SLUG_BLOCKLIST, PRODUCT_PLATFORM_SLUG_BLOCKLIST, SUBSTRING_SLUG_BLOCKLIST, TRADEMARK_EXACT_SLUG_BLOCKLIST, } from "./blocklist.js";
3
+ export { INFRA_SLUG_BLOCKLIST, PRODUCT_PLATFORM_SLUG_BLOCKLIST, SUBSTRING_SLUG_BLOCKLIST, TRADEMARK_EXACT_SLUG_BLOCKLIST, } from "./blocklist.js";
4
+ export const SLUG_MIN_LENGTH = 3;
5
+ export const TEAM_SLUG_MAX_LENGTH = 63;
6
+ export const SPACE_SLUG_MAX_LENGTH = 55;
7
+ const infraReservedSlugs = new Set(INFRA_SLUG_BLOCKLIST);
8
+ const productPlatformReservedSlugs = new Set(PRODUCT_PLATFORM_SLUG_BLOCKLIST);
9
+ const trademarkExactReservedSlugs = new Set(TRADEMARK_EXACT_SLUG_BLOCKLIST);
10
+ export function evaluateSlugCandidate(input, options = {}) {
11
+ const maxLength = options.maxLength ?? TEAM_SLUG_MAX_LENGTH;
12
+ // Explicit user choices are rejected rather than silently reshaped. Case,
13
+ // surrounding whitespace, and diacritics are the only accepted canonicalization.
14
+ const casefolded = input
15
+ .trim()
16
+ .normalize("NFD")
17
+ .replace(/[\u0300-\u036f]/g, "")
18
+ .toLowerCase();
19
+ if (!/^[a-z0-9-]+$/.test(casefolded)) {
20
+ return invalid("Slug may only contain lowercase letters, numbers, and dashes.");
21
+ }
22
+ const structuralCandidate = casefolded;
23
+ if (structuralCandidate.includes("--")) {
24
+ return invalid("Slug cannot contain -- because it is the version/branch hostname separator.");
25
+ }
26
+ const normalized = casefolded.replace(/^-+|-+$/g, "");
27
+ const structuralVerdict = evaluateStructuralRules(normalized, structuralCandidate, maxLength);
28
+ if (!structuralVerdict.ok) {
29
+ return structuralVerdict;
30
+ }
31
+ const reservedVerdict = evaluateReservedRules(normalized);
32
+ if (!reservedVerdict.ok) {
33
+ return reservedVerdict;
34
+ }
35
+ return { ok: true, normalized };
36
+ }
37
+ function evaluateStructuralRules(normalized, structuralCandidate, maxLength) {
38
+ if (normalized.length < SLUG_MIN_LENGTH) {
39
+ return invalid(`Slug must be at least ${SLUG_MIN_LENGTH} characters.`);
40
+ }
41
+ if (normalized.length > maxLength) {
42
+ return invalid(`Slug must be ${maxLength} characters or fewer.`);
43
+ }
44
+ if (structuralCandidate.startsWith("-") || structuralCandidate.endsWith("-")) {
45
+ return invalid("Slug cannot start or end with a dash.");
46
+ }
47
+ if (/^[0-9]+$/.test(normalized)) {
48
+ return invalid("Slug cannot be all numeric.");
49
+ }
50
+ if (normalized.startsWith(MANAGEMENT_LABEL_PREFIX)) {
51
+ return reserved(`Slug cannot start with reserved prefix ${MANAGEMENT_LABEL_PREFIX}.`);
52
+ }
53
+ return { ok: true, normalized };
54
+ }
55
+ // A substring reservation survives numeric suffixing (`paypal-2` still
56
+ // contains `paypal`). Suggestion generators use this structured signal to
57
+ // abandon the base instead of exhausting every suffix.
58
+ function matchingReservedSubstring(normalized) {
59
+ return SUBSTRING_SLUG_BLOCKLIST.find((reservedSubstring) => normalized.includes(reservedSubstring));
60
+ }
61
+ export function slugPolicyFailureSurvivesSuffix(normalized) {
62
+ return (matchingReservedSubstring(normalized) !== undefined ||
63
+ normalized.startsWith(MANAGEMENT_LABEL_PREFIX));
64
+ }
65
+ function evaluateReservedRules(normalized) {
66
+ if (infraReservedSlugs.has(normalized)) {
67
+ return reserved("Slug is reserved for infrastructure.");
68
+ }
69
+ if (productPlatformReservedSlugs.has(normalized)) {
70
+ return reserved("Slug is reserved for Spacefast product and platform use.");
71
+ }
72
+ if (trademarkExactReservedSlugs.has(normalized)) {
73
+ return reserved("Slug is reserved for trademark and impersonation protection.");
74
+ }
75
+ const substringRule = matchingReservedSubstring(normalized);
76
+ if (substringRule) {
77
+ return reserved(`Slug contains reserved substring ${substringRule}.`);
78
+ }
79
+ return { ok: true, normalized };
80
+ }
81
+ function invalid(detail) {
82
+ return { ok: false, reason: "slug_invalid", detail };
83
+ }
84
+ function reserved(detail) {
85
+ return { ok: false, reason: "slug_reserved", detail };
86
+ }
@@ -1 +1,2 @@
1
1
  export declare function mapConcurrent<T, R>(items: readonly T[], limit: number, mapper: (item: T, index: number) => Promise<R>): Promise<R[]>;
2
+ export declare function findConcurrent<T>(items: readonly T[], limit: number, predicate: (item: T, index: number) => Promise<boolean>): Promise<T | undefined>;
@@ -16,3 +16,25 @@ export async function mapConcurrent(items, limit, mapper) {
16
16
  await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
17
17
  return results;
18
18
  }
19
+ // Runs predicates in bounded batches while preserving Array.find semantics:
20
+ // the earliest matching item wins, and only a rejection before that match is
21
+ // observable. Later batches are never started after a match is found.
22
+ export async function findConcurrent(items, limit, predicate) {
23
+ if (!Number.isInteger(limit) || limit < 1) {
24
+ throw new Error("Concurrency limit must be a positive integer.");
25
+ }
26
+ for (let offset = 0; offset < items.length; offset += limit) {
27
+ const batch = items.slice(offset, offset + limit);
28
+ // eslint-disable-next-line no-await-in-loop -- batches are sequential so a match stops later work.
29
+ const results = await Promise.allSettled(batch.map((item, index) => predicate(item, offset + index)));
30
+ for (const [index, result] of results.entries()) {
31
+ if (result.status === "rejected") {
32
+ throw result.reason;
33
+ }
34
+ if (result.value) {
35
+ return batch[index];
36
+ }
37
+ }
38
+ }
39
+ return undefined;
40
+ }
@@ -0,0 +1 @@
1
+ export declare function canonicalizeEmail(value: string): string;
@@ -0,0 +1,3 @@
1
+ export function canonicalizeEmail(value) {
2
+ return value.trim().toLowerCase().normalize("NFC");
3
+ }
@@ -28,6 +28,7 @@ export declare const qk: {
28
28
  space: (id: string) => readonly ["spaces", string];
29
29
  spaceByRef: (teamRef: string, spaceRef: string) => readonly ["spaces", "by-ref", string, string];
30
30
  spaceVersions: (id: string) => readonly ["spaces", string, "versions"];
31
+ spaceVersionsHistory: (id: string) => readonly ["spaces", string, "versions", "history"];
31
32
  spaceCollaboration: (id: string) => readonly ["spaces", string, "collaboration"];
32
33
  spaceCollaborationEmbedConfig: (id: string) => readonly ["spaces", string, "collaboration", "embed-config"];
33
34
  spaceAnnotations: (id: string) => readonly ["spaces", string, "annotations"];
@@ -33,6 +33,10 @@ export const qk = {
33
33
  space: (id) => ["spaces", id],
34
34
  spaceByRef: (teamRef, spaceRef) => ["spaces", "by-ref", teamRef, spaceRef],
35
35
  spaceVersions: (id) => ["spaces", id, "versions"],
36
+ // The cursor-paginated history read (distinct cache shape from the plain,
37
+ // capped-at-50 `spaceVersions` list above) — nested under the same prefix so
38
+ // `invalidateQueries({ queryKey: qk.spaceVersions(id) })` still reaches it.
39
+ spaceVersionsHistory: (id) => [...qk.spaceVersions(id), "history"],
36
40
  spaceCollaboration: (id) => ["spaces", id, "collaboration"],
37
41
  spaceCollaborationEmbedConfig: (id) => ["spaces", id, "collaboration", "embed-config"],
38
42
  spaceAnnotations: (id) => ["spaces", id, "annotations"],
@@ -0,0 +1,2 @@
1
+ import { type CompileSfConfigV1Options, type SfConfigV1CompileResult } from "../contracts/sf-config-v1.js";
2
+ export declare function compileSfConfigV1(source: string, options?: CompileSfConfigV1Options): SfConfigV1CompileResult;