-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathruntime-neutral-contracts.ts
More file actions
227 lines (196 loc) · 9.53 KB
/
Copy pathruntime-neutral-contracts.ts
File metadata and controls
227 lines (196 loc) · 9.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
export type BackendNeutralRuntimeBackendKind = string & {}
export type RuntimeWordPressComponentIntentKind = "wordpress-core" | "plugin" | "mu-plugin" | "theme" | "runtime-overlay" | (string & {})
export type RuntimeWordPressFilesystemIntentType = "directory" | "file" | (string & {})
export type RuntimeWordPressFilesystemIntentMode = "readonly" | "readwrite" | "generated" | (string & {})
export interface RuntimeWordPressComponentIntent {
kind: RuntimeWordPressComponentIntentKind
slug?: string
source?: string
target?: string
activate?: boolean
capabilities?: string[]
metadata?: Record<string, unknown>
}
export interface RuntimeWordPressFilesystemIntent {
type?: RuntimeWordPressFilesystemIntentType
source?: string
target: string
mode?: RuntimeWordPressFilesystemIntentMode
purpose?: string
metadata?: Record<string, unknown>
}
export interface RuntimeWordPressSetupPlanIntent {
schema: "wp-codebox/wordpress-runtime-setup-plan/v1"
backend?: BackendNeutralRuntimeBackendKind
components?: RuntimeWordPressComponentIntent[]
filesystem?: RuntimeWordPressFilesystemIntent[]
metadata?: Record<string, unknown>
}
export interface BackendNeutralRuntimeAssetSpec {
directory?: string
archive?: string
metadata?: Record<string, unknown>
}
export interface BackendNeutralEnvironmentSpec {
kind: string
name?: string
version?: string
assets?: BackendNeutralRuntimeAssetSpec
metadata?: Record<string, unknown>
}
export type RuntimeWordPressInstallModeContract = "install-from-existing-files" | "install-from-existing-files-if-needed" | "do-not-attempt-installing"
export type RuntimeWordPressDatabaseSetupContract = "runtime-managed" | "external"
export type RuntimeWorkerCount = number | "auto"
export interface RuntimeWordPressAssetSpec extends BackendNeutralRuntimeAssetSpec {
wordpressDirectory?: string
wordpressZip?: string
}
/** A startup-time PHP.wasm extension manifest. External extensions are JSPI-only. */
export interface RuntimePHPWasmExtensionManifest {
manifest: string
}
export interface RuntimeWordPressEnvironmentSpec extends BackendNeutralEnvironmentSpec {
blueprint?: unknown
phpVersion?: string
workers?: RuntimeWorkerCount
assets?: RuntimeWordPressAssetSpec
wordpressInstallMode?: RuntimeWordPressInstallModeContract
databaseSetup?: RuntimeWordPressDatabaseSetupContract
extensions?: RuntimePHPWasmExtensionManifest[]
}
export type BackendNeutralReplayStatus = "metadata-only" | "partial-replay" | "replayable-runtime-state" | "runtime-state-artifact" | "not-replayable" | (string & {})
export interface BackendNeutralReplaySpec {
status: BackendNeutralReplayStatus
environment?: BackendNeutralEnvironmentSpec
artifactRefs?: BackendNeutralArtifactRef[]
metadata?: Record<string, unknown>
}
export interface BackendNeutralArtifactRef {
path: string
kind: string
contentType?: string
sha256?: string
metadata?: Record<string, unknown>
}
export interface BackendNeutralRuntimeProvenance {
backend: BackendNeutralRuntimeBackendKind
version?: string
backendPackage?: Record<string, unknown>
environment?: BackendNeutralEnvironmentSpec
metadata?: Record<string, unknown>
}
export interface RuntimeWordPressProvenance extends BackendNeutralRuntimeProvenance {
wordpressVersion?: string
}
export function normalizeBackendNeutralEnvironmentSpec(input: unknown): BackendNeutralEnvironmentSpec {
const value = requireObject(input, "Runtime environment") as Partial<RuntimeWordPressEnvironmentSpec>
const assets = normalizeBackendNeutralAssetSpec(value.assets)
return stripUndefined({
kind: requiredString(value.kind, "environment.kind"),
name: optionalString(value.name, "environment.name"),
version: optionalString(value.version, "environment.version"),
assets,
metadata: normalizeOptionalObject(value.metadata, "environment.metadata"),
})
}
export function normalizeRuntimeWordPressEnvironmentSpec(input: unknown): RuntimeWordPressEnvironmentSpec {
const value = requireObject(input, "Runtime environment") as Partial<RuntimeWordPressEnvironmentSpec>
const neutral = normalizeBackendNeutralEnvironmentSpec(value)
return stripUndefined({
...neutral,
blueprint: value.blueprint,
phpVersion: optionalString(value.phpVersion, "environment.phpVersion"),
workers: normalizeRuntimeWorkerCount(value.workers),
assets: normalizeRuntimeWordPressAssetSpec(value.assets),
wordpressInstallMode: optionalString(value.wordpressInstallMode, "environment.wordpressInstallMode") as RuntimeWordPressInstallModeContract | undefined,
databaseSetup: optionalString(value.databaseSetup, "environment.databaseSetup") as RuntimeWordPressDatabaseSetupContract | undefined,
extensions: normalizeRuntimePHPWasmExtensionManifests(value.extensions),
})
}
function normalizeRuntimeWorkerCount(value: unknown): RuntimeWorkerCount | undefined {
if (value === undefined) return undefined
if (value === "auto") return value
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1 || value > 64) {
throw new Error("environment.workers must be an integer from 1 to 64 or auto.")
}
return value
}
function normalizeRuntimePHPWasmExtensionManifests(input: unknown): RuntimePHPWasmExtensionManifest[] | undefined {
if (input === undefined) return undefined
if (!Array.isArray(input)) throw new Error("environment.extensions must be an array.")
return input.map((entry, index) => {
const value = requireObject(entry, `environment.extensions[${index}]`) as Partial<RuntimePHPWasmExtensionManifest>
const manifest = requiredString(value.manifest, `environment.extensions[${index}].manifest`)
if (manifest.includes("\0")) throw new Error(`environment.extensions[${index}].manifest must not contain a null byte.`)
return { manifest }
})
}
export function normalizeBackendNeutralReplaySpec(input: unknown): BackendNeutralReplaySpec {
const value = requireObject(input, "Runtime replay") as Partial<BackendNeutralReplaySpec>
return stripUndefined({
status: requiredString(value.status, "replay.status") as BackendNeutralReplayStatus,
environment: value.environment === undefined ? undefined : normalizeBackendNeutralEnvironmentSpec(value.environment),
artifactRefs: normalizeArtifactRefs(value.artifactRefs, "replay.artifactRefs"),
metadata: normalizeOptionalObject(value.metadata, "replay.metadata"),
})
}
export function wordpressEnvironmentToBackendNeutral(input: RuntimeWordPressEnvironmentSpec): BackendNeutralEnvironmentSpec {
return normalizeBackendNeutralEnvironmentSpec(input)
}
export const normalizeEnvironmentSpec = normalizeRuntimeWordPressEnvironmentSpec
function normalizeRuntimeWordPressAssetSpec(input: unknown): RuntimeWordPressAssetSpec | undefined {
const value = normalizeBackendNeutralAssetSpec(input) as RuntimeWordPressAssetSpec | undefined
if (input === undefined) return value
const source = requireObject(input, "environment.assets") as Partial<RuntimeWordPressAssetSpec>
return stripUndefined({
...value,
wordpressDirectory: optionalString(source.wordpressDirectory, "environment.assets.wordpressDirectory"),
wordpressZip: optionalString(source.wordpressZip, "environment.assets.wordpressZip"),
})
}
function normalizeBackendNeutralAssetSpec(input: unknown): BackendNeutralRuntimeAssetSpec | undefined {
if (input === undefined) return undefined
const value = requireObject(input, "environment.assets") as Partial<BackendNeutralRuntimeAssetSpec>
return stripUndefined({
directory: optionalString(value.directory, "environment.assets.directory"),
archive: optionalString(value.archive, "environment.assets.archive"),
metadata: normalizeOptionalObject(value.metadata, "environment.assets.metadata"),
})
}
function normalizeArtifactRefs(input: unknown, label: string): BackendNeutralArtifactRef[] | undefined {
if (input === undefined) return undefined
if (!Array.isArray(input)) throw new Error(`${label} must be an array.`)
return input.map((entry, index) => normalizeArtifactRef(entry, `${label}[${index}]`))
}
function normalizeArtifactRef(input: unknown, label: string): BackendNeutralArtifactRef {
const value = requireObject(input, label) as Partial<BackendNeutralArtifactRef>
return stripUndefined({
path: requiredString(value.path, `${label}.path`),
kind: requiredString(value.kind, `${label}.kind`),
contentType: optionalString(value.contentType, `${label}.contentType`),
sha256: optionalString(value.sha256, `${label}.sha256`),
metadata: normalizeOptionalObject(value.metadata, `${label}.metadata`),
})
}
function normalizeOptionalObject(value: unknown, label: string): Record<string, unknown> | undefined {
if (value === undefined) return undefined
return requireObject(value, label)
}
function requireObject(value: unknown, label: string): Record<string, unknown> {
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object.`)
return value as Record<string, unknown>
}
function requiredString(value: unknown, label: string): string {
const normalized = optionalString(value, label)
if (!normalized) throw new Error(`${label} must be a non-empty string.`)
return normalized
}
function optionalString(value: unknown, label: string): string | undefined {
if (value === undefined) return undefined
if (typeof value !== "string") throw new Error(`${label} must be a string.`)
const normalized = value.trim()
return normalized === "" ? undefined : normalized
}
function stripUndefined<T extends Record<string, unknown>>(value: T): T {
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)) as T
}