@astrale-os/adapter-cloudflare 0.5.0-beta.3 → 0.5.0-beta.30

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 (174) hide show
  1. package/README.md +11 -0
  2. package/dist/adapter.d.ts +17 -0
  3. package/dist/adapter.js +174 -0
  4. package/dist/build/artifact.d.ts +7 -0
  5. package/dist/build/artifact.js +150 -0
  6. package/dist/build/bundle/admit.d.ts +1 -0
  7. package/dist/build/bundle/admit.js +9 -0
  8. package/dist/build/closure.d.ts +2 -0
  9. package/dist/build/closure.js +22 -0
  10. package/dist/build/codegen/execution.d.ts +3 -0
  11. package/dist/build/codegen/execution.js +2 -0
  12. package/dist/build/codegen/gateway.d.ts +1 -0
  13. package/dist/build/codegen/gateway.js +1 -0
  14. package/dist/build/codegen/merge.d.ts +0 -1
  15. package/dist/build/codegen/merge.js +0 -1
  16. package/dist/build/codegen/wrangler.d.ts +15 -1
  17. package/dist/build/codegen/wrangler.js +35 -16
  18. package/dist/build/configuration/addressing.d.ts +2 -0
  19. package/dist/build/configuration/addressing.js +11 -0
  20. package/dist/build/configuration/router.d.ts +7 -0
  21. package/dist/build/configuration/router.js +11 -0
  22. package/dist/build/configuration/worker-name.d.ts +2 -0
  23. package/dist/build/configuration/worker-name.js +18 -0
  24. package/dist/build/configuration.d.ts +3 -0
  25. package/dist/build/configuration.js +3 -0
  26. package/dist/build/direct.d.ts +4 -0
  27. package/dist/build/direct.js +52 -0
  28. package/dist/build/frontend/routes.d.ts +2 -0
  29. package/dist/build/frontend/routes.js +11 -0
  30. package/dist/build/frontend-v1.d.ts +16 -0
  31. package/dist/build/frontend-v1.js +133 -0
  32. package/dist/build/gateway/prepare.d.ts +8 -0
  33. package/dist/build/gateway/prepare.js +32 -0
  34. package/dist/build/gateway/source.d.ts +1 -0
  35. package/dist/build/gateway/source.js +7 -0
  36. package/dist/build/index.d.ts +10 -16
  37. package/dist/build/index.js +9 -16
  38. package/dist/build/materialize.d.ts +4 -0
  39. package/dist/build/materialize.js +10 -0
  40. package/dist/build/metafile.d.ts +5 -0
  41. package/dist/build/metafile.js +14 -0
  42. package/dist/build/parse-output.d.ts +0 -1
  43. package/dist/build/parse-output.js +0 -1
  44. package/dist/build/qualification.d.ts +5 -0
  45. package/dist/build/qualification.js +7 -0
  46. package/dist/build/roots.d.ts +2 -0
  47. package/dist/build/roots.js +34 -0
  48. package/dist/build/worker/artifact.d.ts +38 -0
  49. package/dist/build/worker/artifact.js +1 -0
  50. package/dist/build/worker/artifact.load.d.ts +3 -0
  51. package/dist/build/worker/artifact.load.js +266 -0
  52. package/dist/build/worker/artifact.manifest.d.ts +46 -0
  53. package/dist/build/worker/artifact.manifest.js +84 -0
  54. package/dist/build/worker/artifact.paths.d.ts +4 -0
  55. package/dist/build/worker/artifact.paths.js +25 -0
  56. package/dist/build/worker/codegen/entry.d.ts +2 -0
  57. package/dist/build/worker/codegen/entry.js +28 -0
  58. package/dist/build/worker/codegen/service.d.ts +13 -0
  59. package/dist/build/worker/codegen/service.js +44 -0
  60. package/dist/build/worker/execution.prepare.d.ts +18 -0
  61. package/dist/build/worker/execution.prepare.js +102 -0
  62. package/dist/build/worker/index.d.ts +2 -0
  63. package/dist/build/worker/index.js +1 -0
  64. package/dist/build/worker/runtime.d.ts +10 -0
  65. package/dist/build/worker/runtime.js +35 -0
  66. package/dist/build/wrangler/bundle.d.ts +10 -0
  67. package/dist/build/wrangler/bundle.js +29 -0
  68. package/dist/build/wrangler/deploy.d.ts +23 -0
  69. package/dist/build/wrangler/deploy.js +35 -0
  70. package/dist/build/wrangler/development.d.ts +15 -0
  71. package/dist/build/wrangler/development.js +135 -0
  72. package/dist/build/wrangler/namespace.d.ts +10 -0
  73. package/dist/build/wrangler/namespace.js +26 -0
  74. package/dist/build/wrangler/process.d.ts +15 -0
  75. package/dist/build/wrangler/process.js +103 -0
  76. package/dist/build/wrangler/release.d.ts +11 -0
  77. package/dist/build/wrangler/release.js +27 -0
  78. package/dist/build/wrangler/secrets.d.ts +6 -0
  79. package/dist/build/wrangler/secrets.js +58 -0
  80. package/dist/build/wrangler/status.d.ts +14 -0
  81. package/dist/build/wrangler/status.js +56 -0
  82. package/dist/build/wrangler/type-generation.d.ts +6 -0
  83. package/dist/build/wrangler/type-generation.js +19 -0
  84. package/dist/build/wrangler-cli.d.ts +8 -86
  85. package/dist/build/wrangler-cli.js +8 -275
  86. package/dist/cloudflare.d.ts +1 -34
  87. package/dist/cloudflare.js +1 -205
  88. package/dist/deploy/deploy.d.ts +17 -0
  89. package/dist/deploy/deploy.js +234 -0
  90. package/dist/deploy/index.d.ts +1 -0
  91. package/dist/deploy/index.js +1 -0
  92. package/dist/identity/binding.d.ts +2 -0
  93. package/dist/identity/binding.js +2 -0
  94. package/dist/identity/material.d.ts +7 -0
  95. package/dist/identity/material.js +42 -0
  96. package/dist/identity/secret.d.ts +3 -0
  97. package/dist/identity/secret.js +7 -0
  98. package/dist/identity/source.d.ts +26 -0
  99. package/dist/identity/source.js +98 -0
  100. package/dist/index.d.ts +3 -4
  101. package/dist/index.js +3 -4
  102. package/dist/params.d.ts +18 -5
  103. package/dist/params.js +0 -1
  104. package/dist/release/index.d.ts +2 -0
  105. package/dist/release/index.js +2 -0
  106. package/dist/release/namespace.d.ts +2 -0
  107. package/dist/release/namespace.js +6 -0
  108. package/dist/release/target.d.ts +2 -0
  109. package/dist/release/target.js +9 -0
  110. package/dist/worker/entry.d.ts +38 -0
  111. package/dist/worker/entry.js +161 -0
  112. package/dist/worker/fetch.d.ts +11 -0
  113. package/dist/worker/fetch.js +31 -0
  114. package/dist/worker/gateway.d.ts +15 -0
  115. package/dist/worker/gateway.js +26 -0
  116. package/dist/worker/identity.d.ts +7 -0
  117. package/dist/worker/identity.js +23 -0
  118. package/dist/worker/index.d.ts +3 -59
  119. package/dist/worker/index.js +3 -209
  120. package/package.json +21 -18
  121. package/template/astrale.config.ts +19 -11
  122. package/dist/build/codegen/identity.d.ts +0 -10
  123. package/dist/build/codegen/identity.d.ts.map +0 -1
  124. package/dist/build/codegen/identity.js +0 -32
  125. package/dist/build/codegen/identity.js.map +0 -1
  126. package/dist/build/codegen/merge.d.ts.map +0 -1
  127. package/dist/build/codegen/merge.js.map +0 -1
  128. package/dist/build/codegen/worker.d.ts +0 -12
  129. package/dist/build/codegen/worker.d.ts.map +0 -1
  130. package/dist/build/codegen/worker.js +0 -40
  131. package/dist/build/codegen/worker.js.map +0 -1
  132. package/dist/build/codegen/wrangler.d.ts.map +0 -1
  133. package/dist/build/codegen/wrangler.js.map +0 -1
  134. package/dist/build/frontend.d.ts +0 -26
  135. package/dist/build/frontend.d.ts.map +0 -1
  136. package/dist/build/frontend.js +0 -263
  137. package/dist/build/frontend.js.map +0 -1
  138. package/dist/build/index.d.ts.map +0 -1
  139. package/dist/build/index.js.map +0 -1
  140. package/dist/build/parse-output.d.ts.map +0 -1
  141. package/dist/build/parse-output.js.map +0 -1
  142. package/dist/build/prepare.d.ts +0 -18
  143. package/dist/build/prepare.d.ts.map +0 -1
  144. package/dist/build/prepare.js +0 -117
  145. package/dist/build/prepare.js.map +0 -1
  146. package/dist/build/worker.d.ts +0 -15
  147. package/dist/build/worker.d.ts.map +0 -1
  148. package/dist/build/worker.js +0 -21
  149. package/dist/build/worker.js.map +0 -1
  150. package/dist/build/wrangler-cli.d.ts.map +0 -1
  151. package/dist/build/wrangler-cli.js.map +0 -1
  152. package/dist/cloudflare.d.ts.map +0 -1
  153. package/dist/cloudflare.js.map +0 -1
  154. package/dist/index.d.ts.map +0 -1
  155. package/dist/index.js.map +0 -1
  156. package/dist/params.d.ts.map +0 -1
  157. package/dist/params.js.map +0 -1
  158. package/dist/worker/index.d.ts.map +0 -1
  159. package/dist/worker/index.js.map +0 -1
  160. package/src/build/codegen/identity.ts +0 -40
  161. package/src/build/codegen/merge.ts +0 -92
  162. package/src/build/codegen/worker.ts +0 -53
  163. package/src/build/codegen/wrangler.ts +0 -127
  164. package/src/build/frontend.ts +0 -324
  165. package/src/build/index.ts +0 -29
  166. package/src/build/parse-output.ts +0 -60
  167. package/src/build/prepare.ts +0 -161
  168. package/src/build/worker.ts +0 -37
  169. package/src/build/wrangler-cli.ts +0 -362
  170. package/src/cloudflare.ts +0 -251
  171. package/src/index.ts +0 -21
  172. package/src/params.ts +0 -92
  173. package/src/worker/index.ts +0 -312
  174. package/src/worker/tsconfig.json +0 -10
