@sequenceholdings/managed-functions 0.1.1 → 0.3.0
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/README.md +135 -6
- package/dist/capabilities.d.ts +112 -0
- package/dist/capabilities.js +99 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +14 -1
- package/dist/deployed-policy.d.ts +2 -0
- package/dist/deployed-policy.js +88 -0
- package/dist/limits.d.ts +6 -3
- package/dist/limits.js +10 -3
- package/dist/orm.d.ts +65 -0
- package/dist/orm.js +297 -0
- package/dist/preflight.d.ts +9 -0
- package/dist/preflight.js +172 -0
- package/dist/runtime.cjs +626 -9
- package/dist/sdk-range.d.ts +2 -0
- package/dist/sdk-range.js +146 -0
- package/dist/storage.d.ts +76 -0
- package/package.json +16 -3
package/dist/orm.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/orm.ts
|
|
21
|
+
var orm_exports = {};
|
|
22
|
+
__export(orm_exports, {
|
|
23
|
+
FunctionOrmError: () => FunctionOrmError,
|
|
24
|
+
ORM_JSON_SCALARS_HEADER: () => ORM_JSON_SCALARS_HEADER,
|
|
25
|
+
ORM_LOSSLESS_JSON_SCALARS: () => ORM_LOSSLESS_JSON_SCALARS,
|
|
26
|
+
OrmClientError: () => OrmClientError,
|
|
27
|
+
OrmOperationError: () => OrmOperationError,
|
|
28
|
+
createFunctionOrmExecutor: () => createFunctionOrmExecutor,
|
|
29
|
+
createFunctionServiceOrmExecutor: () => createFunctionServiceOrmExecutor
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(orm_exports);
|
|
32
|
+
|
|
33
|
+
// ../orm/dist/client.js
|
|
34
|
+
var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
|
|
35
|
+
var ORM_DATA_TOKEN_HEADER = "x-sequence-data-token";
|
|
36
|
+
var ORM_SERVICE_DATA_TOKEN_HEADER = "x-sequence-data-token-service";
|
|
37
|
+
var MATERIALIZE_ACTOR = "orm:materialize";
|
|
38
|
+
JSON.stringify({ sub: MATERIALIZE_ACTOR, org: null, roles: [], capabilities: [] });
|
|
39
|
+
var OrmClientError = class extends Error {
|
|
40
|
+
constructor(message, status = null) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.status = status;
|
|
43
|
+
}
|
|
44
|
+
name = "OrmClientError";
|
|
45
|
+
};
|
|
46
|
+
var OrmOperationError = class extends OrmClientError {
|
|
47
|
+
constructor(message, status, code, errors = []) {
|
|
48
|
+
super(message, status);
|
|
49
|
+
this.code = code;
|
|
50
|
+
this.errors = errors;
|
|
51
|
+
}
|
|
52
|
+
name = "OrmOperationError";
|
|
53
|
+
};
|
|
54
|
+
function isRecord(value) {
|
|
55
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
56
|
+
}
|
|
57
|
+
function isOperationError(value) {
|
|
58
|
+
if (!isRecord(value) || typeof value.message !== "string") return false;
|
|
59
|
+
if (value.extensions === void 0) return true;
|
|
60
|
+
return isRecord(value.extensions) && (value.extensions.code === void 0 || typeof value.extensions.code === "string");
|
|
61
|
+
}
|
|
62
|
+
function readOperationResponse({
|
|
63
|
+
ok,
|
|
64
|
+
status,
|
|
65
|
+
body
|
|
66
|
+
}) {
|
|
67
|
+
const payload = isRecord(body) ? body : null;
|
|
68
|
+
const rawErrors = payload?.errors;
|
|
69
|
+
if (rawErrors !== void 0 && (!Array.isArray(rawErrors) || !rawErrors.every(isOperationError))) {
|
|
70
|
+
throw new OrmOperationError("malformed response: invalid errors", status, null);
|
|
71
|
+
}
|
|
72
|
+
const errors = rawErrors ?? [];
|
|
73
|
+
if (!ok || errors.length > 0) {
|
|
74
|
+
const first = errors[0];
|
|
75
|
+
throw new OrmOperationError(first?.message ?? `HTTP ${status}`, status, first?.extensions?.code ?? null, errors);
|
|
76
|
+
}
|
|
77
|
+
if (!payload || payload.data === void 0 || payload.data === null) {
|
|
78
|
+
throw new OrmOperationError("malformed response: missing data", status, null);
|
|
79
|
+
}
|
|
80
|
+
if (!isRecord(payload.data)) {
|
|
81
|
+
throw new OrmOperationError("malformed response: data must be an object", status, null);
|
|
82
|
+
}
|
|
83
|
+
return payload.data;
|
|
84
|
+
}
|
|
85
|
+
function validateIdempotencyKey(key) {
|
|
86
|
+
if (key !== void 0 && !/^[A-Za-z0-9._:-]{1,128}$/.test(key)) {
|
|
87
|
+
throw new OrmClientError("idempotencyKey must be 1\u2013128 letters, digits, dots, underscores, colons or hyphens");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
var operationHash = /^sha256:[a-f0-9]{64}$/;
|
|
91
|
+
function readManifestHashes(manifest) {
|
|
92
|
+
if (!isRecord(manifest) || typeof manifest.schemaHash !== "string" || !manifest.schemaHash || !isRecord(manifest.operations)) {
|
|
93
|
+
throw new OrmClientError("invalid operations manifest \u2014 use operations.manifest.json from seq-studio orm generate");
|
|
94
|
+
}
|
|
95
|
+
const hashes = /* @__PURE__ */ new Map();
|
|
96
|
+
for (const [name, entry] of Object.entries(manifest.operations)) {
|
|
97
|
+
if (!/^[_A-Za-z][_0-9A-Za-z]*$/.test(name) || !isRecord(entry) || typeof entry.hash !== "string" || !operationHash.test(entry.hash) || entry.kind !== "query" && entry.kind !== "mutation") {
|
|
98
|
+
throw new OrmClientError(`invalid operations manifest entry "${name}" \u2014 regenerate the manifest`);
|
|
99
|
+
}
|
|
100
|
+
hashes.set(name, entry.hash);
|
|
101
|
+
}
|
|
102
|
+
return hashes;
|
|
103
|
+
}
|
|
104
|
+
function createOperationExecutorWithTransport({ namespace, manifest, transport }) {
|
|
105
|
+
if (typeof namespace !== "string" || !NAMESPACE_PATTERN.test(namespace)) throw new OrmClientError("invalid ORM namespace \u2014 expected a lowercase identifier of at most 41 characters");
|
|
106
|
+
const hashes = readManifestHashes(manifest);
|
|
107
|
+
const knownHashes = new Set(hashes.values());
|
|
108
|
+
async function execute(op, variables = {}, options = {}) {
|
|
109
|
+
validateIdempotencyKey(options.idempotencyKey);
|
|
110
|
+
if (typeof op !== "string" && op.namespace !== namespace) throw new OrmClientError("operation belongs to a different namespace");
|
|
111
|
+
const selector = typeof op === "string" ? op : op.hash;
|
|
112
|
+
const hash = hashes.get(selector) ?? (knownHashes.has(selector) ? selector : void 0);
|
|
113
|
+
if (!hash) throw new OrmClientError(`unknown operation "${op}" \u2014 not in the manifest (${hashes.size} known). Re-run seq-studio orm generate.`);
|
|
114
|
+
if (!isRecord(variables)) throw new OrmClientError("operation variables must be an object");
|
|
115
|
+
const response = await transport({
|
|
116
|
+
namespace,
|
|
117
|
+
op: hash,
|
|
118
|
+
variables,
|
|
119
|
+
...options.idempotencyKey === void 0 ? {} : { idempotencyKey: options.idempotencyKey }
|
|
120
|
+
});
|
|
121
|
+
if (!response || typeof response.ok !== "boolean" || !Number.isInteger(response.status) || response.status < 100 || response.status > 599 || response.ok !== (response.status >= 200 && response.status < 300)) {
|
|
122
|
+
throw new OrmOperationError("malformed transport response", null, null);
|
|
123
|
+
}
|
|
124
|
+
return readOperationResponse(response);
|
|
125
|
+
}
|
|
126
|
+
return { execute };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/orm.ts
|
|
130
|
+
var FunctionOrmError = class extends Error {
|
|
131
|
+
constructor(code, message) {
|
|
132
|
+
super(message);
|
|
133
|
+
this.code = code;
|
|
134
|
+
}
|
|
135
|
+
name = "FunctionOrmError";
|
|
136
|
+
};
|
|
137
|
+
var ORM_JSON_SCALARS_HEADER = "x-sequence-orm-json-scalars";
|
|
138
|
+
var ORM_LOSSLESS_JSON_SCALARS = "lossless-v1";
|
|
139
|
+
function readToken({ request, header, missing }) {
|
|
140
|
+
const { headers } = request;
|
|
141
|
+
let value;
|
|
142
|
+
if (typeof headers.get === "function") {
|
|
143
|
+
value = headers.get(header);
|
|
144
|
+
} else {
|
|
145
|
+
const matches = Object.entries(headers).filter(([name]) => name.toLowerCase() === header);
|
|
146
|
+
if (matches.length > 1) throw invalidContext();
|
|
147
|
+
value = matches[0]?.[1];
|
|
148
|
+
}
|
|
149
|
+
if (value === void 0 || value === null || value === "") {
|
|
150
|
+
throw new FunctionOrmError("MISSING_CALLER_CONTEXT", missing);
|
|
151
|
+
}
|
|
152
|
+
if (typeof value !== "string" || value.length > 32768) throw invalidContext();
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
function invalidContext() {
|
|
156
|
+
return new FunctionOrmError("INVALID_CALLER_CONTEXT", "The invocation has no valid function caller context.");
|
|
157
|
+
}
|
|
158
|
+
function record(value) {
|
|
159
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
160
|
+
}
|
|
161
|
+
function invocationClaims({ token }) {
|
|
162
|
+
const parts = /^([A-Za-z0-9_-]+)\.([A-Za-z0-9_-]{43})$/.exec(token);
|
|
163
|
+
if (!parts?.[1]) throw invalidContext();
|
|
164
|
+
let claims;
|
|
165
|
+
try {
|
|
166
|
+
claims = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
167
|
+
} catch {
|
|
168
|
+
throw invalidContext();
|
|
169
|
+
}
|
|
170
|
+
if (!record(claims) || typeof claims.sub !== "string" || !claims.sub || typeof claims.functionId !== "string" || !claims.functionId || typeof claims.invocationId !== "string" || !claims.invocationId || typeof claims.exp !== "number" || !Number.isSafeInteger(claims.exp) || claims.origin !== void 0 && claims.origin !== "function") throw invalidContext();
|
|
171
|
+
return { sub: claims.sub, exp: claims.exp, assertedBy: claims.assertedBy };
|
|
172
|
+
}
|
|
173
|
+
function callerExpiry({ token }) {
|
|
174
|
+
const claims = invocationClaims({ token });
|
|
175
|
+
if (claims.sub.startsWith("service-account|") || claims.sub.endsWith("@clients") || claims.assertedBy != null) {
|
|
176
|
+
throw new FunctionOrmError(
|
|
177
|
+
"UNSUPPORTED_CALLER",
|
|
178
|
+
"This ORM helper requires a direct human invocation. For system-runner (Lattice) invocations, use createFunctionServiceOrmExecutor to act as the function's attached service account."
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return claims.exp;
|
|
182
|
+
}
|
|
183
|
+
function serviceExpiry({ token }) {
|
|
184
|
+
const claims = invocationClaims({ token });
|
|
185
|
+
if (!claims.sub.startsWith("service-account|") || typeof claims.assertedBy !== "string" || !claims.assertedBy) {
|
|
186
|
+
throw new FunctionOrmError(
|
|
187
|
+
"UNSUPPORTED_CALLER",
|
|
188
|
+
"The service data token does not name the function's attached service account."
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return claims.exp;
|
|
192
|
+
}
|
|
193
|
+
function requireUnexpired({ expiresAt }) {
|
|
194
|
+
if (expiresAt <= Math.floor(Date.now() / 1e3)) {
|
|
195
|
+
throw new FunctionOrmError(
|
|
196
|
+
"EXPIRED_CALLER_CONTEXT",
|
|
197
|
+
"The caller context has expired. Start a new platform invocation; do not reuse its executor."
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function trustedOrigin({ baseUrl }) {
|
|
202
|
+
let url;
|
|
203
|
+
try {
|
|
204
|
+
url = new URL(baseUrl);
|
|
205
|
+
} catch {
|
|
206
|
+
throw new FunctionOrmError("INVALID_CONFIGURATION", "Configure an absolute platform origin for caller ORM access.");
|
|
207
|
+
}
|
|
208
|
+
const localHttp = url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
209
|
+
if (url.protocol !== "https:" && !localHttp || url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
|
|
210
|
+
throw new FunctionOrmError(
|
|
211
|
+
"INVALID_CONFIGURATION",
|
|
212
|
+
"Configure an HTTPS platform origin without credentials, path, query or fragment. HTTP is supported only on loopback for local development."
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return url.origin;
|
|
216
|
+
}
|
|
217
|
+
function createExecutor({
|
|
218
|
+
baseUrl,
|
|
219
|
+
namespace,
|
|
220
|
+
manifest,
|
|
221
|
+
fetchImpl,
|
|
222
|
+
token,
|
|
223
|
+
header,
|
|
224
|
+
expiresAt,
|
|
225
|
+
jsonScalars
|
|
226
|
+
}) {
|
|
227
|
+
const origin = trustedOrigin({ baseUrl });
|
|
228
|
+
requireUnexpired({ expiresAt });
|
|
229
|
+
if (typeof fetchImpl !== "function") {
|
|
230
|
+
throw new FunctionOrmError("INVALID_CONFIGURATION", "Function ORM access requires a fetch implementation.");
|
|
231
|
+
}
|
|
232
|
+
if (jsonScalars !== void 0 && jsonScalars !== ORM_LOSSLESS_JSON_SCALARS) {
|
|
233
|
+
throw new FunctionOrmError("INVALID_CONFIGURATION", `jsonScalars must be ${ORM_LOSSLESS_JSON_SCALARS} when supplied.`);
|
|
234
|
+
}
|
|
235
|
+
return createOperationExecutorWithTransport({
|
|
236
|
+
namespace,
|
|
237
|
+
manifest,
|
|
238
|
+
transport: async ({ namespace: targetNamespace, op, variables, idempotencyKey }) => {
|
|
239
|
+
requireUnexpired({ expiresAt });
|
|
240
|
+
const response = await fetchImpl(`${origin}/_data/v2/${targetNamespace}`, {
|
|
241
|
+
method: "POST",
|
|
242
|
+
headers: {
|
|
243
|
+
"Content-Type": "application/json",
|
|
244
|
+
[header]: token,
|
|
245
|
+
...jsonScalars ? { [ORM_JSON_SCALARS_HEADER]: jsonScalars } : {}
|
|
246
|
+
},
|
|
247
|
+
body: JSON.stringify({ op, variables, idempotencyKey }),
|
|
248
|
+
redirect: "error"
|
|
249
|
+
});
|
|
250
|
+
const body = await response.json().catch(() => null);
|
|
251
|
+
if (jsonScalars && response.ok && response.headers.get(ORM_JSON_SCALARS_HEADER) !== jsonScalars) {
|
|
252
|
+
throw new OrmOperationError(
|
|
253
|
+
`Atlas did not confirm ${jsonScalars} numbers for this ORM response, so its numeric and bigint values may be rounded; update Atlas before relying on exact numbers`,
|
|
254
|
+
response.status,
|
|
255
|
+
"LOSSLESS_TRANSPORT_UNAVAILABLE"
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
return { ok: response.ok, status: response.status, body };
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
function createFunctionOrmExecutor({
|
|
263
|
+
request,
|
|
264
|
+
fetchImpl = globalThis.fetch,
|
|
265
|
+
...options
|
|
266
|
+
}) {
|
|
267
|
+
trustedOrigin({ baseUrl: options.baseUrl });
|
|
268
|
+
const token = readToken({
|
|
269
|
+
request,
|
|
270
|
+
header: ORM_DATA_TOKEN_HEADER,
|
|
271
|
+
missing: "Caller ORM access is unavailable. Declare the function data binding and invoke it through the platform as a signed-in user."
|
|
272
|
+
});
|
|
273
|
+
return createExecutor({ ...options, fetchImpl, token, header: ORM_DATA_TOKEN_HEADER, expiresAt: callerExpiry({ token }) });
|
|
274
|
+
}
|
|
275
|
+
function createFunctionServiceOrmExecutor({
|
|
276
|
+
request,
|
|
277
|
+
fetchImpl = globalThis.fetch,
|
|
278
|
+
...options
|
|
279
|
+
}) {
|
|
280
|
+
trustedOrigin({ baseUrl: options.baseUrl });
|
|
281
|
+
const token = readToken({
|
|
282
|
+
request,
|
|
283
|
+
header: ORM_SERVICE_DATA_TOKEN_HEADER,
|
|
284
|
+
missing: "Service ORM access is unavailable. Declare service_account and the data binding in managed-function.yml and invoke the function through the platform."
|
|
285
|
+
});
|
|
286
|
+
return createExecutor({ ...options, fetchImpl, token, header: ORM_SERVICE_DATA_TOKEN_HEADER, expiresAt: serviceExpiry({ token }) });
|
|
287
|
+
}
|
|
288
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
289
|
+
0 && (module.exports = {
|
|
290
|
+
FunctionOrmError,
|
|
291
|
+
ORM_JSON_SCALARS_HEADER,
|
|
292
|
+
ORM_LOSSLESS_JSON_SCALARS,
|
|
293
|
+
OrmClientError,
|
|
294
|
+
OrmOperationError,
|
|
295
|
+
createFunctionOrmExecutor,
|
|
296
|
+
createFunctionServiceOrmExecutor
|
|
297
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ManagedFunctionLimitCatalog } from './limits';
|
|
2
|
+
import { type ManagedFunctionCapabilities, type PreflightIssue, type PreflightManifest } from './capabilities';
|
|
3
|
+
export declare function preflightManagedFunction({ manifest, limits, capabilities, cliVersion, sdkVersion, }: {
|
|
4
|
+
manifest: PreflightManifest;
|
|
5
|
+
limits: ManagedFunctionLimitCatalog | null;
|
|
6
|
+
capabilities: ManagedFunctionCapabilities | null;
|
|
7
|
+
cliVersion: string;
|
|
8
|
+
sdkVersion: string | null;
|
|
9
|
+
}): PreflightIssue[];
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preflightManagedFunction = preflightManagedFunction;
|
|
4
|
+
const capabilities_1 = require("./capabilities");
|
|
5
|
+
const sdk_range_1 = require("./sdk-range");
|
|
6
|
+
const WORKLOAD_FIELDS = [
|
|
7
|
+
'memory_mb',
|
|
8
|
+
'timeout_seconds',
|
|
9
|
+
'min_instances',
|
|
10
|
+
'max_instances',
|
|
11
|
+
'invoke_rate_per_minute',
|
|
12
|
+
'max_response_bytes',
|
|
13
|
+
'concurrency',
|
|
14
|
+
];
|
|
15
|
+
function preflightManagedFunction({ manifest, limits, capabilities, cliVersion, sdkVersion, }) {
|
|
16
|
+
const tooling = capabilities?.tooling ?? capabilities_1.MANAGED_FUNCTION_CAPABILITIES.tooling;
|
|
17
|
+
const issues = [];
|
|
18
|
+
checkWorkload({ manifest, limits, tooling, cliVersion, sdkVersion, issues });
|
|
19
|
+
checkPostgres({ manifest, capabilities, tooling, cliVersion, sdkVersion, issues });
|
|
20
|
+
checkHeaders({ manifest, capabilities, tooling, cliVersion, sdkVersion, issues });
|
|
21
|
+
checkSdkTimeout({ manifest, limits, capabilities, tooling, cliVersion, sdkVersion, issues });
|
|
22
|
+
return issues;
|
|
23
|
+
}
|
|
24
|
+
function issue({ field, problem, limit, tooling, requiredCli, requiredSdk, cliVersion, sdkVersion, }) {
|
|
25
|
+
const installedSdk = sdkVersion ?? 'not declared';
|
|
26
|
+
return {
|
|
27
|
+
field,
|
|
28
|
+
limit,
|
|
29
|
+
requiredCli,
|
|
30
|
+
requiredSdk,
|
|
31
|
+
message: `${field}: ${problem} Supported limit: ${limit}. ` +
|
|
32
|
+
`Requires ${tooling.cli.package} >= ${requiredCli} (installed ${cliVersion}) ` +
|
|
33
|
+
`and ${tooling.sdk.package} >= ${requiredSdk} (installed ${installedSdk}).`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function numericCap(entry) {
|
|
37
|
+
if (!entry || typeof entry !== 'object')
|
|
38
|
+
return null;
|
|
39
|
+
const min = Reflect.get(entry, 'min');
|
|
40
|
+
const max = Reflect.get(entry, 'max');
|
|
41
|
+
const unit = Reflect.get(entry, 'unit');
|
|
42
|
+
if (typeof min !== 'number' || typeof max !== 'number')
|
|
43
|
+
return null;
|
|
44
|
+
return { min, max, unit: typeof unit === 'string' ? unit : undefined };
|
|
45
|
+
}
|
|
46
|
+
function checkWorkload({ manifest, limits, tooling, cliVersion, sdkVersion, issues, }) {
|
|
47
|
+
const workload = limits?.workload;
|
|
48
|
+
if (!workload)
|
|
49
|
+
return;
|
|
50
|
+
const declared = manifest.limits;
|
|
51
|
+
if (!declared)
|
|
52
|
+
return;
|
|
53
|
+
for (const name of WORKLOAD_FIELDS) {
|
|
54
|
+
const value = declared[name];
|
|
55
|
+
if (typeof value !== 'number')
|
|
56
|
+
continue;
|
|
57
|
+
const cap = numericCap(workload[name]);
|
|
58
|
+
if (!cap)
|
|
59
|
+
continue;
|
|
60
|
+
if (Number.isInteger(value) && value >= cap.min && value <= cap.max)
|
|
61
|
+
continue;
|
|
62
|
+
const unit = cap.unit ? ` ${cap.unit}` : '';
|
|
63
|
+
issues.push(issue({
|
|
64
|
+
field: `limits.${name}`,
|
|
65
|
+
problem: `${value} is outside the range this deployment enforces`,
|
|
66
|
+
limit: `${cap.min} to ${cap.max}${unit}`,
|
|
67
|
+
tooling,
|
|
68
|
+
requiredCli: tooling.cli.min,
|
|
69
|
+
requiredSdk: tooling.sdk.min,
|
|
70
|
+
cliVersion,
|
|
71
|
+
sdkVersion,
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
const cpu = declared.cpu;
|
|
75
|
+
const cpuCap = numericCap(workload.cpu);
|
|
76
|
+
if (cpu === undefined || cpu === 'auto' || !cpuCap)
|
|
77
|
+
return;
|
|
78
|
+
if (typeof cpu === 'number' && Number.isInteger(cpu) && cpu >= cpuCap.min && cpu <= cpuCap.max)
|
|
79
|
+
return;
|
|
80
|
+
issues.push(issue({
|
|
81
|
+
field: 'limits.cpu',
|
|
82
|
+
problem: `${String(cpu)} is outside the range this deployment enforces`,
|
|
83
|
+
limit: `${cpuCap.min} to ${cpuCap.max}, or auto`,
|
|
84
|
+
tooling,
|
|
85
|
+
requiredCli: tooling.cli.min,
|
|
86
|
+
requiredSdk: tooling.sdk.min,
|
|
87
|
+
cliVersion,
|
|
88
|
+
sdkVersion,
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
function checkPostgres({ manifest, capabilities, tooling, cliVersion, sdkVersion, issues, }) {
|
|
92
|
+
const feature = capabilities?.features.postgres_egress;
|
|
93
|
+
if (!feature)
|
|
94
|
+
return;
|
|
95
|
+
const entries = manifest.egress ?? [];
|
|
96
|
+
entries.forEach((entry, index) => {
|
|
97
|
+
if (!(0, capabilities_1.isPostgresEgress)(entry))
|
|
98
|
+
return;
|
|
99
|
+
const field = `egress[${index}]`;
|
|
100
|
+
if (!feature.supported) {
|
|
101
|
+
issues.push(issue({
|
|
102
|
+
field,
|
|
103
|
+
problem: 'PostgreSQL egress is not supported by this deployment',
|
|
104
|
+
limit: 'hostnames, http(s) URLs, and public IPv4 CIDRs',
|
|
105
|
+
tooling,
|
|
106
|
+
requiredCli: feature.min_cli,
|
|
107
|
+
requiredSdk: feature.min_sdk,
|
|
108
|
+
cliVersion,
|
|
109
|
+
sdkVersion,
|
|
110
|
+
}));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if ((0, capabilities_1.versionIsOlder)(cliVersion, feature.min_cli)) {
|
|
114
|
+
issues.push(issue({
|
|
115
|
+
field,
|
|
116
|
+
problem: 'this CLI rejects PostgreSQL egress that this deployment supports',
|
|
117
|
+
limit: feature.limit,
|
|
118
|
+
tooling,
|
|
119
|
+
requiredCli: feature.min_cli,
|
|
120
|
+
requiredSdk: feature.min_sdk,
|
|
121
|
+
cliVersion,
|
|
122
|
+
sdkVersion,
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function needsWideHeaders(manifest) {
|
|
128
|
+
const data = manifest.capabilities?.data;
|
|
129
|
+
const storage = manifest.capabilities?.storage;
|
|
130
|
+
const hasData = !!data && Object.keys(data).length > 0;
|
|
131
|
+
const hasStorage = !!storage && typeof storage === 'object' && Object.keys(storage).length > 0;
|
|
132
|
+
return hasData || hasStorage;
|
|
133
|
+
}
|
|
134
|
+
function checkHeaders({ manifest, capabilities, tooling, cliVersion, sdkVersion, issues, }) {
|
|
135
|
+
const feature = capabilities?.features.http_headers;
|
|
136
|
+
if (!feature || !needsWideHeaders(manifest))
|
|
137
|
+
return;
|
|
138
|
+
const supported = capabilities_1.MANAGED_FUNCTION_CAPABILITIES.features.http_headers.max_bytes;
|
|
139
|
+
if (feature.max_bytes >= supported)
|
|
140
|
+
return;
|
|
141
|
+
issues.push(issue({
|
|
142
|
+
field: feature.field,
|
|
143
|
+
problem: `invocation headers for capabilities.data or capabilities.storage exceed ${feature.max_bytes} bytes and will fail with HTTP 431`,
|
|
144
|
+
limit: feature.limit,
|
|
145
|
+
tooling,
|
|
146
|
+
requiredCli: feature.min_cli,
|
|
147
|
+
requiredSdk: feature.min_sdk,
|
|
148
|
+
cliVersion,
|
|
149
|
+
sdkVersion,
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
function checkSdkTimeout({ manifest, limits, capabilities, tooling, cliVersion, sdkVersion, issues, }) {
|
|
153
|
+
const timeout = manifest.limits?.timeout_seconds;
|
|
154
|
+
if (typeof timeout !== 'number' || !sdkVersion)
|
|
155
|
+
return;
|
|
156
|
+
const feature = capabilities?.features.invoke_timeout ?? capabilities_1.MANAGED_FUNCTION_CAPABILITIES.features.invoke_timeout;
|
|
157
|
+
if (!(0, sdk_range_1.sdkConstraintIsOlder)(sdkVersion, feature.min_sdk))
|
|
158
|
+
return;
|
|
159
|
+
if (timeout <= feature.legacy_sdk_max_seconds)
|
|
160
|
+
return;
|
|
161
|
+
const platformMax = numericCap(limits?.workload?.timeout_seconds)?.max ?? feature.max_seconds;
|
|
162
|
+
issues.push(issue({
|
|
163
|
+
field: feature.field,
|
|
164
|
+
problem: `${timeout} seconds exceeds the installed SDK's ${feature.legacy_sdk_max_seconds} second client cap, behind the platform limit of ${platformMax} seconds`,
|
|
165
|
+
limit: feature.limit,
|
|
166
|
+
tooling,
|
|
167
|
+
requiredCli: feature.min_cli,
|
|
168
|
+
requiredSdk: feature.min_sdk,
|
|
169
|
+
cliVersion,
|
|
170
|
+
sdkVersion,
|
|
171
|
+
}));
|
|
172
|
+
}
|