@oddington/sdk 0.1.3 → 0.1.4
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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The importable client for Oddington apps, routines and response handlers.
|
|
4
4
|
Node operations require Node.js 22.12.0 or later. The root import and hosted
|
|
5
|
-
Data
|
|
5
|
+
Data and Files APIs work in browsers. Installing the package grants no authority.
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
8
|
npm install @oddington/sdk
|
|
@@ -29,6 +29,36 @@ session subscription. Workspace existence and member access are checked by the
|
|
|
29
29
|
OS service. App modules use capability namespaces; routines and handlers receive
|
|
30
30
|
runtime context without calling `os.init`.
|
|
31
31
|
|
|
32
|
+
Create and recover a private immutable original through the same host context:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { files } from '@oddington/sdk';
|
|
36
|
+
|
|
37
|
+
const report = await files.create({
|
|
38
|
+
file: selectedFile, // Web File, path-backed Blob, Uint8Array or byte stream
|
|
39
|
+
name: 'September statement.csv',
|
|
40
|
+
path: 'HSC/2026/09',
|
|
41
|
+
}, { requestKey: 'statement/2026/09' });
|
|
42
|
+
const descriptor = await files.get(report.id);
|
|
43
|
+
const response = await files.read(descriptor.id);
|
|
44
|
+
await response.text(); // Also supports blob(), arrayBuffer() and body streaming.
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Hosted Files retains remote originals only, with empty `enrichments`. It rejects
|
|
48
|
+
`local`, `both` and explicit routine local preparation with `unsupported_context`.
|
|
49
|
+
Unnamed inputs require a safe `name`; streams also require declared `size`.
|
|
50
|
+
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
|
+
Creates allow four concurrent buffered operations and a 30-second deadline;
|
|
53
|
+
transport retries at most once with the same prepared bytes and request key.
|
|
54
|
+
Omitting a key generates one for that invocation. Supply a stable key to recover
|
|
55
|
+
across processes or fresh routine runs. Caller inputs remain owned by the caller.
|
|
56
|
+
Each request resolves current authority; routine Files calls go directly to the
|
|
57
|
+
selected workspace's HTTPS OS host. `FileOperationError` exposes a bounded code,
|
|
58
|
+
status and retryability without server details. Reads return the successful Web
|
|
59
|
+
Response without consuming its body. Creation grants private creator access;
|
|
60
|
+
workspace membership alone never grants another creator's File.
|
|
61
|
+
|
|
32
62
|
Read a named repository through the authenticated host context:
|
|
33
63
|
|
|
34
64
|
```ts
|
|
@@ -62,13 +92,60 @@ Each graph selection sends one request and returns only selected canonical
|
|
|
62
92
|
values, expanded forward links and exact opaque public versions. Cursors
|
|
63
93
|
continue the exact retained artifact and collection selection, even after
|
|
64
94
|
replacement; repeat its options and links while omitting unrelated collections.
|
|
65
|
-
No automatic retry, separate version fetch, search
|
|
66
|
-
|
|
95
|
+
No automatic retry, separate version fetch, search or history operation is
|
|
96
|
+
included. Collection schemas omit Markdown storage hints.
|
|
97
|
+
|
|
98
|
+
`where` combines field conditions with AND. Alongside exact scalar/null equality
|
|
99
|
+
and `$in`, declared one-valued `integer`, `number` and `date` fields accept
|
|
100
|
+
`$gt`, `$gte`, `$lt` and `$lte`:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
await general.records.select({ invoices: { $: { where: {
|
|
104
|
+
amount: { $gte: 10, $lte: 20 },
|
|
105
|
+
date: { $gte: '2026-10-01', $lt: '2026-11-01' },
|
|
106
|
+
month: { $gte: '2026-10', $lte: '2026-10' },
|
|
107
|
+
}, count: true } } });
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Supply one lower bound, one upper bound, or one of each. Bounds cannot combine
|
|
111
|
+
`$gt` with `$gte`, `$lt` with `$lte`, or any bound with `$in`. Operands must be
|
|
112
|
+
valid for the declared field, including numeric precision and real day/month
|
|
113
|
+
dates; values are never coerced. Missing and null values never match bounds.
|
|
114
|
+
A valid interval without matches returns an empty array and requested zero
|
|
115
|
+
count. V0 types check expression structure; runtime schema validation owns field
|
|
116
|
+
eligibility. Repeat the same bounds when continuing a cursor.
|
|
117
|
+
|
|
67
118
|
`DataOperationError.code` distinguishes `context_unavailable`, `unauthenticated`,
|
|
68
119
|
`access_denied`, `invalid_query`, `request_too_large`, `capacity` and `unavailable`.
|
|
69
|
-
|
|
120
|
+
For reads, only `capacity` and `unavailable` are retryable; an error never includes private
|
|
70
121
|
server details. Server boundaries own query bounds and authorization.
|
|
71
122
|
|
|
123
|
+
Guarded updates use the same handle in hosted and explicitly local routines:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const changes = general.changes();
|
|
127
|
+
changes.update(first.invoices[0], { amount: 30 });
|
|
128
|
+
await changes.validate();
|
|
129
|
+
const publication = await changes.publish();
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Preparation captures only the Record's `$ref`, `$version` and explicit set,
|
|
133
|
+
without I/O. Validation checks the complete committed result without effects;
|
|
134
|
+
publication repeats guarded validation and applies the whole update batch.
|
|
135
|
+
Success returns `{ version, created: [], updated: [{ $ref, $version }], deleted: [] }`
|
|
136
|
+
only after its exact index is ready. Later current reads discover that version.
|
|
137
|
+
During `os routine run --local`, publication retains the exact proposal and
|
|
138
|
+
advances only run-local reads. `os routine publish` later applies and indexes
|
|
139
|
+
that immutable proposal; unrelated staged and unstaged work is preserved.
|
|
140
|
+
|
|
141
|
+
`invalid_change` and known-unapplied `conflict` reject the batch.
|
|
142
|
+
`publication_pending` is retryable and may report `uncertain` or `applied`;
|
|
143
|
+
retry the same change set to reconcile its retained identity. A transport failure
|
|
144
|
+
also keeps that identity. Concurrent publication calls coalesce, confirmed calls
|
|
145
|
+
replay the result, and changed intent requires a new change set. Updates are
|
|
146
|
+
bounded to 10,000 Records and the 256 KiB Data request limit. Create, delete,
|
|
147
|
+
`$filing`, search and history are outside this supported Changes slice.
|
|
148
|
+
|
|
72
149
|
Bind a declared Airtable connection with `connections.airtable('project-management')`
|
|
73
150
|
from the package root. Import and binding perform no network or credential work.
|
|
74
151
|
The client exposes `tables.list()` and `records.list`, `get`, `create`, `update`
|
package/dist/index.js
CHANGED
|
@@ -691,8 +691,8 @@ function __awaiter(thisArg, _arguments, P, generator) {
|
|
|
691
691
|
reject(e);
|
|
692
692
|
}
|
|
693
693
|
}
|
|
694
|
-
function step(
|
|
695
|
-
|
|
694
|
+
function step(result2) {
|
|
695
|
+
result2.done ? resolve(result2.value) : adopt(result2.value).then(fulfilled, rejected);
|
|
696
696
|
}
|
|
697
697
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
698
698
|
});
|
|
@@ -1332,19 +1332,19 @@ var require_common = __commonJS((exports) => {
|
|
|
1332
1332
|
}
|
|
1333
1333
|
},
|
|
1334
1334
|
flattenChunks: function(chunks) {
|
|
1335
|
-
var i2, l, len, pos, chunk,
|
|
1335
|
+
var i2, l, len, pos, chunk, result2;
|
|
1336
1336
|
len = 0;
|
|
1337
1337
|
for (i2 = 0, l = chunks.length;i2 < l; i2++) {
|
|
1338
1338
|
len += chunks[i2].length;
|
|
1339
1339
|
}
|
|
1340
|
-
|
|
1340
|
+
result2 = new Uint8Array(len);
|
|
1341
1341
|
pos = 0;
|
|
1342
1342
|
for (i2 = 0, l = chunks.length;i2 < l; i2++) {
|
|
1343
1343
|
chunk = chunks[i2];
|
|
1344
|
-
|
|
1344
|
+
result2.set(chunk, pos);
|
|
1345
1345
|
pos += chunk.length;
|
|
1346
1346
|
}
|
|
1347
|
-
return
|
|
1347
|
+
return result2;
|
|
1348
1348
|
}
|
|
1349
1349
|
};
|
|
1350
1350
|
var fnUntyped = {
|
|
@@ -3130,11 +3130,11 @@ var require_strings = __commonJS((exports) => {
|
|
|
3130
3130
|
return String.fromCharCode.apply(null, utils.shrinkBuf(buf, len));
|
|
3131
3131
|
}
|
|
3132
3132
|
}
|
|
3133
|
-
var
|
|
3133
|
+
var result2 = "";
|
|
3134
3134
|
for (var i2 = 0;i2 < len; i2++) {
|
|
3135
|
-
|
|
3135
|
+
result2 += String.fromCharCode(buf[i2]);
|
|
3136
3136
|
}
|
|
3137
|
-
return
|
|
3137
|
+
return result2;
|
|
3138
3138
|
}
|
|
3139
3139
|
exports.buf2binstring = function(buf) {
|
|
3140
3140
|
return buf2binstring(buf, buf.length);
|
|
@@ -7323,40 +7323,40 @@ var init_PDFContext = __esm(() => {
|
|
|
7323
7323
|
types[_i - 1] = arguments[_i];
|
|
7324
7324
|
}
|
|
7325
7325
|
var preservePDFNull = types.includes(PDFNull_default);
|
|
7326
|
-
var
|
|
7327
|
-
if (!
|
|
7326
|
+
var result2 = ref instanceof PDFRef_default ? this.indirectObjects.get(ref) : ref;
|
|
7327
|
+
if (!result2 || result2 === PDFNull_default && !preservePDFNull)
|
|
7328
7328
|
return;
|
|
7329
7329
|
for (var idx3 = 0, len2 = types.length;idx3 < len2; idx3++) {
|
|
7330
7330
|
var type = types[idx3];
|
|
7331
7331
|
if (type === PDFNull_default) {
|
|
7332
|
-
if (
|
|
7333
|
-
return
|
|
7332
|
+
if (result2 === PDFNull_default)
|
|
7333
|
+
return result2;
|
|
7334
7334
|
} else {
|
|
7335
|
-
if (
|
|
7336
|
-
return
|
|
7335
|
+
if (result2 instanceof type)
|
|
7336
|
+
return result2;
|
|
7337
7337
|
}
|
|
7338
7338
|
}
|
|
7339
|
-
throw new UnexpectedObjectTypeError(types,
|
|
7339
|
+
throw new UnexpectedObjectTypeError(types, result2);
|
|
7340
7340
|
};
|
|
7341
7341
|
PDFContext2.prototype.lookup = function(ref) {
|
|
7342
7342
|
var types = [];
|
|
7343
7343
|
for (var _i = 1;_i < arguments.length; _i++) {
|
|
7344
7344
|
types[_i - 1] = arguments[_i];
|
|
7345
7345
|
}
|
|
7346
|
-
var
|
|
7346
|
+
var result2 = ref instanceof PDFRef_default ? this.indirectObjects.get(ref) : ref;
|
|
7347
7347
|
if (types.length === 0)
|
|
7348
|
-
return
|
|
7348
|
+
return result2;
|
|
7349
7349
|
for (var idx3 = 0, len2 = types.length;idx3 < len2; idx3++) {
|
|
7350
7350
|
var type = types[idx3];
|
|
7351
7351
|
if (type === PDFNull_default) {
|
|
7352
|
-
if (
|
|
7353
|
-
return
|
|
7352
|
+
if (result2 === PDFNull_default)
|
|
7353
|
+
return result2;
|
|
7354
7354
|
} else {
|
|
7355
|
-
if (
|
|
7356
|
-
return
|
|
7355
|
+
if (result2 instanceof type)
|
|
7356
|
+
return result2;
|
|
7357
7357
|
}
|
|
7358
7358
|
}
|
|
7359
|
-
throw new UnexpectedObjectTypeError(types,
|
|
7359
|
+
throw new UnexpectedObjectTypeError(types, result2);
|
|
7360
7360
|
};
|
|
7361
7361
|
PDFContext2.prototype.getObjectRef = function(pdfObject) {
|
|
7362
7362
|
var entries = Array.from(this.indirectObjects.entries());
|
|
@@ -15922,13 +15922,13 @@ var cx = 0, cy = 0, px = 0, py = 0, sx = 0, sy = 0, parameters, parse = function
|
|
|
15922
15922
|
thArc -= 2 * Math.PI;
|
|
15923
15923
|
}
|
|
15924
15924
|
var segments = Math.ceil(Math.abs(thArc / (Math.PI * 0.5 + 0.001)));
|
|
15925
|
-
var
|
|
15925
|
+
var result2 = [];
|
|
15926
15926
|
for (var i3 = 0;i3 < segments; i3++) {
|
|
15927
15927
|
var th2 = th0 + i3 * thArc / segments;
|
|
15928
15928
|
var th3 = th0 + (i3 + 1) * thArc / segments;
|
|
15929
|
-
|
|
15929
|
+
result2[i3] = [xc, yc, th2, th3, rx, ry, sinTh, cosTh];
|
|
15930
15930
|
}
|
|
15931
|
-
return
|
|
15931
|
+
return result2;
|
|
15932
15932
|
}, segmentToBezier = function(cx1, cy1, th0, th1, rx, ry, sinTh, cosTh) {
|
|
15933
15933
|
var a00 = cosTh * rx;
|
|
15934
15934
|
var a01 = -sinTh * ry;
|
|
@@ -15942,7 +15942,7 @@ var cx = 0, cy = 0, px = 0, py = 0, sx = 0, sy = 0, parameters, parse = function
|
|
|
15942
15942
|
var y3 = cy1 + Math.sin(th1);
|
|
15943
15943
|
var x2 = x3 + t * Math.sin(th1);
|
|
15944
15944
|
var y2 = y3 - t * Math.cos(th1);
|
|
15945
|
-
var
|
|
15945
|
+
var result2 = [
|
|
15946
15946
|
a00 * x1 + a01 * y1,
|
|
15947
15947
|
a10 * x1 + a11 * y1,
|
|
15948
15948
|
a00 * x2 + a01 * y2,
|
|
@@ -15950,7 +15950,7 @@ var cx = 0, cy = 0, px = 0, py = 0, sx = 0, sy = 0, parameters, parse = function
|
|
|
15950
15950
|
a00 * x3 + a01 * y3,
|
|
15951
15951
|
a10 * x3 + a11 * y3
|
|
15952
15952
|
];
|
|
15953
|
-
return
|
|
15953
|
+
return result2;
|
|
15954
15954
|
}, svgPathToOperators = function(path) {
|
|
15955
15955
|
return apply(parse(path));
|
|
15956
15956
|
};
|
|
@@ -21684,14 +21684,167 @@ var connections = createConnections(async (name, operation, input) => {
|
|
|
21684
21684
|
class DataOperationError extends Error {
|
|
21685
21685
|
code;
|
|
21686
21686
|
retryable;
|
|
21687
|
-
|
|
21687
|
+
effect;
|
|
21688
|
+
constructor(code, retryable = false, effect) {
|
|
21688
21689
|
super(`Data operation failed: ${code}`);
|
|
21689
21690
|
this.code = code;
|
|
21690
21691
|
this.retryable = retryable;
|
|
21692
|
+
this.effect = effect;
|
|
21691
21693
|
this.name = "DataOperationError";
|
|
21692
21694
|
}
|
|
21693
21695
|
}
|
|
21694
21696
|
|
|
21697
|
+
// ../../shared/data-envelope.ts
|
|
21698
|
+
var DATA_REQUEST_LIMIT = 256 * 1024;
|
|
21699
|
+
var invalidChange = () => {
|
|
21700
|
+
throw new DataOperationError("invalid_change", false, "unapplied");
|
|
21701
|
+
};
|
|
21702
|
+
function mapping(value) {
|
|
21703
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
|
|
21704
|
+
return invalidChange();
|
|
21705
|
+
if (Reflect.ownKeys(value).some((key) => typeof key !== "string" || !Object.getOwnPropertyDescriptor(value, key)?.enumerable || !Object.hasOwn(Object.getOwnPropertyDescriptor(value, key), "value")))
|
|
21706
|
+
return invalidChange();
|
|
21707
|
+
return value;
|
|
21708
|
+
}
|
|
21709
|
+
function captureValue(value, ancestors = new Set, depth = 0) {
|
|
21710
|
+
if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value))
|
|
21711
|
+
return value;
|
|
21712
|
+
if (!value || typeof value !== "object" || ancestors.has(value) || depth > 32)
|
|
21713
|
+
return invalidChange();
|
|
21714
|
+
ancestors.add(value);
|
|
21715
|
+
try {
|
|
21716
|
+
if (Array.isArray(value)) {
|
|
21717
|
+
if (value.length > 1e5 || Object.keys(value).length !== value.length || Object.getOwnPropertySymbols(value).length)
|
|
21718
|
+
return invalidChange();
|
|
21719
|
+
return Array.from(value, (item) => captureValue(item, ancestors, depth + 1));
|
|
21720
|
+
}
|
|
21721
|
+
return Object.fromEntries(Object.entries(mapping(value)).map(([key, item]) => {
|
|
21722
|
+
if (["__proto__", "constructor", "prototype"].includes(key))
|
|
21723
|
+
return invalidChange();
|
|
21724
|
+
return [key, captureValue(item, ancestors, depth + 1)];
|
|
21725
|
+
}));
|
|
21726
|
+
} finally {
|
|
21727
|
+
ancestors.delete(value);
|
|
21728
|
+
}
|
|
21729
|
+
}
|
|
21730
|
+
function captureDataUpdate(record2, set) {
|
|
21731
|
+
const identity = mapping(record2), fields3 = mapping(set);
|
|
21732
|
+
if (!validRecordRef(identity.$ref) || typeof identity.$version !== "string" || !identity.$version.length || identity.$version.length > 512 || !Object.keys(fields3).length || Object.keys(fields3).some((key) => key.startsWith("$") || ["id", "type"].includes(key)))
|
|
21733
|
+
return invalidChange();
|
|
21734
|
+
return { ref: identity.$ref, expectedVersion: identity.$version, set: captureValue(fields3) };
|
|
21735
|
+
}
|
|
21736
|
+
function captureDataUpdates(value) {
|
|
21737
|
+
if (!Array.isArray(value) || !value.length || value.length > 1e4)
|
|
21738
|
+
return invalidChange();
|
|
21739
|
+
const updates = value.map((item) => {
|
|
21740
|
+
const row = mapping(item);
|
|
21741
|
+
if (Object.keys(row).sort().join(",") !== "expectedVersion,ref,set")
|
|
21742
|
+
return invalidChange();
|
|
21743
|
+
return captureDataUpdate({ $ref: row.ref, $version: row.expectedVersion }, row.set);
|
|
21744
|
+
});
|
|
21745
|
+
if (new Set(updates.map((update) => update.ref)).size !== updates.length)
|
|
21746
|
+
return invalidChange();
|
|
21747
|
+
return updates;
|
|
21748
|
+
}
|
|
21749
|
+
function checkDataRequestSize(input) {
|
|
21750
|
+
if (new TextEncoder().encode(JSON.stringify(input)).byteLength > DATA_REQUEST_LIMIT)
|
|
21751
|
+
throw new DataOperationError("request_too_large", false, "unapplied");
|
|
21752
|
+
}
|
|
21753
|
+
function dataTransportError(status, value) {
|
|
21754
|
+
const failures = {
|
|
21755
|
+
400: ["invalid_query", "invalid_change"],
|
|
21756
|
+
401: ["unauthenticated"],
|
|
21757
|
+
403: ["access_denied"],
|
|
21758
|
+
409: ["conflict", "publication_pending"],
|
|
21759
|
+
413: ["request_too_large"],
|
|
21760
|
+
429: ["capacity"],
|
|
21761
|
+
503: ["unavailable", "publication_pending"]
|
|
21762
|
+
};
|
|
21763
|
+
const error = value && typeof value === "object" && "error" in value ? value.error : undefined;
|
|
21764
|
+
const row = error && typeof error === "object" ? error : undefined;
|
|
21765
|
+
const code = failures[status]?.find((allowed) => allowed === row?.code) ?? "unavailable";
|
|
21766
|
+
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);
|
|
21768
|
+
}
|
|
21769
|
+
|
|
21770
|
+
// ../../shared/files-contract.ts
|
|
21771
|
+
class FileOperationError extends Error {
|
|
21772
|
+
code;
|
|
21773
|
+
status;
|
|
21774
|
+
retryable;
|
|
21775
|
+
constructor(code, status, retryable = false) {
|
|
21776
|
+
super(code);
|
|
21777
|
+
this.code = code;
|
|
21778
|
+
this.status = status;
|
|
21779
|
+
this.retryable = retryable;
|
|
21780
|
+
}
|
|
21781
|
+
}
|
|
21782
|
+
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}$/;
|
|
21783
|
+
function parseFileDescriptor(value) {
|
|
21784
|
+
const invalid = () => {
|
|
21785
|
+
throw new FileOperationError("unavailable", 503, true);
|
|
21786
|
+
};
|
|
21787
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
21788
|
+
return invalid();
|
|
21789
|
+
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) || Object.keys(f.enrichments).length !== 0 || !f.details || typeof f.details !== "object" || Array.isArray(f.details))
|
|
21791
|
+
return invalid();
|
|
21792
|
+
const d = f.details;
|
|
21793
|
+
if (d.kind === "other") {
|
|
21794
|
+
if (Object.keys(d).length !== 1 || f.mediaType !== "application/octet-stream")
|
|
21795
|
+
return invalid();
|
|
21796
|
+
} else if (d.kind === "csv") {
|
|
21797
|
+
if (Object.keys(d).sort().join(",") !== "dataRows,delimiter,encoding,headerRow,kind" || f.mediaType !== "text/csv" || !["utf-8", "windows-1252"].includes(d.encoding) || ![",", ";", "\t"].includes(d.delimiter) || !Number.isSafeInteger(d.headerRow) || d.headerRow < 1 || !Number.isSafeInteger(d.dataRows) || d.dataRows < 0)
|
|
21798
|
+
return invalid();
|
|
21799
|
+
} else
|
|
21800
|
+
return invalid();
|
|
21801
|
+
return f;
|
|
21802
|
+
}
|
|
21803
|
+
function safeFileName(value) {
|
|
21804
|
+
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
|
+
}
|
|
21806
|
+
function safeFilePath(value) {
|
|
21807
|
+
return typeof value === "string" && value.length <= 1024 && value.split("/").every(safeFileName);
|
|
21808
|
+
}
|
|
21809
|
+
function parseFileCreateMetadata(value, maxBytes) {
|
|
21810
|
+
const invalid = () => {
|
|
21811
|
+
throw new FileOperationError("invalid_input", 400);
|
|
21812
|
+
};
|
|
21813
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
21814
|
+
return invalid();
|
|
21815
|
+
const input = value;
|
|
21816
|
+
if (Object.keys(input).some((key) => !["name", "path", "storage", "csv", "size", "requestKey"].includes(key)))
|
|
21817
|
+
return invalid();
|
|
21818
|
+
const path = input.path === undefined ? "Files" : input.path;
|
|
21819
|
+
if (!safeFileName(input.name) || !safeFilePath(path))
|
|
21820
|
+
return invalid();
|
|
21821
|
+
if (input.storage === "local" || input.storage === "both")
|
|
21822
|
+
throw new FileOperationError("unsupported_context", 400);
|
|
21823
|
+
if (input.storage !== undefined && input.storage !== "remote")
|
|
21824
|
+
return invalid();
|
|
21825
|
+
if (!Number.isSafeInteger(input.size) || input.size < 0)
|
|
21826
|
+
return invalid();
|
|
21827
|
+
if (input.size > maxBytes)
|
|
21828
|
+
throw new FileOperationError("request_too_large", 413);
|
|
21829
|
+
if (typeof input.requestKey !== "string" || input.requestKey.length < 1 || input.requestKey.length > 256 || input.requestKey !== input.requestKey.trim() || /[\u0000-\u001f\u007f]/.test(input.requestKey))
|
|
21830
|
+
return invalid();
|
|
21831
|
+
let csv;
|
|
21832
|
+
if (input.csv !== undefined) {
|
|
21833
|
+
if (!input.csv || typeof input.csv !== "object" || Array.isArray(input.csv))
|
|
21834
|
+
return invalid();
|
|
21835
|
+
const hints = input.csv;
|
|
21836
|
+
if (Object.keys(hints).some((key) => !["encoding", "delimiter", "headerRow"].includes(key)))
|
|
21837
|
+
return invalid();
|
|
21838
|
+
const encoding = hints.encoding === undefined ? "utf-8" : hints.encoding;
|
|
21839
|
+
const delimiter = hints.delimiter === undefined ? "," : hints.delimiter;
|
|
21840
|
+
const headerRow = hints.headerRow === undefined ? 1 : hints.headerRow;
|
|
21841
|
+
if (encoding !== "utf-8" && encoding !== "windows-1252" || ![",", ";", "\t"].includes(delimiter) || !Number.isSafeInteger(headerRow) || headerRow < 1 || headerRow > 1e5)
|
|
21842
|
+
return invalid();
|
|
21843
|
+
csv = { encoding, delimiter, headerRow };
|
|
21844
|
+
}
|
|
21845
|
+
return { name: input.name, path, storage: "remote", ...csv ? { csv } : {}, size: input.size, requestKey: input.requestKey };
|
|
21846
|
+
}
|
|
21847
|
+
|
|
21695
21848
|
// ../../shared/os-origin.ts
|
|
21696
21849
|
var DEFAULT_ORIGIN = "https://auth.oddington.ai";
|
|
21697
21850
|
function oddingtonOrigin(environment = process.env) {
|
|
@@ -21703,14 +21856,6 @@ function oddingtonOrigin(environment = process.env) {
|
|
|
21703
21856
|
}
|
|
21704
21857
|
|
|
21705
21858
|
// src/routine-local-transport.ts
|
|
21706
|
-
var dataFailures = {
|
|
21707
|
-
400: ["invalid_query"],
|
|
21708
|
-
401: ["unauthenticated"],
|
|
21709
|
-
403: ["access_denied"],
|
|
21710
|
-
413: ["request_too_large"],
|
|
21711
|
-
429: ["capacity"],
|
|
21712
|
-
503: ["unavailable"]
|
|
21713
|
-
};
|
|
21714
21859
|
function localRoutineRequest(environment = typeof process === "undefined" ? {} : process.env) {
|
|
21715
21860
|
const {
|
|
21716
21861
|
ODDINGTON_ROUTINE_LOCAL: mode,
|
|
@@ -21751,9 +21896,7 @@ function localRoutineRequest(environment = typeof process === "undefined" ? {} :
|
|
|
21751
21896
|
const result = JSON.parse(Buffer.concat(chunks).toString());
|
|
21752
21897
|
if (response.statusCode !== 200) {
|
|
21753
21898
|
if (operation === "data/query") {
|
|
21754
|
-
|
|
21755
|
-
const code = dataFailures[response.statusCode ?? 0]?.find((allowed) => allowed === received) ?? "unavailable";
|
|
21756
|
-
throw new DataOperationError(code, code === "capacity" || code === "unavailable");
|
|
21899
|
+
throw dataTransportError(response.statusCode ?? 0, result);
|
|
21757
21900
|
}
|
|
21758
21901
|
throw new Error(typeof result.error === "string" ? result.error : "Local preparation failed");
|
|
21759
21902
|
}
|
|
@@ -21799,6 +21942,34 @@ async function resolveDataContext() {
|
|
|
21799
21942
|
throw new DataOperationError("context_unavailable");
|
|
21800
21943
|
}
|
|
21801
21944
|
}
|
|
21945
|
+
async function resolveFilesContext() {
|
|
21946
|
+
try {
|
|
21947
|
+
const environment = typeof process === "undefined" ? {} : process.env;
|
|
21948
|
+
if (["ODDINGTON_ROUTINE_LOCAL", "ODDINGTON_ROUTINE_LOCAL_SOCKET", "ODDINGTON_ROUTINE_LOCAL_TOKEN"].some((key) => environment[key] !== undefined))
|
|
21949
|
+
throw new Error;
|
|
21950
|
+
const host = memberContext;
|
|
21951
|
+
if (host) {
|
|
21952
|
+
const context = await host();
|
|
21953
|
+
if (!Number.isFinite(context.expiresAt) || context.expiresAt <= Date.now() || typeof context.accessToken !== "string" || !/^[^\s]{1,8192}$/.test(context.accessToken))
|
|
21954
|
+
throw new Error;
|
|
21955
|
+
return {
|
|
21956
|
+
...filesContext(context.workspace, context.endpoint, { authorization: `Bearer ${context.accessToken}` }),
|
|
21957
|
+
isCurrent: context.isCurrent
|
|
21958
|
+
};
|
|
21959
|
+
}
|
|
21960
|
+
if (!/^[0-9a-f]{64}$/.test(environment.ODDINGTON_ROUTINE_RUN ?? ""))
|
|
21961
|
+
throw new Error;
|
|
21962
|
+
const workspace = environment.ODDINGTON_ROUTINE_WORKSPACE;
|
|
21963
|
+
return filesContext(workspace, `https://${workspace}.oddington.ai`, { "x-oddington-run": environment.ODDINGTON_ROUTINE_RUN });
|
|
21964
|
+
} catch {
|
|
21965
|
+
throw new FileOperationError("unsupported_context", 400);
|
|
21966
|
+
}
|
|
21967
|
+
}
|
|
21968
|
+
function filesContext(workspace, endpoint, headers) {
|
|
21969
|
+
if (!workspace || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(workspace) || workspace.length > 63 || ["auth", "www", "api", "os"].includes(workspace) || endpoint !== `https://${workspace}.oddington.ai`)
|
|
21970
|
+
throw new Error;
|
|
21971
|
+
return { url: `${endpoint}/api/files`, headers: { ...headers, "x-oddington-workspace": workspace }, isCurrent: () => true };
|
|
21972
|
+
}
|
|
21802
21973
|
function checked(workspace, endpoint, headers) {
|
|
21803
21974
|
if (!workspace || !/^[a-z0-9][a-z0-9-]{0,127}$/.test(workspace))
|
|
21804
21975
|
throw new Error;
|
|
@@ -21809,19 +21980,12 @@ function checked(workspace, endpoint, headers) {
|
|
|
21809
21980
|
}
|
|
21810
21981
|
|
|
21811
21982
|
// src/data.ts
|
|
21812
|
-
var failures = {
|
|
21813
|
-
400: ["invalid_query"],
|
|
21814
|
-
401: ["unauthenticated"],
|
|
21815
|
-
403: ["access_denied"],
|
|
21816
|
-
413: ["request_too_large"],
|
|
21817
|
-
429: ["capacity"],
|
|
21818
|
-
503: ["unavailable"]
|
|
21819
|
-
};
|
|
21820
21983
|
var data = {
|
|
21821
21984
|
repository(repository) {
|
|
21822
21985
|
async function send(input) {
|
|
21823
21986
|
const context = await resolveDataContext();
|
|
21824
21987
|
try {
|
|
21988
|
+
checkDataRequestSize({ repository, ...input });
|
|
21825
21989
|
if ("local" in context)
|
|
21826
21990
|
return await context.local("data/query", { repository, ...input });
|
|
21827
21991
|
const response = await fetch(context.url, {
|
|
@@ -21833,12 +21997,7 @@ var data = {
|
|
|
21833
21997
|
});
|
|
21834
21998
|
const result = await response.json();
|
|
21835
21999
|
if (!response.ok) {
|
|
21836
|
-
|
|
21837
|
-
const received = error && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
21838
|
-
const code = failures[response.status]?.find((allowed) => allowed === received);
|
|
21839
|
-
if (code)
|
|
21840
|
-
throw new DataOperationError(code, code === "capacity" || code === "unavailable");
|
|
21841
|
-
throw new DataOperationError("unavailable", true);
|
|
22000
|
+
throw dataTransportError(response.status, result);
|
|
21842
22001
|
}
|
|
21843
22002
|
return result;
|
|
21844
22003
|
} catch (error) {
|
|
@@ -21852,10 +22011,249 @@ var data = {
|
|
|
21852
22011
|
list: () => send({ operation: "collections.list" }),
|
|
21853
22012
|
schema: (name) => send({ operation: "collections.schema", name })
|
|
21854
22013
|
},
|
|
21855
|
-
records: { select: (query) => send({ operation: "records.select", query }) }
|
|
22014
|
+
records: { select: (query) => send({ operation: "records.select", query }) },
|
|
22015
|
+
changes() {
|
|
22016
|
+
let updates = [], publicationId;
|
|
22017
|
+
let pending, confirmed;
|
|
22018
|
+
const snapshot = () => {
|
|
22019
|
+
const captured = captureDataUpdates(updates);
|
|
22020
|
+
checkDataRequestSize({
|
|
22021
|
+
repository,
|
|
22022
|
+
operation: "changes.publish",
|
|
22023
|
+
updates: captured,
|
|
22024
|
+
publicationId: publicationId ?? "00000000-0000-4000-8000-000000000000"
|
|
22025
|
+
});
|
|
22026
|
+
return captured;
|
|
22027
|
+
};
|
|
22028
|
+
return {
|
|
22029
|
+
update(record2, set) {
|
|
22030
|
+
if (publicationId)
|
|
22031
|
+
throw new DataOperationError("invalid_change", false, "unapplied");
|
|
22032
|
+
const next = [...updates, captureDataUpdate(record2, set)];
|
|
22033
|
+
captureDataUpdates(next);
|
|
22034
|
+
checkDataRequestSize({
|
|
22035
|
+
repository,
|
|
22036
|
+
operation: "changes.publish",
|
|
22037
|
+
updates: next,
|
|
22038
|
+
publicationId: "00000000-0000-4000-8000-000000000000"
|
|
22039
|
+
});
|
|
22040
|
+
updates = next;
|
|
22041
|
+
},
|
|
22042
|
+
async validate() {
|
|
22043
|
+
await send({ operation: "changes.validate", updates: snapshot() });
|
|
22044
|
+
},
|
|
22045
|
+
publish() {
|
|
22046
|
+
if (confirmed)
|
|
22047
|
+
return Promise.resolve(structuredClone(confirmed));
|
|
22048
|
+
if (!pending) {
|
|
22049
|
+
let captured;
|
|
22050
|
+
try {
|
|
22051
|
+
captured = snapshot();
|
|
22052
|
+
} catch (error) {
|
|
22053
|
+
return Promise.reject(error);
|
|
22054
|
+
}
|
|
22055
|
+
publicationId ??= crypto.randomUUID();
|
|
22056
|
+
pending = send({ operation: "changes.publish", updates: captured, publicationId }).then((result) => {
|
|
22057
|
+
confirmed = structuredClone(result);
|
|
22058
|
+
return result;
|
|
22059
|
+
}).catch((error) => {
|
|
22060
|
+
if (error instanceof DataOperationError)
|
|
22061
|
+
throw new DataOperationError(error.code, error.retryable, error.effect ?? (["unavailable", "publication_pending"].includes(error.code) ? "uncertain" : "unapplied"));
|
|
22062
|
+
throw new DataOperationError("unavailable", true, "uncertain");
|
|
22063
|
+
}).finally(() => {
|
|
22064
|
+
pending = undefined;
|
|
22065
|
+
});
|
|
22066
|
+
}
|
|
22067
|
+
return pending.then((result) => structuredClone(result));
|
|
22068
|
+
}
|
|
22069
|
+
};
|
|
22070
|
+
}
|
|
21856
22071
|
};
|
|
21857
22072
|
}
|
|
21858
22073
|
};
|
|
22074
|
+
// src/files.ts
|
|
22075
|
+
var MAX_BYTES = 16 * 1024 * 1024;
|
|
22076
|
+
var METADATA_BYTES = 4096;
|
|
22077
|
+
var RESULT_BYTES = 16 * 1024;
|
|
22078
|
+
var TIMEOUT_MS = 30000;
|
|
22079
|
+
var MAX_ACTIVE = 4;
|
|
22080
|
+
var ATTEMPTS = 2;
|
|
22081
|
+
var active = 0;
|
|
22082
|
+
var invalid = () => new FileOperationError("invalid_input", 400);
|
|
22083
|
+
var unavailable = () => new FileOperationError("unavailable", 503, true);
|
|
22084
|
+
var failures = {
|
|
22085
|
+
400: ["invalid_input", "unsupported_context"],
|
|
22086
|
+
401: ["unauthenticated"],
|
|
22087
|
+
403: ["access_denied"],
|
|
22088
|
+
405: ["method_not_allowed"],
|
|
22089
|
+
409: ["conflict"],
|
|
22090
|
+
413: ["request_too_large"],
|
|
22091
|
+
429: ["capacity"],
|
|
22092
|
+
503: ["integrity", "unavailable"]
|
|
22093
|
+
};
|
|
22094
|
+
async function boundedBytes(stream, limit) {
|
|
22095
|
+
if (!stream)
|
|
22096
|
+
return new Uint8Array;
|
|
22097
|
+
const reader = stream.getReader();
|
|
22098
|
+
const bytes = new Uint8Array(limit);
|
|
22099
|
+
let size = 0;
|
|
22100
|
+
try {
|
|
22101
|
+
for (;; ) {
|
|
22102
|
+
const { done, value } = await reader.read();
|
|
22103
|
+
if (done)
|
|
22104
|
+
break;
|
|
22105
|
+
if (!(value instanceof Uint8Array))
|
|
22106
|
+
throw invalid();
|
|
22107
|
+
size += value.byteLength;
|
|
22108
|
+
if (size > limit)
|
|
22109
|
+
throw new FileOperationError("request_too_large", 413);
|
|
22110
|
+
bytes.set(value, size - value.byteLength);
|
|
22111
|
+
}
|
|
22112
|
+
} finally {
|
|
22113
|
+
reader.releaseLock();
|
|
22114
|
+
}
|
|
22115
|
+
return bytes.slice(0, size);
|
|
22116
|
+
}
|
|
22117
|
+
async function timed(work, parent) {
|
|
22118
|
+
const controller = new AbortController;
|
|
22119
|
+
let timer;
|
|
22120
|
+
let abort;
|
|
22121
|
+
const expiry = new Promise((_, reject) => {
|
|
22122
|
+
abort = () => {
|
|
22123
|
+
controller.abort();
|
|
22124
|
+
reject(unavailable());
|
|
22125
|
+
};
|
|
22126
|
+
timer = setTimeout(abort, TIMEOUT_MS);
|
|
22127
|
+
parent?.addEventListener("abort", abort, { once: true });
|
|
22128
|
+
if (parent?.aborted)
|
|
22129
|
+
abort();
|
|
22130
|
+
});
|
|
22131
|
+
try {
|
|
22132
|
+
return await Promise.race([work(controller.signal), expiry]);
|
|
22133
|
+
} finally {
|
|
22134
|
+
clearTimeout(timer);
|
|
22135
|
+
parent?.removeEventListener("abort", abort);
|
|
22136
|
+
}
|
|
22137
|
+
}
|
|
22138
|
+
async function result(response) {
|
|
22139
|
+
try {
|
|
22140
|
+
const bytes = await boundedBytes(response.body, RESULT_BYTES);
|
|
22141
|
+
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
22142
|
+
} catch {
|
|
22143
|
+
throw unavailable();
|
|
22144
|
+
}
|
|
22145
|
+
}
|
|
22146
|
+
async function send(suffix, init, descriptor, initial, parent) {
|
|
22147
|
+
const original = initial ?? await resolveFilesContext();
|
|
22148
|
+
const checkCurrent = () => {
|
|
22149
|
+
if (parent?.aborted)
|
|
22150
|
+
throw unavailable();
|
|
22151
|
+
if (!original.isCurrent())
|
|
22152
|
+
throw new FileOperationError("unsupported_context", 400);
|
|
22153
|
+
};
|
|
22154
|
+
for (let attempt = 0;attempt < ATTEMPTS; attempt++) {
|
|
22155
|
+
checkCurrent();
|
|
22156
|
+
const context = attempt === 0 && !initial ? original : await resolveFilesContext();
|
|
22157
|
+
checkCurrent();
|
|
22158
|
+
if (context.url !== original.url)
|
|
22159
|
+
throw new FileOperationError("unsupported_context", 400);
|
|
22160
|
+
try {
|
|
22161
|
+
return await timed(async (signal) => {
|
|
22162
|
+
const response = await fetch(`${context.url}${suffix}`, {
|
|
22163
|
+
...init,
|
|
22164
|
+
signal,
|
|
22165
|
+
headers: { ...context.headers, ...init.headers },
|
|
22166
|
+
redirect: "error",
|
|
22167
|
+
cache: "no-store"
|
|
22168
|
+
});
|
|
22169
|
+
if (!response.ok) {
|
|
22170
|
+
const value = await result(response);
|
|
22171
|
+
const error = value && typeof value === "object" && "error" in value ? value.error : undefined;
|
|
22172
|
+
const received = error && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
22173
|
+
const code = failures[response.status]?.find((code2) => code2 === received);
|
|
22174
|
+
throw code ? new FileOperationError(code, response.status, code === "capacity" || code === "unavailable") : unavailable();
|
|
22175
|
+
}
|
|
22176
|
+
if (response.status !== 200)
|
|
22177
|
+
throw unavailable();
|
|
22178
|
+
checkCurrent();
|
|
22179
|
+
if (!descriptor)
|
|
22180
|
+
return response;
|
|
22181
|
+
const file = parseFileDescriptor(await result(response));
|
|
22182
|
+
if (file.size > MAX_BYTES)
|
|
22183
|
+
throw unavailable();
|
|
22184
|
+
checkCurrent();
|
|
22185
|
+
return file;
|
|
22186
|
+
}, parent);
|
|
22187
|
+
} catch (error) {
|
|
22188
|
+
const failure = error instanceof FileOperationError ? error : unavailable();
|
|
22189
|
+
if (!failure.retryable || attempt + 1 === ATTEMPTS)
|
|
22190
|
+
throw failure;
|
|
22191
|
+
}
|
|
22192
|
+
}
|
|
22193
|
+
throw unavailable();
|
|
22194
|
+
}
|
|
22195
|
+
function idPath(id2) {
|
|
22196
|
+
if (typeof id2 !== "string" || !FILE_ID_PATTERN.test(id2))
|
|
22197
|
+
throw invalid();
|
|
22198
|
+
return `/${id2}`;
|
|
22199
|
+
}
|
|
22200
|
+
var files = {
|
|
22201
|
+
async create(input, options) {
|
|
22202
|
+
const context = await resolveFilesContext();
|
|
22203
|
+
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")))
|
|
22204
|
+
throw invalid();
|
|
22205
|
+
const blob = input.file instanceof Blob;
|
|
22206
|
+
const array2 = input.file instanceof Uint8Array;
|
|
22207
|
+
const stream = input.file instanceof ReadableStream;
|
|
22208
|
+
if (!blob && !array2 && !stream)
|
|
22209
|
+
throw invalid();
|
|
22210
|
+
const intrinsic = blob ? input.file.size : array2 ? input.file.byteLength : undefined;
|
|
22211
|
+
if (intrinsic !== undefined && input.size !== undefined && input.size !== intrinsic)
|
|
22212
|
+
throw invalid();
|
|
22213
|
+
let name = input.name;
|
|
22214
|
+
if (name === undefined && blob && "name" in input.file && typeof input.file.name === "string") {
|
|
22215
|
+
name = typeof globalThis.File !== "undefined" && input.file instanceof globalThis.File ? input.file.name : input.file.name.split(/[\\/]/).at(-1);
|
|
22216
|
+
}
|
|
22217
|
+
const metadata = parseFileCreateMetadata({
|
|
22218
|
+
name,
|
|
22219
|
+
size: intrinsic ?? input.size,
|
|
22220
|
+
path: input.path,
|
|
22221
|
+
storage: input.storage,
|
|
22222
|
+
csv: input.csv,
|
|
22223
|
+
requestKey: options?.requestKey === undefined ? crypto.randomUUID() : options.requestKey
|
|
22224
|
+
}, MAX_BYTES);
|
|
22225
|
+
const header = encodeURIComponent(JSON.stringify(metadata));
|
|
22226
|
+
if (new TextEncoder().encode(header).byteLength > METADATA_BYTES)
|
|
22227
|
+
throw invalid();
|
|
22228
|
+
if (active >= MAX_ACTIVE)
|
|
22229
|
+
throw new FileOperationError("capacity", 429, true);
|
|
22230
|
+
active++;
|
|
22231
|
+
return timed(async (signal) => {
|
|
22232
|
+
try {
|
|
22233
|
+
const bytes = array2 ? input.file.slice() : await boundedBytes(blob ? input.file.stream() : input.file, metadata.size);
|
|
22234
|
+
if (bytes.byteLength !== metadata.size)
|
|
22235
|
+
throw invalid();
|
|
22236
|
+
if (signal.aborted)
|
|
22237
|
+
throw unavailable();
|
|
22238
|
+
return await send("", {
|
|
22239
|
+
method: "POST",
|
|
22240
|
+
headers: { "content-type": "application/octet-stream", "x-oddington-file": header },
|
|
22241
|
+
body: bytes.buffer
|
|
22242
|
+
}, true, context, signal);
|
|
22243
|
+
} finally {
|
|
22244
|
+
active--;
|
|
22245
|
+
}
|
|
22246
|
+
}).catch((error) => {
|
|
22247
|
+
throw error instanceof FileOperationError ? error : unavailable();
|
|
22248
|
+
});
|
|
22249
|
+
},
|
|
22250
|
+
async get(id2) {
|
|
22251
|
+
return send(idPath(id2), { method: "GET" }, true);
|
|
22252
|
+
},
|
|
22253
|
+
async read(id2) {
|
|
22254
|
+
return send(`${idPath(id2)}/content`, { method: "GET" }, false);
|
|
22255
|
+
}
|
|
22256
|
+
};
|
|
21859
22257
|
// src/os.ts
|
|
21860
22258
|
var installedDispose;
|
|
21861
22259
|
function init({ auth }) {
|
|
@@ -21870,7 +22268,7 @@ function init({ auth }) {
|
|
|
21870
22268
|
workspace = slug;
|
|
21871
22269
|
}
|
|
21872
22270
|
} catch {}
|
|
21873
|
-
let
|
|
22271
|
+
let active2 = true;
|
|
21874
22272
|
let revision = 0;
|
|
21875
22273
|
let person;
|
|
21876
22274
|
const { data: { subscription } } = auth.onAuthStateChange((event, session) => {
|
|
@@ -21880,11 +22278,11 @@ function init({ auth }) {
|
|
|
21880
22278
|
person = next;
|
|
21881
22279
|
});
|
|
21882
22280
|
const resolve = async () => {
|
|
21883
|
-
if (!
|
|
22281
|
+
if (!active2 || !endpoint || !workspace)
|
|
21884
22282
|
throw new Error("Hosted Data context unavailable.");
|
|
21885
22283
|
const started = revision;
|
|
21886
22284
|
const { data: { session }, error } = await auth.getSession();
|
|
21887
|
-
if (!
|
|
22285
|
+
if (!active2 || started !== revision || error || !session || person !== undefined && person !== session.user.id) {
|
|
21888
22286
|
throw new Error("Hosted Data context unavailable.");
|
|
21889
22287
|
}
|
|
21890
22288
|
const expiresAt = (session.expires_at ?? 0) * 1000;
|
|
@@ -21892,13 +22290,19 @@ function init({ auth }) {
|
|
|
21892
22290
|
throw new Error("Hosted Data context unavailable.");
|
|
21893
22291
|
}
|
|
21894
22292
|
person = session.user.id;
|
|
21895
|
-
return {
|
|
22293
|
+
return {
|
|
22294
|
+
workspace,
|
|
22295
|
+
endpoint,
|
|
22296
|
+
accessToken: session.access_token,
|
|
22297
|
+
expiresAt,
|
|
22298
|
+
isCurrent: () => active2 && revision === started && person === session.user.id
|
|
22299
|
+
};
|
|
21896
22300
|
};
|
|
21897
22301
|
const uninstall = installMemberContext(resolve);
|
|
21898
22302
|
const dispose = () => {
|
|
21899
|
-
if (!
|
|
22303
|
+
if (!active2)
|
|
21900
22304
|
return;
|
|
21901
|
-
|
|
22305
|
+
active2 = false;
|
|
21902
22306
|
revision++;
|
|
21903
22307
|
subscription.unsubscribe();
|
|
21904
22308
|
uninstall();
|
|
@@ -21932,7 +22336,7 @@ function routinePublicationClient(options = {}) {
|
|
|
21932
22336
|
} : async () => {
|
|
21933
22337
|
throw new Error("Member publication requires a host-supplied request");
|
|
21934
22338
|
});
|
|
21935
|
-
async function
|
|
22339
|
+
async function send2(operation, body) {
|
|
21936
22340
|
if (local)
|
|
21937
22341
|
return await local(operation, body);
|
|
21938
22342
|
const response = await request(`/api/cli/workspaces/${workspace}/inbox/${operation}`, {
|
|
@@ -21940,23 +22344,23 @@ function routinePublicationClient(options = {}) {
|
|
|
21940
22344
|
headers: { "content-type": "application/json", ...headers },
|
|
21941
22345
|
body: JSON.stringify(body)
|
|
21942
22346
|
});
|
|
21943
|
-
const
|
|
22347
|
+
const result2 = await response.json();
|
|
21944
22348
|
if (!response.ok && operation === "records/update")
|
|
21945
|
-
throw new RoutineRecordWriteError(
|
|
22349
|
+
throw new RoutineRecordWriteError(result2.error ?? "Could not confirm routine preparation", result2.effect ?? "uncertain");
|
|
21946
22350
|
if (!response.ok)
|
|
21947
|
-
throw new Error(
|
|
21948
|
-
return
|
|
22351
|
+
throw new Error(result2.error ?? "Could not confirm routine publication");
|
|
22352
|
+
return result2;
|
|
21949
22353
|
}
|
|
21950
22354
|
const transport = {
|
|
21951
22355
|
async publish(body) {
|
|
21952
|
-
return await
|
|
22356
|
+
return await send2("publish", body);
|
|
21953
22357
|
},
|
|
21954
22358
|
async resolve(body) {
|
|
21955
|
-
await
|
|
22359
|
+
await send2("resolve", body);
|
|
21956
22360
|
}
|
|
21957
22361
|
};
|
|
21958
22362
|
async function readRecords(input) {
|
|
21959
|
-
return await
|
|
22363
|
+
return await send2("records/read", validateRecordRead(input));
|
|
21960
22364
|
}
|
|
21961
22365
|
return {
|
|
21962
22366
|
...createInboxPublisher(transport),
|
|
@@ -21966,17 +22370,17 @@ function routinePublicationClient(options = {}) {
|
|
|
21966
22370
|
async writeRecords(input, options2) {
|
|
21967
22371
|
if (!capability)
|
|
21968
22372
|
throw new Error("A trusted routine launch is required");
|
|
21969
|
-
return await
|
|
22373
|
+
return await send2("records/update", validateRoutineRecordWrite({ input, requestKey: options2?.requestKey }));
|
|
21970
22374
|
},
|
|
21971
22375
|
async updateRecords(input, options2) {
|
|
21972
22376
|
if (!capability)
|
|
21973
22377
|
throw new Error("A trusted routine launch is required");
|
|
21974
|
-
return await
|
|
22378
|
+
return await send2("records/update", validateRoutineRecordWrite({ input: validateRecordUpdate(input), requestKey: options2?.requestKey }));
|
|
21975
22379
|
},
|
|
21976
22380
|
async deleteRecords(input, options2) {
|
|
21977
22381
|
if (!capability)
|
|
21978
22382
|
throw new Error("A trusted routine launch is required");
|
|
21979
|
-
return await
|
|
22383
|
+
return await send2("records/update", validateRoutineRecordWrite({ input: validateRecordDelete(input), requestKey: options2?.requestKey }));
|
|
21980
22384
|
}
|
|
21981
22385
|
},
|
|
21982
22386
|
files: {
|
|
@@ -21998,14 +22402,14 @@ function routinePublicationClient(options = {}) {
|
|
|
21998
22402
|
},
|
|
21999
22403
|
body: Uint8Array.from(bytes).buffer
|
|
22000
22404
|
});
|
|
22001
|
-
const
|
|
22405
|
+
const result2 = await response.json();
|
|
22002
22406
|
if (!response.ok)
|
|
22003
|
-
throw new Error(
|
|
22004
|
-
return await verifyInboxFile(bytes, validateInboxFile(
|
|
22407
|
+
throw new Error(result2.error ?? "Could not retain file");
|
|
22408
|
+
return await verifyInboxFile(bytes, validateInboxFile(result2));
|
|
22005
22409
|
}
|
|
22006
22410
|
},
|
|
22007
22411
|
async check(body) {
|
|
22008
|
-
await
|
|
22412
|
+
await send2("check", { requestKey: body.requestKey, item: validatePublication(body.item) });
|
|
22009
22413
|
}
|
|
22010
22414
|
};
|
|
22011
22415
|
}
|
|
@@ -22019,6 +22423,7 @@ export {
|
|
|
22019
22423
|
recordBlock,
|
|
22020
22424
|
question,
|
|
22021
22425
|
os,
|
|
22426
|
+
files,
|
|
22022
22427
|
fileBlock,
|
|
22023
22428
|
defineHandler,
|
|
22024
22429
|
data,
|
|
@@ -22027,6 +22432,7 @@ export {
|
|
|
22027
22432
|
asksBlock,
|
|
22028
22433
|
RoutineRecordWriteError,
|
|
22029
22434
|
InboxContractError,
|
|
22435
|
+
FileOperationError,
|
|
22030
22436
|
DataOperationError,
|
|
22031
22437
|
AirtableOperationError
|
|
22032
22438
|
};
|
|
@@ -13,10 +13,17 @@ 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 } 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';
|
|
17
17
|
export declare const data: {
|
|
18
18
|
repository(name: string): import('../../../shared/data-contract.js').DataRepository;
|
|
19
19
|
};
|
|
20
|
+
export { FileOperationError } from '../../../shared/files-contract.js';
|
|
21
|
+
export type { File, FileInput, FileStorage, FileCreateInput, FileCreateOptions, FileErrorCode } from '../../../shared/files-contract.js';
|
|
22
|
+
export declare const files: {
|
|
23
|
+
create(input: import('../../../shared/files-contract.js').FileCreateInput, options?: import('../../../shared/files-contract.js').FileCreateOptions): Promise<import('../../../shared/files-contract.js').File>;
|
|
24
|
+
get(id: string): Promise<import('../../../shared/files-contract.js').File>;
|
|
25
|
+
read(id: string): Promise<Response>;
|
|
26
|
+
};
|
|
20
27
|
/** The host's existing refreshing session source; supplies identity, never membership. */
|
|
21
28
|
export type HostedSessionSource = {
|
|
22
29
|
getSession(): Promise<{
|
|
@@ -44,10 +44,38 @@ export type CollectionSchema = CollectionSummary & {
|
|
|
44
44
|
fields: PublicField[];
|
|
45
45
|
};
|
|
46
46
|
export type QueryScalar = string | number | boolean | null;
|
|
47
|
+
type LowerBound = {
|
|
48
|
+
$gt: number | string;
|
|
49
|
+
$gte?: never;
|
|
50
|
+
} | {
|
|
51
|
+
$gte: number | string;
|
|
52
|
+
$gt?: never;
|
|
53
|
+
};
|
|
54
|
+
type UpperBound = {
|
|
55
|
+
$lt: number | string;
|
|
56
|
+
$lte?: never;
|
|
57
|
+
} | {
|
|
58
|
+
$lte: number | string;
|
|
59
|
+
$lt?: never;
|
|
60
|
+
};
|
|
61
|
+
export type RangeCondition = ((LowerBound & {
|
|
62
|
+
$lt?: never;
|
|
63
|
+
$lte?: never;
|
|
64
|
+
}) | (UpperBound & {
|
|
65
|
+
$gt?: never;
|
|
66
|
+
$gte?: never;
|
|
67
|
+
}) | (LowerBound & UpperBound)) & {
|
|
68
|
+
$in?: never;
|
|
69
|
+
};
|
|
70
|
+
export type QueryCondition = QueryScalar | RangeCondition | {
|
|
71
|
+
$in: QueryScalar[];
|
|
72
|
+
$gt?: never;
|
|
73
|
+
$gte?: never;
|
|
74
|
+
$lt?: never;
|
|
75
|
+
$lte?: never;
|
|
76
|
+
};
|
|
47
77
|
export type SelectionOptions = {
|
|
48
|
-
where?: Record<string,
|
|
49
|
-
$in: QueryScalar[];
|
|
50
|
-
}>;
|
|
78
|
+
where?: Record<string, QueryCondition>;
|
|
51
79
|
fields?: string[];
|
|
52
80
|
order?: Record<string, 'asc' | 'desc'>;
|
|
53
81
|
limit?: number;
|
|
@@ -78,6 +106,19 @@ export type RecordsResult = {
|
|
|
78
106
|
$meta: SelectionMetadata;
|
|
79
107
|
[collection: string]: SelectedRecord[] | SelectionMetadata;
|
|
80
108
|
};
|
|
109
|
+
export type RecordIdentity = Pick<SelectedRecord, '$ref' | '$version'>;
|
|
110
|
+
export type RecordUpdateSet = Record<string, DataValue>;
|
|
111
|
+
export type DataPublication = {
|
|
112
|
+
version: string;
|
|
113
|
+
created: [];
|
|
114
|
+
updated: RecordIdentity[];
|
|
115
|
+
deleted: [];
|
|
116
|
+
};
|
|
117
|
+
export interface DataChangeSet {
|
|
118
|
+
update(record: RecordIdentity, set: RecordUpdateSet): void;
|
|
119
|
+
validate(): Promise<void>;
|
|
120
|
+
publish(): Promise<DataPublication>;
|
|
121
|
+
}
|
|
81
122
|
export interface DataRepository {
|
|
82
123
|
collections: {
|
|
83
124
|
list(): Promise<CollectionSummary[]>;
|
|
@@ -86,10 +127,14 @@ export interface DataRepository {
|
|
|
86
127
|
records: {
|
|
87
128
|
select(query: RecordsQuery): Promise<RecordsResult>;
|
|
88
129
|
};
|
|
130
|
+
changes(): DataChangeSet;
|
|
89
131
|
}
|
|
90
|
-
export type DataErrorCode = 'context_unavailable' | 'unauthenticated' | 'access_denied' | 'invalid_query' | 'request_too_large' | 'capacity' | 'unavailable';
|
|
132
|
+
export type DataErrorCode = 'context_unavailable' | 'unauthenticated' | 'access_denied' | 'invalid_query' | 'request_too_large' | 'capacity' | 'unavailable' | 'invalid_change' | 'conflict' | 'publication_pending';
|
|
133
|
+
export type DataEffect = 'unapplied' | 'uncertain' | 'applied';
|
|
91
134
|
export declare class DataOperationError extends Error {
|
|
92
135
|
readonly code: DataErrorCode;
|
|
93
136
|
readonly retryable: boolean;
|
|
94
|
-
|
|
137
|
+
readonly effect?: DataEffect | undefined;
|
|
138
|
+
constructor(code: DataErrorCode, retryable?: boolean, effect?: DataEffect | undefined);
|
|
95
139
|
}
|
|
140
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Provider-neutral authored contract. Registries and object locations stay server-side. */
|
|
2
|
+
export type FileStorage = 'local' | 'remote' | 'both';
|
|
3
|
+
export type FileInput = Blob | Uint8Array | ReadableStream<Uint8Array>;
|
|
4
|
+
export type FileCsvHints = {
|
|
5
|
+
encoding?: 'utf-8' | 'windows-1252';
|
|
6
|
+
delimiter?: ',' | ';' | '\t';
|
|
7
|
+
headerRow?: number;
|
|
8
|
+
};
|
|
9
|
+
export type FileCreateInput = {
|
|
10
|
+
file: FileInput;
|
|
11
|
+
size?: number;
|
|
12
|
+
path?: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
storage?: FileStorage;
|
|
15
|
+
csv?: FileCsvHints;
|
|
16
|
+
};
|
|
17
|
+
export type FileCreateOptions = {
|
|
18
|
+
requestKey?: string;
|
|
19
|
+
};
|
|
20
|
+
export type File = {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
path: string;
|
|
24
|
+
mediaType: string;
|
|
25
|
+
size: number;
|
|
26
|
+
sha256: string;
|
|
27
|
+
enrichments: Record<string, {
|
|
28
|
+
mediaType: string;
|
|
29
|
+
size: number;
|
|
30
|
+
sha256: string;
|
|
31
|
+
}>;
|
|
32
|
+
details: {
|
|
33
|
+
kind: 'csv';
|
|
34
|
+
encoding: 'utf-8' | 'windows-1252';
|
|
35
|
+
delimiter: ',' | ';' | '\t';
|
|
36
|
+
headerRow: number;
|
|
37
|
+
dataRows: number;
|
|
38
|
+
} | {
|
|
39
|
+
kind: 'pdf';
|
|
40
|
+
pages?: number;
|
|
41
|
+
} | {
|
|
42
|
+
kind: 'other';
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
export type FileErrorCode = 'invalid_input' | 'unsupported_context' | 'unauthenticated' | 'access_denied' | 'request_too_large' | 'conflict' | 'integrity' | 'unavailable' | 'capacity' | 'method_not_allowed';
|
|
46
|
+
export declare class FileOperationError extends Error {
|
|
47
|
+
readonly code: FileErrorCode;
|
|
48
|
+
readonly status: number;
|
|
49
|
+
readonly retryable: boolean;
|
|
50
|
+
constructor(code: FileErrorCode, status: number, retryable?: boolean);
|
|
51
|
+
}
|
|
52
|
+
export declare const FILE_ID_PATTERN: RegExp;
|
|
53
|
+
/** Treat descriptor transport as untrusted; no provider fields escape the boundary. */
|
|
54
|
+
export declare function parseFileDescriptor(value: unknown): File;
|
|
55
|
+
export declare function safeFileName(value: unknown): value is string;
|
|
56
|
+
export declare function safeFilePath(value: unknown): value is string;
|
|
57
|
+
/** Private transport metadata: normalized material options exclude the request key and size declaration. */
|
|
58
|
+
export type FileCreateMetadata = {
|
|
59
|
+
name: string;
|
|
60
|
+
path: string;
|
|
61
|
+
storage: 'remote';
|
|
62
|
+
csv?: Required<FileCsvHints>;
|
|
63
|
+
size: number;
|
|
64
|
+
requestKey: string;
|
|
65
|
+
};
|
|
66
|
+
export declare function parseFileCreateMetadata(value: unknown, maxBytes: number): FileCreateMetadata;
|