@@ -0,0 +1,234 @@
1
+ import { addressing, assemble } from '@astrale-os/sdk/deployment/release';
2
+ import { mkdir, mkdtemp, rm } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { workerName } from '../build/configuration.js';
5
+ import { admitDirectPlacement } from '../build/direct.js';
6
+ import { loadWorkerArtifact } from '../build/worker/index.js';
7
+ import { WranglerDeployError, WranglerDispatchNamespaceError } from '../build/wrangler-cli.js';
8
+ import { signingIdentitySecrets } from '../identity/secret.js';
9
+ import { releaseNamespace } from '../release/index.js';
10
+ const NONE = Object.freeze({ kind: 'none' });
11
+ /** Establish private placement and place one revision before advancing the public gateway. */
12
+ export async function deployCloudflare(parameters, context, dependencies) {
13
+ let secrets;
14
+ try {
15
+ const identity = await dependencies.signingIdentity({
16
+ projectDir: context.projectDir,
17
+ source: parameters.signingIdentity,
18
+ origin: context.artifact.build.schema.compiled.root.origin,
19
+ mode: 'deploy',
20
+ });
21
+ secrets = signingIdentitySecrets(context.secrets, identity);
22
+ }
23
+ catch (cause) {
24
+ return failed('prerequisite', progress({}), cause);
25
+ }
26
+ let worker;
27
+ try {
28
+ worker = loadWorkerArtifact(context.artifact);
29
+ for (const name of worker.bindings.requiredSecrets) {
30
+ if (!Object.hasOwn(secrets, name)) {
31
+ throw new TypeError(`Cloudflare Worker requires secret ${name}.`);
32
+ }
33
+ }
34
+ admitDirectPlacement(context.artifact, worker, parameters);
35
+ }
36
+ catch (cause) {
37
+ return failed('prerequisite', progress({}), cause);
38
+ }
39
+ const stateDirectory = join(context.projectDir, '.astrale');
40
+ await mkdir(stateDirectory, { recursive: true });
41
+ const directory = await mkdtemp(join(stateDirectory, 'deploy-'));
42
+ try {
43
+ await dependencies.materialize(directory, context.artifact.files);
44
+ const name = workerName(parameters, context.artifact.build.schema.compiled.root.origin);
45
+ const releaseConfigPath = join(directory, 'wrangler.release.jsonc');
46
+ const gatewayConfigPath = join(directory, 'wrangler.gateway.jsonc');
47
+ const namespace = releaseNamespace(name);
48
+ try {
49
+ await dependencies.ensureWranglerDispatchNamespace({
50
+ projectDir: context.projectDir,
51
+ namespace,
52
+ signal: context.signal,
53
+ });
54
+ }
55
+ catch (cause) {
56
+ const provider = cause instanceof WranglerDispatchNamespaceError && cause.effect === 'none'
57
+ ? NONE
58
+ : Object.freeze({ kind: 'unknown' });
59
+ return failed('provider', progress({ provider }), cause);
60
+ }
61
+ let revision;
62
+ try {
63
+ revision = await dependencies.runWranglerReleaseDeploy({
64
+ projectDir: context.projectDir,
65
+ configPath: releaseConfigPath,
66
+ namespace,
67
+ secrets,
68
+ signal: context.signal,
69
+ });
70
+ }
71
+ catch (cause) {
72
+ const provider = partialProvider(cause, context.artifact);
73
+ const configuration = Object.freeze({ kind: 'unknown' });
74
+ return failed('provider', progress({ provider, configuration }), cause);
75
+ }
76
+ const configuration = Object.freeze({
77
+ kind: 'applied',
78
+ evidence: Object.freeze({ names: Object.keys(secrets).sort() }),
79
+ });
80
+ const retained = Object.freeze({
81
+ kind: 'partial',
82
+ evidence: Object.freeze({
83
+ provider: 'cloudflare',
84
+ deployment: revision.deploymentVersion,
85
+ artifact: context.artifact.digest,
86
+ }),
87
+ });
88
+ let gateway;
89
+ try {
90
+ gateway = await dependencies.runWranglerDeploy({
91
+ projectDir: context.projectDir,
92
+ configPath: gatewayConfigPath,
93
+ ...(parameters.route === undefined ? {} : { route: parameters.route }),
94
+ signal: context.signal,
95
+ });
96
+ }
97
+ catch (cause) {
98
+ return failed('provider', progress({ provider: retained, configuration }), cause);
99
+ }
100
+ let selected;
101
+ try {
102
+ selected = observeAddressing(context.artifact, gateway.url, name);
103
+ }
104
+ catch (cause) {
105
+ return failed('addressing', progress({
106
+ provider: retained,
107
+ configuration,
108
+ }), cause);
109
+ }
110
+ let observed;
111
+ try {
112
+ observed = await dependencies.runWranglerDeploymentStatus({
113
+ projectDir: context.projectDir,
114
+ configPath: gatewayConfigPath,
115
+ workerName: name,
116
+ signal: context.signal,
117
+ });
118
+ requireCurrentDeployment(observed, gateway.deploymentVersion);
119
+ }
120
+ catch (cause) {
121
+ return failed('readiness', progress({
122
+ provider: retained,
123
+ configuration,
124
+ }), cause);
125
+ }
126
+ let release;
127
+ try {
128
+ release = assemble(context.artifact.build, selected);
129
+ }
130
+ catch (cause) {
131
+ return failed('release', progress({ provider: retained, configuration }), cause);
132
+ }
133
+ try {
134
+ await dependencies.waitForReadiness(release, context.signal);
135
+ }
136
+ catch (cause) {
137
+ return failed('readiness', progress({ provider: retained, configuration }), cause, release);
138
+ }
139
+ const provider = Object.freeze({
140
+ kind: 'applied',
141
+ evidence: Object.freeze({
142
+ provider: 'cloudflare',
143
+ deployment: revision.deploymentVersion,
144
+ artifact: context.artifact.digest,
145
+ issuer: selected.issuer,
146
+ }),
147
+ });
148
+ return Object.freeze({
149
+ status: 'ready',
150
+ phase: 'complete',
151
+ progress: Object.freeze({ ...progress({ configuration }), provider }),
152
+ release,
153
+ });
154
+ }
155
+ finally {
156
+ await rm(directory, { recursive: true, force: true });
157
+ }
158
+ }
159
+ function partialProvider(cause, artifact) {
160
+ if (!(cause instanceof WranglerDeployError) || cause.applied?.deploymentVersion === undefined) {
161
+ return Object.freeze({ kind: 'unknown' });
162
+ }
163
+ let issuer;
164
+ if (cause.applied.url !== undefined) {
165
+ try {
166
+ issuer = addressing(cause.applied.url).issuer;
167
+ }
168
+ catch {
169
+ // The provider effect remains known when its printed URL is not Addressing.
170
+ }
171
+ }
172
+ return Object.freeze({
173
+ kind: 'partial',
174
+ evidence: Object.freeze({
175
+ provider: 'cloudflare',
176
+ deployment: cause.applied.deploymentVersion,
177
+ artifact: artifact.digest,
178
+ ...(issuer === undefined ? {} : { issuer }),
179
+ }),
180
+ });
181
+ }
182
+ function requireCurrentDeployment(observed, expectedVersion) {
183
+ if (observed.versions.length !== 1 ||
184
+ observed.versions[0].percentage !== 100 ||
185
+ observed.versions[0].version !== expectedVersion) {
186
+ throw new TypeError('Cloudflare gateway is not one exact Worker version at 100%.');
187
+ }
188
+ }
189
+ function observeAddressing(artifact, deployedUrl, expectedWorker) {
190
+ if (artifact.addressing.kind === 'configured') {
191
+ if (deployedUrl === undefined)
192
+ return artifact.addressing.addressing;
193
+ const observed = addressing(deployedUrl);
194
+ if (observed.issuer !== artifact.addressing.addressing.issuer) {
195
+ throw new TypeError('Cloudflare deployed URL differs from configured Addressing.');
196
+ }
197
+ return observed;
198
+ }
199
+ if (deployedUrl === undefined) {
200
+ throw new TypeError('Cloudflare did not report provider-assigned Addressing.');
201
+ }
202
+ const observed = addressing(deployedUrl);
203
+ if (artifact.addressing.claim !== expectedWorker) {
204
+ throw new TypeError('PreparedArtifact worker claim differs from the gateway target.');
205
+ }
206
+ const url = new URL(observed.issuer);
207
+ const labels = url.hostname.split('.');
208
+ if (url.protocol !== 'https:' ||
209
+ labels.length < 4 ||
210
+ labels[0] !== artifact.addressing.claim ||
211
+ labels.at(-2) !== 'workers' ||
212
+ labels.at(-1) !== 'dev') {
213
+ throw new TypeError('Cloudflare workers.dev URL does not satisfy its prepared worker claim.');
214
+ }
215
+ return observed;
216
+ }
217
+ function progress(input) {
218
+ return Object.freeze({
219
+ prerequisite: input.prerequisite ?? NONE,
220
+ provider: input.provider ?? NONE,
221
+ configuration: input.configuration ?? NONE,
222
+ installation: input.installation ?? NONE,
223
+ });
224
+ }
225
+ function failed(phase, progressValue, cause, release) {
226
+ const error = cause instanceof Error ? cause : new Error(String(cause));
227
+ return Object.freeze({
228
+ status: 'failed',
229
+ phase,
230
+ progress: progressValue,
231
+ error,
232
+ ...(release === undefined ? {} : { release }),
233
+ });
234
+ }
@@ -0,0 +1 @@
1
+ export { deployCloudflare, type CloudflareDeployDependencies } from './deploy.js';
@@ -0,0 +1 @@
1
+ export { deployCloudflare } from './deploy.js';
@@ -0,0 +1,2 @@
1
+ /** Adapter-owned Worker secret that carries the execution identity private JWK. */
2
+ export declare const SIGNING_IDENTITY_BINDING = "ASTRALE_SIGNING_IDENTITY";
@@ -0,0 +1,2 @@
1
+ /** Adapter-owned Worker secret that carries the execution identity private JWK. */
2
+ export const SIGNING_IDENTITY_BINDING = 'ASTRALE_SIGNING_IDENTITY';
@@ -0,0 +1,7 @@
1
+ import type { JsonWebKey } from '@astrale-os/sdk/auth';
2
+ export type SigningIdentityMaterial = JsonWebKey & {
3
+ readonly kid: string;
4
+ };
5
+ export declare function createSigningIdentity(): Promise<SigningIdentityMaterial>;
6
+ /** Admit private Ed25519 material and its collision-resistant public identity. */
7
+ export declare function admitSigningIdentity(input: unknown, owner: string, legacyKeyId?: string): Promise<SigningIdentityMaterial>;
@@ -0,0 +1,42 @@
1
+ import { calculateJwkThumbprint, exportJWK, generateKeyPair, importJWK } from 'jose';
2
+ export async function createSigningIdentity() {
3
+ const { privateKey } = await generateKeyPair('EdDSA', { extractable: true });
4
+ const exported = await exportJWK(privateKey);
5
+ return admitSigningIdentity({
6
+ ...exported,
7
+ alg: 'EdDSA',
8
+ kid: await calculateJwkThumbprint(exported),
9
+ }, 'generated identity');
10
+ }
11
+ /** Admit private Ed25519 material and its collision-resistant public identity. */
12
+ export async function admitSigningIdentity(input, owner, legacyKeyId) {
13
+ if (input === null || typeof input !== 'object' || Array.isArray(input))
14
+ invalid(owner);
15
+ const value = input;
16
+ if (value.kty !== 'OKP' ||
17
+ value.crv !== 'Ed25519' ||
18
+ value.alg !== 'EdDSA' ||
19
+ typeof value.x !== 'string' ||
20
+ value.x.length === 0 ||
21
+ typeof value.d !== 'string' ||
22
+ value.d.length === 0) {
23
+ invalid(owner);
24
+ }
25
+ const thumbprint = await calculateJwkThumbprint(value);
26
+ const admitted = legacyKeyId !== undefined && (value.kid === undefined || value.kid === legacyKeyId)
27
+ ? { ...value, kid: thumbprint }
28
+ : value;
29
+ if (admitted.kid !== thumbprint) {
30
+ throw new TypeError(`Cloudflare signing identity '${owner}' must use its JWK thumbprint as kid.`);
31
+ }
32
+ try {
33
+ await importJWK(admitted, 'EdDSA');
34
+ }
35
+ catch (cause) {
36
+ throw invalid(owner, cause);
37
+ }
38
+ return admitted;
39
+ }
40
+ function invalid(owner, cause) {
41
+ throw new TypeError(`Cloudflare signing identity '${owner}' is not a private Ed25519 JWK.`, cause === undefined ? undefined : { cause });
42
+ }
@@ -0,0 +1,3 @@
1
+ import type { SigningIdentitySource } from './source.js';
2
+ /** Add the adapter-owned identity without allowing a second secret authority. */
3
+ export declare function signingIdentitySecrets(secrets: Readonly<Record<string, string>>, identity: SigningIdentitySource): Readonly<Record<string, string>>;
@@ -0,0 +1,7 @@
1
+ /** Add the adapter-owned identity without allowing a second secret authority. */
2
+ export function signingIdentitySecrets(secrets, identity) {
3
+ if (Object.hasOwn(secrets, identity.binding)) {
4
+ throw new TypeError(`${identity.binding} is adapter-owned; configure 'signingIdentity' instead of declaring that secret.`);
5
+ }
6
+ return Object.freeze({ ...secrets, [identity.binding]: identity.secret });
7
+ }
@@ -0,0 +1,26 @@
1
+ import { SIGNING_IDENTITY_BINDING } from './binding.js';
2
+ export interface SigningIdentitySource {
3
+ readonly binding: typeof SIGNING_IDENTITY_BINDING;
4
+ readonly path: string;
5
+ readonly secret: string;
6
+ readonly keyId: string;
7
+ }
8
+ export interface SigningIdentityInput {
9
+ readonly projectDir: string;
10
+ readonly origin: string;
11
+ readonly source?: string;
12
+ readonly mode: 'local' | 'deploy';
13
+ }
14
+ /** Resolve one admitted execution identity for local or remote provider custody. */
15
+ export declare function signingIdentity(input: SigningIdentityInput): Promise<SigningIdentitySource>;
16
+ /**
17
+ * Read an explicitly retained deployment identity. Deployment never generates
18
+ * one: a missing file is a pre-provider configuration failure, not an implicit
19
+ * key rotation.
20
+ */
21
+ export declare function readSigningIdentity(projectDir: string, declaration: string | undefined): Promise<SigningIdentitySource>;
22
+ /**
23
+ * Resolve local development identity. The default is generated once inside
24
+ * `.astrale`; an explicit source is always read-only and must already exist.
25
+ */
26
+ export declare function localSigningIdentity(projectDir: string, declaration: string | undefined, origin: string): Promise<SigningIdentitySource>;
@@ -0,0 +1,98 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
4
+ import { SIGNING_IDENTITY_BINDING } from './binding.js';
5
+ import { admitSigningIdentity, createSigningIdentity } from './material.js';
6
+ const DEFAULT_IDENTITY = join('.astrale', 'identity.json');
7
+ /** Resolve one admitted execution identity for local or remote provider custody. */
8
+ export function signingIdentity(input) {
9
+ return input.mode === 'local'
10
+ ? localSigningIdentity(input.projectDir, input.source, input.origin)
11
+ : readSigningIdentity(input.projectDir, input.source);
12
+ }
13
+ /**
14
+ * Read an explicitly retained deployment identity. Deployment never generates
15
+ * one: a missing file is a pre-provider configuration failure, not an implicit
16
+ * key rotation.
17
+ */
18
+ export async function readSigningIdentity(projectDir, declaration) {
19
+ if (declaration === undefined || declaration.length === 0) {
20
+ throw new TypeError("Cloudflare deployment requires 'signingIdentity: <private-jwk-file>'. " +
21
+ 'Run local development once to create .astrale/identity.json, then retain that file in managed secret storage.');
22
+ }
23
+ return readSource(pathOf(projectDir, declaration), declaration);
24
+ }
25
+ /**
26
+ * Resolve local development identity. The default is generated once inside
27
+ * `.astrale`; an explicit source is always read-only and must already exist.
28
+ */
29
+ export async function localSigningIdentity(projectDir, declaration, origin) {
30
+ if (declaration !== undefined)
31
+ return readSource(pathOf(projectDir, declaration), declaration);
32
+ const path = join(projectDir, DEFAULT_IDENTITY);
33
+ if (!existsSync(path)) {
34
+ const legacy = join(projectDir, '.astrale', 'identity.ts');
35
+ if (existsSync(legacy))
36
+ await migrateLegacyIdentity(legacy, path, origin);
37
+ else
38
+ await generateIdentity(path);
39
+ }
40
+ return readSource(path, DEFAULT_IDENTITY);
41
+ }
42
+ async function generateIdentity(path) {
43
+ const value = await createSigningIdentity();
44
+ await mkdir(dirname(path), { recursive: true });
45
+ await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
46
+ process.stderr.write(`\x1b[33m!\x1b[0m new signing identity generated at .astrale/identity.json (kid ${value.kid}).\n` +
47
+ ` Retain it in managed secret storage before deploying; remote deploy fails closed without an explicit identity file.\n`);
48
+ }
49
+ async function migrateLegacyIdentity(legacy, destination, origin) {
50
+ const source = await readFile(legacy, 'utf8');
51
+ const match = /export const PRIVATE_JWK = (\{[\s\S]*\}) as const\n?$/u.exec(source);
52
+ if (match?.[1] === undefined)
53
+ throw invalidIdentity(legacy);
54
+ let decoded;
55
+ try {
56
+ decoded = JSON.parse(match[1]);
57
+ }
58
+ catch (cause) {
59
+ throw invalidIdentity(legacy, cause);
60
+ }
61
+ const value = await admitSigningIdentity(decoded, legacy, `${origin}-key`);
62
+ await mkdir(dirname(destination), { recursive: true });
63
+ await writeFile(destination, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
64
+ process.stderr.write(`\x1b[33m!\x1b[0m migrated .astrale/identity.ts to secret-backed .astrale/identity.json (kid ${value.kid}).\n`);
65
+ }
66
+ async function readSource(path, declaration) {
67
+ let source;
68
+ try {
69
+ source = await readFile(path, 'utf8');
70
+ }
71
+ catch (cause) {
72
+ if (!(cause instanceof Error && 'code' in cause && cause.code === 'ENOENT'))
73
+ throw cause;
74
+ throw new TypeError(`Cloudflare signing identity '${declaration}' is missing at ${path}.`, {
75
+ cause,
76
+ });
77
+ }
78
+ let decoded;
79
+ try {
80
+ decoded = JSON.parse(source);
81
+ }
82
+ catch (cause) {
83
+ throw invalidIdentity(declaration, cause);
84
+ }
85
+ const value = await admitSigningIdentity(decoded, declaration);
86
+ return Object.freeze({
87
+ binding: SIGNING_IDENTITY_BINDING,
88
+ path,
89
+ secret: JSON.stringify(value),
90
+ keyId: value.kid,
91
+ });
92
+ }
93
+ function pathOf(projectDir, declaration) {
94
+ return isAbsolute(declaration) ? declaration : resolve(projectDir, declaration);
95
+ }
96
+ function invalidIdentity(owner, cause) {
97
+ return new TypeError(`Cloudflare signing identity '${owner}' is not a private Ed25519 JWK.`, cause === undefined ? undefined : { cause });
98
+ }
package/dist/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  /**
2
- * `@astrale-os/adapter-cloudflare` — deploy an Astrale domain as a standalone
3
- * Cloudflare Worker on YOUR OWN Cloudflare account.
2
+ * `@astrale-os/adapter-cloudflare` — deploy an Astrale application through
3
+ * Cloudflare Workers on YOUR OWN Cloudflare account.
4
4
  *
5
5
  * import { cloudflare } from '@astrale-os/adapter-cloudflare'
6
6
  *
7
7
  * adapter: cloudflare({
8
8
  * dev: { secrets: '.env.dev' },
9
- * prod: { route: 'crm.acme.dev', secrets: '.env.prod' },
9
+ * prod: { route: 'crm.acme.dev', secrets: '.env.prod', signingIdentity: '.astrale/identity.json' },
10
10
  * })
11
11
  *
12
12
  * `dev` runs `wrangler dev` locally; an env with no `route` ships to
@@ -18,4 +18,3 @@
18
18
  */
19
19
  export { cloudflare } from './cloudflare.js';
20
20
  export type { CloudflareParams } from './params.js';
21
- //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  /**
2
- * `@astrale-os/adapter-cloudflare` — deploy an Astrale domain as a standalone
3
- * Cloudflare Worker on YOUR OWN Cloudflare account.
2
+ * `@astrale-os/adapter-cloudflare` — deploy an Astrale application through
3
+ * Cloudflare Workers on YOUR OWN Cloudflare account.
4
4
  *
5
5
  * import { cloudflare } from '@astrale-os/adapter-cloudflare'
6
6
  *
7
7
  * adapter: cloudflare({
8
8
  * dev: { secrets: '.env.dev' },
9
- * prod: { route: 'crm.acme.dev', secrets: '.env.prod' },
9
+ * prod: { route: 'crm.acme.dev', secrets: '.env.prod', signingIdentity: '.astrale/identity.json' },
10
10
  * })
11
11
  *
12
12
  * `dev` runs `wrangler dev` locally; an env with no `route` ships to
@@ -17,4 +17,3 @@
17
17
  * `/build` toolkit and ships it through the Astrale platform.
18
18
  */
19
19
  export { cloudflare } from './cloudflare.js';
20
- //# sourceMappingURL=index.js.map
package/dist/params.d.ts CHANGED
@@ -17,8 +17,22 @@ export interface CloudflareParams {
17
17
  * localhost) but still used as the placeholder base domain there.
18
18
  */
19
19
  readonly route?: string;
20
+ /**
21
+ * Exact public Addressing expected after deployment. Use this for an already
22
+ * known provider-assigned address such as `*.workers.dev`; `route` remains
23
+ * the field that asks Cloudflare to attach a custom hostname. When both are
24
+ * present they must identify the same HTTPS origin.
25
+ */
26
+ readonly addressing?: string;
20
27
  /** Gitignored secrets file (whole contents = secrets), e.g. `'.env.dev'`. */
21
28
  readonly secrets?: string;
29
+ /**
30
+ * Private Ed25519 JWK file retained outside source control. Remote deploys
31
+ * require this explicit source so a fresh runner cannot silently rotate an
32
+ * installed Domain identity. Local watch generates `.astrale/identity.json`
33
+ * once when this field is omitted.
34
+ */
35
+ readonly signingIdentity?: string;
22
36
  /** Local dev port for `wrangler dev`. Default 8787. */
23
37
  readonly port?: number;
24
38
  /**
@@ -60,10 +74,10 @@ export interface CloudflareParams {
60
74
  * instead of the public edge. A Cloudflare Worker can't fetch a same-zone
61
75
  * hostname served by another Worker — Cloudflare 522s it — so without this the
62
76
  * worker can't fetch an instance kernel's JWKS to verify an inbound credential
63
- * from that instance, and every cross-instance call (install hooks, the GUI's
64
- * delegated calls, authed views) fails with `2002 Credential verification
65
- * failed`. Point it at a router Worker that reaches those hosts internally
66
- * (Astrale: `admin-router`).
77
+ * from that instance, and every cross-instance call (install hooks, delegated
78
+ * GUI calls, and traffic initiated from a Shell-handshaken View) fails with
79
+ * `2002 Credential verification failed`. Point it at a router Worker that
80
+ * reaches those hosts internally (Astrale: `admin-router`).
67
81
  *
68
82
  * Wires BOTH halves the SDK needs: the `<binding>` service binding in the
69
83
  * generated wrangler config, and a `routeSubrequest` predicate in the worker
@@ -88,4 +102,3 @@ export interface CloudflareParams {
88
102
  readonly minLabels?: number;
89
103
  };
90
104
  }
91
- //# sourceMappingURL=params.d.ts.map
package/dist/params.js CHANGED
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=params.js.map
@@ -0,0 +1,2 @@
1
+ export { releaseNamespace } from './namespace.js';
2
+ export { releaseWorkerName } from './target.js';
@@ -0,0 +1,2 @@
1
+ export { releaseNamespace } from './namespace.js';
2
+ export { releaseWorkerName } from './target.js';
@@ -0,0 +1,2 @@
1
+ /** One private dispatch namespace per stable public Worker. */
2
+ export declare function releaseNamespace(worker: string): string;
@@ -0,0 +1,6 @@
1
+ import { createHash } from 'node:crypto';
2
+ /** One private dispatch namespace per stable public Worker. */
3
+ export function releaseNamespace(worker) {
4
+ const digest = createHash('sha256').update(worker).digest('hex');
5
+ return `astrale-${digest.slice(0, 24)}`;
6
+ }
@@ -0,0 +1,2 @@
1
+ /** Exact Schema revisions select stable Workers inside the private namespace. */
2
+ export declare function releaseWorkerName(revision: string): string;
@@ -0,0 +1,9 @@
1
+ const REVISION = /^sha256:([0-9a-f]{64})$/u;
2
+ /** Exact Schema revisions select stable Workers inside the private namespace. */
3
+ export function releaseWorkerName(revision) {
4
+ const digest = REVISION.exec(revision)?.[1];
5
+ if (digest === undefined)
6
+ throw new TypeError('Cloudflare Release revision is invalid.');
7
+ const encoded = BigInt(`0x${digest}`).toString(36).padStart(50, '0');
8
+ return `r-${encoded}`;
9
+ }
@@ -0,0 +1,38 @@
1
+ import type { JsonWebKey } from '@astrale-os/sdk/auth';
2
+ import type { BuildMaterial } from '@astrale-os/sdk/deployment/build/material';
3
+ import type { ExecutionContext } from '@astrale-os/sdk/execution';
4
+ import type { Runtime } from '@astrale-os/sdk/runtime';
5
+ export interface Fetcher {
6
+ fetch(request: Request): Response | Promise<Response>;
7
+ }
8
+ export interface CloudflareAdapterBindings {
9
+ readonly ASTRALE_SIGNING_IDENTITY?: string;
10
+ readonly WORKER_URL?: string;
11
+ readonly IDENTITY_ISS?: string;
12
+ readonly ASSETS?: Fetcher;
13
+ readonly SELF?: Fetcher;
14
+ readonly FRONTEND_DEV_URL?: string;
15
+ readonly DISPATCHER?: {
16
+ get(name: string): Fetcher;
17
+ };
18
+ }
19
+ export interface CloudflareWorkerConfig<RuntimeValue extends Runtime, Bindings extends CloudflareAdapterBindings, Environment> {
20
+ /** Load the exact Runtime and Build once, after the isolate has accepted a request. */
21
+ readonly load: () => CloudflareWorkerSource<RuntimeValue> | Promise<CloudflareWorkerSource<RuntimeValue>>;
22
+ readonly privateKey: JsonWebKey | ((bindings: Bindings) => JsonWebKey);
23
+ readonly resolveEnvironment?: (bindings: Bindings) => Environment;
24
+ readonly resolveUrl?: (bindings: Bindings, requestOrigin: string) => string;
25
+ readonly providerAssignedWorker?: string;
26
+ readonly selfBinding?: (bindings: Bindings) => Fetcher | null | undefined;
27
+ readonly routeSubrequest?: (url: URL, bindings: Bindings) => Fetcher | null | undefined;
28
+ readonly invocationTimeoutMs?: number;
29
+ }
30
+ export interface CloudflareWorkerSource<RuntimeValue extends Runtime> {
31
+ readonly runtime: RuntimeValue;
32
+ readonly material: BuildMaterial;
33
+ }
34
+ export interface CloudflareWorker<Bindings> {
35
+ fetch(request: Request, environment: Bindings, executionContext?: ExecutionContext): Response | Promise<Response>;
36
+ }
37
+ /** Compose one exact revision Service; the provider gateway owns revision selection. */
38
+ export declare function cloudflareWorkerEntry<Bindings extends CloudflareAdapterBindings, Environment = Bindings>(): <RuntimeValue extends Runtime>(config: CloudflareWorkerConfig<RuntimeValue, Bindings, Environment>) => CloudflareWorker<Bindings>;