@oddington/sdk 0.1.34 → 0.1.35
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 +11 -7
- package/dist/browser.js +289 -284
- package/dist/index.js +3 -3
- package/dist/types/packages/sdk/src/public.d.ts +2 -26
- package/dist/types/shared/files-contract.d.ts +30 -1
- package/dist/types/shared/routine-deployment-contract.d.ts +4 -4
- package/package.json +1 -1
- package/dist/types/shared/record-write.d.ts +0 -33
package/dist/browser.js
CHANGED
|
@@ -1,14 +1,49 @@
|
|
|
1
|
+
var fileValidationReasons = {
|
|
2
|
+
invalid_metadata: "File metadata must contain only the supported fields.",
|
|
3
|
+
invalid_name: "File name must be 1\u2013255 characters, with no reserved names, path separators, control characters or trailing spaces/dots.",
|
|
4
|
+
invalid_path: "File path must contain safe relative folder names and cannot start with .os.",
|
|
5
|
+
invalid_storage: "File storage must be local or remote.",
|
|
6
|
+
invalid_media_type: "File media type must name one valid type/subtype.",
|
|
7
|
+
invalid_size: "File size must be a non-negative safe integer byte count.",
|
|
8
|
+
invalid_request_key: "File request key must contain 1\u2013256 characters, with no surrounding whitespace or control characters.",
|
|
9
|
+
invalid_enrichment_name: "Enrichment name must be a safe filename and cannot be original or original.*.",
|
|
10
|
+
invalid_replacement: "Enrichment replacement must be a SHA-256 hash of 64 lowercase hexadecimal characters.",
|
|
11
|
+
invalid_bytes: "File content must contain byte chunks."
|
|
12
|
+
};
|
|
13
|
+
function parseFileErrorDetail(value) {
|
|
14
|
+
if (!value || typeof value != "object" || Array.isArray(value)) return;
|
|
15
|
+
let v = value;
|
|
16
|
+
if (typeof v.reason != "string") return;
|
|
17
|
+
if (Object.hasOwn(fileValidationReasons, v.reason))
|
|
18
|
+
return Object.keys(v).length === 1 ? { reason: v.reason } : void 0;
|
|
19
|
+
if (v.reason !== "size_mismatch" && v.reason !== "size_limit") return;
|
|
20
|
+
let count = v.reason === "size_mismatch" ? "expectedBytes" : "limitBytes";
|
|
21
|
+
if (!(Object.keys(v).some((key) => !["reason", count, "receivedBytes", "receivedAtLeast"].includes(key)) || !Number.isSafeInteger(v[count]) || v[count] < 0 || !Number.isSafeInteger(v.receivedBytes) || v.receivedBytes < 0 || v.receivedAtLeast !== void 0 && v.receivedAtLeast !== !0))
|
|
22
|
+
return {
|
|
23
|
+
reason: v.reason,
|
|
24
|
+
[count]: v[count],
|
|
25
|
+
receivedBytes: v.receivedBytes,
|
|
26
|
+
...v.receivedAtLeast ? { receivedAtLeast: !0 } : {}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function fileErrorExplanation(detail) {
|
|
30
|
+
return detail.reason === "size_mismatch" ? `File byte count mismatch: expected ${detail.expectedBytes} bytes; received ${detail.receivedAtLeast ? "at least " : ""}${detail.receivedBytes} bytes.` : detail.reason === "size_limit" ? `File size exceeds the ${detail.limitBytes}-byte limit; received ${detail.receivedAtLeast ? "at least " : ""}${detail.receivedBytes} bytes.` : fileValidationReasons[detail.reason];
|
|
31
|
+
}
|
|
1
32
|
var FileOperationError = class extends Error {
|
|
2
|
-
constructor(code, status, retryable = !1) {
|
|
3
|
-
|
|
33
|
+
constructor(code, status, retryable = !1, detail) {
|
|
34
|
+
let safe = ["invalid_input", "request_too_large"].includes(code) ? parseFileErrorDetail(detail) : void 0;
|
|
35
|
+
super(safe ? `${code}: ${fileErrorExplanation(safe)}` : code);
|
|
4
36
|
this.code = code;
|
|
5
37
|
this.status = status;
|
|
6
38
|
this.retryable = retryable;
|
|
39
|
+
safe && (this.detail = Object.freeze(safe));
|
|
7
40
|
}
|
|
8
41
|
code;
|
|
9
42
|
status;
|
|
10
43
|
retryable;
|
|
11
|
-
|
|
44
|
+
detail;
|
|
45
|
+
};
|
|
46
|
+
var FILE_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/, FILE_ORIGINAL_MAX_BYTES = 5 * 1024 ** 3;
|
|
12
47
|
function parseFileDescriptor(value) {
|
|
13
48
|
let invalid9 = () => {
|
|
14
49
|
throw new FileOperationError("unavailable", 503, !0);
|
|
@@ -34,13 +69,22 @@ function safeEnrichmentName(value) {
|
|
|
34
69
|
return safeFileName(value) && !/^original(?:\.|$)/i.test(value);
|
|
35
70
|
}
|
|
36
71
|
function parseFileEnrichMetadata(value, maxBytes) {
|
|
37
|
-
let invalid9 = () => {
|
|
38
|
-
throw new FileOperationError("invalid_input", 400);
|
|
72
|
+
let invalid9 = (reason = "invalid_metadata") => {
|
|
73
|
+
throw new FileOperationError("invalid_input", 400, !1, { reason });
|
|
39
74
|
};
|
|
40
75
|
if (!value || typeof value != "object" || Array.isArray(value)) return invalid9();
|
|
41
76
|
let d = value;
|
|
42
|
-
if (Object.keys(d).some((key) => !["name", "size", "mediaType", "replace"].includes(key))
|
|
43
|
-
if (d.
|
|
77
|
+
if (Object.keys(d).some((key) => !["name", "size", "mediaType", "replace"].includes(key))) return invalid9();
|
|
78
|
+
if (!safeEnrichmentName(d.name)) return invalid9("invalid_enrichment_name");
|
|
79
|
+
if (!Number.isSafeInteger(d.size) || d.size < 0) return invalid9("invalid_size");
|
|
80
|
+
if (d.mediaType !== void 0 && (typeof d.mediaType != "string" || d.mediaType.length > 255 || /[\u0000-\u001f\u007f]/.test(d.mediaType))) return invalid9("invalid_media_type");
|
|
81
|
+
if (d.replace !== void 0 && (typeof d.replace != "string" || !/^[0-9a-f]{64}$/.test(d.replace))) return invalid9("invalid_replacement");
|
|
82
|
+
if (d.size > maxBytes) throw new FileOperationError(
|
|
83
|
+
"request_too_large",
|
|
84
|
+
413,
|
|
85
|
+
!1,
|
|
86
|
+
{ reason: "size_limit", limitBytes: maxBytes, receivedBytes: d.size }
|
|
87
|
+
);
|
|
44
88
|
return d;
|
|
45
89
|
}
|
|
46
90
|
function safeFileName(value) {
|
|
@@ -54,9 +98,9 @@ function safeManagedFilePath(value) {
|
|
|
54
98
|
}
|
|
55
99
|
function declaredOriginalMediaType(value) {
|
|
56
100
|
if (typeof value != "string" || value.length > 255 || /[\u0000-\u001f\u007f-\u009f]/.test(value))
|
|
57
|
-
throw new FileOperationError("invalid_input", 400);
|
|
101
|
+
throw new FileOperationError("invalid_input", 400, !1, { reason: "invalid_media_type" });
|
|
58
102
|
let normalized = value.split(";", 1)[0].trim().toLowerCase(), token = "[a-z0-9!#$%&'+.^_`|~-]+";
|
|
59
|
-
if (!new RegExp(`^${token}/${token}$`).test(normalized)) throw new FileOperationError("invalid_input", 400);
|
|
103
|
+
if (!new RegExp(`^${token}/${token}$`).test(normalized)) throw new FileOperationError("invalid_input", 400, !1, { reason: "invalid_media_type" });
|
|
60
104
|
return normalized;
|
|
61
105
|
}
|
|
62
106
|
function isCanonicalOriginalMediaType(value) {
|
|
@@ -67,18 +111,25 @@ function isCanonicalOriginalMediaType(value) {
|
|
|
67
111
|
}
|
|
68
112
|
}
|
|
69
113
|
function parseFileCreateMetadata(value, maxBytes) {
|
|
70
|
-
let invalid9 = () => {
|
|
71
|
-
throw new FileOperationError("invalid_input", 400);
|
|
114
|
+
let invalid9 = (reason = "invalid_metadata") => {
|
|
115
|
+
throw new FileOperationError("invalid_input", 400, !1, { reason });
|
|
72
116
|
};
|
|
73
117
|
if (!value || typeof value != "object" || Array.isArray(value)) return invalid9();
|
|
74
118
|
let input = value;
|
|
75
119
|
if (Object.keys(input).some((key) => !["name", "path", "storage", "mediaType", "size", "requestKey"].includes(key))) return invalid9();
|
|
76
120
|
let path = input.path === void 0 ? "Files" : input.path;
|
|
77
|
-
if (!safeFileName(input.name)
|
|
121
|
+
if (!safeFileName(input.name)) return invalid9("invalid_name");
|
|
122
|
+
if (!safeManagedFilePath(path)) return invalid9("invalid_path");
|
|
123
|
+
if (input.storage !== void 0 && input.storage !== "remote" && input.storage !== "local") return invalid9("invalid_storage");
|
|
78
124
|
let mediaType = input.mediaType === void 0 ? "application/octet-stream" : declaredOriginalMediaType(input.mediaType);
|
|
79
|
-
if (!Number.isSafeInteger(input.size) || input.size < 0) return invalid9();
|
|
80
|
-
if (input.size > maxBytes) throw new FileOperationError(
|
|
81
|
-
|
|
125
|
+
if (!Number.isSafeInteger(input.size) || input.size < 0) return invalid9("invalid_size");
|
|
126
|
+
if (input.size > maxBytes) throw new FileOperationError(
|
|
127
|
+
"request_too_large",
|
|
128
|
+
413,
|
|
129
|
+
!1,
|
|
130
|
+
{ reason: "size_limit", limitBytes: maxBytes, receivedBytes: input.size }
|
|
131
|
+
);
|
|
132
|
+
return typeof input.requestKey != "string" || input.requestKey.length < 1 || input.requestKey.length > 256 || input.requestKey !== input.requestKey.trim() || /[\u0000-\u001f\u007f]/.test(input.requestKey) ? invalid9("invalid_request_key") : {
|
|
82
133
|
name: input.name,
|
|
83
134
|
path,
|
|
84
135
|
...input.storage ? { storage: input.storage } : {},
|
|
@@ -88,6 +139,116 @@ function parseFileCreateMetadata(value, maxBytes) {
|
|
|
88
139
|
};
|
|
89
140
|
}
|
|
90
141
|
|
|
142
|
+
var AppRoutineContractError = class extends Error {
|
|
143
|
+
constructor(code) {
|
|
144
|
+
super(code);
|
|
145
|
+
this.code = code;
|
|
146
|
+
}
|
|
147
|
+
code;
|
|
148
|
+
}, propertyName = /^[a-z][A-Za-z0-9]*(?![\s\S])/, stepId = /^[a-z0-9]+(?:-[a-z0-9]+)*(?![\s\S])/, routineName = /^[a-z0-9][a-z0-9-]{0,62}(?![\s\S])/;
|
|
149
|
+
function isAppRoutineName(value) {
|
|
150
|
+
return typeof value == "string" && routineName.test(value);
|
|
151
|
+
}
|
|
152
|
+
var owns = (value, key) => Object.hasOwn(value, key), invalidContract = () => {
|
|
153
|
+
throw new AppRoutineContractError("invalid_contract");
|
|
154
|
+
}, invalidValue = () => {
|
|
155
|
+
throw new AppRoutineContractError("invalid_value");
|
|
156
|
+
};
|
|
157
|
+
function record(value) {
|
|
158
|
+
return value !== null && typeof value == "object" && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
159
|
+
}
|
|
160
|
+
function fields(value, required, optional = []) {
|
|
161
|
+
return required.every((key) => owns(value, key)) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
|
|
162
|
+
}
|
|
163
|
+
function denseArray(value) {
|
|
164
|
+
return Array.isArray(value) && Object.keys(value).length === value.length && Array.from({ length: value.length }, (_, index) => owns(value, String(index))).every(Boolean);
|
|
165
|
+
}
|
|
166
|
+
function parseAppRoutineContract(value) {
|
|
167
|
+
let properties = 0;
|
|
168
|
+
function schema2(value2, depth = 1) {
|
|
169
|
+
if (depth > 8 || !record(value2)) return invalidContract();
|
|
170
|
+
switch (value2.type) {
|
|
171
|
+
case "object": {
|
|
172
|
+
if (!fields(value2, ["type", "properties", "additionalProperties"], ["required"]) || value2.additionalProperties !== !1 || !record(value2.properties)) return invalidContract();
|
|
173
|
+
let entries = Object.entries(value2.properties);
|
|
174
|
+
if (properties += entries.length, properties > 64 || entries.some(([key]) => !propertyName.test(key))) return invalidContract();
|
|
175
|
+
let required;
|
|
176
|
+
if (owns(value2, "required")) {
|
|
177
|
+
if (!denseArray(value2.required) || value2.required.some((key) => typeof key != "string" || !owns(value2.properties, key)) || new Set(value2.required).size !== value2.required.length) return invalidContract();
|
|
178
|
+
required = [...value2.required];
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
type: "object",
|
|
182
|
+
properties: Object.fromEntries(entries.map(([key, child]) => [key, schema2(child, depth + 1)])),
|
|
183
|
+
...required === void 0 ? {} : { required },
|
|
184
|
+
additionalProperties: !1
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
case "array":
|
|
188
|
+
return !fields(value2, ["type", "items", "maxItems"]) || !Number.isSafeInteger(value2.maxItems) || value2.maxItems < 1 || value2.maxItems > 64 ? invalidContract() : { type: "array", items: schema2(value2.items, depth + 1), maxItems: value2.maxItems };
|
|
189
|
+
case "boolean":
|
|
190
|
+
return fields(value2, ["type"]) ? { type: value2.type } : invalidContract();
|
|
191
|
+
case "file":
|
|
192
|
+
return !fields(value2, ["type"], ["storage", "path"]) || owns(value2, "storage") && value2.storage !== "local" && value2.storage !== "remote" || owns(value2, "path") && !safeManagedFilePath(value2.path) ? invalidContract() : {
|
|
193
|
+
type: "file",
|
|
194
|
+
...owns(value2, "storage") ? { storage: value2.storage } : {},
|
|
195
|
+
...owns(value2, "path") ? { path: value2.path } : {}
|
|
196
|
+
};
|
|
197
|
+
case "string":
|
|
198
|
+
return !fields(value2, ["type"], ["maxLength"]) || owns(value2, "maxLength") && (!Number.isSafeInteger(value2.maxLength) || value2.maxLength < 0 || value2.maxLength > 4096) ? invalidContract() : { type: "string", ...owns(value2, "maxLength") ? { maxLength: value2.maxLength } : {} };
|
|
199
|
+
case "integer":
|
|
200
|
+
case "number": {
|
|
201
|
+
let validNumber = value2.type === "integer" ? Number.isSafeInteger : Number.isFinite;
|
|
202
|
+
return !fields(value2, ["type"], ["minimum", "maximum"]) || owns(value2, "minimum") && !validNumber(value2.minimum) || owns(value2, "maximum") && !validNumber(value2.maximum) || owns(value2, "minimum") && owns(value2, "maximum") && value2.minimum > value2.maximum ? invalidContract() : {
|
|
203
|
+
type: value2.type,
|
|
204
|
+
...owns(value2, "minimum") ? { minimum: value2.minimum } : {},
|
|
205
|
+
...owns(value2, "maximum") ? { maximum: value2.maximum } : {}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
default:
|
|
209
|
+
return invalidContract();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (!record(value) || !fields(value, ["routine", "input", "steps"], ["result"]) || !isAppRoutineName(value.routine) || !denseArray(value.steps) || value.steps.length < 1 || value.steps.length > 64) return invalidContract();
|
|
213
|
+
let input = schema2(value.input), result2 = owns(value, "result") ? schema2(value.result) : void 0, ids = /* @__PURE__ */ new Set(), steps = value.steps.map((step) => !record(step) || !fields(step, ["id"], ["summary"]) || typeof step.id != "string" || !stepId.test(step.id) || ids.has(step.id) ? invalidContract() : (ids.add(step.id), { id: step.id, ...owns(step, "summary") ? { summary: schema2(step.summary) } : {} }));
|
|
214
|
+
return { routine: value.routine, input, steps, ...result2 === void 0 ? {} : { result: result2 } };
|
|
215
|
+
}
|
|
216
|
+
function jsonData(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
217
|
+
if (!(value === null || typeof value == "string" || typeof value == "boolean" || typeof value == "number" && Number.isFinite(value))) {
|
|
218
|
+
if (!record(value) && !denseArray(value) || ancestors.has(value)) return invalidValue();
|
|
219
|
+
ancestors.add(value);
|
|
220
|
+
for (let child of Object.values(value)) jsonData(child, ancestors);
|
|
221
|
+
ancestors.delete(value);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function validateRoutineSchemaValue(schema2, value) {
|
|
225
|
+
switch (schema2.type) {
|
|
226
|
+
case "object":
|
|
227
|
+
if (!record(value) || (schema2.required ?? []).some((key) => !owns(value, key)) || Object.keys(value).some((key) => !owns(schema2.properties, key))) return invalidValue();
|
|
228
|
+
for (let [key, child] of Object.entries(value)) validateRoutineSchemaValue(schema2.properties[key], child);
|
|
229
|
+
return;
|
|
230
|
+
case "array":
|
|
231
|
+
if (!denseArray(value) || value.length > schema2.maxItems) return invalidValue();
|
|
232
|
+
for (let child of value) validateRoutineSchemaValue(schema2.items, child);
|
|
233
|
+
return;
|
|
234
|
+
case "boolean":
|
|
235
|
+
return typeof value != "boolean" ? invalidValue() : void 0;
|
|
236
|
+
case "string":
|
|
237
|
+
return typeof value != "string" || Array.from(value).length > (schema2.maxLength ?? 4096) ? invalidValue() : void 0;
|
|
238
|
+
case "integer":
|
|
239
|
+
case "number":
|
|
240
|
+
return typeof value != "number" || !(schema2.type === "integer" ? Number.isSafeInteger(value) : Number.isFinite(value)) || schema2.minimum !== void 0 && value < schema2.minimum || schema2.maximum !== void 0 && value > schema2.maximum ? invalidValue() : void 0;
|
|
241
|
+
case "file":
|
|
242
|
+
try {
|
|
243
|
+
jsonData(value);
|
|
244
|
+
let file2 = parseFileDescriptor(value);
|
|
245
|
+
if (schema2.path !== void 0 && file2.path !== schema2.path) return invalidValue();
|
|
246
|
+
} catch {
|
|
247
|
+
return invalidValue();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
91
252
|
var InboxContractError = class extends Error {
|
|
92
253
|
constructor(message2) {
|
|
93
254
|
super(message2), this.name = "InboxContractError";
|
|
@@ -157,7 +318,7 @@ function validateInboxFile(value) {
|
|
|
157
318
|
let size = natural(v.size, "file size");
|
|
158
319
|
return size > FILE_ORIGINAL_MAX_BYTES && fail("File exceeds supported size"), { id: fileId, name: name2, mediaType, size, sha256: v.sha256 };
|
|
159
320
|
}
|
|
160
|
-
function
|
|
321
|
+
function fields2(value) {
|
|
161
322
|
let result2 = array(value, "selected fields").map((entry) => {
|
|
162
323
|
let field = object(entry, "field");
|
|
163
324
|
return keys(field, ["label", "value"], "field"), { label: text(field.label, "field label"), value: text(field.value, "field value") };
|
|
@@ -184,7 +345,7 @@ function validateBlock(value) {
|
|
|
184
345
|
label,
|
|
185
346
|
...target,
|
|
186
347
|
...expanded,
|
|
187
|
-
record: { ref: id(r.ref, "record reference"), title: text(r.title, "record title"), fields:
|
|
348
|
+
record: { ref: id(r.ref, "record reference"), title: text(r.title, "record title"), fields: fields2(r.fields) }
|
|
188
349
|
};
|
|
189
350
|
}
|
|
190
351
|
if (b.kind === "records") {
|
|
@@ -194,7 +355,7 @@ function validateBlock(value) {
|
|
|
194
355
|
ref: id(r.ref, "record reference"),
|
|
195
356
|
title: text(r.title, "record title"),
|
|
196
357
|
...r.description === void 0 ? {} : { description: text(r.description, "record description") },
|
|
197
|
-
...r.facts === void 0 ? {} : { facts:
|
|
358
|
+
...r.facts === void 0 ? {} : { facts: fields2(r.facts) },
|
|
198
359
|
...r.view === void 0 ? {} : { view: id(r.view, "row View target") }
|
|
199
360
|
};
|
|
200
361
|
});
|
|
@@ -452,138 +613,6 @@ function createInboxPublisher(transport) {
|
|
|
452
613
|
};
|
|
453
614
|
}
|
|
454
615
|
|
|
455
|
-
var REPOSITORY = /^[a-z0-9][a-z0-9._/-]{0,127}$/;
|
|
456
|
-
function validRepositoryAlias(value) {
|
|
457
|
-
return typeof value == "string" && REPOSITORY.test(value) && !value.includes("//") && !value.includes("..");
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
var RECORD_READ_INPUT_LIMIT = 1024 * 1024, RECORD_CAPTURE_OUTPUT_LIMIT = 25 * 1024 * 1024;
|
|
461
|
-
function validRecordRef(value) {
|
|
462
|
-
return typeof value == "string" && value.length <= 512 && /^[_a-zA-Z0-9][a-zA-Z0-9_-]*(\/[_a-zA-Z0-9][a-zA-Z0-9_-]*)+$/.test(value);
|
|
463
|
-
}
|
|
464
|
-
function validateRecordRead(value) {
|
|
465
|
-
if (!value || typeof value != "object" || Array.isArray(value))
|
|
466
|
-
throw new InboxContractError("Invalid selected-record read");
|
|
467
|
-
let input = value;
|
|
468
|
-
if (Object.keys(input).some((key) => !["repository", "refs", "atCommit"].includes(key)) || !validRepositoryAlias(input.repository) || !Array.isArray(input.refs) || !input.refs.length || input.refs.length > 1e4 || input.refs.some((ref) => !validRecordRef(ref)) || new Set(input.refs).size !== input.refs.length || input.atCommit !== void 0 && (typeof input.atCommit != "string" || !/^[0-9a-f]{40}$/.test(input.atCommit)))
|
|
469
|
-
throw new InboxContractError("Invalid selected-record read");
|
|
470
|
-
return {
|
|
471
|
-
repository: input.repository,
|
|
472
|
-
refs: [...input.refs],
|
|
473
|
-
...input.atCommit === void 0 ? {} : { atCommit: input.atCommit }
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
var AppRoutineContractError = class extends Error {
|
|
478
|
-
constructor(code) {
|
|
479
|
-
super(code);
|
|
480
|
-
this.code = code;
|
|
481
|
-
}
|
|
482
|
-
code;
|
|
483
|
-
}, propertyName = /^[a-z][A-Za-z0-9]*(?![\s\S])/, stepId = /^[a-z0-9]+(?:-[a-z0-9]+)*(?![\s\S])/, routineName = /^[a-z0-9][a-z0-9-]{0,62}(?![\s\S])/;
|
|
484
|
-
function isAppRoutineName(value) {
|
|
485
|
-
return typeof value == "string" && routineName.test(value);
|
|
486
|
-
}
|
|
487
|
-
var owns = (value, key) => Object.hasOwn(value, key), invalidContract = () => {
|
|
488
|
-
throw new AppRoutineContractError("invalid_contract");
|
|
489
|
-
}, invalidValue = () => {
|
|
490
|
-
throw new AppRoutineContractError("invalid_value");
|
|
491
|
-
};
|
|
492
|
-
function record(value) {
|
|
493
|
-
return value !== null && typeof value == "object" && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
494
|
-
}
|
|
495
|
-
function fields2(value, required, optional = []) {
|
|
496
|
-
return required.every((key) => owns(value, key)) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
|
|
497
|
-
}
|
|
498
|
-
function denseArray(value) {
|
|
499
|
-
return Array.isArray(value) && Object.keys(value).length === value.length && Array.from({ length: value.length }, (_, index) => owns(value, String(index))).every(Boolean);
|
|
500
|
-
}
|
|
501
|
-
function parseAppRoutineContract(value) {
|
|
502
|
-
let properties = 0;
|
|
503
|
-
function schema2(value2, depth = 1) {
|
|
504
|
-
if (depth > 8 || !record(value2)) return invalidContract();
|
|
505
|
-
switch (value2.type) {
|
|
506
|
-
case "object": {
|
|
507
|
-
if (!fields2(value2, ["type", "properties", "additionalProperties"], ["required"]) || value2.additionalProperties !== !1 || !record(value2.properties)) return invalidContract();
|
|
508
|
-
let entries = Object.entries(value2.properties);
|
|
509
|
-
if (properties += entries.length, properties > 64 || entries.some(([key]) => !propertyName.test(key))) return invalidContract();
|
|
510
|
-
let required;
|
|
511
|
-
if (owns(value2, "required")) {
|
|
512
|
-
if (!denseArray(value2.required) || value2.required.some((key) => typeof key != "string" || !owns(value2.properties, key)) || new Set(value2.required).size !== value2.required.length) return invalidContract();
|
|
513
|
-
required = [...value2.required];
|
|
514
|
-
}
|
|
515
|
-
return {
|
|
516
|
-
type: "object",
|
|
517
|
-
properties: Object.fromEntries(entries.map(([key, child]) => [key, schema2(child, depth + 1)])),
|
|
518
|
-
...required === void 0 ? {} : { required },
|
|
519
|
-
additionalProperties: !1
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
case "array":
|
|
523
|
-
return !fields2(value2, ["type", "items", "maxItems"]) || !Number.isSafeInteger(value2.maxItems) || value2.maxItems < 1 || value2.maxItems > 64 ? invalidContract() : { type: "array", items: schema2(value2.items, depth + 1), maxItems: value2.maxItems };
|
|
524
|
-
case "boolean":
|
|
525
|
-
return fields2(value2, ["type"]) ? { type: value2.type } : invalidContract();
|
|
526
|
-
case "file":
|
|
527
|
-
return !fields2(value2, ["type"], ["storage", "path"]) || owns(value2, "storage") && value2.storage !== "local" && value2.storage !== "remote" || owns(value2, "path") && !safeManagedFilePath(value2.path) ? invalidContract() : {
|
|
528
|
-
type: "file",
|
|
529
|
-
...owns(value2, "storage") ? { storage: value2.storage } : {},
|
|
530
|
-
...owns(value2, "path") ? { path: value2.path } : {}
|
|
531
|
-
};
|
|
532
|
-
case "string":
|
|
533
|
-
return !fields2(value2, ["type"], ["maxLength"]) || owns(value2, "maxLength") && (!Number.isSafeInteger(value2.maxLength) || value2.maxLength < 0 || value2.maxLength > 4096) ? invalidContract() : { type: "string", ...owns(value2, "maxLength") ? { maxLength: value2.maxLength } : {} };
|
|
534
|
-
case "integer":
|
|
535
|
-
case "number": {
|
|
536
|
-
let validNumber = value2.type === "integer" ? Number.isSafeInteger : Number.isFinite;
|
|
537
|
-
return !fields2(value2, ["type"], ["minimum", "maximum"]) || owns(value2, "minimum") && !validNumber(value2.minimum) || owns(value2, "maximum") && !validNumber(value2.maximum) || owns(value2, "minimum") && owns(value2, "maximum") && value2.minimum > value2.maximum ? invalidContract() : {
|
|
538
|
-
type: value2.type,
|
|
539
|
-
...owns(value2, "minimum") ? { minimum: value2.minimum } : {},
|
|
540
|
-
...owns(value2, "maximum") ? { maximum: value2.maximum } : {}
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
default:
|
|
544
|
-
return invalidContract();
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
if (!record(value) || !fields2(value, ["routine", "input", "steps"], ["result"]) || !isAppRoutineName(value.routine) || !denseArray(value.steps) || value.steps.length < 1 || value.steps.length > 64) return invalidContract();
|
|
548
|
-
let input = schema2(value.input), result2 = owns(value, "result") ? schema2(value.result) : void 0, ids = /* @__PURE__ */ new Set(), steps = value.steps.map((step) => !record(step) || !fields2(step, ["id"], ["summary"]) || typeof step.id != "string" || !stepId.test(step.id) || ids.has(step.id) ? invalidContract() : (ids.add(step.id), { id: step.id, ...owns(step, "summary") ? { summary: schema2(step.summary) } : {} }));
|
|
549
|
-
return { routine: value.routine, input, steps, ...result2 === void 0 ? {} : { result: result2 } };
|
|
550
|
-
}
|
|
551
|
-
function jsonData(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
552
|
-
if (!(value === null || typeof value == "string" || typeof value == "boolean" || typeof value == "number" && Number.isFinite(value))) {
|
|
553
|
-
if (!record(value) && !denseArray(value) || ancestors.has(value)) return invalidValue();
|
|
554
|
-
ancestors.add(value);
|
|
555
|
-
for (let child of Object.values(value)) jsonData(child, ancestors);
|
|
556
|
-
ancestors.delete(value);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
function validateRoutineSchemaValue(schema2, value) {
|
|
560
|
-
switch (schema2.type) {
|
|
561
|
-
case "object":
|
|
562
|
-
if (!record(value) || (schema2.required ?? []).some((key) => !owns(value, key)) || Object.keys(value).some((key) => !owns(schema2.properties, key))) return invalidValue();
|
|
563
|
-
for (let [key, child] of Object.entries(value)) validateRoutineSchemaValue(schema2.properties[key], child);
|
|
564
|
-
return;
|
|
565
|
-
case "array":
|
|
566
|
-
if (!denseArray(value) || value.length > schema2.maxItems) return invalidValue();
|
|
567
|
-
for (let child of value) validateRoutineSchemaValue(schema2.items, child);
|
|
568
|
-
return;
|
|
569
|
-
case "boolean":
|
|
570
|
-
return typeof value != "boolean" ? invalidValue() : void 0;
|
|
571
|
-
case "string":
|
|
572
|
-
return typeof value != "string" || Array.from(value).length > (schema2.maxLength ?? 4096) ? invalidValue() : void 0;
|
|
573
|
-
case "integer":
|
|
574
|
-
case "number":
|
|
575
|
-
return typeof value != "number" || !(schema2.type === "integer" ? Number.isSafeInteger(value) : Number.isFinite(value)) || schema2.minimum !== void 0 && value < schema2.minimum || schema2.maximum !== void 0 && value > schema2.maximum ? invalidValue() : void 0;
|
|
576
|
-
case "file":
|
|
577
|
-
try {
|
|
578
|
-
jsonData(value);
|
|
579
|
-
let file2 = parseFileDescriptor(value);
|
|
580
|
-
if (schema2.path !== void 0 && file2.path !== schema2.path) return invalidValue();
|
|
581
|
-
} catch {
|
|
582
|
-
return invalidValue();
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
616
|
var ROUTINE_DECLARATION_SCHEMA = "https://schemas.oddington.ai/routine/v1.json", ROUTINE_SOURCE_LIMITS = { files: 2e4, bytes: 256 * 1024 * 1024, declarationBytes: 64 * 1024 };
|
|
588
617
|
var invalid = (field) => {
|
|
589
618
|
throw new Error(`Invalid routine declaration: ${field}`);
|
|
@@ -603,11 +632,9 @@ function parseRoutineDeclaration(value) {
|
|
|
603
632
|
(!routineAlias(key) || !["read", "write"].includes(repositories[key])) && invalid(`access.repositories.${key}`), grants[key] = repositories[key];
|
|
604
633
|
access.files !== void 0 && !["read", "read-write"].includes(access.files) && invalid("access.files");
|
|
605
634
|
let runtime = object2(v.runtime === void 0 ? {} : v.runtime, ["features", "resources", "maxDuration"], "runtime");
|
|
606
|
-
runtime.
|
|
607
|
-
let
|
|
608
|
-
|
|
609
|
-
let retry = object2(v.retry === void 0 ? {} : v.retry, ["maxAttempts"], "retry"), maxAttempts = retry.maxAttempts === void 0 ? 1 : retry.maxAttempts;
|
|
610
|
-
(!Number.isInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 3) && invalid("retry.maxAttempts");
|
|
635
|
+
runtime.features !== void 0 && (!Array.isArray(runtime.features) || runtime.features.length !== 0) && invalid("runtime.features"), runtime.resources !== void 0 && runtime.resources !== "standard" && invalid("runtime.resources"), runtime.maxDuration !== void 0 && runtime.maxDuration !== "15m" && invalid("runtime.maxDuration");
|
|
636
|
+
let retry = object2(v.retry === void 0 ? {} : v.retry, ["maxAttempts"], "retry");
|
|
637
|
+
retry.maxAttempts !== void 0 && retry.maxAttempts !== 1 && invalid("retry.maxAttempts");
|
|
611
638
|
let assets = v.assets === void 0 ? [] : v.assets;
|
|
612
639
|
return (!Array.isArray(assets) || assets.length > 64 || assets.some((item) => !routineAssetPattern(item)) || new Set(assets).size !== assets.length) && invalid("assets"), {
|
|
613
640
|
$schema: ROUTINE_DECLARATION_SCHEMA,
|
|
@@ -618,12 +645,8 @@ function parseRoutineDeclaration(value) {
|
|
|
618
645
|
...access.files === void 0 ? {} : { files: access.files },
|
|
619
646
|
publish: list(access.publish, "access.publish", ["asks", "digests"])
|
|
620
647
|
},
|
|
621
|
-
runtime: {
|
|
622
|
-
|
|
623
|
-
resources: runtime.resources ?? "standard",
|
|
624
|
-
maxDuration: duration
|
|
625
|
-
},
|
|
626
|
-
retry: { maxAttempts },
|
|
648
|
+
runtime: { features: [], resources: "standard", maxDuration: "15m" },
|
|
649
|
+
retry: { maxAttempts: 1 },
|
|
627
650
|
assets: [...assets].sort()
|
|
628
651
|
};
|
|
629
652
|
}
|
|
@@ -947,6 +970,11 @@ var DataOperationError = class extends Error {
|
|
|
947
970
|
component;
|
|
948
971
|
};
|
|
949
972
|
|
|
973
|
+
var RECORD_READ_INPUT_LIMIT = 1024 * 1024, RECORD_CAPTURE_OUTPUT_LIMIT = 25 * 1024 * 1024;
|
|
974
|
+
function validRecordRef(value) {
|
|
975
|
+
return typeof value == "string" && value.length <= 512 && /^[_a-zA-Z0-9][a-zA-Z0-9_-]*(\/[_a-zA-Z0-9][a-zA-Z0-9_-]*)+$/.test(value);
|
|
976
|
+
}
|
|
977
|
+
|
|
950
978
|
var invalid4 = () => {
|
|
951
979
|
throw new DataOperationError("invalid_query");
|
|
952
980
|
}, historyText = (value) => typeof value == "string" && !/[\u0000\uD800-\uDFFF]/u.test(value);
|
|
@@ -1471,8 +1499,13 @@ async function boundedBytes(stream, limit, signal) {
|
|
|
1471
1499
|
let { done, value } = await reader.read();
|
|
1472
1500
|
if (signal?.aborted) throw unavailable2();
|
|
1473
1501
|
if (done) break;
|
|
1474
|
-
if (!(value instanceof Uint8Array)) throw
|
|
1475
|
-
if (size += value.byteLength, size > limit) throw new FileOperationError(
|
|
1502
|
+
if (!(value instanceof Uint8Array)) throw new FileOperationError("invalid_input", 400, !1, { reason: "invalid_bytes" });
|
|
1503
|
+
if (size += value.byteLength, size > limit) throw new FileOperationError(
|
|
1504
|
+
"request_too_large",
|
|
1505
|
+
413,
|
|
1506
|
+
!1,
|
|
1507
|
+
{ reason: "size_limit", limitBytes: limit, receivedBytes: size, receivedAtLeast: !0 }
|
|
1508
|
+
);
|
|
1476
1509
|
bytes.set(value, size - value.byteLength);
|
|
1477
1510
|
}
|
|
1478
1511
|
} catch (error) {
|
|
@@ -1524,7 +1557,12 @@ async function send(suffix, init2, descriptor, initial, parent) {
|
|
|
1524
1557
|
})();
|
|
1525
1558
|
if (!response2.ok) {
|
|
1526
1559
|
let value2 = await result(response2), error = value2 && typeof value2 == "object" && "error" in value2 ? value2.error : void 0, received = error && typeof error == "object" && "code" in error ? error.code : void 0, code = failures[response2.status]?.find((code2) => code2 === received);
|
|
1527
|
-
throw code ? new FileOperationError(
|
|
1560
|
+
throw code ? new FileOperationError(
|
|
1561
|
+
code,
|
|
1562
|
+
response2.status,
|
|
1563
|
+
code === "capacity" || code === "unavailable",
|
|
1564
|
+
parseFileErrorDetail(error?.detail)
|
|
1565
|
+
) : unavailable2();
|
|
1528
1566
|
}
|
|
1529
1567
|
if (response2.status !== (descriptor === 204 ? 204 : 200) || descriptor === 204 && response2.body !== null)
|
|
1530
1568
|
throw response2.body?.cancel().catch(() => {
|
|
@@ -1588,7 +1626,7 @@ async function multipartCreate(input, metadata, exchange, release = () => {
|
|
|
1588
1626
|
};
|
|
1589
1627
|
for (let sent = 0, partNumber = 1; sent < metadata.size || metadata.size === 0 && partNumber === 1; partNumber++) {
|
|
1590
1628
|
let length = Math.min(PART_BYTES, metadata.size - sent), part;
|
|
1591
|
-
if (input.file instanceof Uint8Array) part = input.file.subarray(sent, sent + length)
|
|
1629
|
+
if (input.file instanceof Uint8Array) part = new Uint8Array(input.file.subarray(sent, sent + length));
|
|
1592
1630
|
else if (input.file instanceof Blob) {
|
|
1593
1631
|
let slice = input.file.slice(sent, sent + length);
|
|
1594
1632
|
part = new Uint8Array(await request2(() => slice.arrayBuffer()));
|
|
@@ -1717,7 +1755,13 @@ var files = {
|
|
|
1717
1755
|
let blob = input.file instanceof Blob, array3 = input.file instanceof Uint8Array, stream = input.file instanceof ReadableStream;
|
|
1718
1756
|
if (!blob && !array3 && !stream) throw invalid7();
|
|
1719
1757
|
let intrinsic = blob ? input.file.size : array3 ? input.file.byteLength : void 0;
|
|
1720
|
-
if (intrinsic !== void 0 && input.size !== void 0 && input.size !== intrinsic)
|
|
1758
|
+
if (intrinsic !== void 0 && input.size !== void 0 && input.size !== intrinsic)
|
|
1759
|
+
throw !Number.isSafeInteger(input.size) || input.size < 0 ? new FileOperationError("invalid_input", 400, !1, { reason: "invalid_size" }) : new FileOperationError(
|
|
1760
|
+
"invalid_input",
|
|
1761
|
+
400,
|
|
1762
|
+
!1,
|
|
1763
|
+
{ reason: "size_mismatch", expectedBytes: input.size, receivedBytes: intrinsic }
|
|
1764
|
+
);
|
|
1721
1765
|
let name2 = input.name;
|
|
1722
1766
|
name2 === void 0 && blob && "name" in input.file && typeof input.file.name == "string" && (name2 = typeof globalThis.File < "u" && input.file instanceof globalThis.File ? input.file.name : input.file.name.split(/[\\/]/).at(-1));
|
|
1723
1767
|
let mediaType = declaredOriginalMediaType(input.mediaType === void 0 ? blob && input.file.type ? input.file.type : "application/octet-stream" : input.mediaType), metadata = parseFileCreateMetadata({
|
|
@@ -1754,12 +1798,17 @@ var files = {
|
|
|
1754
1798
|
}
|
|
1755
1799
|
return metadata.size > MAX_BYTES ? multipartCreate(input, metadata, (suffix, init2, kind, signal) => kind === !0 ? send(suffix, init2, !0, context, signal) : send(suffix, init2, "control", context, signal)) : timed(async (signal) => {
|
|
1756
1800
|
try {
|
|
1757
|
-
let bytes = array3 ? input.file
|
|
1801
|
+
let bytes = array3 ? new Uint8Array(input.file) : await boundedBytes(
|
|
1758
1802
|
blob ? input.file.stream() : input.file,
|
|
1759
1803
|
metadata.size,
|
|
1760
1804
|
signal
|
|
1761
1805
|
);
|
|
1762
|
-
if (bytes.byteLength !== metadata.size) throw
|
|
1806
|
+
if (bytes.byteLength !== metadata.size) throw new FileOperationError(
|
|
1807
|
+
"invalid_input",
|
|
1808
|
+
400,
|
|
1809
|
+
!1,
|
|
1810
|
+
{ reason: "size_mismatch", expectedBytes: metadata.size, receivedBytes: bytes.byteLength }
|
|
1811
|
+
);
|
|
1763
1812
|
if (signal.aborted) throw unavailable2();
|
|
1764
1813
|
return await send("", {
|
|
1765
1814
|
method: "POST",
|
|
@@ -1785,7 +1834,7 @@ var files = {
|
|
|
1785
1834
|
let buffer = new Uint8Array(Math.min(MAX_BYTES, input.content.length * 3)), encoded = new TextEncoder().encodeInto(input.content, buffer);
|
|
1786
1835
|
if (encoded.read !== input.content.length) throw new FileOperationError("request_too_large", 413);
|
|
1787
1836
|
bytes = buffer.slice(0, encoded.written);
|
|
1788
|
-
} else bytes = input.content
|
|
1837
|
+
} else bytes = new Uint8Array(input.content);
|
|
1789
1838
|
let metadata = parseFileEnrichMetadata({
|
|
1790
1839
|
name: input.name,
|
|
1791
1840
|
size: bytes.byteLength,
|
|
@@ -2130,68 +2179,6 @@ var connections = /* @__PURE__ */ runtimeConnections();
|
|
|
2130
2179
|
|
|
2131
2180
|
var LOCAL_ROUTINE_VALUE_BYTES = 256 * 1024;
|
|
2132
2181
|
|
|
2133
|
-
var RECORD_WRITE_INPUT_LIMIT = 8 * 1024 * 1024, RoutineRecordWriteError = class extends Error {
|
|
2134
|
-
constructor(message2, effect) {
|
|
2135
|
-
super(message2);
|
|
2136
|
-
this.effect = effect;
|
|
2137
|
-
this.name = "RoutineRecordWriteError";
|
|
2138
|
-
}
|
|
2139
|
-
effect;
|
|
2140
|
-
};
|
|
2141
|
-
function validateRecordWrite(value) {
|
|
2142
|
-
let input = serializable(value);
|
|
2143
|
-
if (!input || Array.isArray(input) || typeof input != "object" || Object.keys(input).sort().join(",") !== "records,repository" || !validRepositoryAlias(input.repository) || !Array.isArray(input.records) || !input.records.length || input.records.length > 1e4 || new Set(input.records.map((edit) => edit?.ref)).size !== input.records.length)
|
|
2144
|
-
throw new InboxContractError("Invalid record update batch");
|
|
2145
|
-
for (let edit of input.records) {
|
|
2146
|
-
if (!edit || Array.isArray(edit) || typeof edit != "object" || !validRecordRef(edit.ref))
|
|
2147
|
-
throw new InboxContractError("Invalid record edit");
|
|
2148
|
-
if ("expectedVersion" in edit) {
|
|
2149
|
-
if (Object.keys(edit).sort().join(",") !== "expectedVersion,ref,set" || typeof edit.expectedVersion != "string" || !/^record-v1:[0-9a-f]{64}$/.test(edit.expectedVersion) || !edit.set || Array.isArray(edit.set) || typeof edit.set != "object" || !Object.keys(edit.set).length)
|
|
2150
|
-
throw new InboxContractError("Invalid record edit");
|
|
2151
|
-
continue;
|
|
2152
|
-
}
|
|
2153
|
-
if (Object.keys(edit).sort().join(",") !== "fields,path,ref" || typeof edit.path != "string" || !validCreatePath(edit.path) || !edit.fields || Array.isArray(edit.fields) || typeof edit.fields != "object" || !Object.keys(edit.fields).length)
|
|
2154
|
-
throw new InboxContractError("Invalid record creation");
|
|
2155
|
-
}
|
|
2156
|
-
return structuredClone(input);
|
|
2157
|
-
}
|
|
2158
|
-
function validateRecordUpdate(value) {
|
|
2159
|
-
let input = validateRecordWrite(value);
|
|
2160
|
-
if (input.records.some((record4) => !("expectedVersion" in record4)))
|
|
2161
|
-
throw new InboxContractError("Record creation is unavailable for this operation");
|
|
2162
|
-
return input;
|
|
2163
|
-
}
|
|
2164
|
-
function validateRecordDelete(value) {
|
|
2165
|
-
let input = serializable(value);
|
|
2166
|
-
if (!input || Array.isArray(input) || typeof input != "object" || Object.keys(input).sort().join(",") !== "records,repository" || !validRepositoryAlias(input.repository) || !Array.isArray(input.records) || !input.records.length || input.records.length > 1e4 || new Set(input.records.map((record4) => record4?.ref)).size !== input.records.length)
|
|
2167
|
-
throw new InboxContractError("Invalid record deletion batch");
|
|
2168
|
-
for (let record4 of input.records)
|
|
2169
|
-
if (!record4 || Array.isArray(record4) || typeof record4 != "object" || Object.keys(record4).sort().join(",") !== "expectedVersion,ref" || !validRecordRef(record4.ref) || typeof record4.expectedVersion != "string" || !/^record-v1:[0-9a-f]{64}$/.test(record4.expectedVersion))
|
|
2170
|
-
throw new InboxContractError("Invalid record deletion");
|
|
2171
|
-
return structuredClone(input);
|
|
2172
|
-
}
|
|
2173
|
-
function validateRecordChange(value) {
|
|
2174
|
-
let input = serializable(value);
|
|
2175
|
-
if (!input || Array.isArray(input) || typeof input != "object" || Object.keys(input).sort().join(",") !== "records,repository" || !validRepositoryAlias(input.repository) || !Array.isArray(input.records) || !input.records.length || input.records.length > 1e4 || new Set(input.records.map((record4) => record4?.ref)).size !== input.records.length || new TextEncoder().encode(JSON.stringify(input)).byteLength > RECORD_WRITE_INPUT_LIMIT)
|
|
2176
|
-
throw new InboxContractError("Invalid record change batch");
|
|
2177
|
-
let writes = [], deletes = [];
|
|
2178
|
-
for (let record4 of input.records) {
|
|
2179
|
-
if (!record4 || typeof record4 != "object" || Array.isArray(record4))
|
|
2180
|
-
throw new InboxContractError("Invalid record change");
|
|
2181
|
-
"set" in record4 || "fields" in record4 ? writes.push(record4) : deletes.push(record4);
|
|
2182
|
-
}
|
|
2183
|
-
return writes.length && validateRecordWrite({ repository: input.repository, records: writes }), deletes.length && validateRecordDelete({ repository: input.repository, records: deletes }), structuredClone(input);
|
|
2184
|
-
}
|
|
2185
|
-
function validateRoutineRecordWrite(value) {
|
|
2186
|
-
let body = serializable(value);
|
|
2187
|
-
if (!body || typeof body != "object" || Array.isArray(body) || Object.keys(body).sort().join(",") !== "input,requestKey" || typeof body.requestKey != "string" || !body.requestKey.trim() || body.requestKey !== body.requestKey.trim() || body.requestKey.length > 512)
|
|
2188
|
-
throw new InboxContractError("A stable routine request key is required");
|
|
2189
|
-
return { input: validateRecordChange(body.input), requestKey: body.requestKey };
|
|
2190
|
-
}
|
|
2191
|
-
function validCreatePath(value) {
|
|
2192
|
-
return value === value.trim() && value.endsWith(".md") && !value.startsWith("/") && value.split("/").every((segment) => !!segment && ![".", ".."].includes(segment) && /^[a-zA-Z0-9_.-]+$/.test(segment));
|
|
2193
|
-
}
|
|
2194
|
-
|
|
2195
2182
|
function locationIdentity(location) {
|
|
2196
2183
|
let coordinates = location.provider === "os" ? [location.file] : location.provider === "gmail" ? [location.connection, location.message, location.attachment] : location.provider === "dropbox" ? [location.connection, location.file] : void 0;
|
|
2197
2184
|
if (coordinates?.every((value) => typeof value == "string" && value.length > 0))
|
|
@@ -3008,7 +2995,15 @@ function validPair(value) {
|
|
|
3008
2995
|
return validProof(pair.credential) && validProof(pair.renewalCredential) && pair.credential !== pair.renewalCredential && validBinding(pair.context);
|
|
3009
2996
|
}
|
|
3010
2997
|
|
|
3011
|
-
var
|
|
2998
|
+
var WorkspaceAdmissionError = class extends Error {
|
|
2999
|
+
constructor(status) {
|
|
3000
|
+
super("Workspace admission unavailable");
|
|
3001
|
+
this.status = status;
|
|
3002
|
+
}
|
|
3003
|
+
status;
|
|
3004
|
+
};
|
|
3005
|
+
|
|
3006
|
+
var unavailable3 = () => new InboxOperationError(503, "unavailable"), stale = () => new InboxOperationError(401, "authority_unavailable"), contextFailure = (error) => error instanceof WorkspaceAdmissionError ? new InboxOperationError(error.status, error.status === 401 ? "authority_unavailable" : error.status === 403 ? "access_denied" : "unavailable") : stale();
|
|
3012
3007
|
function hostFailure(error, context) {
|
|
3013
3008
|
return !context.isCurrent() || context.expiresAt <= Date.now() || error instanceof InboxContextError && error.status === 401 ? stale() : error instanceof InboxContextError && error.status === 403 ? new InboxOperationError(403, "access_denied") : unavailable3();
|
|
3014
3009
|
}
|
|
@@ -3016,8 +3011,8 @@ async function send2(operation2, input, admitted) {
|
|
|
3016
3011
|
let envelope = parseInboxRequest({ operation: operation2, input }), context;
|
|
3017
3012
|
try {
|
|
3018
3013
|
context = admitted ?? await resolveAppContext();
|
|
3019
|
-
} catch {
|
|
3020
|
-
throw
|
|
3014
|
+
} catch (error) {
|
|
3015
|
+
throw contextFailure(error);
|
|
3021
3016
|
}
|
|
3022
3017
|
if (context.kind === "local") {
|
|
3023
3018
|
if (!isAppQuestionOperation(operation2)) throw new InboxOperationError(403, "access_denied");
|
|
@@ -3125,8 +3120,8 @@ var inbox = {
|
|
|
3125
3120
|
let items = [], beforeNumber, initial;
|
|
3126
3121
|
try {
|
|
3127
3122
|
initial = await resolveAppContext();
|
|
3128
|
-
} catch {
|
|
3129
|
-
throw
|
|
3123
|
+
} catch (error) {
|
|
3124
|
+
throw contextFailure(error);
|
|
3130
3125
|
}
|
|
3131
3126
|
let host;
|
|
3132
3127
|
try {
|
|
@@ -3164,8 +3159,8 @@ var inbox = {
|
|
|
3164
3159
|
let routines = Object.freeze(supplied.routines), questions = [], beforeNumber, initial;
|
|
3165
3160
|
try {
|
|
3166
3161
|
initial = await resolveAppContext();
|
|
3167
|
-
} catch {
|
|
3168
|
-
throw
|
|
3162
|
+
} catch (error) {
|
|
3163
|
+
throw contextFailure(error);
|
|
3169
3164
|
}
|
|
3170
3165
|
let check = () => {
|
|
3171
3166
|
if (!initial.isCurrent() || initial.expiresAt <= Date.now()) throw stale();
|
|
@@ -3215,14 +3210,15 @@ function init({ auth: input }) {
|
|
|
3215
3210
|
}
|
|
3216
3211
|
let active2 = !0, revision = 0, person, token, binding = {}, local2 = localOrigin ? appContext(localOrigin, () => active2) : void 0, subscription, stopDisposal, resolve = async () => {
|
|
3217
3212
|
if (local2) return local2.resolve();
|
|
3218
|
-
if (!active2 || !endpoint || !workspace || globalThis.location.origin !== endpoint) throw new Error("Hosted Data context unavailable.");
|
|
3213
|
+
if (registered && await registered.ready(), !active2 || !endpoint || !workspace || globalThis.location.origin !== endpoint) throw new Error("Hosted Data context unavailable.");
|
|
3219
3214
|
let started = revision, { data: { session }, error } = await auth2.getSession();
|
|
3220
3215
|
if (!active2 || globalThis.location.origin !== endpoint || started !== revision || error || !session || person !== void 0 && person !== session.user.id)
|
|
3221
3216
|
throw new Error("Hosted Data context unavailable.");
|
|
3222
3217
|
let expiresAt = (session.expires_at ?? 0) * 1e3;
|
|
3223
3218
|
if (!Number.isFinite(expiresAt) || expiresAt <= Date.now() || !session.user.id)
|
|
3224
3219
|
throw new Error("Hosted Data context unavailable.");
|
|
3225
|
-
person = session.user.id
|
|
3220
|
+
if (person = session.user.id, registered && await registered.admitWorkspace(), !active2 || globalThis.location.origin !== endpoint || started !== revision || person !== session.user.id)
|
|
3221
|
+
throw new Error("Hosted Data context unavailable.");
|
|
3226
3222
|
let isCurrent = () => active2 && globalThis.location.origin === endpoint && revision === started && person === session.user.id;
|
|
3227
3223
|
return {
|
|
3228
3224
|
workspace,
|
|
@@ -3369,6 +3365,36 @@ function createBrowserAuth(runtime) {
|
|
|
3369
3365
|
function current2(c, origin, revision) {
|
|
3370
3366
|
return c.session && !validSession(c.session) && expire(c), c.active && c.revision === revision && globalThis.location?.origin === origin;
|
|
3371
3367
|
}
|
|
3368
|
+
async function admitWorkspace(c, origin) {
|
|
3369
|
+
if (origin === "https://auth.oddington.ai" || origin === "http://127.0.0.1:5176") return;
|
|
3370
|
+
let revision = c.revision;
|
|
3371
|
+
if (!current2(c, origin, revision) || !c.session || c.state.pending === "sign_out") throw runtime.failure("stale");
|
|
3372
|
+
if (c.admission?.revision !== revision) {
|
|
3373
|
+
let token = c.session.access_token, result2 = (async () => {
|
|
3374
|
+
let status;
|
|
3375
|
+
try {
|
|
3376
|
+
let response2 = await fetch(`${origin}/api/auth`, {
|
|
3377
|
+
method: "POST",
|
|
3378
|
+
headers: { authorization: `Bearer ${token}` },
|
|
3379
|
+
credentials: "same-origin",
|
|
3380
|
+
redirect: "error",
|
|
3381
|
+
cache: "no-store",
|
|
3382
|
+
signal: AbortSignal.timeout(15e3)
|
|
3383
|
+
});
|
|
3384
|
+
status = response2.status;
|
|
3385
|
+
let value = status === 200 ? await response2.json() : null;
|
|
3386
|
+
status === 200 && value?.admitted !== !0 && (status = 503), status !== 200 && response2.body?.cancel().catch(() => {
|
|
3387
|
+
});
|
|
3388
|
+
} catch {
|
|
3389
|
+
status = 503;
|
|
3390
|
+
}
|
|
3391
|
+
if (!current2(c, origin, revision) || c.state.pending === "sign_out") throw runtime.failure("stale");
|
|
3392
|
+
if (status !== 200) throw new WorkspaceAdmissionError(status === 401 ? 401 : status === 403 ? 403 : 503);
|
|
3393
|
+
})();
|
|
3394
|
+
c.admission = { revision, result: result2 };
|
|
3395
|
+
}
|
|
3396
|
+
if (await c.admission.result, !current2(c, origin, revision)) throw runtime.failure("stale");
|
|
3397
|
+
}
|
|
3372
3398
|
return { create() {
|
|
3373
3399
|
let origin = globalThis.location?.origin ?? "", c = coordinators.get(origin);
|
|
3374
3400
|
c || (c = createCoordinator(origin), coordinators.set(origin, c));
|
|
@@ -3458,7 +3484,9 @@ function createBrowserAuth(runtime) {
|
|
|
3458
3484
|
async sendCode(email) {
|
|
3459
3485
|
if (assertActive(), !validEmail(email)) throw runtime.failure("invalid_input");
|
|
3460
3486
|
await action("send_code", async (provider2) => {
|
|
3461
|
-
let { error } = await provider2.signInWithOtp({ email: email.trim(), options: {
|
|
3487
|
+
let { error } = await provider2.signInWithOtp({ email: email.trim(), options: {
|
|
3488
|
+
shouldCreateUser: origin !== "https://auth.oddington.ai" && origin !== "http://127.0.0.1:5176"
|
|
3489
|
+
} });
|
|
3462
3490
|
if (error && !accountRefusal(error)) throw runtime.failure("unavailable");
|
|
3463
3491
|
});
|
|
3464
3492
|
},
|
|
@@ -3470,6 +3498,11 @@ function createBrowserAuth(runtime) {
|
|
|
3470
3498
|
if (!data3.session || !validSession(data3.session)) throw runtime.failure("invalid_code");
|
|
3471
3499
|
if (!current2(coordinator, origin, revision) && (coordinator.session?.access_token !== data3.session.access_token || coordinator.revision !== revision + 1 || coordinator.state.pending !== "verify_code")) throw runtime.failure("stale");
|
|
3472
3500
|
accept(coordinator, "SIGNED_IN", data3.session);
|
|
3501
|
+
try {
|
|
3502
|
+
await admitWorkspace(coordinator, origin);
|
|
3503
|
+
} catch (error2) {
|
|
3504
|
+
if (!(error2 instanceof WorkspaceAdmissionError)) throw error2;
|
|
3505
|
+
}
|
|
3473
3506
|
});
|
|
3474
3507
|
},
|
|
3475
3508
|
async signOut() {
|
|
@@ -3495,6 +3528,12 @@ function createBrowserAuth(runtime) {
|
|
|
3495
3528
|
}
|
|
3496
3529
|
});
|
|
3497
3530
|
return registerBrowserAuth(handle2, {
|
|
3531
|
+
async ready() {
|
|
3532
|
+
assertActive(), await coordinator.ready, assertActive();
|
|
3533
|
+
},
|
|
3534
|
+
async admitWorkspace() {
|
|
3535
|
+
assertActive(), await admitWorkspace(coordinator, origin), assertActive();
|
|
3536
|
+
},
|
|
3498
3537
|
onDispose(callback) {
|
|
3499
3538
|
return active2 ? (disposalObservers.add(callback), () => {
|
|
3500
3539
|
disposalObservers.delete(callback);
|
|
@@ -3926,18 +3965,11 @@ function validateFileAddOptions(value) {
|
|
|
3926
3965
|
return v;
|
|
3927
3966
|
}
|
|
3928
3967
|
|
|
3929
|
-
var PREPARED_RECORD_INPUT_LIMIT = 64 * 1024 * 1024;
|
|
3930
|
-
|
|
3931
|
-
function routineDataEnvelope(command) {
|
|
3932
|
-
return "apply" in command ? { operation: "changes.publish", captured: command.apply } : { operation: "changes.publish", request: command };
|
|
3933
|
-
}
|
|
3934
|
-
|
|
3935
3968
|
function routinePublicationClient2(options = {}) {
|
|
3936
3969
|
let environment = {}, installed2 = routineTransport(), local2 = installed2?.request ?? void 0, workspace = installed2 ? "installed" : options.workspace ?? environment.OS_ROUTINE_WORKSPACE, capability = options.capability ?? environment.OS_ROUTINE_RUN;
|
|
3937
3970
|
if (!workspace || !/^[a-z0-9][a-z0-9-]*$/.test(workspace) || capability !== void 0 && !/^[0-9a-f]{64}$/.test(capability))
|
|
3938
3971
|
throw new Error("A workspace and valid optional routine capability are required");
|
|
3939
3972
|
let workingRoot = options.workingRoot ?? environment.OS_INBOX_WORKING_ROOT ?? void 0, headers = capability ? { "x-os-run": capability } : {}, request2 = options.request ?? (capability ? async (path, init2) => {
|
|
3940
|
-
if (path === "/api/data") return fetch(`https://${workspace}.oddington.ai/api/data`, { ...init2, redirect: "error" });
|
|
3941
3973
|
let origin = `https://${workspace}.oddington.ai`, target = new URL(path, `${origin}/`);
|
|
3942
3974
|
if (workspace.length > 63 || ["auth", "www", "api", "os"].includes(workspace) || target.origin !== origin || target.search || target.hash || target.username || target.password || !/^\/api\/inbox\/(check|publish|resolve|files)$/.test(path) || init2?.method !== "POST")
|
|
3943
3975
|
throw new Error("Invalid routine endpoint");
|
|
@@ -3947,49 +3979,23 @@ function routinePublicationClient2(options = {}) {
|
|
|
3947
3979
|
});
|
|
3948
3980
|
async function send3(operation2, body) {
|
|
3949
3981
|
if (local2) return await local2(operation2, body);
|
|
3950
|
-
let
|
|
3982
|
+
let response2 = await request2(`/api/inbox/${operation2}`, {
|
|
3951
3983
|
method: "POST",
|
|
3952
|
-
headers: {
|
|
3953
|
-
|
|
3954
|
-
...headers,
|
|
3955
|
-
...writing || reading ? { "x-os-workspace": workspace, "x-os-captured-records": "1" } : {}
|
|
3956
|
-
},
|
|
3957
|
-
body: JSON.stringify(writing ? routineDataEnvelope(validateRoutineRecordWrite(body)) : reading ? { operation: "records.select", request: validateRecordRead(body) } : body)
|
|
3984
|
+
headers: { "content-type": "application/json", ...headers },
|
|
3985
|
+
body: JSON.stringify(body)
|
|
3958
3986
|
}), result2 = await response2.json();
|
|
3959
|
-
if (!response2.ok && operation2 === "records/update")
|
|
3960
|
-
throw new RoutineRecordWriteError(result2.error ?? "Could not confirm routine preparation", result2.effect ?? "uncertain");
|
|
3961
3987
|
if (!response2.ok) throw new Error(result2.error ?? "Could not confirm routine publication");
|
|
3962
3988
|
return result2;
|
|
3963
3989
|
}
|
|
3964
|
-
let transport = {
|
|
3965
|
-
async publish(body) {
|
|
3966
|
-
return await send3("publish", body);
|
|
3967
|
-
},
|
|
3968
|
-
async resolve(body) {
|
|
3969
|
-
await send3("resolve", body);
|
|
3970
|
-
}
|
|
3971
|
-
};
|
|
3972
|
-
async function readRecords(input) {
|
|
3973
|
-
return await send3("records/read", validateRecordRead(input));
|
|
3974
|
-
}
|
|
3975
3990
|
return {
|
|
3976
|
-
...createInboxPublisher(
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
readRecords,
|
|
3980
|
-
async writeRecords(input, options2) {
|
|
3981
|
-
if (!capability && !installed2) throw new Error("A trusted routine launch is required");
|
|
3982
|
-
return await send3("records/update", validateRoutineRecordWrite({ input, requestKey: options2?.requestKey }));
|
|
3983
|
-
},
|
|
3984
|
-
async updateRecords(input, options2) {
|
|
3985
|
-
if (!capability && !installed2) throw new Error("A trusted routine launch is required");
|
|
3986
|
-
return await send3("records/update", validateRoutineRecordWrite({ input: validateRecordUpdate(input), requestKey: options2?.requestKey }));
|
|
3991
|
+
...createInboxPublisher({
|
|
3992
|
+
async publish(body) {
|
|
3993
|
+
return await send3("publish", body);
|
|
3987
3994
|
},
|
|
3988
|
-
async
|
|
3989
|
-
|
|
3990
|
-
return await send3("records/update", validateRoutineRecordWrite({ input: validateRecordDelete(input), requestKey: options2?.requestKey }));
|
|
3995
|
+
async resolve(body) {
|
|
3996
|
+
await send3("resolve", body);
|
|
3991
3997
|
}
|
|
3992
|
-
},
|
|
3998
|
+
}),
|
|
3993
3999
|
files: {
|
|
3994
4000
|
async add(value) {
|
|
3995
4001
|
let input = validateFileAddOptions(value);
|
|
@@ -4010,7 +4016,6 @@ export {
|
|
|
4010
4016
|
FileOperationError,
|
|
4011
4017
|
InboxContractError,
|
|
4012
4018
|
InboxOperationError,
|
|
4013
|
-
RoutineRecordWriteError,
|
|
4014
4019
|
app,
|
|
4015
4020
|
asksBlock,
|
|
4016
4021
|
auth,
|