@spacefast/common 0.0.23 → 0.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/domains.d.ts +1 -1
- package/dist/config/domains.js +1 -0
- package/dist/config/index.d.ts +0 -1
- package/dist/config/index.js +0 -1
- package/dist/contracts/abuse.d.ts +2 -0
- package/dist/contracts/abuse.js +12 -0
- package/dist/contracts/access.d.ts +121 -9
- package/dist/contracts/access.js +73 -9
- package/dist/contracts/activity.d.ts +1 -1
- package/dist/contracts/activity.js +0 -9
- package/dist/contracts/api-keys.d.ts +21 -21
- package/dist/contracts/beta.js +1 -1
- package/dist/contracts/billing.d.ts +1 -34
- package/dist/contracts/billing.js +4 -53
- package/dist/contracts/builds.d.ts +8 -8
- package/dist/contracts/builds.js +10 -3
- package/dist/contracts/cli.d.ts +10 -0
- package/dist/contracts/cli.js +10 -0
- package/dist/contracts/collab-public.d.ts +63 -0
- package/dist/contracts/collab-public.js +46 -0
- package/dist/contracts/comments.d.ts +283 -54
- package/dist/contracts/comments.js +173 -52
- package/dist/contracts/common.d.ts +3 -3
- package/dist/contracts/device-auth.d.ts +6 -6
- package/dist/contracts/device-auth.js +1 -1
- package/dist/contracts/docs.d.ts +5 -5
- package/dist/contracts/domains.d.ts +36 -36
- package/dist/contracts/enums.d.ts +33 -30
- package/dist/contracts/enums.js +52 -38
- package/dist/contracts/error-code-meta.d.ts +37 -126
- package/dist/contracts/error-code-meta.js +12 -41
- package/dist/contracts/error-codes.d.ts +1 -1
- package/dist/contracts/error-codes.js +10 -32
- package/dist/contracts/execution.d.ts +35 -57
- package/dist/contracts/execution.js +41 -40
- package/dist/contracts/feature-lifecycle-core.d.ts +0 -6
- package/dist/contracts/feature-lifecycle-core.js +2 -31
- package/dist/contracts/feature-lifecycle.d.ts +8 -38
- package/dist/contracts/feature-lifecycle.js +16 -48
- package/dist/contracts/features.d.ts +15 -13
- package/dist/contracts/features.js +39 -38
- package/dist/contracts/functions.d.ts +97 -70
- package/dist/contracts/functions.js +101 -34
- package/dist/contracts/git.d.ts +1 -1
- package/dist/contracts/grants.d.ts +7 -3
- package/dist/contracts/grants.js +18 -9
- package/dist/contracts/internal.d.ts +14 -1
- package/dist/contracts/internal.js +17 -7
- package/dist/contracts/oauth-resources.d.ts +4 -3
- package/dist/contracts/oauth-resources.js +12 -3
- package/dist/contracts/operations.d.ts +90 -5
- package/dist/contracts/operations.js +75 -1
- package/dist/contracts/pages.d.ts +6 -1
- package/dist/contracts/pages.js +24 -1
- package/dist/contracts/plan-policy.d.ts +1 -3
- package/dist/contracts/plan-policy.js +1 -2
- package/dist/contracts/platform.d.ts +61 -148
- package/dist/contracts/platform.js +70 -126
- package/dist/contracts/publish-archive.d.ts +498 -0
- package/dist/contracts/publish-archive.js +25 -0
- package/dist/contracts/resources.d.ts +12 -24
- package/dist/contracts/resources.js +32 -12
- package/dist/contracts/runtime-api.d.ts +369 -175
- package/dist/contracts/runtime-api.js +372 -348
- package/dist/contracts/runtime-app.d.ts +127 -120
- package/dist/contracts/runtime-app.js +19 -10
- package/dist/contracts/runtime-db.d.ts +1 -1
- package/dist/contracts/runtime-services.d.ts +219 -0
- package/dist/contracts/runtime-services.js +437 -0
- package/dist/contracts/runtime-storage.d.ts +45 -32
- package/dist/contracts/runtime-storage.js +30 -8
- package/dist/contracts/space-config.d.ts +10 -16
- package/dist/contracts/space-config.js +14 -10
- package/dist/contracts/spaces.d.ts +241 -436
- package/dist/contracts/spaces.js +128 -145
- package/dist/contracts/superadmin-activity.d.ts +57 -4
- package/dist/contracts/superadmin-activity.js +48 -2
- package/dist/contracts/superadmin-search.d.ts +126 -0
- package/dist/contracts/superadmin-search.js +61 -0
- package/dist/contracts/superadmin-spaces.d.ts +358 -89
- package/dist/contracts/superadmin-spaces.js +97 -24
- package/dist/contracts/superadmin-teams.d.ts +28 -10
- package/dist/contracts/superadmin-teams.js +5 -8
- package/dist/contracts/superadmin.d.ts +0 -32
- package/dist/contracts/superadmin.js +3 -11
- package/dist/contracts/tags.d.ts +9 -9
- package/dist/contracts/teams.d.ts +38 -0
- package/dist/contracts/teams.js +34 -16
- package/dist/contracts/transfers.d.ts +0 -49
- package/dist/contracts/transfers.js +2 -6
- package/dist/contracts/usage.d.ts +61 -0
- package/dist/contracts/usage.js +64 -0
- package/dist/contracts/variables.d.ts +2 -7
- package/dist/contracts/variables.js +6 -5
- package/dist/contracts/webhooks.d.ts +2 -2
- package/dist/contracts/zero.d.ts +87 -56
- package/dist/contracts/zero.js +82 -20
- package/dist/dashboard-paths/index.d.ts +1 -1
- package/dist/dashboard-paths/index.js +1 -0
- package/dist/docs/agent-prose.d.ts +1 -1
- package/dist/docs/agent-prose.js +57 -61
- package/dist/docs/agent-setup.js +40 -13
- package/dist/docs/catalog.d.ts +19 -19
- package/dist/docs/catalog.js +30 -19
- package/dist/docs/error-docs.js +63 -151
- package/dist/test-helpers/fetch-stub.d.ts +16 -0
- package/dist/test-helpers/fetch-stub.js +24 -0
- package/dist/utils/build-settings.js +9 -3
- package/dist/utils/canonical-json.d.ts +10 -0
- package/dist/utils/canonical-json.js +21 -0
- package/dist/utils/cast-transport.d.ts +28 -0
- package/dist/utils/cast-transport.js +52 -0
- package/dist/utils/claim-token.d.ts +0 -8
- package/dist/utils/claim-token.js +3 -15
- package/dist/utils/credential-policy.d.ts +1 -1
- package/dist/utils/env-file.d.ts +17 -0
- package/dist/utils/env-file.js +96 -0
- package/dist/utils/error-code.d.ts +2 -0
- package/dist/utils/error-code.js +7 -0
- package/dist/utils/generate-space-name.d.ts +8 -10
- package/dist/utils/generate-space-name.js +206 -21
- package/dist/utils/grant-decision.fixtures.json +2938 -0
- package/dist/utils/grants.d.ts +2 -0
- package/dist/utils/grants.js +25 -2
- package/dist/utils/gravatar.d.ts +9 -0
- package/dist/utils/gravatar.js +31 -0
- package/dist/utils/idempotency.d.ts +6 -0
- package/dist/utils/idempotency.js +28 -0
- package/dist/utils/local-space-state.d.ts +1 -5
- package/dist/utils/local-space-state.js +7 -26
- package/dist/utils/page-fonts.d.ts +2 -2
- package/dist/utils/page-fonts.js +22 -4
- package/dist/utils/pages.d.ts +1 -2
- package/dist/utils/pages.js +21 -5
- package/dist/utils/publish-form-data.d.ts +12 -0
- package/dist/utils/publish-form-data.js +10 -0
- package/dist/utils/publish-policy.d.ts +20 -0
- package/dist/utils/publish-policy.fixtures.json +397 -0
- package/dist/utils/publish-policy.js +36 -19
- package/dist/utils/push-new.d.ts +7 -0
- package/dist/utils/push-new.js +7 -0
- package/dist/utils/query-keys.d.ts +0 -1
- package/dist/utils/query-keys.js +0 -1
- package/dist/utils/runtime-paths.d.ts +5 -0
- package/dist/utils/runtime-paths.js +5 -0
- package/dist/utils/runtime-upload-batch.d.ts +0 -5
- package/dist/utils/runtime-upload-batch.js +1 -27
- package/dist/utils/runtime-upload.d.ts +3 -13
- package/dist/utils/runtime-upload.js +4 -14
- package/dist/utils/space-config.d.ts +0 -33
- package/dist/utils/space-config.js +5 -326
- package/dist/utils/storage-policy.d.ts +1 -1
- package/dist/utils/storage-policy.js +3 -1
- package/dist/utils/structured-error.d.ts +14 -0
- package/dist/utils/structured-error.js +22 -0
- package/dist/utils/version-path.js +12 -3
- package/dist/vocabulary.d.ts +5 -3
- package/dist/vocabulary.js +13 -10
- package/package.json +10 -5
- package/dist/config/postgres-budget.d.ts +0 -129
- package/dist/config/postgres-budget.js +0 -140
- package/dist/config/postgres-budget.json +0 -51
- package/dist/contracts/archives.d.ts +0 -331
- package/dist/contracts/archives.js +0 -120
- package/dist/contracts/deployments.d.ts +0 -574
- package/dist/contracts/deployments.js +0 -97
- package/dist/contracts/sites.d.ts +0 -416
- package/dist/contracts/sites.js +0 -168
- package/dist/contracts/superadmin-queues.d.ts +0 -414
- package/dist/contracts/superadmin-queues.js +0 -202
- package/dist/contracts/superadmin-runtime.d.ts +0 -27
- package/dist/contracts/superadmin-runtime.js +0 -38
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/utils/api.d.ts +0 -4
- package/dist/utils/api.js +0 -13
package/dist/docs/error-docs.js
CHANGED
|
@@ -49,26 +49,18 @@ export const ERROR_DOCS = {
|
|
|
49
49
|
summary: "This anonymous space expired before it was claimed.",
|
|
50
50
|
fix: "Publish a fresh space and share its claim link right away this time. If you meant to update a live space, check .spacefast/ (or your saved receipt) for its spaceId. Publish to that space with its credential instead of creating a new space.",
|
|
51
51
|
},
|
|
52
|
-
anonymous_pool_unavailable: {
|
|
53
|
-
summary: "No pooled capacity is available for anonymous publishes right now.",
|
|
54
|
-
fix: "Retry shortly. Capacity replenishes automatically.",
|
|
55
|
-
},
|
|
56
52
|
anonymous_publish_rate_limited: {
|
|
57
53
|
summary: "Too many anonymous publishes came from this network in a short window.",
|
|
58
54
|
fix: "Wait for the window to pass, or sign in with an API key for higher limits.",
|
|
59
55
|
},
|
|
60
56
|
anonymous_space_limit_reached: {
|
|
61
|
-
summary: "
|
|
62
|
-
fix: "
|
|
57
|
+
summary: "This client already holds the maximum number of unclaimed Spaces.",
|
|
58
|
+
fix: "Sign in and publish into your account, or claim or delete one of the unclaimed Spaces you already have.",
|
|
63
59
|
},
|
|
64
60
|
api_key_scope_underivable: {
|
|
65
61
|
summary: "A concrete access policy cannot be derived from the requested scope and permissions.",
|
|
66
62
|
fix: "Point scope at a resource you can already reach (an existing space owned by a team). Request only permissions your credential already has.",
|
|
67
63
|
},
|
|
68
|
-
archive_body_required: {
|
|
69
|
-
summary: "This request must carry an archive body but none was provided.",
|
|
70
|
-
fix: "Send the zip bytes as the request body with the matching content-type header.",
|
|
71
|
-
},
|
|
72
64
|
archive_too_large: {
|
|
73
65
|
summary: "The uploaded archive exceeds the maximum allowed size.",
|
|
74
66
|
fix: "Reduce the archive size or upload files individually through a version upload session.",
|
|
@@ -81,26 +73,6 @@ export const ERROR_DOCS = {
|
|
|
81
73
|
summary: "The device login is not approved yet.",
|
|
82
74
|
fix: "Keep polling at the advised interval until the user approves the request.",
|
|
83
75
|
},
|
|
84
|
-
billing_receipt_invalid: {
|
|
85
|
-
summary: "The store receipt or notification cannot be verified with the payment provider.",
|
|
86
|
-
fix: "Re-check the transaction id or purchase token and retry. Contact support if it persists.",
|
|
87
|
-
},
|
|
88
|
-
billing_receipt_not_bound_to_team: {
|
|
89
|
-
summary: "The store purchase was made under a different account token than this team's.",
|
|
90
|
-
fix: "Verify the purchase from the team that initiated it, or contact support to rebind it.",
|
|
91
|
-
},
|
|
92
|
-
billing_store_not_configured: {
|
|
93
|
-
summary: "App Store or Google Play billing is not configured on this deployment.",
|
|
94
|
-
fix: "Use another billing method, or configure the store credentials on the server.",
|
|
95
|
-
},
|
|
96
|
-
billing_subscription_conflict: {
|
|
97
|
-
summary: "The team already has an active subscription with another payment provider.",
|
|
98
|
-
fix: "Cancel the existing subscription before subscribing through a different provider.",
|
|
99
|
-
},
|
|
100
|
-
billing_unknown_product: {
|
|
101
|
-
summary: "The purchased store product does not map to any plan sold by this deployment.",
|
|
102
|
-
fix: "Check the product id against the store catalog returned by the billing endpoints.",
|
|
103
|
-
},
|
|
104
76
|
build_command_missing: {
|
|
105
77
|
summary: "A build or install command cannot be found during the build run.",
|
|
106
78
|
fix: "Check the command spelling and ensure its tool is declared in package.json or available in the build image.",
|
|
@@ -137,6 +109,14 @@ export const ERROR_DOCS = {
|
|
|
137
109
|
summary: "The build exceeded its time limit before producing a version.",
|
|
138
110
|
fix: "Speed up the build or raise the build timeout in the build settings, then start a new build.",
|
|
139
111
|
},
|
|
112
|
+
blob_gate_record_missing: {
|
|
113
|
+
summary: "The file behind this download link no longer exists.",
|
|
114
|
+
fix: "Ask the owner for a fresh link; the underlying object was deleted.",
|
|
115
|
+
},
|
|
116
|
+
blob_gate_token_invalid: {
|
|
117
|
+
summary: "The download token is invalid or expired.",
|
|
118
|
+
fix: "Request a fresh download link and use it within its lifetime.",
|
|
119
|
+
},
|
|
140
120
|
cache_purge_domain_required: {
|
|
141
121
|
summary: "A purge request must name the domain to purge.",
|
|
142
122
|
fix: "Pass the domain to purge in the request.",
|
|
@@ -173,10 +153,6 @@ export const ERROR_DOCS = {
|
|
|
173
153
|
summary: "This CLI version is too old to talk to the API safely.",
|
|
174
154
|
fix: "Upgrade the CLI to the latest release and retry.",
|
|
175
155
|
},
|
|
176
|
-
comment_screenshot_rate_limited: {
|
|
177
|
-
summary: "Too many comment screenshot uploads were attempted.",
|
|
178
|
-
fix: "Wait for the one-minute window to reset, then retry the screenshot upload.",
|
|
179
|
-
},
|
|
180
156
|
config_file_too_large: {
|
|
181
157
|
summary: "The sf.jsonc configuration file exceeds the maximum size.",
|
|
182
158
|
fix: "Keep the configuration file under the documented size limit (256 KB).",
|
|
@@ -210,11 +186,11 @@ export const ERROR_DOCS = {
|
|
|
210
186
|
fix: "Reduce the number of template entries in sf.jsonc. Still stuck? POST /v1/feedback with the error code and request id.",
|
|
211
187
|
},
|
|
212
188
|
continuation_unavailable: {
|
|
213
|
-
summary: "This
|
|
214
|
-
fix: "If the space is claimed, ask its owner for an API key (dashboard: Account → API keys). If it is still unclaimed, keep using the
|
|
189
|
+
summary: "This space key can no longer be exchanged for an API key.",
|
|
190
|
+
fix: "If the space is claimed, ask its owner for an API key (dashboard: Account → API keys). If it is still unclaimed, keep using the space key as bearer auth instead of exchanging it. Still stuck? POST /v1/feedback with the error code and request id.",
|
|
215
191
|
},
|
|
216
192
|
continuation_used: {
|
|
217
|
-
summary: "This
|
|
193
|
+
summary: "This space key was already exchanged for a durable API key.",
|
|
218
194
|
fix: "Authenticate with the exchanged API key (check .spacefast/state.json or wherever it was saved). If it was lost, ask the space owner to create a new API key.",
|
|
219
195
|
},
|
|
220
196
|
credential_expired: {
|
|
@@ -241,10 +217,6 @@ export const ERROR_DOCS = {
|
|
|
241
217
|
summary: "CSAM enforcement policy cannot be set below the mandatory account-suspend floor.",
|
|
242
218
|
fix: "Set the CSAM category enforcement tier to account suspension.",
|
|
243
219
|
},
|
|
244
|
-
custom_certificates_require_dedicated_site: {
|
|
245
|
-
summary: "Custom certificates are unavailable while this space uses shared placement.",
|
|
246
|
-
fix: "Move the space to dedicated placement before managing custom certificates.",
|
|
247
|
-
},
|
|
248
220
|
data_location_immutable: {
|
|
249
221
|
summary: "This request tried to change data location through an immutable path.",
|
|
250
222
|
fix: "Data location is fixed when the space is created and cannot be changed afterwards. Create a new space in the region you want.",
|
|
@@ -473,14 +445,6 @@ export const ERROR_DOCS = {
|
|
|
473
445
|
summary: "The token is expired.",
|
|
474
446
|
fix: "Request a new token and retry.",
|
|
475
447
|
},
|
|
476
|
-
export_archive_not_ready: {
|
|
477
|
-
summary: "The export archive is still being prepared.",
|
|
478
|
-
fix: "Poll the export until its status is ready, then download the archive.",
|
|
479
|
-
},
|
|
480
|
-
export_version_not_found: {
|
|
481
|
-
summary: "The version requested for export was not found.",
|
|
482
|
-
fix: "List the space's versions and export an existing version id.",
|
|
483
|
-
},
|
|
484
448
|
feature_unavailable: {
|
|
485
449
|
summary: "This feature is not available on this deployment or plan.",
|
|
486
450
|
fix: "Check the feature's availability and try again when it is enabled for your team.",
|
|
@@ -497,10 +461,6 @@ export const ERROR_DOCS = {
|
|
|
497
461
|
summary: "Proxy routes to external upstreams are not available on the Free plan.",
|
|
498
462
|
fix: "Remove the proxy rules before publishing. Still stuck? POST /v1/feedback with the error code and request id.",
|
|
499
463
|
},
|
|
500
|
-
google_play_webhook_identity_not_configured: {
|
|
501
|
-
summary: "Google Play Pub/Sub push authentication is not configured on this deployment.",
|
|
502
|
-
fix: "Configure the exact push audience and dedicated Pub/Sub service-account email before enabling RTDN delivery.",
|
|
503
|
-
},
|
|
504
464
|
idempotency_conflict_in_progress: {
|
|
505
465
|
summary: "Another request with the same Idempotency-Key is still in flight.",
|
|
506
466
|
fix: "Wait for the original request to finish. Its response will be replayed.",
|
|
@@ -509,18 +469,6 @@ export const ERROR_DOCS = {
|
|
|
509
469
|
summary: "The Idempotency-Key was reused with a different request body.",
|
|
510
470
|
fix: "Use a fresh key for new requests. Keys bind to one exact request body.",
|
|
511
471
|
},
|
|
512
|
-
import_archive_not_uploaded: {
|
|
513
|
-
summary: "The import has no uploaded archive yet.",
|
|
514
|
-
fix: "Upload the archive to the import's upload target, then finalize.",
|
|
515
|
-
},
|
|
516
|
-
import_not_cancelable: {
|
|
517
|
-
summary: "The import is past the point where it can be canceled.",
|
|
518
|
-
fix: "Wait for the import to finish. Completed imports can be deleted as versions.",
|
|
519
|
-
},
|
|
520
|
-
import_not_waiting_for_archive: {
|
|
521
|
-
summary: "The import is not waiting for an archive upload.",
|
|
522
|
-
fix: "Check the import status. If the archive step is complete, continue to the next step.",
|
|
523
|
-
},
|
|
524
472
|
incumbent_controls_dns: {
|
|
525
473
|
summary: "Another team controls this domain's DNS, so the operation is blocked.",
|
|
526
474
|
fix: "Prove control by completing domain verification, or coordinate with the incumbent.",
|
|
@@ -538,8 +486,8 @@ export const ERROR_DOCS = {
|
|
|
538
486
|
fix: "Retry the request. If it persists, contact support with the requestId.",
|
|
539
487
|
},
|
|
540
488
|
invalid_claim_token: {
|
|
541
|
-
summary: "The
|
|
542
|
-
fix: "Use the exact claim
|
|
489
|
+
summary: "The space key is invalid or does not match this space.",
|
|
490
|
+
fix: "Use the exact space key or claim link returned by the publish that created the space.",
|
|
543
491
|
},
|
|
544
492
|
invalid_continuation_token: {
|
|
545
493
|
summary: "The continuation token is invalid, expired, or already finished.",
|
|
@@ -565,10 +513,6 @@ export const ERROR_DOCS = {
|
|
|
565
513
|
summary: "A file path in the request is not a valid space path.",
|
|
566
514
|
fix: "Use forward-slash relative paths without traversal segments.",
|
|
567
515
|
},
|
|
568
|
-
invalid_google_play_webhook_identity: {
|
|
569
|
-
summary: "The Google Play notification did not carry a valid Pub/Sub push identity.",
|
|
570
|
-
fix: "Send the notification through the configured authenticated Pub/Sub push subscription.",
|
|
571
|
-
},
|
|
572
516
|
invalid_grant: {
|
|
573
517
|
summary: "The authorization grant is invalid or was already used.",
|
|
574
518
|
fix: "Restart the login flow to obtain a new grant.",
|
|
@@ -769,10 +713,6 @@ export const ERROR_DOCS = {
|
|
|
769
713
|
summary: "The placement burstable option was used with an unsupported placement mode.",
|
|
770
714
|
fix: 'Set placement.mode to "dedicated" before setting placement.burstable, or remove the burstable option.',
|
|
771
715
|
},
|
|
772
|
-
placement_mode_requires_plan: {
|
|
773
|
-
summary: "The requested placement mode requires a plan entitlement.",
|
|
774
|
-
fix: "Upgrade the team plan or choose shared placement.",
|
|
775
|
-
},
|
|
776
716
|
platform_tenant_immutable_client: {
|
|
777
717
|
summary: "The tenant's provider client cannot be changed.",
|
|
778
718
|
fix: "Create a new tenant with the desired provider client instead.",
|
|
@@ -781,10 +721,18 @@ export const ERROR_DOCS = {
|
|
|
781
721
|
summary: "The requested key policy grants more than your own credential allows.",
|
|
782
722
|
fix: "Request a policy that is a subset of the credential creating the key.",
|
|
783
723
|
},
|
|
724
|
+
ingest_admission_exceeded: {
|
|
725
|
+
summary: "Too many concurrent uploads for this space.",
|
|
726
|
+
fix: "Wait for the Retry-After window, then resume; at most 4 uploads run per space at once.",
|
|
727
|
+
},
|
|
784
728
|
principal_space_mismatch: {
|
|
785
729
|
summary: "The space does not belong to the principal in the request.",
|
|
786
730
|
fix: "Use the space's owning team or account in the request path.",
|
|
787
731
|
},
|
|
732
|
+
promote_admission_exceeded: {
|
|
733
|
+
summary: "Too many archived files are being restored for this space right now.",
|
|
734
|
+
fix: "Retry shortly; restores are limited to protect the host.",
|
|
735
|
+
},
|
|
788
736
|
provider_error: {
|
|
789
737
|
summary: "The hosting provider reported an error.",
|
|
790
738
|
fix: "Retry. If the error persists, contact support with the requestId.",
|
|
@@ -797,6 +745,10 @@ export const ERROR_DOCS = {
|
|
|
797
745
|
summary: "A hosting-provider job timed out while processing this operation.",
|
|
798
746
|
fix: "Retry the operation. If the provider is slow right now, wait and retry again.",
|
|
799
747
|
},
|
|
748
|
+
provider_privilege: {
|
|
749
|
+
summary: "The hosting provider does not grant this client permission to change the setting.",
|
|
750
|
+
fix: "Leave the provider-owned setting unchanged or contact support to request the required provider capability.",
|
|
751
|
+
},
|
|
800
752
|
provider_runtime_feature_unsupported: {
|
|
801
753
|
summary: "The hosting provider does not support this runtime feature.",
|
|
802
754
|
fix: "Check the feature's availability for this site's runtime.",
|
|
@@ -813,6 +765,10 @@ export const ERROR_DOCS = {
|
|
|
813
765
|
summary: "The proxy upstream hostname cannot be resolved.",
|
|
814
766
|
fix: "Verify the upstream DNS name resolves publicly, then publish again.",
|
|
815
767
|
},
|
|
768
|
+
publish_session_expired: {
|
|
769
|
+
summary: "This publish session expired before it was finalized.",
|
|
770
|
+
fix: "Start a new publish; declared uploads are kept for 24 hours only.",
|
|
771
|
+
},
|
|
816
772
|
publish_archive_compression_ratio_exceeded: {
|
|
817
773
|
summary: "The archive's compression ratio is suspiciously high and was rejected.",
|
|
818
774
|
fix: "Repackage the content without highly-compressible filler data.",
|
|
@@ -889,6 +845,14 @@ export const ERROR_DOCS = {
|
|
|
889
845
|
summary: "The named job queue does not exist.",
|
|
890
846
|
fix: "Check the queue name against the operator queue list.",
|
|
891
847
|
},
|
|
848
|
+
response_headers_unservable: {
|
|
849
|
+
summary: "A file needs a response header that the platform cannot deliver for large files.",
|
|
850
|
+
fix: "Reduce the file below 100 MiB or drop the custom header.",
|
|
851
|
+
},
|
|
852
|
+
response_table_too_large: {
|
|
853
|
+
summary: "A compiled response table exceeded the 1 MiB per-file limit.",
|
|
854
|
+
fix: "This version has an unusually dense path set; contact support with the requestId.",
|
|
855
|
+
},
|
|
892
856
|
rate_limited: {
|
|
893
857
|
summary: "Too many requests were made in a short window.",
|
|
894
858
|
fix: "Wait for the window in the Retry-After header to pass, then retry.",
|
|
@@ -905,6 +869,10 @@ export const ERROR_DOCS = {
|
|
|
905
869
|
summary: "The resource changed after you loaded it, so the stale write was rejected.",
|
|
906
870
|
fix: "Use the current resource returned in error.details.current, reapply the intended fields, and send its revision as expectedRevision.",
|
|
907
871
|
},
|
|
872
|
+
review_item_claimed: {
|
|
873
|
+
summary: "Another reviewer already claimed this review item.",
|
|
874
|
+
fix: "Pick a different item, or ask the current assignee (error.details.assignedToUserId) to release it.",
|
|
875
|
+
},
|
|
908
876
|
routing_rules_over_plan: {
|
|
909
877
|
summary: "The published _redirects and _headers exceed the plan's routing-rule allowance.",
|
|
910
878
|
fix: "Reduce the rule count. Still stuck? POST /v1/feedback with the error code and request id.",
|
|
@@ -925,10 +893,6 @@ export const ERROR_DOCS = {
|
|
|
925
893
|
summary: "`sf dev` cannot run this runtime locally yet.",
|
|
926
894
|
fix: "Run your framework's own dev server (`next dev`, `vite`, …) and publish with `sf publish` when you are ready.",
|
|
927
895
|
},
|
|
928
|
-
runtime_engine_zip_missing: {
|
|
929
|
-
summary: "The runtime engine artifact is missing from this deployment.",
|
|
930
|
-
fix: "Rebuild the control plane so dist/runtime-engine.zip exists.",
|
|
931
|
-
},
|
|
932
896
|
runtime_hostname_unassigned: {
|
|
933
897
|
summary: "The hostname is not assigned to a runtime, so the request cannot be routed.",
|
|
934
898
|
fix: `Verify the binding is active and DNS points at ${BRAND.productName}.`,
|
|
@@ -965,6 +929,10 @@ export const ERROR_DOCS = {
|
|
|
965
929
|
summary: "The runtime operation referenced by this request was not found.",
|
|
966
930
|
fix: "Re-request instructions and retry with a current operation id.",
|
|
967
931
|
},
|
|
932
|
+
runtime_purge_failed: {
|
|
933
|
+
summary: "Content updated but the edge cache purge did not confirm.",
|
|
934
|
+
fix: "The runtime retries automatically; recent visitors may briefly see the previous version.",
|
|
935
|
+
},
|
|
968
936
|
runtime_scope_forbidden: {
|
|
969
937
|
summary: "The runtime token's scope does not cover this path or action.",
|
|
970
938
|
fix: "Use the token minted for this exact session, space, and step.",
|
|
@@ -1059,19 +1027,15 @@ export const ERROR_DOCS = {
|
|
|
1059
1027
|
},
|
|
1060
1028
|
space_already_claimed: {
|
|
1061
1029
|
summary: "The space was already claimed. A claim can only succeed once.",
|
|
1062
|
-
fix: "If you published this space, exchange your
|
|
1063
|
-
},
|
|
1064
|
-
space_capacity_warming: {
|
|
1065
|
-
summary: "This space is waiting on serving capacity that is being provisioned in the background.",
|
|
1066
|
-
fix: "Wait for the Retry-After window, then send the same publish again. No version was created and no files were uploaded.",
|
|
1030
|
+
fix: "If you published this space, exchange your space key once at POST /v1/claim/exchange. Keep publishing to the same spaceId with that durable API key. Otherwise ask the owner for access — do not publish a duplicate space.",
|
|
1067
1031
|
},
|
|
1068
1032
|
space_claim_unavailable: {
|
|
1069
1033
|
summary: "This space cannot be claimed.",
|
|
1070
|
-
fix: "Only anonymous, unexpired spaces with a valid
|
|
1034
|
+
fix: "Only anonymous, unexpired spaces with a valid space key can be claimed.",
|
|
1071
1035
|
},
|
|
1072
1036
|
space_claimed_credential_available: {
|
|
1073
|
-
summary: "The space was claimed and its owner kept agent publishing enabled; this
|
|
1074
|
-
fix: "POST /v1/
|
|
1037
|
+
summary: "The space was claimed and its owner kept agent publishing enabled; this space key now works exactly once as an exchange voucher.",
|
|
1038
|
+
fix: "POST /v1/claim/exchange with this space key as bearer auth. Save the returned API key (e.g. .spacefast/state.json), then retry the request with it as Authorization: Bearer. Do not publish a new space.",
|
|
1075
1039
|
},
|
|
1076
1040
|
space_disabled: {
|
|
1077
1041
|
summary: "The space is disabled and cannot be served or modified.",
|
|
@@ -1093,30 +1057,6 @@ export const ERROR_DOCS = {
|
|
|
1093
1057
|
summary: "The space is missing its persisted default hostname identity.",
|
|
1094
1058
|
fix: "Contact support. Retrying cannot safely recreate a permanent hostname identity.",
|
|
1095
1059
|
},
|
|
1096
|
-
space_import_archive_invalid: {
|
|
1097
|
-
summary: `The import archive is not a valid ${BRAND.productName} export.`,
|
|
1098
|
-
fix: "Export the source space again and upload the unmodified archive.",
|
|
1099
|
-
},
|
|
1100
|
-
space_import_archive_too_large: {
|
|
1101
|
-
summary: "The import archive exceeds the maximum allowed size.",
|
|
1102
|
-
fix: "Reduce the export size, or import versions individually.",
|
|
1103
|
-
},
|
|
1104
|
-
space_import_config_only_archive: {
|
|
1105
|
-
summary: "The archive contains only configuration and cannot be imported as content.",
|
|
1106
|
-
fix: "Include files in the export, or apply the configuration directly.",
|
|
1107
|
-
},
|
|
1108
|
-
space_import_runtime_schema_unsupported: {
|
|
1109
|
-
summary: "The export was produced by an unsupported runtime schema version.",
|
|
1110
|
-
fix: "Re-export from a current runtime, or upgrade before exporting.",
|
|
1111
|
-
},
|
|
1112
|
-
space_move_would_strand_zero_data: {
|
|
1113
|
-
summary: "The Space runs Zero, whose database and object storage live on the site the Space is placed on. Relocating it would strand that data on the old site, so the move is refused.",
|
|
1114
|
-
fix: "Nothing was moved and no data was lost. Migrating Zero data between sites is not implemented. Contact support if the space genuinely has to be relocated.",
|
|
1115
|
-
},
|
|
1116
|
-
space_moving: {
|
|
1117
|
-
summary: "The space is moving between sites, which blocks this operation.",
|
|
1118
|
-
fix: "Wait for the move to finish, then retry.",
|
|
1119
|
-
},
|
|
1120
1060
|
space_no_live_version: {
|
|
1121
1061
|
summary: "The space has no live version, so this operation has nothing to act on.",
|
|
1122
1062
|
fix: "Publish a version to the space, then retry.",
|
|
@@ -1174,36 +1114,36 @@ export const ERROR_DOCS = {
|
|
|
1174
1114
|
fix: "Target a static-runtime space.",
|
|
1175
1115
|
},
|
|
1176
1116
|
storage_auth_required: {
|
|
1177
|
-
summary: "This storage operation requires an authenticated
|
|
1178
|
-
fix: "Sign in to the
|
|
1117
|
+
summary: "This storage operation requires an authenticated visitor.",
|
|
1118
|
+
fix: "Sign in to the space, then retry the storage operation.",
|
|
1179
1119
|
},
|
|
1180
1120
|
storage_bucket_unavailable: {
|
|
1181
1121
|
summary: "No usable internal storage bucket is configured for this operation.",
|
|
1182
1122
|
fix: "Check the storage bucket registry and credential environment references, then retry.",
|
|
1183
1123
|
},
|
|
1184
1124
|
storage_content_blocked: {
|
|
1185
|
-
summary: "The object contains executable or active web content that
|
|
1125
|
+
summary: "The object contains executable or active web content that Spacefast storage does not serve.",
|
|
1186
1126
|
fix: "Upload a passive file type without HTML, script, SVG, PHP, or executable bytes.",
|
|
1187
1127
|
},
|
|
1188
1128
|
storage_delete_forbidden: {
|
|
1189
|
-
summary: "The current
|
|
1129
|
+
summary: "The current visitor did not upload this object.",
|
|
1190
1130
|
fix: "Sign in as the original uploader before deleting the object.",
|
|
1191
1131
|
},
|
|
1192
1132
|
storage_empty_file: {
|
|
1193
|
-
summary: "The
|
|
1133
|
+
summary: "The storage upload is empty.",
|
|
1194
1134
|
fix: "Upload a file containing at least 1 byte.",
|
|
1195
1135
|
},
|
|
1196
1136
|
storage_file_too_large: {
|
|
1197
|
-
summary: "The
|
|
1137
|
+
summary: "The storage object exceeds the 5 MiB file limit.",
|
|
1198
1138
|
fix: "Upload a smaller object.",
|
|
1199
1139
|
},
|
|
1200
1140
|
storage_key_invalid: {
|
|
1201
|
-
summary: "The
|
|
1202
|
-
fix: "Use the
|
|
1141
|
+
summary: "The storage object id is malformed.",
|
|
1142
|
+
fix: "Use the 32-character object id returned by storage.upload.",
|
|
1203
1143
|
},
|
|
1204
1144
|
storage_object_not_found: {
|
|
1205
|
-
summary: "The requested
|
|
1206
|
-
fix: "Check the object
|
|
1145
|
+
summary: "The requested storage object does not exist.",
|
|
1146
|
+
fix: "Check the object id or upload the object again.",
|
|
1207
1147
|
},
|
|
1208
1148
|
storage_quota_already_unlimited: {
|
|
1209
1149
|
summary: "The team already has unlimited storage.",
|
|
@@ -1215,16 +1155,12 @@ export const ERROR_DOCS = {
|
|
|
1215
1155
|
},
|
|
1216
1156
|
storage_quota_exceeded: {
|
|
1217
1157
|
summary: "The operation would exceed its storage allowance.",
|
|
1218
|
-
fix: "Delete unused
|
|
1158
|
+
fix: "Delete unused storage objects or old versions, reduce the upload size, then retry.",
|
|
1219
1159
|
},
|
|
1220
1160
|
storage_unavailable: {
|
|
1221
|
-
summary: "The
|
|
1161
|
+
summary: "The runtime cannot read or persist object storage.",
|
|
1222
1162
|
fix: "Retry on a healthy runtime. Contact support if the failure persists.",
|
|
1223
1163
|
},
|
|
1224
|
-
storage_usage_unavailable: {
|
|
1225
|
-
summary: "Storage usage cannot be computed right now.",
|
|
1226
|
-
fix: "Retry shortly.",
|
|
1227
|
-
},
|
|
1228
1164
|
stripe_error: {
|
|
1229
1165
|
summary: "The billing provider reported an error.",
|
|
1230
1166
|
fix: "Retry. If it persists, check the team's billing settings.",
|
|
@@ -1305,10 +1241,6 @@ export const ERROR_DOCS = {
|
|
|
1305
1241
|
summary: "Spaces cannot be transferred across tenants.",
|
|
1306
1242
|
fix: "Export the space and import it under the target tenant instead.",
|
|
1307
1243
|
},
|
|
1308
|
-
transfer_move_failed: {
|
|
1309
|
-
summary: "The space move backing this transfer failed.",
|
|
1310
|
-
fix: "Retry the transfer. If it persists, contact support with the transfer id.",
|
|
1311
|
-
},
|
|
1312
1244
|
transfer_not_cancelable: {
|
|
1313
1245
|
summary: "The transfer is past the point where it can be canceled.",
|
|
1314
1246
|
fix: "Wait for the transfer to finish.",
|
|
@@ -1361,10 +1293,6 @@ export const ERROR_DOCS = {
|
|
|
1361
1293
|
summary: "A request field failed validation.",
|
|
1362
1294
|
fix: "Fix the field referenced by pointer and details, then retry.",
|
|
1363
1295
|
},
|
|
1364
|
-
variable_cascade_failed: {
|
|
1365
|
-
summary: "Republishing spaces after a variable change failed.",
|
|
1366
|
-
fix: "Check the operation's diagnostics. Live content keeps serving the previous values.",
|
|
1367
|
-
},
|
|
1368
1296
|
verification_required: {
|
|
1369
1297
|
summary: "Domain verification is required before this operation.",
|
|
1370
1298
|
fix: "Add the verification DNS record, then run verify on the domain.",
|
|
@@ -1461,10 +1389,6 @@ export const ERROR_DOCS = {
|
|
|
1461
1389
|
summary: "WHOIS data is unavailable for this domain right now.",
|
|
1462
1390
|
fix: "Retry later.",
|
|
1463
1391
|
},
|
|
1464
|
-
zero_activating: {
|
|
1465
|
-
summary: "Zero endpoints are activating.",
|
|
1466
|
-
fix: "Wait for the dedicated runtime move to complete, then retry the request.",
|
|
1467
|
-
},
|
|
1468
1392
|
zero_ai_unavailable: {
|
|
1469
1393
|
summary: "The local Zero AI service is not configured.",
|
|
1470
1394
|
fix: "Configure an AI handler in the project, then restart `sf dev` before calling the AI service.",
|
|
@@ -1701,10 +1625,6 @@ export const ERROR_DOCS = {
|
|
|
1701
1625
|
summary: "The Zero endpoints payload exceeds the supported endpoint count.",
|
|
1702
1626
|
fix: "Reduce the project to at most 128 Zero endpoints, then publish again.",
|
|
1703
1627
|
},
|
|
1704
|
-
zero_requires_dedicated_plan: {
|
|
1705
|
-
summary: "Zero endpoints require a plan with dedicated runtime isolation.",
|
|
1706
|
-
fix: "Upgrade the team to a plan that includes Zero and dedicated runtime, then publish again.",
|
|
1707
|
-
},
|
|
1708
1628
|
zero_envelope_encode_failed: {
|
|
1709
1629
|
summary: "The PHP bridge cannot encode the Zero runner request envelope.",
|
|
1710
1630
|
fix: "Retry with valid request data. Report the issue if it persists.",
|
|
@@ -1877,14 +1797,6 @@ export const ERROR_DOCS = {
|
|
|
1877
1797
|
summary: "The Zero run-handler payload exceeds the supported handler count.",
|
|
1878
1798
|
fix: "Reduce the project to at most 128 Zero run handlers, then publish again.",
|
|
1879
1799
|
},
|
|
1880
|
-
zero_shopify_unavailable: {
|
|
1881
|
-
summary: "The local Zero Shopify service is not configured.",
|
|
1882
|
-
fix: "Configure a Shopify handler in the project, then restart `sf dev` before calling the Shopify service.",
|
|
1883
|
-
},
|
|
1884
|
-
wp_cloud_anonymous_pool_required: {
|
|
1885
|
-
summary: "This operation requires an anonymous pool site.",
|
|
1886
|
-
fix: "Target a pool-class site.",
|
|
1887
|
-
},
|
|
1888
1800
|
wp_cloud_assignment_kind_mismatch: {
|
|
1889
1801
|
summary: "The domain assignment's kind does not match the site.",
|
|
1890
1802
|
fix: "Verify the assignment against the site's hosting kind.",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** The original fetch implementation captured before a test installs a stub. */
|
|
2
|
+
export declare const realFetch: typeof fetch;
|
|
3
|
+
export type FetchStubHandler = (request: Request) => Response | Promise<Response>;
|
|
4
|
+
export declare function stubFetch(handler: FetchStubHandler): void;
|
|
5
|
+
export declare function restoreFetch(): void;
|
|
6
|
+
/** Build the RFC 9457 problem response served by Spacefast APIs. */
|
|
7
|
+
export declare function problemResponse(input: {
|
|
8
|
+
code: string;
|
|
9
|
+
status: number;
|
|
10
|
+
detail: string;
|
|
11
|
+
pointer?: string;
|
|
12
|
+
details?: unknown;
|
|
13
|
+
suggestions?: readonly string[];
|
|
14
|
+
next?: readonly string[];
|
|
15
|
+
requestId?: string;
|
|
16
|
+
}): Response;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { errorDocsUrl, errorTitle, PROBLEM_CONTENT_TYPE } from "../contracts/error-codes.js";
|
|
2
|
+
/** The original fetch implementation captured before a test installs a stub. */
|
|
3
|
+
export const realFetch = globalThis.fetch;
|
|
4
|
+
export function stubFetch(handler) {
|
|
5
|
+
globalThis.fetch = (async (input, init) => handler(input instanceof Request && init === undefined ? input : new Request(input, init)));
|
|
6
|
+
}
|
|
7
|
+
export function restoreFetch() {
|
|
8
|
+
globalThis.fetch = realFetch;
|
|
9
|
+
}
|
|
10
|
+
/** Build the RFC 9457 problem response served by Spacefast APIs. */
|
|
11
|
+
export function problemResponse(input) {
|
|
12
|
+
return new Response(JSON.stringify({
|
|
13
|
+
type: errorDocsUrl(input.code),
|
|
14
|
+
title: errorTitle(input.code),
|
|
15
|
+
status: input.status,
|
|
16
|
+
detail: input.detail,
|
|
17
|
+
code: input.code,
|
|
18
|
+
...(input.pointer === undefined ? {} : { pointer: input.pointer }),
|
|
19
|
+
...(input.details === undefined ? {} : { details: input.details }),
|
|
20
|
+
...(input.suggestions === undefined ? {} : { suggestions: input.suggestions }),
|
|
21
|
+
...(input.next === undefined ? {} : { next: input.next }),
|
|
22
|
+
requestId: input.requestId ?? "req_test",
|
|
23
|
+
}), { status: input.status, headers: { "content-type": PROBLEM_CONTENT_TYPE } });
|
|
24
|
+
}
|
|
@@ -8,6 +8,7 @@ const PACKAGE_MANAGER_ONLY_FILES = new Set([
|
|
|
8
8
|
"pnpm-lock.yaml",
|
|
9
9
|
"yarn.lock",
|
|
10
10
|
"package-lock.json",
|
|
11
|
+
"npm-shrinkwrap.json",
|
|
11
12
|
"Gemfile",
|
|
12
13
|
"Gemfile.lock",
|
|
13
14
|
]);
|
|
@@ -711,7 +712,7 @@ function jekyllBuildCommand(files) {
|
|
|
711
712
|
function frameworkInstallCommand(frameworkPreset, files) {
|
|
712
713
|
return frameworkPreset === "jekyll" ? jekyllInstallCommand(files) : null;
|
|
713
714
|
}
|
|
714
|
-
function installCommandForManager(packageManager, packageJson) {
|
|
715
|
+
function installCommandForManager(packageManager, packageJson, files) {
|
|
715
716
|
switch (packageManager) {
|
|
716
717
|
case "bun":
|
|
717
718
|
return "bun install --frozen-lockfile";
|
|
@@ -720,7 +721,11 @@ function installCommandForManager(packageManager, packageJson) {
|
|
|
720
721
|
case "yarn":
|
|
721
722
|
return "corepack enable && (yarn install --immutable || yarn install --frozen-lockfile)";
|
|
722
723
|
default:
|
|
723
|
-
|
|
724
|
+
if (!packageJson)
|
|
725
|
+
return null;
|
|
726
|
+
return files.has("package-lock.json") || files.has("npm-shrinkwrap.json")
|
|
727
|
+
? "npm ci"
|
|
728
|
+
: "npm install";
|
|
724
729
|
}
|
|
725
730
|
}
|
|
726
731
|
function buildCommandForDefaults(packageJson, run, frameworkPreset, files) {
|
|
@@ -740,7 +745,7 @@ function packageDefaults(input, files) {
|
|
|
740
745
|
const run = packageManager === "npm" ? "npm run" : packageManager === "bun" ? "bun run" : packageManager;
|
|
741
746
|
const frameworkPreset = detectFrameworkPreset(files, dependencies);
|
|
742
747
|
const installCommands = [
|
|
743
|
-
installCommandForManager(packageManager, packageJson),
|
|
748
|
+
installCommandForManager(packageManager, packageJson, files),
|
|
744
749
|
frameworkInstallCommand(frameworkPreset, files),
|
|
745
750
|
].filter((command) => Boolean(command));
|
|
746
751
|
const jekyllConfig = input.jekyllYaml
|
|
@@ -768,6 +773,7 @@ export function detectBuildSettings(input) {
|
|
|
768
773
|
files.has("pnpm-lock.yaml") ? "pnpm-lock.yaml" : null,
|
|
769
774
|
files.has("yarn.lock") ? "yarn.lock" : null,
|
|
770
775
|
files.has("package-lock.json") ? "package-lock.json" : null,
|
|
776
|
+
files.has("npm-shrinkwrap.json") ? "npm-shrinkwrap.json" : null,
|
|
771
777
|
files.has("Gemfile") ? "Gemfile" : null,
|
|
772
778
|
files.has("Gemfile.lock") ? "Gemfile.lock" : null,
|
|
773
779
|
files.has("_config.yml") ? "_config.yml" : null,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursively key-sorted JSON, so a value round-tripped through Postgres jsonb
|
|
3
|
+
* (which canonicalizes key order) digests identically to one freshly built in
|
|
4
|
+
* JS. Arrays keep their original order since order is meaningful there.
|
|
5
|
+
*
|
|
6
|
+
* Every digest that has to agree across the control plane, the CLI and the
|
|
7
|
+
* capsule compiler is built on this exact spelling — change it and stored
|
|
8
|
+
* hashes stop matching freshly computed ones.
|
|
9
|
+
*/
|
|
10
|
+
export declare function canonicalJson(value: unknown): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursively key-sorted JSON, so a value round-tripped through Postgres jsonb
|
|
3
|
+
* (which canonicalizes key order) digests identically to one freshly built in
|
|
4
|
+
* JS. Arrays keep their original order since order is meaningful there.
|
|
5
|
+
*
|
|
6
|
+
* Every digest that has to agree across the control plane, the CLI and the
|
|
7
|
+
* capsule compiler is built on this exact spelling — change it and stored
|
|
8
|
+
* hashes stop matching freshly computed ones.
|
|
9
|
+
*/
|
|
10
|
+
export function canonicalJson(value) {
|
|
11
|
+
if (Array.isArray(value)) {
|
|
12
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
13
|
+
}
|
|
14
|
+
if (value && typeof value === "object") {
|
|
15
|
+
return `{${Object.entries(value)
|
|
16
|
+
.toSorted(([left], [right]) => left.localeCompare(right))
|
|
17
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`)
|
|
18
|
+
.join(",")}}`;
|
|
19
|
+
}
|
|
20
|
+
return JSON.stringify(value);
|
|
21
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a client addresses Cast over Phoenix. Both Spacefast clients — the
|
|
3
|
+
* dashboard's notice subscription and the injected Collab SDK — build their
|
|
4
|
+
* socket and their topics here, and nowhere else.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* The Phoenix topic a Cast room's messages travel on.
|
|
8
|
+
*
|
|
9
|
+
* Phoenix routes a broadcast by the *literal* topic string. Cast broadcasts
|
|
10
|
+
* anything computed outside a channel process — Space notices, REST-published
|
|
11
|
+
* room events — to the topic `Cast.RoomTopic.for/2` builds, so a client that
|
|
12
|
+
* encodes the room key any other way joins a real and permanently silent room.
|
|
13
|
+
* Both sides therefore encode exactly once, here and in that module: the `~n`
|
|
14
|
+
* table first (it survives round-tripping a key that itself contains `~` or
|
|
15
|
+
* `%`), then `encodeURIComponent`.
|
|
16
|
+
*
|
|
17
|
+
* Proven by delivery, not by inspection: `./cast-transport.test.ts` pins the
|
|
18
|
+
* strings, and on the other side `room_channel_notice_test.exs` and
|
|
19
|
+
* `spacefast_inbox_fanout_test.exs` join those literal topics and assert a real
|
|
20
|
+
* Cast broadcast arrives on them.
|
|
21
|
+
*/
|
|
22
|
+
export declare function castRoomTopic(resourceKey: string, roomKey: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Phoenix's own `Socket` appends `/websocket` to whatever endpoint it is given,
|
|
25
|
+
* so a configured URL that already carries the suffix would otherwise become
|
|
26
|
+
* `/websocket/websocket`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function phoenixEndpoint(url: string): string;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a client addresses Cast over Phoenix. Both Spacefast clients — the
|
|
3
|
+
* dashboard's notice subscription and the injected Collab SDK — build their
|
|
4
|
+
* socket and their topics here, and nowhere else.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* `~n` escapes, the inverse of `CastWeb.ApiContext.decode_path_segment/1`.
|
|
8
|
+
* `~` goes first in intent: the map is applied in one pass, so an already
|
|
9
|
+
* escaped marker can never be escaped twice.
|
|
10
|
+
*/
|
|
11
|
+
const ESCAPED = new Map([
|
|
12
|
+
["~", "~0"],
|
|
13
|
+
["/", "~1"],
|
|
14
|
+
[":", "~2"],
|
|
15
|
+
["\\", "~3"],
|
|
16
|
+
["%", "~4"],
|
|
17
|
+
["\0", "~5"],
|
|
18
|
+
]);
|
|
19
|
+
/**
|
|
20
|
+
* The Phoenix topic a Cast room's messages travel on.
|
|
21
|
+
*
|
|
22
|
+
* Phoenix routes a broadcast by the *literal* topic string. Cast broadcasts
|
|
23
|
+
* anything computed outside a channel process — Space notices, REST-published
|
|
24
|
+
* room events — to the topic `Cast.RoomTopic.for/2` builds, so a client that
|
|
25
|
+
* encodes the room key any other way joins a real and permanently silent room.
|
|
26
|
+
* Both sides therefore encode exactly once, here and in that module: the `~n`
|
|
27
|
+
* table first (it survives round-tripping a key that itself contains `~` or
|
|
28
|
+
* `%`), then `encodeURIComponent`.
|
|
29
|
+
*
|
|
30
|
+
* Proven by delivery, not by inspection: `./cast-transport.test.ts` pins the
|
|
31
|
+
* strings, and on the other side `room_channel_notice_test.exs` and
|
|
32
|
+
* `spacefast_inbox_fanout_test.exs` join those literal topics and assert a real
|
|
33
|
+
* Cast broadcast arrives on them.
|
|
34
|
+
*/
|
|
35
|
+
export function castRoomTopic(resourceKey, roomKey) {
|
|
36
|
+
let escaped = "";
|
|
37
|
+
for (const character of roomKey)
|
|
38
|
+
escaped += ESCAPED.get(character) ?? character;
|
|
39
|
+
return `room:${resourceKey}:${encodeURIComponent(escaped)}`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Phoenix's own `Socket` appends `/websocket` to whatever endpoint it is given,
|
|
43
|
+
* so a configured URL that already carries the suffix would otherwise become
|
|
44
|
+
* `/websocket/websocket`.
|
|
45
|
+
*/
|
|
46
|
+
export function phoenixEndpoint(url) {
|
|
47
|
+
const parsed = new URL(url);
|
|
48
|
+
if (parsed.pathname.endsWith("/websocket")) {
|
|
49
|
+
parsed.pathname = parsed.pathname.slice(0, -"/websocket".length);
|
|
50
|
+
}
|
|
51
|
+
return parsed.href.replace(/\/$/, "");
|
|
52
|
+
}
|