@oddington/sdk 0.1.4 → 0.1.5
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
CHANGED
|
@@ -42,23 +42,71 @@ const report = await files.create({
|
|
|
42
42
|
const descriptor = await files.get(report.id);
|
|
43
43
|
const response = await files.read(descriptor.id);
|
|
44
44
|
await response.text(); // Also supports blob(), arrayBuffer() and body streaming.
|
|
45
|
+
const reading = await files.enrich({ file: report.id, name: 'read.md', content: readingMarkdown });
|
|
46
|
+
await files.enrich({ file: report.id, name: 'extracted.json', content: JSON.stringify(extractedValue) });
|
|
47
|
+
const current = await files.get(report.id); // Discover retained enrichment descriptors.
|
|
48
|
+
const context = await files.read(current.id, ['read.md', 'extracted.json']);
|
|
49
|
+
await context['read.md'].text();
|
|
50
|
+
await context['extracted.json'].json(); // Each body is independently consumed.
|
|
51
|
+
await files.enrich({ file: report.id, name: 'read.md', content: revisedMarkdown, replace: reading.sha256 });
|
|
52
|
+
await files.upload(report.id); // Move a local File and every retained enrichment to remote storage.
|
|
45
53
|
```
|
|
46
54
|
|
|
47
|
-
Hosted Files retains remote originals
|
|
48
|
-
`local
|
|
55
|
+
Hosted Files retains remote originals and caller-produced named enrichments;
|
|
56
|
+
`storage: 'local'` requires a managed CLI parent. Configure that parent with
|
|
57
|
+
`ODDINGTON_FILES_ROOT` pointing to an empty or already OS-managed dedicated
|
|
58
|
+
directory outside tracked Git content. `ODDINGTON_FILES_DEFAULT=local|remote`
|
|
59
|
+
selects its default; without it, remote remains the default. Per-File `storage`
|
|
60
|
+
overrides it. The root stays private to the parent. Local creation without a
|
|
61
|
+
configured root fails with `unsupported_context`. Files have one durable home;
|
|
62
|
+
`both` is invalid. Local retention establishes no hosted registration.
|
|
63
|
+
Fresh runs recover the same creator's File by its request key, and verify
|
|
64
|
+
retained bytes before returning a descriptor or content. A dead parent fails
|
|
65
|
+
closed. `os routine prepare` seals checked local File references and defers
|
|
66
|
+
new remote Files, their enrichments and explicit uploads until `os routine publish`; legacy Inbox
|
|
67
|
+
file preparation remains additive. Existing hosted Files are read-only during
|
|
68
|
+
preparation.
|
|
69
|
+
`upload(id)` preserves the File descriptor, creator access and existing Source
|
|
70
|
+
references. Local bytes remain authoritative until all remote objects verify;
|
|
71
|
+
retrying a completed upload returns the same File. Later reads and enrichments
|
|
72
|
+
follow remote storage; any remaining managed local bytes are disposable cache.
|
|
73
|
+
An unreferenced local File is disclosed remotely only when explicitly uploaded
|
|
74
|
+
or successfully published as a Source. An authorized hosted caller receives
|
|
75
|
+
non-retryable `local_only` (409) until upload succeeds; unrelated callers receive
|
|
76
|
+
`access_denied`.
|
|
49
77
|
Unnamed inputs require a safe `name`; streams also require declared `size`.
|
|
50
78
|
Blob and byte-array declarations must match their intrinsic size. Originals are
|
|
51
|
-
bounded to 16 MiB, metadata to 4 KiB, and descriptor/error bodies to 16 KiB.
|
|
52
|
-
|
|
79
|
+
bounded to 16 MiB per object, metadata to 4 KiB, and descriptor/error bodies to 16 KiB.
|
|
80
|
+
Operations share four concurrent slots and a 30-second deadline;
|
|
53
81
|
transport retries at most once with the same prepared bytes and request key.
|
|
54
82
|
Omitting a key generates one for that invocation. Supply a stable key to recover
|
|
55
83
|
across processes or fresh routine runs. Caller inputs remain owned by the caller.
|
|
56
|
-
Each request resolves current authority; routine Files calls
|
|
57
|
-
selected workspace's HTTPS OS host
|
|
84
|
+
Each request resolves current authority; hosted routine Files calls use the
|
|
85
|
+
selected workspace's HTTPS OS host, and managed calls use the authenticated
|
|
86
|
+
private parent transport. `FileOperationError` exposes a bounded code,
|
|
58
87
|
status and retryability without server details. Reads return the successful Web
|
|
59
|
-
Response without consuming its body.
|
|
88
|
+
Response without consuming its body. Local transfer is bounded and buffered
|
|
89
|
+
before constructing that Response. Creation grants private creator access;
|
|
60
90
|
workspace membership alone never grants another creator's File.
|
|
61
91
|
|
|
92
|
+
`enrich` accepts a string encoded as UTF-8 or exact `Uint8Array` bytes, a safe
|
|
93
|
+
flat filename, and an optional MIME hint. OS verifies text/Markdown/JSON or
|
|
94
|
+
reduces the content to generic binary metadata; it performs no extraction or
|
|
95
|
+
model work. The first enrichment atomically promotes the storage bundle while
|
|
96
|
+
preserving original bytes, File identity, name, path and access. An identical
|
|
97
|
+
retry returns the retained descriptor. Different content conflicts unless
|
|
98
|
+
`replace` matches the current enrichment hash; repeating a completed replacement
|
|
99
|
+
still succeeds with its preceding hash. There is no enrichment request key.
|
|
100
|
+
|
|
101
|
+
`read(id)` returns the original; `read(id, name)` returns one enrichment.
|
|
102
|
+
`read(id, names)` validates at most 16 distinct safe names, deduplicates repeats,
|
|
103
|
+
and returns an object of unconsumed Responses after all reads succeed. An empty
|
|
104
|
+
list returns an empty object in a valid runtime context. Missing authorized names
|
|
105
|
+
return `invalid_input`; a failed keyed request cancels obtained bodies. Each name
|
|
106
|
+
is read independently with fresh authority; there is no cross-name revision snapshot.
|
|
107
|
+
Names cannot contain separators or use the reserved `original` basename with
|
|
108
|
+
or without an extension. Enrichments share their parent File's access and retention.
|
|
109
|
+
|
|
62
110
|
Read a named repository through the authenticated host context:
|
|
63
111
|
|
|
64
112
|
```ts
|
|
@@ -81,7 +129,7 @@ Binding is inert. Each operation resolves current workspace, endpoint and
|
|
|
81
129
|
short-lived authority from the trusted app host or managed routine context.
|
|
82
130
|
Customer calls cannot configure those values. Missing or expired context
|
|
83
131
|
rejects; a named handle does not expand repository access. During explicit
|
|
84
|
-
`os routine
|
|
132
|
+
`os routine prepare`, the same calls use the matched checkout's committed
|
|
85
133
|
version through a private parent transport. Dirty files are ignored. An OS
|
|
86
134
|
cache outside the checkout reuses derived data; reads require no remote access
|
|
87
135
|
or preparation storage. Later accepted record effects advance the run's view
|
|
@@ -120,21 +168,37 @@ eligibility. Repeat the same bounds when continuing a cursor.
|
|
|
120
168
|
For reads, only `capacity` and `unavailable` are retryable; an error never includes private
|
|
121
169
|
server details. Server boundaries own query bounds and authorization.
|
|
122
170
|
|
|
123
|
-
|
|
171
|
+
Schema-driven creates, guarded updates and guarded deletions use the same handle
|
|
172
|
+
in immediate and prepared routines:
|
|
124
173
|
|
|
125
174
|
```ts
|
|
126
175
|
const changes = general.changes();
|
|
176
|
+
changes.create({
|
|
177
|
+
$ref: 'invoices/INV-124', $filing: { year: '2026' },
|
|
178
|
+
type: 'invoice', number: 'INV-124', status: 'draft', amount: 1200,
|
|
179
|
+
});
|
|
127
180
|
changes.update(first.invoices[0], { amount: 30 });
|
|
181
|
+
changes.delete(first.invoices[1]);
|
|
128
182
|
await changes.validate();
|
|
129
183
|
const publication = await changes.publish();
|
|
130
184
|
```
|
|
131
185
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
186
|
+
Creation captures a complete Record and stable `$ref`, without I/O or a caller
|
|
187
|
+
path. OS resolves exactly one collection and its schema-declared filing path.
|
|
188
|
+
Optional `$filing` supplies only unresolved named safe components and is never
|
|
189
|
+
stored or returned as Record data; missing components reject by name. Updates
|
|
190
|
+
capture only `$ref`, `$version` and explicit fields. Deletion captures only the
|
|
191
|
+
selected `$ref` and `$version`. Links within the deletion set are allowed;
|
|
192
|
+
schema-declared links from any committed Record outside it refuse the whole group,
|
|
193
|
+
even if a proposed update removes that link. Validation checks the complete
|
|
194
|
+
candidate graph without effects; publication repeats authoritative validation
|
|
195
|
+
and applies every create/update/delete in one commit.
|
|
196
|
+
Success returns `{ version, created: [{ $ref, $version }], updated: [{ $ref, $version }], deleted: [{ $ref, $version }] }`
|
|
136
197
|
only after its exact index is ready. Later current reads discover that version.
|
|
137
|
-
|
|
198
|
+
Deleted identities keep their captured versions; deletion creates no new Record
|
|
199
|
+
version or tombstone. A Source deletion removes only that guarded Record, retaining
|
|
200
|
+
its File, enrichments and existing durable uses.
|
|
201
|
+
During `os routine prepare`, publication retains the exact proposal and
|
|
138
202
|
advances only run-local reads. `os routine publish` later applies and indexes
|
|
139
203
|
that immutable proposal; unrelated staged and unstaged work is preserved.
|
|
140
204
|
|
|
@@ -143,8 +207,10 @@ that immutable proposal; unrelated staged and unstaged work is preserved.
|
|
|
143
207
|
retry the same change set to reconcile its retained identity. A transport failure
|
|
144
208
|
also keeps that identity. Concurrent publication calls coalesce, confirmed calls
|
|
145
209
|
replay the result, and changed intent requires a new change set. Updates are
|
|
146
|
-
bounded to 10,000
|
|
147
|
-
|
|
210
|
+
bounded with creates and deletions to 10,000 total operations and the 256 KiB Data
|
|
211
|
+
request limit, with one operation per ref. Search and history are outside this
|
|
212
|
+
Changes slice. Source Records use ordinary schema rules; Data also validates OS
|
|
213
|
+
File identity/access and registers durable uses from its exact ready artifact.
|
|
148
214
|
|
|
149
215
|
Bind a declared Airtable connection with `connections.airtable('project-management')`
|
|
150
216
|
from the package root. Import and binding perform no network or credential work.
|
|
@@ -182,8 +248,14 @@ The defaults read `ODDINGTON_ROUTINE_WORKSPACE`, `ODDINGTON_ROUTINE_RUN` and
|
|
|
182
248
|
`ODDINGTON_INBOX_WORKING_ROOT` (or the current directory). Callers may supply the
|
|
183
249
|
existing `workspace`, `capability` and `workingRoot` options. Runtime context
|
|
184
250
|
provides authority; callers cannot select repository remotes or expand grants.
|
|
185
|
-
|
|
186
|
-
|
|
251
|
+
`os routine run` publishes as capabilities are called. The same calls prepare
|
|
252
|
+
work when launched with `os routine prepare`, and `os routine publish` owns
|
|
253
|
+
explicit publication and recovery of that prepared run. Preparation may contact
|
|
254
|
+
OS for authentication and reads, and declared connections may contact their
|
|
255
|
+
providers; it does not promise offline execution. Execution location and File
|
|
256
|
+
storage are independent of the publication lifecycle. `os routine run --local`
|
|
257
|
+
remains a deprecated compatibility alias for `os routine prepare` and prints
|
|
258
|
+
the replacement command.
|
|
187
259
|
|
|
188
260
|
Retain evidence with `inbox.files.add({ path: 'evidence.txt' })`. Paths stay within
|
|
189
261
|
the invocation's working root and uploaded bytes are verified against the retained
|
package/dist/index.js
CHANGED
|
@@ -5985,9 +5985,9 @@ var init_errors = __esm(() => {
|
|
|
5985
5985
|
}(Error);
|
|
5986
5986
|
CorruptPageTreeError = function(_super) {
|
|
5987
5987
|
__extends(CorruptPageTreeError2, _super);
|
|
5988
|
-
function CorruptPageTreeError2(targetIndex,
|
|
5988
|
+
function CorruptPageTreeError2(targetIndex, operation2) {
|
|
5989
5989
|
var _this = this;
|
|
5990
|
-
var msg = "Failed to " +
|
|
5990
|
+
var msg = "Failed to " + operation2 + " at targetIndex=" + targetIndex + " due to corrupt page tree: It is likely that one or more 'Count' entries are invalid";
|
|
5991
5991
|
_this = _super.call(this, msg) || this;
|
|
5992
5992
|
return _this;
|
|
5993
5993
|
}
|
|
@@ -21329,9 +21329,22 @@ function validateRecordDelete(value) {
|
|
|
21329
21329
|
}
|
|
21330
21330
|
function validateRecordChange(value) {
|
|
21331
21331
|
const input = serializable(value);
|
|
21332
|
-
if (Array.isArray(input
|
|
21333
|
-
|
|
21334
|
-
|
|
21332
|
+
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((record) => record?.ref)).size !== input.records.length || new TextEncoder().encode(JSON.stringify(input)).byteLength > RECORD_WRITE_INPUT_LIMIT)
|
|
21333
|
+
throw new InboxContractError("Invalid record change batch");
|
|
21334
|
+
const writes = [], deletes = [];
|
|
21335
|
+
for (const record of input.records) {
|
|
21336
|
+
if (!record || typeof record !== "object" || Array.isArray(record))
|
|
21337
|
+
throw new InboxContractError("Invalid record change");
|
|
21338
|
+
if ("set" in record || "fields" in record)
|
|
21339
|
+
writes.push(record);
|
|
21340
|
+
else
|
|
21341
|
+
deletes.push(record);
|
|
21342
|
+
}
|
|
21343
|
+
if (writes.length)
|
|
21344
|
+
validateRecordWrite({ repository: input.repository, records: writes });
|
|
21345
|
+
if (deletes.length)
|
|
21346
|
+
validateRecordDelete({ repository: input.repository, records: deletes });
|
|
21347
|
+
return structuredClone(input);
|
|
21335
21348
|
}
|
|
21336
21349
|
function validateRoutineRecordWrite(value) {
|
|
21337
21350
|
const body = serializable(value);
|
|
@@ -21685,11 +21698,13 @@ class DataOperationError extends Error {
|
|
|
21685
21698
|
code;
|
|
21686
21699
|
retryable;
|
|
21687
21700
|
effect;
|
|
21688
|
-
|
|
21701
|
+
component;
|
|
21702
|
+
constructor(code, retryable = false, effect, component) {
|
|
21689
21703
|
super(`Data operation failed: ${code}`);
|
|
21690
21704
|
this.code = code;
|
|
21691
21705
|
this.retryable = retryable;
|
|
21692
21706
|
this.effect = effect;
|
|
21707
|
+
this.component = component;
|
|
21693
21708
|
this.name = "DataOperationError";
|
|
21694
21709
|
}
|
|
21695
21710
|
}
|
|
@@ -21727,14 +21742,20 @@ function captureValue(value, ancestors = new Set, depth = 0) {
|
|
|
21727
21742
|
ancestors.delete(value);
|
|
21728
21743
|
}
|
|
21729
21744
|
}
|
|
21745
|
+
function captureDataDelete(record2) {
|
|
21746
|
+
const identity = mapping(record2);
|
|
21747
|
+
if (!validRecordRef(identity.$ref) || typeof identity.$version !== "string" || !identity.$version.length || identity.$version.length > 512)
|
|
21748
|
+
return invalidChange();
|
|
21749
|
+
return { ref: identity.$ref, expectedVersion: identity.$version };
|
|
21750
|
+
}
|
|
21730
21751
|
function captureDataUpdate(record2, set) {
|
|
21731
|
-
const identity =
|
|
21732
|
-
if (!
|
|
21752
|
+
const identity = captureDataDelete(record2), fields3 = mapping(set);
|
|
21753
|
+
if (!Object.keys(fields3).length || Object.keys(fields3).some((key) => key.startsWith("$") || ["id", "type"].includes(key)))
|
|
21733
21754
|
return invalidChange();
|
|
21734
|
-
return {
|
|
21755
|
+
return { ...identity, set: captureValue(fields3) };
|
|
21735
21756
|
}
|
|
21736
|
-
function captureDataUpdates(value) {
|
|
21737
|
-
if (!Array.isArray(value) || !value.length || value.length > 1e4)
|
|
21757
|
+
function captureDataUpdates(value, allowEmpty = false) {
|
|
21758
|
+
if (!Array.isArray(value) || !allowEmpty && !value.length || value.length > 1e4)
|
|
21738
21759
|
return invalidChange();
|
|
21739
21760
|
const updates = value.map((item) => {
|
|
21740
21761
|
const row = mapping(item);
|
|
@@ -21746,6 +21767,46 @@ function captureDataUpdates(value) {
|
|
|
21746
21767
|
return invalidChange();
|
|
21747
21768
|
return updates;
|
|
21748
21769
|
}
|
|
21770
|
+
function captureDataCreate(value) {
|
|
21771
|
+
const row = mapping(value);
|
|
21772
|
+
if (!validRecordRef(row.$ref) || Object.keys(row).some((key) => key.startsWith("$") && !["$ref", "$filing"].includes(key)))
|
|
21773
|
+
return invalidChange();
|
|
21774
|
+
const { $ref: ref, $filing, ...fields3 } = row;
|
|
21775
|
+
let filing;
|
|
21776
|
+
if ($filing !== undefined) {
|
|
21777
|
+
const components = mapping($filing);
|
|
21778
|
+
if (Object.entries(components).some(([name, value2]) => !/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name) || ["constructor", "prototype"].includes(name) || typeof value2 !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value2)))
|
|
21779
|
+
return invalidChange();
|
|
21780
|
+
filing = { ...components };
|
|
21781
|
+
}
|
|
21782
|
+
return { ref, fields: captureValue(fields3), ...filing ? { filing } : {} };
|
|
21783
|
+
}
|
|
21784
|
+
function captureDataChanges(updatesValue, createsValue = [], deletesValue = []) {
|
|
21785
|
+
const updates = captureDataUpdates(updatesValue, true);
|
|
21786
|
+
if (!Array.isArray(createsValue) || createsValue.length > 1e4)
|
|
21787
|
+
return invalidChange();
|
|
21788
|
+
const creates = createsValue.map((item) => {
|
|
21789
|
+
const row = mapping(item);
|
|
21790
|
+
if (Object.keys(row).some((key) => !["ref", "fields", "filing"].includes(key)) || !Object.hasOwn(row, "ref") || !Object.hasOwn(row, "fields"))
|
|
21791
|
+
return invalidChange();
|
|
21792
|
+
const fields3 = mapping(row.fields);
|
|
21793
|
+
if (Object.keys(fields3).some((key) => key.startsWith("$")))
|
|
21794
|
+
return invalidChange();
|
|
21795
|
+
return captureDataCreate({ ...fields3, $ref: row.ref, ...Object.hasOwn(row, "filing") ? { $filing: row.filing } : {} });
|
|
21796
|
+
});
|
|
21797
|
+
if (!Array.isArray(deletesValue) || deletesValue.length > 1e4)
|
|
21798
|
+
return invalidChange();
|
|
21799
|
+
const deletes = deletesValue.map((item) => {
|
|
21800
|
+
const row = mapping(item);
|
|
21801
|
+
if (Object.keys(row).sort().join(",") !== "expectedVersion,ref")
|
|
21802
|
+
return invalidChange();
|
|
21803
|
+
return captureDataDelete({ $ref: row.ref, $version: row.expectedVersion });
|
|
21804
|
+
});
|
|
21805
|
+
const refs = [...updates, ...creates, ...deletes].map((item) => item.ref);
|
|
21806
|
+
if (!refs.length || refs.length > 1e4 || new Set(refs).size !== refs.length)
|
|
21807
|
+
return invalidChange();
|
|
21808
|
+
return { updates, creates, deletes };
|
|
21809
|
+
}
|
|
21749
21810
|
function checkDataRequestSize(input) {
|
|
21750
21811
|
if (new TextEncoder().encode(JSON.stringify(input)).byteLength > DATA_REQUEST_LIMIT)
|
|
21751
21812
|
throw new DataOperationError("request_too_large", false, "unapplied");
|
|
@@ -21764,7 +21825,7 @@ function dataTransportError(status, value) {
|
|
|
21764
21825
|
const row = error && typeof error === "object" ? error : undefined;
|
|
21765
21826
|
const code = failures[status]?.find((allowed) => allowed === row?.code) ?? "unavailable";
|
|
21766
21827
|
const effect = ["unapplied", "uncertain", "applied"].includes(String(row?.effect)) ? row.effect : undefined;
|
|
21767
|
-
return new DataOperationError(code, ["capacity", "unavailable", "publication_pending"].includes(code), code === "conflict" ? "unapplied" : effect);
|
|
21828
|
+
return new DataOperationError(code, ["capacity", "unavailable", "publication_pending"].includes(code), code === "conflict" ? "unapplied" : effect, code === "invalid_change" && typeof row?.component === "string" && /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(row.component) ? row.component : undefined);
|
|
21768
21829
|
}
|
|
21769
21830
|
|
|
21770
21831
|
// ../../shared/files-contract.ts
|
|
@@ -21787,7 +21848,7 @@ function parseFileDescriptor(value) {
|
|
|
21787
21848
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
21788
21849
|
return invalid();
|
|
21789
21850
|
const f = value;
|
|
21790
|
-
if (Object.keys(f).sort().join(",") !== "details,enrichments,id,mediaType,name,path,sha256,size" || typeof f.id !== "string" || !FILE_ID_PATTERN.test(f.id) || !safeFileName(f.name) || !safeFilePath(f.path) || !Number.isSafeInteger(f.size) || f.size < 0 || typeof f.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(f.sha256) || !f.enrichments || typeof f.enrichments !== "object" || Array.isArray(f.enrichments) ||
|
|
21851
|
+
if (Object.keys(f).sort().join(",") !== "details,enrichments,id,mediaType,name,path,sha256,size" || typeof f.id !== "string" || !FILE_ID_PATTERN.test(f.id) || !safeFileName(f.name) || !safeFilePath(f.path) || !Number.isSafeInteger(f.size) || f.size < 0 || f.size > 16 * 1024 * 1024 || typeof f.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(f.sha256) || !f.enrichments || typeof f.enrichments !== "object" || Array.isArray(f.enrichments) || !f.details || typeof f.details !== "object" || Array.isArray(f.details))
|
|
21791
21852
|
return invalid();
|
|
21792
21853
|
const d = f.details;
|
|
21793
21854
|
if (d.kind === "other") {
|
|
@@ -21798,8 +21859,42 @@ function parseFileDescriptor(value) {
|
|
|
21798
21859
|
return invalid();
|
|
21799
21860
|
} else
|
|
21800
21861
|
return invalid();
|
|
21862
|
+
for (const [name, descriptor] of Object.entries(f.enrichments)) {
|
|
21863
|
+
if (!safeEnrichmentName(name))
|
|
21864
|
+
return invalid();
|
|
21865
|
+
parseEnrichmentDescriptor(descriptor);
|
|
21866
|
+
}
|
|
21867
|
+
if (new TextEncoder().encode(JSON.stringify(f)).byteLength > 16 * 1024)
|
|
21868
|
+
return invalid();
|
|
21801
21869
|
return f;
|
|
21802
21870
|
}
|
|
21871
|
+
function parseEnrichmentDescriptor(value) {
|
|
21872
|
+
const invalid = () => {
|
|
21873
|
+
throw new FileOperationError("unavailable", 503, true);
|
|
21874
|
+
};
|
|
21875
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
21876
|
+
return invalid();
|
|
21877
|
+
const d = value;
|
|
21878
|
+
if (Object.keys(d).sort().join(",") !== "mediaType,sha256,size" || !["application/octet-stream", "text/plain", "text/markdown", "application/json"].includes(d.mediaType) || !Number.isSafeInteger(d.size) || d.size < 0 || d.size > 16 * 1024 * 1024 || typeof d.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(d.sha256))
|
|
21879
|
+
return invalid();
|
|
21880
|
+
return d;
|
|
21881
|
+
}
|
|
21882
|
+
function safeEnrichmentName(value) {
|
|
21883
|
+
return safeFileName(value) && !/^original(?:\.|$)/i.test(value);
|
|
21884
|
+
}
|
|
21885
|
+
function parseFileEnrichMetadata(value, maxBytes) {
|
|
21886
|
+
const invalid = () => {
|
|
21887
|
+
throw new FileOperationError("invalid_input", 400);
|
|
21888
|
+
};
|
|
21889
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
21890
|
+
return invalid();
|
|
21891
|
+
const d = value;
|
|
21892
|
+
if (Object.keys(d).some((key) => !["name", "size", "mediaType", "replace"].includes(key)) || !safeEnrichmentName(d.name) || !Number.isSafeInteger(d.size) || d.size < 0 || d.mediaType !== undefined && (typeof d.mediaType !== "string" || d.mediaType.length > 255 || /[\u0000-\u001f\u007f]/.test(d.mediaType)) || d.replace !== undefined && (typeof d.replace !== "string" || !/^[0-9a-f]{64}$/.test(d.replace)))
|
|
21893
|
+
return invalid();
|
|
21894
|
+
if (d.size > maxBytes)
|
|
21895
|
+
throw new FileOperationError("request_too_large", 413);
|
|
21896
|
+
return d;
|
|
21897
|
+
}
|
|
21803
21898
|
function safeFileName(value) {
|
|
21804
21899
|
return typeof value === "string" && value.length > 0 && value.length <= 255 && value === value.trim() && value !== "." && value !== ".." && !/[\u0000-\u001f\u007f-\u009f/\\:*?"<>|]/u.test(value) && !/[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/.test(value) && !/[. ]$/.test(value) && !/^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(value);
|
|
21805
21900
|
}
|
|
@@ -21818,9 +21913,7 @@ function parseFileCreateMetadata(value, maxBytes) {
|
|
|
21818
21913
|
const path = input.path === undefined ? "Files" : input.path;
|
|
21819
21914
|
if (!safeFileName(input.name) || !safeFilePath(path))
|
|
21820
21915
|
return invalid();
|
|
21821
|
-
if (input.storage
|
|
21822
|
-
throw new FileOperationError("unsupported_context", 400);
|
|
21823
|
-
if (input.storage !== undefined && input.storage !== "remote")
|
|
21916
|
+
if (input.storage !== undefined && input.storage !== "remote" && input.storage !== "local")
|
|
21824
21917
|
return invalid();
|
|
21825
21918
|
if (!Number.isSafeInteger(input.size) || input.size < 0)
|
|
21826
21919
|
return invalid();
|
|
@@ -21842,7 +21935,14 @@ function parseFileCreateMetadata(value, maxBytes) {
|
|
|
21842
21935
|
return invalid();
|
|
21843
21936
|
csv = { encoding, delimiter, headerRow };
|
|
21844
21937
|
}
|
|
21845
|
-
return {
|
|
21938
|
+
return {
|
|
21939
|
+
name: input.name,
|
|
21940
|
+
path,
|
|
21941
|
+
...input.storage ? { storage: input.storage } : {},
|
|
21942
|
+
...csv ? { csv } : {},
|
|
21943
|
+
size: input.size,
|
|
21944
|
+
requestKey: input.requestKey
|
|
21945
|
+
};
|
|
21846
21946
|
}
|
|
21847
21947
|
|
|
21848
21948
|
// ../../shared/os-origin.ts
|
|
@@ -21856,6 +21956,21 @@ function oddingtonOrigin(environment = process.env) {
|
|
|
21856
21956
|
}
|
|
21857
21957
|
|
|
21858
21958
|
// src/routine-local-transport.ts
|
|
21959
|
+
function privateRoutineRequest(environment = typeof process === "undefined" ? {} : process.env) {
|
|
21960
|
+
const prepared = ["ODDINGTON_ROUTINE_LOCAL", "ODDINGTON_ROUTINE_LOCAL_SOCKET", "ODDINGTON_ROUTINE_LOCAL_TOKEN"].some((key) => environment[key] !== undefined);
|
|
21961
|
+
const managed = ["ODDINGTON_FILES_SOCKET", "ODDINGTON_FILES_TOKEN"].some((key) => environment[key] !== undefined);
|
|
21962
|
+
if (!prepared && !managed)
|
|
21963
|
+
return;
|
|
21964
|
+
const socket = prepared ? environment.ODDINGTON_ROUTINE_LOCAL_SOCKET : environment.ODDINGTON_FILES_SOCKET;
|
|
21965
|
+
const token = prepared ? environment.ODDINGTON_ROUTINE_LOCAL_TOKEN : environment.ODDINGTON_FILES_TOKEN;
|
|
21966
|
+
const request = localRoutineRequest(prepared ? environment : {
|
|
21967
|
+
ODDINGTON_ROUTINE_LOCAL: "1",
|
|
21968
|
+
ODDINGTON_ROUTINE_LOCAL_SOCKET: socket,
|
|
21969
|
+
ODDINGTON_ROUTINE_LOCAL_TOKEN: token
|
|
21970
|
+
});
|
|
21971
|
+
const isCurrent = () => prepared ? environment.ODDINGTON_ROUTINE_LOCAL === "1" && environment.ODDINGTON_ROUTINE_LOCAL_SOCKET === socket && environment.ODDINGTON_ROUTINE_LOCAL_TOKEN === token : environment.ODDINGTON_FILES_SOCKET === socket && environment.ODDINGTON_FILES_TOKEN === token;
|
|
21972
|
+
return { request, isCurrent, identity: `private:${socket}` };
|
|
21973
|
+
}
|
|
21859
21974
|
function localRoutineRequest(environment = typeof process === "undefined" ? {} : process.env) {
|
|
21860
21975
|
const {
|
|
21861
21976
|
ODDINGTON_ROUTINE_LOCAL: mode,
|
|
@@ -21865,11 +21980,11 @@ function localRoutineRequest(environment = typeof process === "undefined" ? {} :
|
|
|
21865
21980
|
if (mode === undefined && socket === undefined && token === undefined)
|
|
21866
21981
|
return;
|
|
21867
21982
|
if (mode !== "1" || !socket || !token || !/^[0-9a-f]{64}$/.test(token))
|
|
21868
|
-
throw new Error("
|
|
21983
|
+
throw new Error("Preparation transport unavailable; nothing can publish");
|
|
21869
21984
|
const tcp = /^tcp:\/\/127\.0\.0\.1:([1-9][0-9]{0,4})$/.exec(socket);
|
|
21870
21985
|
const endpoint = tcp && tcp[0] === socket && Number(tcp[1]) <= 65535 ? { hostname: "127.0.0.1", port: Number(tcp[1]) } : socket.startsWith("/") && !socket.startsWith("//") && !socket.includes("\x00") ? { socketPath: socket } : undefined;
|
|
21871
21986
|
if (!endpoint)
|
|
21872
|
-
throw new Error("
|
|
21987
|
+
throw new Error("Preparation transport unavailable; nothing can publish");
|
|
21873
21988
|
return async (operation, input) => {
|
|
21874
21989
|
const { request } = await import("node:http");
|
|
21875
21990
|
return await new Promise((resolve, reject) => {
|
|
@@ -21880,7 +21995,7 @@ function localRoutineRequest(environment = typeof process === "undefined" ? {} :
|
|
|
21880
21995
|
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
21881
21996
|
timeout: 120000
|
|
21882
21997
|
}, (response) => {
|
|
21883
|
-
const
|
|
21998
|
+
const bytes = Buffer.alloc(33554432);
|
|
21884
21999
|
let size = 0;
|
|
21885
22000
|
response.on("data", (chunk) => {
|
|
21886
22001
|
size += chunk.length;
|
|
@@ -21888,17 +22003,37 @@ function localRoutineRequest(environment = typeof process === "undefined" ? {} :
|
|
|
21888
22003
|
response.destroy();
|
|
21889
22004
|
reject(new Error("Local result exceeds supported size"));
|
|
21890
22005
|
} else
|
|
21891
|
-
|
|
22006
|
+
bytes.set(chunk, size - chunk.length);
|
|
21892
22007
|
});
|
|
21893
22008
|
response.on("error", reject);
|
|
21894
22009
|
response.on("end", () => {
|
|
21895
22010
|
try {
|
|
21896
|
-
const result = JSON.parse(
|
|
22011
|
+
const result = JSON.parse(bytes.subarray(0, size).toString());
|
|
21897
22012
|
if (response.statusCode !== 200) {
|
|
22013
|
+
if (operation === "files/request") {
|
|
22014
|
+
const error = result.error;
|
|
22015
|
+
const status = response.statusCode ?? 503;
|
|
22016
|
+
const codes = [
|
|
22017
|
+
"invalid_input",
|
|
22018
|
+
"unsupported_context",
|
|
22019
|
+
"unauthenticated",
|
|
22020
|
+
"access_denied",
|
|
22021
|
+
"request_too_large",
|
|
22022
|
+
"conflict",
|
|
22023
|
+
"local_only",
|
|
22024
|
+
"integrity",
|
|
22025
|
+
"unavailable",
|
|
22026
|
+
"capacity",
|
|
22027
|
+
"method_not_allowed"
|
|
22028
|
+
];
|
|
22029
|
+
if (error?.code && codes.includes(error.code))
|
|
22030
|
+
throw new FileOperationError(error.code, status, error.code === "capacity" || error.code === "unavailable");
|
|
22031
|
+
throw new FileOperationError("unavailable", 503, true);
|
|
22032
|
+
}
|
|
21898
22033
|
if (operation === "data/query") {
|
|
21899
22034
|
throw dataTransportError(response.statusCode ?? 0, result);
|
|
21900
22035
|
}
|
|
21901
|
-
throw new Error(typeof result.error === "string" ? result.error : "
|
|
22036
|
+
throw new Error(typeof result.error === "string" ? result.error : "Preparation failed");
|
|
21902
22037
|
}
|
|
21903
22038
|
resolve(result);
|
|
21904
22039
|
} catch (error) {
|
|
@@ -21906,8 +22041,8 @@ function localRoutineRequest(environment = typeof process === "undefined" ? {} :
|
|
|
21906
22041
|
}
|
|
21907
22042
|
});
|
|
21908
22043
|
});
|
|
21909
|
-
call.on("error", () => reject(new Error("
|
|
21910
|
-
call.on("timeout", () => call.destroy(new Error("
|
|
22044
|
+
call.on("error", () => reject(new Error("Preparation transport unavailable; nothing was published")));
|
|
22045
|
+
call.on("timeout", () => call.destroy(new Error("Preparation timed out")));
|
|
21911
22046
|
call.end(JSON.stringify(input));
|
|
21912
22047
|
});
|
|
21913
22048
|
};
|
|
@@ -21927,6 +22062,9 @@ async function resolveDataContext() {
|
|
|
21927
22062
|
const local = localRoutineRequest();
|
|
21928
22063
|
if (local)
|
|
21929
22064
|
return { local };
|
|
22065
|
+
const managed = privateRoutineRequest();
|
|
22066
|
+
if (managed)
|
|
22067
|
+
return { local: managed.request };
|
|
21930
22068
|
const host = memberContext;
|
|
21931
22069
|
if (host !== undefined) {
|
|
21932
22070
|
const context = await host();
|
|
@@ -21945,8 +22083,14 @@ async function resolveDataContext() {
|
|
|
21945
22083
|
async function resolveFilesContext() {
|
|
21946
22084
|
try {
|
|
21947
22085
|
const environment = typeof process === "undefined" ? {} : process.env;
|
|
21948
|
-
|
|
21949
|
-
|
|
22086
|
+
const local = privateRoutineRequest(environment);
|
|
22087
|
+
if (local)
|
|
22088
|
+
return {
|
|
22089
|
+
url: local.identity,
|
|
22090
|
+
headers: {},
|
|
22091
|
+
isCurrent: local.isCurrent,
|
|
22092
|
+
local: local.request
|
|
22093
|
+
};
|
|
21950
22094
|
const host = memberContext;
|
|
21951
22095
|
if (host) {
|
|
21952
22096
|
const context = await host();
|
|
@@ -21954,13 +22098,14 @@ async function resolveFilesContext() {
|
|
|
21954
22098
|
throw new Error;
|
|
21955
22099
|
return {
|
|
21956
22100
|
...filesContext(context.workspace, context.endpoint, { authorization: `Bearer ${context.accessToken}` }),
|
|
21957
|
-
isCurrent: context.isCurrent
|
|
22101
|
+
isCurrent: context.isCurrent,
|
|
22102
|
+
local: undefined
|
|
21958
22103
|
};
|
|
21959
22104
|
}
|
|
21960
22105
|
if (!/^[0-9a-f]{64}$/.test(environment.ODDINGTON_ROUTINE_RUN ?? ""))
|
|
21961
22106
|
throw new Error;
|
|
21962
22107
|
const workspace = environment.ODDINGTON_ROUTINE_WORKSPACE;
|
|
21963
|
-
return filesContext(workspace, `https://${workspace}.oddington.ai`, { "x-oddington-run": environment.ODDINGTON_ROUTINE_RUN });
|
|
22108
|
+
return { ...filesContext(workspace, `https://${workspace}.oddington.ai`, { "x-oddington-run": environment.ODDINGTON_ROUTINE_RUN }), local: undefined };
|
|
21964
22109
|
} catch {
|
|
21965
22110
|
throw new FileOperationError("unsupported_context", 400);
|
|
21966
22111
|
}
|
|
@@ -22013,34 +22158,71 @@ var data = {
|
|
|
22013
22158
|
},
|
|
22014
22159
|
records: { select: (query) => send({ operation: "records.select", query }) },
|
|
22015
22160
|
changes() {
|
|
22016
|
-
let updates = [], publicationId;
|
|
22161
|
+
let updates = [], creates = [], deletes = [], publicationId;
|
|
22017
22162
|
let pending, confirmed;
|
|
22018
22163
|
const snapshot = () => {
|
|
22019
|
-
const captured =
|
|
22164
|
+
const captured = captureDataChanges(updates, creates, deletes);
|
|
22165
|
+
const input = {
|
|
22166
|
+
updates: captured.updates,
|
|
22167
|
+
...captured.creates.length ? { creates: captured.creates } : {},
|
|
22168
|
+
...captured.deletes.length ? { deletes: captured.deletes } : {}
|
|
22169
|
+
};
|
|
22020
22170
|
checkDataRequestSize({
|
|
22021
22171
|
repository,
|
|
22022
22172
|
operation: "changes.publish",
|
|
22023
|
-
|
|
22173
|
+
...input,
|
|
22024
22174
|
publicationId: publicationId ?? "00000000-0000-4000-8000-000000000000"
|
|
22025
22175
|
});
|
|
22026
|
-
return
|
|
22176
|
+
return input;
|
|
22027
22177
|
};
|
|
22028
22178
|
return {
|
|
22179
|
+
create(record2) {
|
|
22180
|
+
if (publicationId)
|
|
22181
|
+
throw new DataOperationError("invalid_change", false, "unapplied");
|
|
22182
|
+
const next = [...creates, captureDataCreate(record2)];
|
|
22183
|
+
captureDataChanges(updates, next, deletes);
|
|
22184
|
+
checkDataRequestSize({
|
|
22185
|
+
repository,
|
|
22186
|
+
operation: "changes.publish",
|
|
22187
|
+
updates,
|
|
22188
|
+
creates: next,
|
|
22189
|
+
...deletes.length ? { deletes } : {},
|
|
22190
|
+
publicationId: "00000000-0000-4000-8000-000000000000"
|
|
22191
|
+
});
|
|
22192
|
+
creates = next;
|
|
22193
|
+
},
|
|
22029
22194
|
update(record2, set) {
|
|
22030
22195
|
if (publicationId)
|
|
22031
22196
|
throw new DataOperationError("invalid_change", false, "unapplied");
|
|
22032
22197
|
const next = [...updates, captureDataUpdate(record2, set)];
|
|
22033
|
-
|
|
22198
|
+
captureDataChanges(next, creates, deletes);
|
|
22034
22199
|
checkDataRequestSize({
|
|
22035
22200
|
repository,
|
|
22036
22201
|
operation: "changes.publish",
|
|
22037
22202
|
updates: next,
|
|
22203
|
+
...creates.length ? { creates } : {},
|
|
22204
|
+
...deletes.length ? { deletes } : {},
|
|
22038
22205
|
publicationId: "00000000-0000-4000-8000-000000000000"
|
|
22039
22206
|
});
|
|
22040
22207
|
updates = next;
|
|
22041
22208
|
},
|
|
22209
|
+
delete(record2) {
|
|
22210
|
+
if (publicationId)
|
|
22211
|
+
throw new DataOperationError("invalid_change", false, "unapplied");
|
|
22212
|
+
const next = [...deletes, captureDataDelete(record2)];
|
|
22213
|
+
captureDataChanges(updates, creates, next);
|
|
22214
|
+
checkDataRequestSize({
|
|
22215
|
+
repository,
|
|
22216
|
+
operation: "changes.publish",
|
|
22217
|
+
updates,
|
|
22218
|
+
deletes: next,
|
|
22219
|
+
...creates.length ? { creates } : {},
|
|
22220
|
+
publicationId: "00000000-0000-4000-8000-000000000000"
|
|
22221
|
+
});
|
|
22222
|
+
deletes = next;
|
|
22223
|
+
},
|
|
22042
22224
|
async validate() {
|
|
22043
|
-
await send({ operation: "changes.validate",
|
|
22225
|
+
await send({ operation: "changes.validate", ...snapshot() });
|
|
22044
22226
|
},
|
|
22045
22227
|
publish() {
|
|
22046
22228
|
if (confirmed)
|
|
@@ -22053,12 +22235,12 @@ var data = {
|
|
|
22053
22235
|
return Promise.reject(error);
|
|
22054
22236
|
}
|
|
22055
22237
|
publicationId ??= crypto.randomUUID();
|
|
22056
|
-
pending = send({ operation: "changes.publish",
|
|
22238
|
+
pending = send({ operation: "changes.publish", ...captured, publicationId }).then((result) => {
|
|
22057
22239
|
confirmed = structuredClone(result);
|
|
22058
22240
|
return result;
|
|
22059
22241
|
}).catch((error) => {
|
|
22060
22242
|
if (error instanceof DataOperationError)
|
|
22061
|
-
throw new DataOperationError(error.code, error.retryable, error.effect ?? (["unavailable", "publication_pending"].includes(error.code) ? "uncertain" : "unapplied"));
|
|
22243
|
+
throw new DataOperationError(error.code, error.retryable, error.effect ?? (["unavailable", "publication_pending"].includes(error.code) ? "uncertain" : "unapplied"), error.component);
|
|
22062
22244
|
throw new DataOperationError("unavailable", true, "uncertain");
|
|
22063
22245
|
}).finally(() => {
|
|
22064
22246
|
pending = undefined;
|
|
@@ -22086,7 +22268,7 @@ var failures = {
|
|
|
22086
22268
|
401: ["unauthenticated"],
|
|
22087
22269
|
403: ["access_denied"],
|
|
22088
22270
|
405: ["method_not_allowed"],
|
|
22089
|
-
409: ["conflict"],
|
|
22271
|
+
409: ["conflict", "local_only"],
|
|
22090
22272
|
413: ["request_too_large"],
|
|
22091
22273
|
429: ["capacity"],
|
|
22092
22274
|
503: ["integrity", "unavailable"]
|
|
@@ -22144,7 +22326,7 @@ async function result(response) {
|
|
|
22144
22326
|
}
|
|
22145
22327
|
}
|
|
22146
22328
|
async function send(suffix, init, descriptor, initial, parent) {
|
|
22147
|
-
const original = initial
|
|
22329
|
+
const original = initial;
|
|
22148
22330
|
const checkCurrent = () => {
|
|
22149
22331
|
if (parent?.aborted)
|
|
22150
22332
|
throw unavailable();
|
|
@@ -22153,37 +22335,71 @@ async function send(suffix, init, descriptor, initial, parent) {
|
|
|
22153
22335
|
};
|
|
22154
22336
|
for (let attempt = 0;attempt < ATTEMPTS; attempt++) {
|
|
22155
22337
|
checkCurrent();
|
|
22156
|
-
const context =
|
|
22338
|
+
const context = await resolveFilesContext();
|
|
22157
22339
|
checkCurrent();
|
|
22158
22340
|
if (context.url !== original.url)
|
|
22159
22341
|
throw new FileOperationError("unsupported_context", 400);
|
|
22160
22342
|
try {
|
|
22161
|
-
return await
|
|
22162
|
-
const response = await
|
|
22343
|
+
return await (async () => {
|
|
22344
|
+
const response = context.local ? await (async () => {
|
|
22345
|
+
const body = init.body instanceof ArrayBuffer ? new Uint8Array(init.body) : undefined;
|
|
22346
|
+
let binary = "";
|
|
22347
|
+
if (body)
|
|
22348
|
+
for (let offset = 0;offset < body.length; offset += 8192)
|
|
22349
|
+
binary += String.fromCharCode(...body.subarray(offset, offset + 8192));
|
|
22350
|
+
const value2 = await context.local("files/request", {
|
|
22351
|
+
suffix,
|
|
22352
|
+
method: init.method,
|
|
22353
|
+
headers: Object.fromEntries(new Headers(init.headers)),
|
|
22354
|
+
...body ? { bytes: btoa(binary) } : {}
|
|
22355
|
+
});
|
|
22356
|
+
if (value2.bytes !== undefined) {
|
|
22357
|
+
if (value2.bytes.length > Math.ceil(MAX_BYTES / 3) * 4)
|
|
22358
|
+
throw unavailable();
|
|
22359
|
+
const data2 = atob(value2.bytes);
|
|
22360
|
+
return new Response(Uint8Array.from(data2, (c) => c.charCodeAt(0)), { status: value2.status, headers: value2.headers });
|
|
22361
|
+
}
|
|
22362
|
+
return Response.json(value2.json, { status: value2.status, headers: value2.headers });
|
|
22363
|
+
})() : await fetch(`${context.url}${suffix}`, {
|
|
22163
22364
|
...init,
|
|
22164
|
-
signal,
|
|
22365
|
+
signal: parent,
|
|
22165
22366
|
headers: { ...context.headers, ...init.headers },
|
|
22166
22367
|
redirect: "error",
|
|
22167
22368
|
cache: "no-store"
|
|
22168
22369
|
});
|
|
22169
22370
|
if (!response.ok) {
|
|
22170
|
-
const
|
|
22171
|
-
const error =
|
|
22371
|
+
const value2 = await result(response);
|
|
22372
|
+
const error = value2 && typeof value2 === "object" && "error" in value2 ? value2.error : undefined;
|
|
22172
22373
|
const received = error && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
22173
22374
|
const code = failures[response.status]?.find((code2) => code2 === received);
|
|
22174
22375
|
throw code ? new FileOperationError(code, response.status, code === "capacity" || code === "unavailable") : unavailable();
|
|
22175
22376
|
}
|
|
22176
|
-
if (response.status !== 200)
|
|
22377
|
+
if (response.status !== 200) {
|
|
22378
|
+
response.body?.cancel().catch(() => {});
|
|
22177
22379
|
throw unavailable();
|
|
22178
|
-
|
|
22380
|
+
}
|
|
22381
|
+
try {
|
|
22382
|
+
checkCurrent();
|
|
22383
|
+
} catch (error) {
|
|
22384
|
+
response.body?.cancel().catch(() => {});
|
|
22385
|
+
throw error;
|
|
22386
|
+
}
|
|
22179
22387
|
if (!descriptor)
|
|
22180
22388
|
return response;
|
|
22181
|
-
const
|
|
22182
|
-
|
|
22183
|
-
|
|
22389
|
+
const value = await result(response);
|
|
22390
|
+
let parsed;
|
|
22391
|
+
if (descriptor === "enrichment") {
|
|
22392
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
22393
|
+
throw unavailable();
|
|
22394
|
+
const { name, ...metadata } = value;
|
|
22395
|
+
if (!safeEnrichmentName(name))
|
|
22396
|
+
throw unavailable();
|
|
22397
|
+
parsed = { name, ...parseEnrichmentDescriptor(metadata) };
|
|
22398
|
+
} else
|
|
22399
|
+
parsed = parseFileDescriptor(value);
|
|
22184
22400
|
checkCurrent();
|
|
22185
|
-
return
|
|
22186
|
-
}
|
|
22401
|
+
return parsed;
|
|
22402
|
+
})();
|
|
22187
22403
|
} catch (error) {
|
|
22188
22404
|
const failure = error instanceof FileOperationError ? error : unavailable();
|
|
22189
22405
|
if (!failure.retryable || attempt + 1 === ATTEMPTS)
|
|
@@ -22197,7 +22413,77 @@ function idPath(id2) {
|
|
|
22197
22413
|
throw invalid();
|
|
22198
22414
|
return `/${id2}`;
|
|
22199
22415
|
}
|
|
22416
|
+
async function operation(work, initial, parent) {
|
|
22417
|
+
const context = initial ?? await resolveFilesContext();
|
|
22418
|
+
if (active >= MAX_ACTIVE)
|
|
22419
|
+
throw new FileOperationError("capacity", 429, true);
|
|
22420
|
+
active++;
|
|
22421
|
+
return timed(async (signal) => {
|
|
22422
|
+
try {
|
|
22423
|
+
return await work(context, signal);
|
|
22424
|
+
} finally {
|
|
22425
|
+
active--;
|
|
22426
|
+
}
|
|
22427
|
+
}, parent).catch((error) => {
|
|
22428
|
+
throw error instanceof FileOperationError ? error : unavailable();
|
|
22429
|
+
});
|
|
22430
|
+
}
|
|
22431
|
+
function contentPath(id2, name) {
|
|
22432
|
+
const path = idPath(id2);
|
|
22433
|
+
if (name === undefined)
|
|
22434
|
+
return `${path}/content`;
|
|
22435
|
+
if (!safeEnrichmentName(name))
|
|
22436
|
+
throw invalid();
|
|
22437
|
+
return `${path}/enrichments/${encodeURIComponent(name)}/content`;
|
|
22438
|
+
}
|
|
22439
|
+
async function read(id2, names) {
|
|
22440
|
+
const context = await resolveFilesContext();
|
|
22441
|
+
idPath(id2);
|
|
22442
|
+
if (names === undefined || typeof names === "string") {
|
|
22443
|
+
const path = contentPath(id2, names);
|
|
22444
|
+
return operation((context2, signal) => send(path, { method: "GET" }, false, context2, signal), context);
|
|
22445
|
+
}
|
|
22446
|
+
if (!Array.isArray(names))
|
|
22447
|
+
throw invalid();
|
|
22448
|
+
const distinct = [];
|
|
22449
|
+
const seen = new Set;
|
|
22450
|
+
for (const name of names) {
|
|
22451
|
+
if (typeof name !== "string" || !safeEnrichmentName(name))
|
|
22452
|
+
throw invalid();
|
|
22453
|
+
if (seen.has(name))
|
|
22454
|
+
continue;
|
|
22455
|
+
if (seen.size === 16)
|
|
22456
|
+
throw invalid();
|
|
22457
|
+
seen.add(name);
|
|
22458
|
+
distinct.push(name);
|
|
22459
|
+
}
|
|
22460
|
+
const paths = distinct.map((name) => contentPath(id2, name));
|
|
22461
|
+
return timed(async (parent) => {
|
|
22462
|
+
const responses = {};
|
|
22463
|
+
try {
|
|
22464
|
+
for (let i = 0;i < paths.length; i++) {
|
|
22465
|
+
const response = await operation((context2, signal) => send(paths[i], { method: "GET" }, false, context2, signal), context, parent);
|
|
22466
|
+
Object.defineProperty(responses, distinct[i], { value: response, enumerable: true });
|
|
22467
|
+
}
|
|
22468
|
+
if (!context.isCurrent())
|
|
22469
|
+
throw new FileOperationError("unsupported_context", 400);
|
|
22470
|
+
return responses;
|
|
22471
|
+
} catch (error) {
|
|
22472
|
+
for (const response of Object.values(responses))
|
|
22473
|
+
response.body?.cancel().catch(() => {});
|
|
22474
|
+
throw error;
|
|
22475
|
+
}
|
|
22476
|
+
});
|
|
22477
|
+
}
|
|
22200
22478
|
var files = {
|
|
22479
|
+
async upload(id2) {
|
|
22480
|
+
return operation(async (context, signal) => {
|
|
22481
|
+
const file = await send(`${idPath(id2)}/upload`, { method: "POST" }, true, context, signal);
|
|
22482
|
+
if (file.id !== id2)
|
|
22483
|
+
throw unavailable();
|
|
22484
|
+
return file;
|
|
22485
|
+
});
|
|
22486
|
+
},
|
|
22201
22487
|
async create(input, options) {
|
|
22202
22488
|
const context = await resolveFilesContext();
|
|
22203
22489
|
if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key) => !["file", "size", "path", "name", "storage", "csv"].includes(key)) || options !== undefined && (!options || typeof options !== "object" || Array.isArray(options) || Object.keys(options).some((key) => key !== "requestKey")))
|
|
@@ -22222,6 +22508,8 @@ var files = {
|
|
|
22222
22508
|
csv: input.csv,
|
|
22223
22509
|
requestKey: options?.requestKey === undefined ? crypto.randomUUID() : options.requestKey
|
|
22224
22510
|
}, MAX_BYTES);
|
|
22511
|
+
if (!context.local && metadata.storage === "local")
|
|
22512
|
+
throw new FileOperationError("unsupported_context", 400);
|
|
22225
22513
|
const header = encodeURIComponent(JSON.stringify(metadata));
|
|
22226
22514
|
if (new TextEncoder().encode(header).byteLength > METADATA_BYTES)
|
|
22227
22515
|
throw invalid();
|
|
@@ -22247,12 +22535,45 @@ var files = {
|
|
|
22247
22535
|
throw error instanceof FileOperationError ? error : unavailable();
|
|
22248
22536
|
});
|
|
22249
22537
|
},
|
|
22538
|
+
async enrich(input) {
|
|
22539
|
+
const context = await resolveFilesContext();
|
|
22540
|
+
if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key) => !["file", "name", "content", "mediaType", "replace"].includes(key)) || typeof input.content !== "string" && !(input.content instanceof Uint8Array))
|
|
22541
|
+
throw invalid();
|
|
22542
|
+
const path = `${idPath(input.file)}/enrichments`;
|
|
22543
|
+
if (typeof input.content === "string" ? input.content.length > MAX_BYTES : input.content.byteLength > MAX_BYTES)
|
|
22544
|
+
throw new FileOperationError("request_too_large", 413);
|
|
22545
|
+
return operation(async (context2, signal) => {
|
|
22546
|
+
let bytes;
|
|
22547
|
+
if (typeof input.content === "string") {
|
|
22548
|
+
const buffer = new Uint8Array(Math.min(MAX_BYTES, input.content.length * 3));
|
|
22549
|
+
const encoded = new TextEncoder().encodeInto(input.content, buffer);
|
|
22550
|
+
if (encoded.read !== input.content.length)
|
|
22551
|
+
throw new FileOperationError("request_too_large", 413);
|
|
22552
|
+
bytes = buffer.slice(0, encoded.written);
|
|
22553
|
+
} else
|
|
22554
|
+
bytes = input.content.slice();
|
|
22555
|
+
const metadata = parseFileEnrichMetadata({
|
|
22556
|
+
name: input.name,
|
|
22557
|
+
size: bytes.byteLength,
|
|
22558
|
+
...input.mediaType === undefined ? {} : { mediaType: input.mediaType },
|
|
22559
|
+
...input.replace === undefined ? {} : { replace: input.replace }
|
|
22560
|
+
}, MAX_BYTES);
|
|
22561
|
+
const header = encodeURIComponent(JSON.stringify(metadata));
|
|
22562
|
+
if (new TextEncoder().encode(header).byteLength > METADATA_BYTES)
|
|
22563
|
+
throw invalid();
|
|
22564
|
+
const result2 = await send(path, { method: "POST", headers: {
|
|
22565
|
+
"content-type": "application/octet-stream",
|
|
22566
|
+
"x-oddington-enrichment": header
|
|
22567
|
+
}, body: bytes.buffer }, "enrichment", context2, signal);
|
|
22568
|
+
if (result2.name !== metadata.name)
|
|
22569
|
+
throw unavailable();
|
|
22570
|
+
return result2;
|
|
22571
|
+
}, context);
|
|
22572
|
+
},
|
|
22250
22573
|
async get(id2) {
|
|
22251
|
-
return send(idPath(id2), { method: "GET" }, true);
|
|
22574
|
+
return operation((context, signal) => send(idPath(id2), { method: "GET" }, true, context, signal));
|
|
22252
22575
|
},
|
|
22253
|
-
|
|
22254
|
-
return send(`${idPath(id2)}/content`, { method: "GET" }, false);
|
|
22255
|
-
}
|
|
22576
|
+
read
|
|
22256
22577
|
};
|
|
22257
22578
|
// src/os.ts
|
|
22258
22579
|
var installedDispose;
|
|
@@ -22336,16 +22657,16 @@ function routinePublicationClient(options = {}) {
|
|
|
22336
22657
|
} : async () => {
|
|
22337
22658
|
throw new Error("Member publication requires a host-supplied request");
|
|
22338
22659
|
});
|
|
22339
|
-
async function send2(
|
|
22660
|
+
async function send2(operation2, body) {
|
|
22340
22661
|
if (local)
|
|
22341
|
-
return await local(
|
|
22342
|
-
const response = await request(`/api/cli/workspaces/${workspace}/inbox/${
|
|
22662
|
+
return await local(operation2, body);
|
|
22663
|
+
const response = await request(`/api/cli/workspaces/${workspace}/inbox/${operation2}`, {
|
|
22343
22664
|
method: "POST",
|
|
22344
22665
|
headers: { "content-type": "application/json", ...headers },
|
|
22345
22666
|
body: JSON.stringify(body)
|
|
22346
22667
|
});
|
|
22347
22668
|
const result2 = await response.json();
|
|
22348
|
-
if (!response.ok &&
|
|
22669
|
+
if (!response.ok && operation2 === "records/update")
|
|
22349
22670
|
throw new RoutineRecordWriteError(result2.error ?? "Could not confirm routine preparation", result2.effect ?? "uncertain");
|
|
22350
22671
|
if (!response.ok)
|
|
22351
22672
|
throw new Error(result2.error ?? "Could not confirm routine publication");
|
|
@@ -13,16 +13,22 @@ export { AirtableOperationError } from '../../../shared/airtable-contract.js';
|
|
|
13
13
|
export type { AirtableField, AirtableTable, AirtableRecord, AirtableListInput, AirtableGetInput, AirtableCreateInput, AirtableUpdateInput, AirtableDeleteInput, AirtableReceipt, AirtableWriteResult, AirtableClient, Connections } from '../../../shared/airtable-contract.js';
|
|
14
14
|
export declare const connections: import('../../../shared/airtable-contract.js').Connections;
|
|
15
15
|
export { DataOperationError } from '../../../shared/data-contract.js';
|
|
16
|
-
export type { DataRepository, DataErrorCode, PublicValueDefinition, PublicField, CollectionSummary, CollectionSchema, QueryScalar, SelectionOptions, RecordSelection, RecordsQuery, DataValue, SelectedRecord, SelectionMetadata, RecordsResult, RecordIdentity, RecordUpdateSet, DataPublication, DataChangeSet, DataEffect } from '../../../shared/data-contract.js';
|
|
16
|
+
export type { DataRepository, DataErrorCode, PublicValueDefinition, PublicField, CollectionSummary, CollectionSchema, QueryScalar, SelectionOptions, RecordSelection, RecordsQuery, DataValue, SelectedRecord, SelectionMetadata, RecordsResult, RecordIdentity, RecordCreation, RecordUpdateSet, DataPublication, DataChangeSet, DataEffect } from '../../../shared/data-contract.js';
|
|
17
17
|
export declare const data: {
|
|
18
18
|
repository(name: string): import('../../../shared/data-contract.js').DataRepository;
|
|
19
19
|
};
|
|
20
20
|
export { FileOperationError } from '../../../shared/files-contract.js';
|
|
21
|
-
export type { File, FileInput, FileStorage, FileCreateInput, FileCreateOptions, FileErrorCode } from '../../../shared/files-contract.js';
|
|
21
|
+
export type { File, FileInput, FileStorage, FileCreateInput, FileCreateOptions, FileEnrichment, FileEnrichOptions, FileEnrichResult, FileErrorCode } from '../../../shared/files-contract.js';
|
|
22
22
|
export declare const files: {
|
|
23
23
|
create(input: import('../../../shared/files-contract.js').FileCreateInput, options?: import('../../../shared/files-contract.js').FileCreateOptions): Promise<import('../../../shared/files-contract.js').File>;
|
|
24
24
|
get(id: string): Promise<import('../../../shared/files-contract.js').File>;
|
|
25
|
+
upload(id: string): Promise<import('../../../shared/files-contract.js').File>;
|
|
26
|
+
enrich(input: import('../../../shared/files-contract.js').FileEnrichOptions): Promise<import('../../../shared/files-contract.js').FileEnrichResult>;
|
|
25
27
|
read(id: string): Promise<Response>;
|
|
28
|
+
read(id: string, name: string): Promise<Response>;
|
|
29
|
+
read<const Names extends readonly string[]>(id: string, names: Names): Promise<{
|
|
30
|
+
[Name in Names[number]]: Response;
|
|
31
|
+
}>;
|
|
26
32
|
};
|
|
27
33
|
/** The host's existing refreshing session source; supplies identity, never membership. */
|
|
28
34
|
export type HostedSessionSource = {
|
|
@@ -108,14 +108,23 @@ export type RecordsResult = {
|
|
|
108
108
|
};
|
|
109
109
|
export type RecordIdentity = Pick<SelectedRecord, '$ref' | '$version'>;
|
|
110
110
|
export type RecordUpdateSet = Record<string, DataValue>;
|
|
111
|
+
export type RecordCreation = {
|
|
112
|
+
$ref: string;
|
|
113
|
+
$filing?: Record<string, string>;
|
|
114
|
+
$version?: never;
|
|
115
|
+
$path?: never;
|
|
116
|
+
[field: string]: DataValue | undefined;
|
|
117
|
+
};
|
|
111
118
|
export type DataPublication = {
|
|
112
119
|
version: string;
|
|
113
|
-
created: [];
|
|
120
|
+
created: RecordIdentity[];
|
|
114
121
|
updated: RecordIdentity[];
|
|
115
|
-
deleted: [];
|
|
122
|
+
deleted: RecordIdentity[];
|
|
116
123
|
};
|
|
117
124
|
export interface DataChangeSet {
|
|
125
|
+
create(record: RecordCreation): void;
|
|
118
126
|
update(record: RecordIdentity, set: RecordUpdateSet): void;
|
|
127
|
+
delete(record: RecordIdentity): void;
|
|
119
128
|
validate(): Promise<void>;
|
|
120
129
|
publish(): Promise<DataPublication>;
|
|
121
130
|
}
|
|
@@ -135,6 +144,7 @@ export declare class DataOperationError extends Error {
|
|
|
135
144
|
readonly code: DataErrorCode;
|
|
136
145
|
readonly retryable: boolean;
|
|
137
146
|
readonly effect?: DataEffect | undefined;
|
|
138
|
-
|
|
147
|
+
readonly component?: string | undefined;
|
|
148
|
+
constructor(code: DataErrorCode, retryable?: boolean, effect?: DataEffect | undefined, component?: string | undefined);
|
|
139
149
|
}
|
|
140
150
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Provider-neutral authored contract. Registries and object locations stay server-side. */
|
|
2
|
-
export type FileStorage = 'local' | 'remote'
|
|
2
|
+
export type FileStorage = 'local' | 'remote';
|
|
3
3
|
export type FileInput = Blob | Uint8Array | ReadableStream<Uint8Array>;
|
|
4
4
|
export type FileCsvHints = {
|
|
5
5
|
encoding?: 'utf-8' | 'windows-1252';
|
|
@@ -17,6 +17,21 @@ export type FileCreateInput = {
|
|
|
17
17
|
export type FileCreateOptions = {
|
|
18
18
|
requestKey?: string;
|
|
19
19
|
};
|
|
20
|
+
export type FileEnrichment = {
|
|
21
|
+
mediaType: string;
|
|
22
|
+
size: number;
|
|
23
|
+
sha256: string;
|
|
24
|
+
};
|
|
25
|
+
export type FileEnrichOptions = {
|
|
26
|
+
file: string;
|
|
27
|
+
name: string;
|
|
28
|
+
content: string | Uint8Array;
|
|
29
|
+
mediaType?: string;
|
|
30
|
+
replace?: string;
|
|
31
|
+
};
|
|
32
|
+
export type FileEnrichResult = FileEnrichment & {
|
|
33
|
+
name: string;
|
|
34
|
+
};
|
|
20
35
|
export type File = {
|
|
21
36
|
id: string;
|
|
22
37
|
name: string;
|
|
@@ -24,11 +39,7 @@ export type File = {
|
|
|
24
39
|
mediaType: string;
|
|
25
40
|
size: number;
|
|
26
41
|
sha256: string;
|
|
27
|
-
enrichments: Record<string,
|
|
28
|
-
mediaType: string;
|
|
29
|
-
size: number;
|
|
30
|
-
sha256: string;
|
|
31
|
-
}>;
|
|
42
|
+
enrichments: Record<string, FileEnrichment>;
|
|
32
43
|
details: {
|
|
33
44
|
kind: 'csv';
|
|
34
45
|
encoding: 'utf-8' | 'windows-1252';
|
|
@@ -42,7 +53,7 @@ export type File = {
|
|
|
42
53
|
kind: 'other';
|
|
43
54
|
};
|
|
44
55
|
};
|
|
45
|
-
export type FileErrorCode = 'invalid_input' | 'unsupported_context' | 'unauthenticated' | 'access_denied' | 'request_too_large' | 'conflict' | 'integrity' | 'unavailable' | 'capacity' | 'method_not_allowed';
|
|
56
|
+
export type FileErrorCode = 'invalid_input' | 'unsupported_context' | 'unauthenticated' | 'access_denied' | 'request_too_large' | 'conflict' | 'local_only' | 'integrity' | 'unavailable' | 'capacity' | 'method_not_allowed';
|
|
46
57
|
export declare class FileOperationError extends Error {
|
|
47
58
|
readonly code: FileErrorCode;
|
|
48
59
|
readonly status: number;
|
|
@@ -52,13 +63,22 @@ export declare class FileOperationError extends Error {
|
|
|
52
63
|
export declare const FILE_ID_PATTERN: RegExp;
|
|
53
64
|
/** Treat descriptor transport as untrusted; no provider fields escape the boundary. */
|
|
54
65
|
export declare function parseFileDescriptor(value: unknown): File;
|
|
66
|
+
export declare function parseEnrichmentDescriptor(value: unknown): FileEnrichment;
|
|
67
|
+
export declare function safeEnrichmentName(value: unknown): value is string;
|
|
68
|
+
export type FileEnrichMetadata = {
|
|
69
|
+
name: string;
|
|
70
|
+
size: number;
|
|
71
|
+
mediaType?: string;
|
|
72
|
+
replace?: string;
|
|
73
|
+
};
|
|
74
|
+
export declare function parseFileEnrichMetadata(value: unknown, maxBytes: number): FileEnrichMetadata;
|
|
55
75
|
export declare function safeFileName(value: unknown): value is string;
|
|
56
76
|
export declare function safeFilePath(value: unknown): value is string;
|
|
57
77
|
/** Private transport metadata: normalized material options exclude the request key and size declaration. */
|
|
58
78
|
export type FileCreateMetadata = {
|
|
59
79
|
name: string;
|
|
60
80
|
path: string;
|
|
61
|
-
storage
|
|
81
|
+
storage?: FileStorage;
|
|
62
82
|
csv?: Required<FileCsvHints>;
|
|
63
83
|
size: number;
|
|
64
84
|
requestKey: string;
|
|
@@ -12,7 +12,11 @@ export type RecordDeleteRequest = {
|
|
|
12
12
|
repository: string;
|
|
13
13
|
records: RecordDelete[];
|
|
14
14
|
};
|
|
15
|
-
export type
|
|
15
|
+
export type RecordChange = RecordWrite | RecordDelete;
|
|
16
|
+
export type RecordChangeRequest = {
|
|
17
|
+
repository: string;
|
|
18
|
+
records: RecordChange[];
|
|
19
|
+
};
|
|
16
20
|
export type RoutineRecordWriteRequest = {
|
|
17
21
|
input: RecordChangeRequest;
|
|
18
22
|
requestKey: string;
|