@oddington/sdk 0.1.0 → 0.1.2
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
|
@@ -13,6 +13,37 @@ import { defineHandler, resolveAsk, routinePublicationClient } from '@oddington/
|
|
|
13
13
|
const inbox = routinePublicationClient();
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
Bind a declared Airtable connection with `connections.airtable('project-management')`
|
|
17
|
+
from the package root. Import and binding perform no network or credential work.
|
|
18
|
+
The client exposes `tables.list()` and `records.list`, `get`, `create`, `update`
|
|
19
|
+
and `delete`. Reads allow table names or stable IDs and return all ordered pages;
|
|
20
|
+
writes require a stable table ID and one nonempty batch of at most ten records.
|
|
21
|
+
Operations cannot select another workspace, base, token or provider URL.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { connections } from '@oddington/sdk';
|
|
25
|
+
|
|
26
|
+
const airtable = connections.airtable('project-management');
|
|
27
|
+
const changed = await airtable.records.update({ table: 'tblContacts', records: [{
|
|
28
|
+
id: 'recRetained', expected: { Email: 'james@example.com', Company: null },
|
|
29
|
+
set: { Company: ['recCompany'] },
|
|
30
|
+
}] });
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Preserve original reviewed `expected` values. Non-null values must match exactly;
|
|
34
|
+
`null` requires an omitted field key and cannot be used as an update value. Reads
|
|
35
|
+
preserve Airtable's omitted-field shape. Create requires nonempty `fields`, update
|
|
36
|
+
requires nonempty `set`, and untouched fields remain unchanged. Guarded methods
|
|
37
|
+
read before a single batch write; this leaves a concurrency race and provides no
|
|
38
|
+
atomic compare-and-swap, rollback or implicit retry.
|
|
39
|
+
|
|
40
|
+
Success returns a safe Airtable `receipt` with the connection, configured base,
|
|
41
|
+
table, operation and acknowledged IDs. Create/update also return acknowledged
|
|
42
|
+
records separately. `AirtableOperationError` reports `unapplied`, `partial` or
|
|
43
|
+
`uncertain` effects and known safe `receipts`. Stop after partial or uncertain
|
|
44
|
+
work; never repeat the whole change blindly. Credentials must never be retained
|
|
45
|
+
in files, publication, logs, handler input, errors or receipts.
|
|
46
|
+
|
|
16
47
|
Managed routine launches supply workspace, scoped capability and working root.
|
|
17
48
|
The defaults read `ODDINGTON_ROUTINE_WORKSPACE`, `ODDINGTON_ROUTINE_RUN` and
|
|
18
49
|
`ODDINGTON_INBOX_WORKING_ROOT` (or the current directory). Callers may supply the
|
|
@@ -59,11 +90,3 @@ installation. Customer manifests cannot replace the runner-owned SDK. The runner
|
|
|
59
90
|
selects the approved code revision and handler, and supplies only scoped capability
|
|
60
91
|
transport. The package contains public runtime code, declarations, this README and
|
|
61
92
|
its license; it has no executable, operator/server code or infrastructure credentials.
|
|
62
|
-
|
|
63
|
-
From the product checkout, run `bun run --filter @oddington/sdk build`, then
|
|
64
|
-
`bun run --filter @oddington/sdk verify:package`. Verification installs the actual
|
|
65
|
-
tarball offline into a temporary consumer without the CLI, imports and typechecks
|
|
66
|
-
through `@oddington/sdk`, and exercises retained evidence, handlerless publication
|
|
67
|
-
and guarded repository calls. Set `PACKAGE_VERIFY_NODE` to the Node 22.12.0
|
|
68
|
-
executable for supported-baseline proof. The CLI's corresponding verifier installs
|
|
69
|
-
both local tarballs, checks `os --help` and refuses the removed subpath.
|
package/dist/index.js
CHANGED
|
@@ -7202,9 +7202,9 @@ var require_PDFContext = __commonJS((exports) => {
|
|
|
7202
7202
|
return array2;
|
|
7203
7203
|
} else {
|
|
7204
7204
|
var dict = PDFDict_1.default.withContext(this);
|
|
7205
|
-
var
|
|
7206
|
-
for (var idx = 0, len =
|
|
7207
|
-
var key =
|
|
7205
|
+
var keys3 = Object.keys(literal);
|
|
7206
|
+
for (var idx = 0, len = keys3.length;idx < len; idx++) {
|
|
7207
|
+
var key = keys3[idx];
|
|
7208
7208
|
var value = literal[key];
|
|
7209
7209
|
if (value !== undefined)
|
|
7210
7210
|
dict.set(PDFName_1.default.of(key), this.obj(value));
|
|
@@ -13306,9 +13306,9 @@ var require_PDFWidgetAnnotation = __commonJS((exports) => {
|
|
|
13306
13306
|
var _a;
|
|
13307
13307
|
var normal = (_a = this.getAppearances()) === null || _a === undefined ? undefined : _a.normal;
|
|
13308
13308
|
if (normal instanceof PDFDict_1.default) {
|
|
13309
|
-
var
|
|
13310
|
-
for (var idx = 0, len =
|
|
13311
|
-
var key =
|
|
13309
|
+
var keys3 = normal.keys();
|
|
13310
|
+
for (var idx = 0, len = keys3.length;idx < len; idx++) {
|
|
13311
|
+
var key = keys3[idx];
|
|
13312
13312
|
if (key !== PDFName_1.default.of("Off"))
|
|
13313
13313
|
return key;
|
|
13314
13314
|
}
|
|
@@ -14091,28 +14091,28 @@ var require_PDFAcroForm = __commonJS((exports) => {
|
|
|
14091
14091
|
this.dict = dict;
|
|
14092
14092
|
}
|
|
14093
14093
|
PDFAcroForm2.prototype.Fields = function() {
|
|
14094
|
-
var
|
|
14095
|
-
if (
|
|
14096
|
-
return
|
|
14094
|
+
var fields3 = this.dict.lookup(PDFName_1.default.of("Fields"));
|
|
14095
|
+
if (fields3 instanceof PDFArray_1.default)
|
|
14096
|
+
return fields3;
|
|
14097
14097
|
return;
|
|
14098
14098
|
};
|
|
14099
14099
|
PDFAcroForm2.prototype.getFields = function() {
|
|
14100
14100
|
var Fields = this.normalizedEntries().Fields;
|
|
14101
|
-
var
|
|
14101
|
+
var fields3 = new Array(Fields.size());
|
|
14102
14102
|
for (var idx = 0, len = Fields.size();idx < len; idx++) {
|
|
14103
14103
|
var ref = Fields.get(idx);
|
|
14104
14104
|
var dict = Fields.lookup(idx, PDFDict_1.default);
|
|
14105
|
-
|
|
14105
|
+
fields3[idx] = [utils_1.createPDFAcroField(dict, ref), ref];
|
|
14106
14106
|
}
|
|
14107
|
-
return
|
|
14107
|
+
return fields3;
|
|
14108
14108
|
};
|
|
14109
14109
|
PDFAcroForm2.prototype.getAllFields = function() {
|
|
14110
14110
|
var allFields = [];
|
|
14111
|
-
var pushFields = function(
|
|
14112
|
-
if (!
|
|
14111
|
+
var pushFields = function(fields3) {
|
|
14112
|
+
if (!fields3)
|
|
14113
14113
|
return;
|
|
14114
|
-
for (var idx = 0, len =
|
|
14115
|
-
var field =
|
|
14114
|
+
for (var idx = 0, len = fields3.length;idx < len; idx++) {
|
|
14115
|
+
var field = fields3[idx];
|
|
14116
14116
|
allFields.push(field);
|
|
14117
14117
|
var fieldModel = field[0];
|
|
14118
14118
|
if (fieldModel instanceof PDFAcroNonTerminal_1.default) {
|
|
@@ -14129,13 +14129,13 @@ var require_PDFAcroForm = __commonJS((exports) => {
|
|
|
14129
14129
|
};
|
|
14130
14130
|
PDFAcroForm2.prototype.removeField = function(field) {
|
|
14131
14131
|
var parent = field.getParent();
|
|
14132
|
-
var
|
|
14133
|
-
var index =
|
|
14134
|
-
if (
|
|
14132
|
+
var fields3 = parent === undefined ? this.normalizedEntries().Fields : parent.Kids();
|
|
14133
|
+
var index = fields3 === null || fields3 === undefined ? undefined : fields3.indexOf(field.ref);
|
|
14134
|
+
if (fields3 === undefined || index === undefined) {
|
|
14135
14135
|
throw new Error("Tried to remove inexistent field " + field.getFullyQualifiedName());
|
|
14136
14136
|
}
|
|
14137
|
-
|
|
14138
|
-
if (parent !== undefined &&
|
|
14137
|
+
fields3.remove(index);
|
|
14138
|
+
if (parent !== undefined && fields3.size() === 0) {
|
|
14139
14139
|
this.removeField(parent);
|
|
14140
14140
|
}
|
|
14141
14141
|
};
|
|
@@ -19086,20 +19086,20 @@ var require_PDFForm = __commonJS((exports) => {
|
|
|
19086
19086
|
};
|
|
19087
19087
|
PDFForm2.prototype.getFields = function() {
|
|
19088
19088
|
var allFields = this.acroForm.getAllFields();
|
|
19089
|
-
var
|
|
19089
|
+
var fields3 = [];
|
|
19090
19090
|
for (var idx = 0, len = allFields.length;idx < len; idx++) {
|
|
19091
19091
|
var _a = allFields[idx], acroField = _a[0], ref = _a[1];
|
|
19092
19092
|
var field = convertToPDFField(acroField, ref, this.doc);
|
|
19093
19093
|
if (field)
|
|
19094
|
-
|
|
19094
|
+
fields3.push(field);
|
|
19095
19095
|
}
|
|
19096
|
-
return
|
|
19096
|
+
return fields3;
|
|
19097
19097
|
};
|
|
19098
19098
|
PDFForm2.prototype.getFieldMaybe = function(name) {
|
|
19099
19099
|
utils_1.assertIs(name, "name", ["string"]);
|
|
19100
|
-
var
|
|
19101
|
-
for (var idx = 0, len =
|
|
19102
|
-
var field =
|
|
19100
|
+
var fields3 = this.getFields();
|
|
19101
|
+
for (var idx = 0, len = fields3.length;idx < len; idx++) {
|
|
19102
|
+
var field = fields3[idx];
|
|
19103
19103
|
if (field.getName() === name)
|
|
19104
19104
|
return field;
|
|
19105
19105
|
}
|
|
@@ -19222,9 +19222,9 @@ var require_PDFForm = __commonJS((exports) => {
|
|
|
19222
19222
|
if (options.updateFieldAppearances) {
|
|
19223
19223
|
this.updateFieldAppearances();
|
|
19224
19224
|
}
|
|
19225
|
-
var
|
|
19226
|
-
for (var i = 0, lenFields =
|
|
19227
|
-
var field =
|
|
19225
|
+
var fields3 = this.getFields();
|
|
19226
|
+
for (var i = 0, lenFields = fields3.length;i < lenFields; i++) {
|
|
19227
|
+
var field = fields3[i];
|
|
19228
19228
|
var widgets = field.acroField.getWidgets();
|
|
19229
19229
|
for (var j = 0, lenWidgets = widgets.length;j < lenWidgets; j++) {
|
|
19230
19230
|
var widget = widgets[j];
|
|
@@ -19271,9 +19271,9 @@ var require_PDFForm = __commonJS((exports) => {
|
|
|
19271
19271
|
PDFForm2.prototype.updateFieldAppearances = function(font) {
|
|
19272
19272
|
utils_1.assertOrUndefined(font, "font", [[PDFFont_1.default, "PDFFont"]]);
|
|
19273
19273
|
font = font !== null && font !== undefined ? font : this.getDefaultFont();
|
|
19274
|
-
var
|
|
19275
|
-
for (var idx = 0, len =
|
|
19276
|
-
var field =
|
|
19274
|
+
var fields3 = this.getFields();
|
|
19275
|
+
for (var idx = 0, len = fields3.length;idx < len; idx++) {
|
|
19276
|
+
var field = fields3[idx];
|
|
19277
19277
|
if (field.needsAppearancesUpdate()) {
|
|
19278
19278
|
field.defaultUpdateAppearances(font);
|
|
19279
19279
|
}
|
|
@@ -19351,9 +19351,9 @@ var require_PDFForm = __commonJS((exports) => {
|
|
|
19351
19351
|
return nonTerminal;
|
|
19352
19352
|
};
|
|
19353
19353
|
PDFForm2.prototype.findNonTerminal = function(partialName, parent) {
|
|
19354
|
-
var
|
|
19355
|
-
for (var idx = 0, len =
|
|
19356
|
-
var _a =
|
|
19354
|
+
var fields3 = parent instanceof core_1.PDFAcroForm ? this.acroForm.getFields() : core_1.createPDFAcroFields(parent.Kids());
|
|
19355
|
+
for (var idx = 0, len = fields3.length;idx < len; idx++) {
|
|
19356
|
+
var _a = fields3[idx], field = _a[0], ref = _a[1];
|
|
19357
19357
|
if (field.getPartialName() === partialName) {
|
|
19358
19358
|
if (field instanceof core_1.PDFAcroNonTerminal)
|
|
19359
19359
|
return [field, ref];
|
|
@@ -19408,9 +19408,9 @@ var require_PDFForm = __commonJS((exports) => {
|
|
|
19408
19408
|
var parent = _a[0], parentRef = _a[1];
|
|
19409
19409
|
var field = _b[0], fieldRef = _b[1];
|
|
19410
19410
|
var entries = parent.normalizedEntries();
|
|
19411
|
-
var
|
|
19412
|
-
for (var idx = 0, len =
|
|
19413
|
-
if (
|
|
19411
|
+
var fields3 = core_1.createPDFAcroFields("Kids" in entries ? entries.Kids : entries.Fields);
|
|
19412
|
+
for (var idx = 0, len = fields3.length;idx < len; idx++) {
|
|
19413
|
+
if (fields3[idx][0].getPartialName() === partialName) {
|
|
19414
19414
|
throw new errors_1.FieldAlreadyExistsError(partialName);
|
|
19415
19415
|
}
|
|
19416
19416
|
}
|
|
@@ -21549,27 +21549,6 @@ function validateResponseDefinition(value) {
|
|
|
21549
21549
|
return fail("Unsupported option source");
|
|
21550
21550
|
}
|
|
21551
21551
|
}
|
|
21552
|
-
function validateResponse(definition, value) {
|
|
21553
|
-
const saved = validateResponseDefinition(definition);
|
|
21554
|
-
const response = object(value, "response");
|
|
21555
|
-
if (saved.source.type === "records") {
|
|
21556
|
-
keys(response, ["selection"], "response");
|
|
21557
|
-
const record = validateRecordReference(response.selection);
|
|
21558
|
-
if (record.repository !== saved.source.collection.repository)
|
|
21559
|
-
fail("Record belongs to another repository");
|
|
21560
|
-
return { selection: record };
|
|
21561
|
-
}
|
|
21562
|
-
keys(response, ["selection", "note"], "response");
|
|
21563
|
-
const choice = id(response.selection, "response selection");
|
|
21564
|
-
const option = saved.source.options.find((option2) => option2.value === choice);
|
|
21565
|
-
if (!option)
|
|
21566
|
-
fail("Choice was not published");
|
|
21567
|
-
if (option.note)
|
|
21568
|
-
return { selection: choice, note: text(response.note, "required note").trim() };
|
|
21569
|
-
if (Object.hasOwn(response, "note"))
|
|
21570
|
-
fail("This choice does not accept a note");
|
|
21571
|
-
return { selection: choice };
|
|
21572
|
-
}
|
|
21573
21552
|
function handlerReference(value) {
|
|
21574
21553
|
const source = object(value, "handler reference");
|
|
21575
21554
|
keys(source, ["id", "input"], "handler reference");
|
|
@@ -21662,27 +21641,10 @@ function validatePublication(value) {
|
|
|
21662
21641
|
recipients: [...new Set([...extra, ...questions.flatMap((entry) => [entry.to, ...entry.allowedResponders])])]
|
|
21663
21642
|
};
|
|
21664
21643
|
}
|
|
21665
|
-
function recipientPresentation(value) {
|
|
21666
|
-
const publication = validatePublication(value);
|
|
21667
|
-
if (publication.kind === "digest")
|
|
21668
|
-
return publication;
|
|
21669
|
-
return { ...publication, questions: publication.questions.map(({ handler: _private, ...question2 }) => question2) };
|
|
21670
|
-
}
|
|
21671
|
-
function oddington(value) {
|
|
21672
|
-
const source = object(value, "Oddington assignment");
|
|
21673
|
-
keys(source, ["reason"], "Oddington assignment");
|
|
21674
|
-
return { type: "oddington", reason: text(source.reason, "Oddington reason") };
|
|
21675
|
-
}
|
|
21676
21644
|
function continueAsk(value) {
|
|
21677
21645
|
const source = object(value, "continueAsk");
|
|
21678
|
-
keys(source, [
|
|
21679
|
-
|
|
21680
|
-
return { type: "continue" };
|
|
21681
|
-
const assignment = object(source.assignTo, "assignment");
|
|
21682
|
-
keys(assignment, ["type", "reason"], "assignment");
|
|
21683
|
-
if (assignment.type !== "oddington")
|
|
21684
|
-
fail("Only Oddington assignment is supported");
|
|
21685
|
-
return { type: "continue", assignTo: oddington({ reason: assignment.reason }) };
|
|
21646
|
+
keys(source, [], "continueAsk");
|
|
21647
|
+
return { type: "continue" };
|
|
21686
21648
|
}
|
|
21687
21649
|
function resolution(value) {
|
|
21688
21650
|
const source = object(value, "resolution");
|
|
@@ -21694,23 +21656,6 @@ function resolution(value) {
|
|
|
21694
21656
|
function resolveAsk(value) {
|
|
21695
21657
|
return { type: "resolve", ...resolution(value) };
|
|
21696
21658
|
}
|
|
21697
|
-
function validateHandlerTransition(value, state) {
|
|
21698
|
-
const source = object(value, "transition");
|
|
21699
|
-
if (source.type === "resolve") {
|
|
21700
|
-
keys(source, ["type", "outcome", "evidence"], "transition");
|
|
21701
|
-
return resolveAsk({
|
|
21702
|
-
outcome: source.outcome,
|
|
21703
|
-
...source.evidence === undefined ? {} : { evidence: source.evidence }
|
|
21704
|
-
});
|
|
21705
|
-
}
|
|
21706
|
-
if (source.type !== "continue")
|
|
21707
|
-
fail("Unsupported handler transition");
|
|
21708
|
-
keys(source, ["type", "assignTo"], "transition");
|
|
21709
|
-
const transition = continueAsk(source.assignTo === undefined ? {} : { assignTo: source.assignTo });
|
|
21710
|
-
if (transition.type === "continue" && !transition.assignTo && !state.hasOtherViableWork)
|
|
21711
|
-
fail("continueAsk would strand the ask without viable work");
|
|
21712
|
-
return transition;
|
|
21713
|
-
}
|
|
21714
21659
|
function validateResolution(value) {
|
|
21715
21660
|
const source = object(value, "published resolution");
|
|
21716
21661
|
keys(source, ["expectedRevision", "outcome", "evidence"], "published resolution");
|
|
@@ -21842,146 +21787,340 @@ function validateRoutineRecordWrite(value) {
|
|
|
21842
21787
|
function validCreatePath(value) {
|
|
21843
21788
|
return value === value.trim() && value.endsWith(".md") && !value.startsWith("/") && value.split("/").every((segment) => !!segment && ![".", ".."].includes(segment) && /^[a-zA-Z0-9_.-]+$/.test(segment));
|
|
21844
21789
|
}
|
|
21845
|
-
// ../../shared/
|
|
21846
|
-
function
|
|
21847
|
-
|
|
21848
|
-
if (!input.repository)
|
|
21849
|
-
fail2("Repository capture is required");
|
|
21850
|
-
return input;
|
|
21790
|
+
// ../../shared/connections.ts
|
|
21791
|
+
function validGoogleScopes(value) {
|
|
21792
|
+
return Array.isArray(value) && value.length > 0 && value.length <= 32 && value.every((scope) => typeof scope === "string" && scope.length <= 256 && /^(https:\/\/www\.googleapis\.com\/auth\/[A-Za-z0-9._-]+|https:\/\/mail\.google\.com\/|openid|email|profile)$/.test(scope)) && new Set(value).size === value.length;
|
|
21851
21793
|
}
|
|
21852
|
-
|
|
21853
|
-
|
|
21854
|
-
|
|
21855
|
-
];
|
|
21794
|
+
|
|
21795
|
+
// ../../shared/connection-lease.ts
|
|
21796
|
+
var ROUTINE_CONNECTION_CREDENTIALS_ENV = "ODDINGTON_ROUTINE_CONNECTION_CREDENTIALS";
|
|
21797
|
+
var connectionUuid = (v) => typeof v === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(v);
|
|
21798
|
+
function parseConnectionLease(raw, now = Date.now()) {
|
|
21799
|
+
const v = raw;
|
|
21800
|
+
if (!v || typeof v !== "object" || Array.isArray(v) || !connectionUuid(v.connectionId) || !connectionUuid(v.runId) || typeof v.name !== "string" || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(v.name) || typeof v.accessToken !== "string" || !/^[A-Za-z0-9._~+/-]{1,8192}={0,2}$/.test(v.accessToken))
|
|
21801
|
+
throw new Error("Invalid connection credential");
|
|
21802
|
+
if (v.provider === "gmail") {
|
|
21803
|
+
if (Object.keys(v).sort().join(",") !== "accessToken,accountEmail,connectionId,expiresAt,name,provider,runId,scopes" || typeof v.accountEmail !== "string" || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v.accountEmail) || !validGoogleScopes(v.scopes) || typeof v.expiresAt !== "string" || !Number.isFinite(Date.parse(v.expiresAt)) || Date.parse(v.expiresAt) <= now + 60000 || Date.parse(v.expiresAt) > now + 3600000)
|
|
21804
|
+
throw new Error("Invalid connection credential");
|
|
21805
|
+
return v;
|
|
21806
|
+
}
|
|
21807
|
+
if (v.provider === "airtable" && Object.keys(v).sort().join(",") === "accessToken,baseId,connectionId,name,provider,runExpiresAt,runId" && typeof v.baseId === "string" && /^app[A-Za-z0-9]+$/.test(v.baseId) && typeof v.runExpiresAt === "string" && Number.isFinite(Date.parse(v.runExpiresAt)) && Date.parse(v.runExpiresAt) > now + 60000 && Date.parse(v.runExpiresAt) <= now + 15 * 60000)
|
|
21808
|
+
return v;
|
|
21809
|
+
throw new Error("Invalid connection credential");
|
|
21810
|
+
}
|
|
21811
|
+
|
|
21812
|
+
// ../../shared/airtable-contract.ts
|
|
21813
|
+
class AirtableOperationError extends Error {
|
|
21814
|
+
effect;
|
|
21815
|
+
receipts;
|
|
21816
|
+
constructor(message, effect = "unapplied", receipts = []) {
|
|
21817
|
+
super(message);
|
|
21818
|
+
this.effect = effect;
|
|
21819
|
+
this.receipts = receipts;
|
|
21820
|
+
this.name = "AirtableOperationError";
|
|
21821
|
+
}
|
|
21822
|
+
}
|
|
21823
|
+
|
|
21824
|
+
// ../../shared/airtable-client.ts
|
|
21825
|
+
var recordId = (v) => typeof v === "string" && /^rec[A-Za-z0-9]+$/.test(v);
|
|
21826
|
+
var tableId = (v) => typeof v === "string" && /^tbl[A-Za-z0-9]+$/.test(v);
|
|
21856
21827
|
function fail2(message) {
|
|
21857
|
-
throw new
|
|
21828
|
+
throw new AirtableOperationError(message);
|
|
21858
21829
|
}
|
|
21859
|
-
function object2(
|
|
21860
|
-
|
|
21861
|
-
|
|
21862
|
-
|
|
21863
|
-
return copy;
|
|
21830
|
+
function object2(v) {
|
|
21831
|
+
if (!v || typeof v !== "object" || Array.isArray(v) || ![null, Object.prototype].includes(Object.getPrototypeOf(v)))
|
|
21832
|
+
fail2("Invalid Airtable data");
|
|
21833
|
+
return v;
|
|
21864
21834
|
}
|
|
21865
|
-
function
|
|
21866
|
-
|
|
21835
|
+
function keys2(v, allowed, required = allowed) {
|
|
21836
|
+
if (Object.keys(v).some((k) => !allowed.includes(k)) || required.some((k) => !Object.hasOwn(v, k)))
|
|
21837
|
+
fail2("Invalid Airtable input");
|
|
21867
21838
|
}
|
|
21868
|
-
function
|
|
21869
|
-
return typeof
|
|
21839
|
+
function text2(v) {
|
|
21840
|
+
return typeof v === "string" && v.length > 0 && v.length <= 1024;
|
|
21870
21841
|
}
|
|
21871
|
-
function
|
|
21872
|
-
|
|
21873
|
-
|
|
21874
|
-
|
|
21875
|
-
|
|
21876
|
-
|
|
21877
|
-
|
|
21878
|
-
|
|
21879
|
-
labels.add(field.label);
|
|
21880
|
-
recordValue(field.value);
|
|
21842
|
+
function fields2(v, nonempty = false) {
|
|
21843
|
+
const result = object2(v);
|
|
21844
|
+
if (nonempty && !Object.keys(result).length || Object.keys(result).some((k) => !text2(k)))
|
|
21845
|
+
fail2("Invalid Airtable fields");
|
|
21846
|
+
try {
|
|
21847
|
+
return serializable(result);
|
|
21848
|
+
} catch {
|
|
21849
|
+
return fail2("Invalid Airtable fields");
|
|
21881
21850
|
}
|
|
21882
21851
|
}
|
|
21883
|
-
function
|
|
21884
|
-
const
|
|
21885
|
-
|
|
21886
|
-
|
|
21887
|
-
|
|
21888
|
-
|
|
21889
|
-
|
|
21890
|
-
|
|
21891
|
-
|
|
21892
|
-
|
|
21893
|
-
|
|
21894
|
-
|
|
21895
|
-
|
|
21896
|
-
|
|
21897
|
-
|
|
21898
|
-
|
|
21899
|
-
|
|
21900
|
-
|
|
21901
|
-
|
|
21902
|
-
|
|
21852
|
+
function record(v) {
|
|
21853
|
+
const r = object2(v);
|
|
21854
|
+
if (!recordId(r.id) || r.createdTime !== undefined && (typeof r.createdTime !== "string" || !Number.isFinite(Date.parse(r.createdTime))))
|
|
21855
|
+
fail2("Malformed Airtable record");
|
|
21856
|
+
return { id: r.id, ...r.createdTime === undefined ? {} : { createdTime: r.createdTime }, fields: fields2(r.fields) };
|
|
21857
|
+
}
|
|
21858
|
+
function equal(a, b) {
|
|
21859
|
+
if (a === b)
|
|
21860
|
+
return true;
|
|
21861
|
+
if (!a || !b || typeof a !== "object" || typeof b !== "object" || Array.isArray(a) !== Array.isArray(b))
|
|
21862
|
+
return false;
|
|
21863
|
+
const left = Object.keys(a), right = Object.keys(b);
|
|
21864
|
+
return left.length === right.length && left.every((k) => Object.hasOwn(b, k) && equal(a[k], b[k]));
|
|
21865
|
+
}
|
|
21866
|
+
function createConnections(transport) {
|
|
21867
|
+
return { airtable(name) {
|
|
21868
|
+
if (typeof name !== "string" || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(name))
|
|
21869
|
+
fail2("Invalid Airtable connection name");
|
|
21870
|
+
const call = (operation, input) => transport(name, operation, input);
|
|
21871
|
+
return {
|
|
21872
|
+
tables: { list: async () => await call("tables.list", {}) },
|
|
21873
|
+
records: {
|
|
21874
|
+
list: async (input) => await call("records.list", input),
|
|
21875
|
+
get: async (input) => await call("records.get", input),
|
|
21876
|
+
create: async (input) => await call("records.create", input),
|
|
21877
|
+
update: async (input) => await call("records.update", input),
|
|
21878
|
+
delete: async (input) => await call("records.delete", input)
|
|
21879
|
+
}
|
|
21880
|
+
};
|
|
21881
|
+
} };
|
|
21882
|
+
}
|
|
21883
|
+
async function executeAirtableOperation(authority, operation, raw, options = {}) {
|
|
21884
|
+
const { name, baseId, accessToken } = authority;
|
|
21885
|
+
if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(name) || !/^app[A-Za-z0-9]+$/.test(baseId) || !/^[A-Za-z0-9._~+/-]{1,8192}={0,2}$/.test(accessToken))
|
|
21886
|
+
fail2("Airtable connection unavailable");
|
|
21887
|
+
let input;
|
|
21888
|
+
try {
|
|
21889
|
+
input = object2(serializable(raw));
|
|
21890
|
+
} catch {
|
|
21891
|
+
return fail2("Invalid Airtable input");
|
|
21892
|
+
}
|
|
21893
|
+
const mutation = ["records.create", "records.update", "records.delete"].includes(operation);
|
|
21894
|
+
const receipt = (ids2) => ({
|
|
21895
|
+
provider: "airtable",
|
|
21896
|
+
connection: `airtable/${name}`,
|
|
21897
|
+
baseId,
|
|
21898
|
+
table: input.table,
|
|
21899
|
+
operation: operation.slice(8),
|
|
21900
|
+
records: ids2.map((id2) => ({ id: id2 }))
|
|
21901
|
+
});
|
|
21902
|
+
async function request(path2, init = {}, write = false) {
|
|
21903
|
+
let response;
|
|
21904
|
+
try {
|
|
21905
|
+
response = await (options.fetch ?? fetch)(`https://api.airtable.com/v0/${path2}`, {
|
|
21906
|
+
...init,
|
|
21907
|
+
headers: { authorization: `Bearer ${accessToken}`, "content-type": "application/json" },
|
|
21908
|
+
redirect: "manual",
|
|
21909
|
+
signal: options.signal ? AbortSignal.any([options.signal, AbortSignal.timeout(15000)]) : AbortSignal.timeout(15000)
|
|
21910
|
+
});
|
|
21911
|
+
} catch {
|
|
21912
|
+
throw new AirtableOperationError("Airtable request acknowledgment unavailable", write ? "uncertain" : "unapplied");
|
|
21903
21913
|
}
|
|
21904
|
-
|
|
21905
|
-
|
|
21906
|
-
|
|
21907
|
-
fail2("Invalid Record list or remaining count");
|
|
21908
|
-
v.items.forEach(recordValue);
|
|
21909
|
-
break;
|
|
21914
|
+
if (!response.ok) {
|
|
21915
|
+
await response.body?.cancel().catch(() => {});
|
|
21916
|
+
throw new AirtableOperationError("Airtable request rejected", write && (response.status >= 500 || response.status === 408 || response.status < 400) ? "uncertain" : "unapplied");
|
|
21910
21917
|
}
|
|
21911
|
-
|
|
21912
|
-
|
|
21913
|
-
|
|
21914
|
-
|
|
21918
|
+
try {
|
|
21919
|
+
if (!response.headers.get("content-type")?.toLowerCase().includes("application/json"))
|
|
21920
|
+
throw new Error;
|
|
21921
|
+
const value = object2(await response.json());
|
|
21922
|
+
const safe = serializable(value);
|
|
21923
|
+
if (Object.hasOwn(value, "error") || JSON.stringify(safe).includes(accessToken))
|
|
21924
|
+
throw new Error;
|
|
21925
|
+
return value;
|
|
21926
|
+
} catch {
|
|
21927
|
+
throw new AirtableOperationError("Malformed Airtable acknowledgment", write ? "uncertain" : "unapplied");
|
|
21915
21928
|
}
|
|
21916
|
-
default:
|
|
21917
|
-
fail2("Unknown Record value");
|
|
21918
21929
|
}
|
|
21919
|
-
|
|
21920
|
-
|
|
21921
|
-
|
|
21922
|
-
|
|
21923
|
-
|
|
21924
|
-
|
|
21925
|
-
|
|
21926
|
-
|
|
21927
|
-
|
|
21928
|
-
|
|
21929
|
-
|
|
21930
|
-
|
|
21931
|
-
|
|
21932
|
-
|
|
21933
|
-
|
|
21934
|
-
|
|
21935
|
-
|
|
21930
|
+
if (operation === "tables.list") {
|
|
21931
|
+
keys2(input, []);
|
|
21932
|
+
const tables = [], seen = new Set;
|
|
21933
|
+
let offset;
|
|
21934
|
+
do {
|
|
21935
|
+
const query2 = new URLSearchParams(offset ? { offset } : {});
|
|
21936
|
+
const page = await request(`meta/bases/${baseId}/tables?${query2}`);
|
|
21937
|
+
if (!Array.isArray(page.tables))
|
|
21938
|
+
fail2("Malformed Airtable tables");
|
|
21939
|
+
for (const value of page.tables) {
|
|
21940
|
+
const t = object2(value);
|
|
21941
|
+
if (!tableId(t.id) || !text2(t.name) || !text2(t.primaryFieldId) || !Array.isArray(t.fields) || tables.some((existing) => existing.id === t.id))
|
|
21942
|
+
fail2("Malformed Airtable table");
|
|
21943
|
+
const metadata = t.fields.map((value2) => {
|
|
21944
|
+
const f = object2(value2);
|
|
21945
|
+
if (typeof f.id !== "string" || !/^fld[A-Za-z0-9]+$/.test(f.id) || !text2(f.name) || !text2(f.type))
|
|
21946
|
+
fail2("Malformed Airtable field");
|
|
21947
|
+
return { id: f.id, name: f.name, type: f.type, ...f.options === undefined ? {} : { options: serializable(f.options) } };
|
|
21948
|
+
});
|
|
21949
|
+
if (!metadata.some((f) => f.id === t.primaryFieldId) || new Set(metadata.map((f) => f.id)).size !== metadata.length)
|
|
21950
|
+
fail2("Malformed Airtable fields");
|
|
21951
|
+
tables.push({ id: t.id, name: t.name, primaryFieldId: t.primaryFieldId, fields: metadata });
|
|
21952
|
+
}
|
|
21953
|
+
offset = nextOffset(page.offset, seen);
|
|
21954
|
+
} while (offset);
|
|
21955
|
+
return tables;
|
|
21936
21956
|
}
|
|
21937
|
-
|
|
21938
|
-
|
|
21939
|
-
|
|
21940
|
-
|
|
21941
|
-
|
|
21942
|
-
|
|
21943
|
-
|
|
21944
|
-
|
|
21945
|
-
|
|
21946
|
-
|
|
21947
|
-
|
|
21948
|
-
|
|
21949
|
-
|
|
21950
|
-
const c = object2(entry, ["id", "label", "type", "unit", "values", "missing", "missingLabel"], ["id", "label", "type", "missing"], "column");
|
|
21951
|
-
if (!text2(c.id) || !text2(c.label) || ids.has(c.id) || c.missing !== "reject" || !["string", "date", "integer", "number", "boolean", "enum", "nullable-enum", "link"].includes(c.type) || (["integer", "number"].includes(c.type) ? !text2(c.unit) : c.unit !== undefined) || (c.type === "nullable-enum" ? !text2(c.missingLabel) : c.missingLabel !== undefined))
|
|
21952
|
-
fail2("Invalid column definition or units");
|
|
21953
|
-
if (["enum", "nullable-enum"].includes(c.type)) {
|
|
21954
|
-
if (!Array.isArray(c.values) || !c.values.length || c.values.some((s) => !text2(s)) || new Set(c.values).size !== c.values.length)
|
|
21955
|
-
fail2("Enum columns require distinct values");
|
|
21956
|
-
} else if (c.values !== undefined)
|
|
21957
|
-
fail2("Only enum columns accept values");
|
|
21958
|
-
ids.add(c.id);
|
|
21957
|
+
if (!text2(input.table) || input.table === "." || input.table === "..")
|
|
21958
|
+
fail2("Invalid Airtable table");
|
|
21959
|
+
if (mutation && !tableId(input.table))
|
|
21960
|
+
fail2("Airtable writes require a stable table ID");
|
|
21961
|
+
const path = `${baseId}/${encodeURIComponent(input.table)}`;
|
|
21962
|
+
if (operation === "records.get") {
|
|
21963
|
+
keys2(input, ["table", "id"]);
|
|
21964
|
+
if (!recordId(input.id))
|
|
21965
|
+
fail2("Invalid Airtable record ID");
|
|
21966
|
+
const result2 = record(await request(`${path}/${input.id}`));
|
|
21967
|
+
if (result2.id !== input.id)
|
|
21968
|
+
fail2("Malformed Airtable record");
|
|
21969
|
+
return result2;
|
|
21959
21970
|
}
|
|
21960
|
-
|
|
21961
|
-
|
|
21962
|
-
|
|
21963
|
-
|
|
21964
|
-
|
|
21965
|
-
|
|
21966
|
-
|
|
21967
|
-
const
|
|
21968
|
-
|
|
21969
|
-
|
|
21970
|
-
|
|
21971
|
-
|
|
21972
|
-
|
|
21971
|
+
if (operation === "records.list") {
|
|
21972
|
+
keys2(input, ["table", "fields"], ["table"]);
|
|
21973
|
+
if (input.fields !== undefined && (!Array.isArray(input.fields) || !input.fields.length || input.fields.some((f) => !text2(f)) || new Set(input.fields).size !== input.fields.length))
|
|
21974
|
+
fail2("Invalid Airtable field selection");
|
|
21975
|
+
const records2 = [], ids2 = new Set, seen = new Set;
|
|
21976
|
+
let offset;
|
|
21977
|
+
do {
|
|
21978
|
+
const query2 = new URLSearchParams;
|
|
21979
|
+
for (const field of input.fields ?? [])
|
|
21980
|
+
query2.append("fields[]", field);
|
|
21981
|
+
if (offset)
|
|
21982
|
+
query2.set("offset", offset);
|
|
21983
|
+
const page = await request(`${path}?${query2}`);
|
|
21984
|
+
if (!Array.isArray(page.records))
|
|
21985
|
+
fail2("Malformed Airtable records");
|
|
21986
|
+
for (const value of page.records) {
|
|
21987
|
+
const r = record(value);
|
|
21988
|
+
if (ids2.has(r.id))
|
|
21989
|
+
fail2("Malformed Airtable pagination");
|
|
21990
|
+
ids2.add(r.id);
|
|
21991
|
+
records2.push(r);
|
|
21992
|
+
}
|
|
21993
|
+
offset = nextOffset(page.offset, seen);
|
|
21994
|
+
} while (offset);
|
|
21995
|
+
return records2;
|
|
21973
21996
|
}
|
|
21974
|
-
if (
|
|
21975
|
-
fail2("
|
|
21976
|
-
|
|
21997
|
+
if (!mutation)
|
|
21998
|
+
fail2("Unsupported Airtable operation");
|
|
21999
|
+
keys2(input, ["table", "records"]);
|
|
22000
|
+
if (!Array.isArray(input.records) || !input.records.length || input.records.length > 10)
|
|
22001
|
+
fail2("Invalid Airtable batch");
|
|
22002
|
+
const kind = operation.slice(8), ids = new Set;
|
|
22003
|
+
const records = input.records.map((value) => {
|
|
22004
|
+
const r = object2(value);
|
|
22005
|
+
keys2(r, kind === "create" ? ["fields"] : kind === "update" ? ["id", "expected", "set"] : ["id", "expected"]);
|
|
22006
|
+
if (kind === "create")
|
|
22007
|
+
return { fields: fields2(r.fields, true) };
|
|
22008
|
+
if (!recordId(r.id) || ids.has(r.id))
|
|
22009
|
+
fail2("Invalid Airtable batch record ID");
|
|
22010
|
+
ids.add(r.id);
|
|
22011
|
+
const set = kind === "update" ? fields2(r.set, true) : undefined;
|
|
22012
|
+
if (set && Object.values(set).some((value2) => value2 === null))
|
|
22013
|
+
fail2("Airtable guard null cannot be an update value");
|
|
22014
|
+
return { id: r.id, expected: fields2(r.expected), ...set ? { set } : {} };
|
|
22015
|
+
});
|
|
22016
|
+
for (const r of records) {
|
|
22017
|
+
if (!("id" in r))
|
|
22018
|
+
continue;
|
|
22019
|
+
const current = record(await request(`${path}/${r.id}`));
|
|
22020
|
+
if (current.id !== r.id)
|
|
22021
|
+
fail2("Malformed Airtable record");
|
|
22022
|
+
for (const [key, value] of Object.entries(r.expected)) {
|
|
22023
|
+
if (value === null ? Object.hasOwn(current.fields, key) : !Object.hasOwn(current.fields, key) || !equal(current.fields[key], value))
|
|
22024
|
+
fail2("Airtable reviewed values are stale");
|
|
22025
|
+
}
|
|
22026
|
+
}
|
|
22027
|
+
await options.beforeMutation?.();
|
|
22028
|
+
const query = new URLSearchParams;
|
|
22029
|
+
if (kind === "delete")
|
|
22030
|
+
for (const id2 of ids)
|
|
22031
|
+
query.append("records[]", id2);
|
|
22032
|
+
const result = await request(`${path}${kind === "delete" ? `?${query}` : ""}`, {
|
|
22033
|
+
method: kind === "create" ? "POST" : kind === "update" ? "PATCH" : "DELETE",
|
|
22034
|
+
...kind === "delete" ? {} : { body: JSON.stringify({ records: records.map((r) => ("id" in r) ? { id: r.id, fields: r.set } : r) }) }
|
|
22035
|
+
}, true);
|
|
22036
|
+
const acknowledged = [], returned = [];
|
|
22037
|
+
try {
|
|
22038
|
+
if (!Array.isArray(result.records))
|
|
22039
|
+
throw new Error;
|
|
22040
|
+
for (const value of result.records) {
|
|
22041
|
+
const r = object2(value);
|
|
22042
|
+
if (!recordId(r.id) || acknowledged.includes(r.id) || kind !== "create" && !ids.has(r.id) || kind === "delete" && r.deleted !== true)
|
|
22043
|
+
throw new Error;
|
|
22044
|
+
acknowledged.push(r.id);
|
|
22045
|
+
if (kind !== "delete")
|
|
22046
|
+
returned.push(record(r));
|
|
22047
|
+
}
|
|
22048
|
+
if (acknowledged.length !== records.length)
|
|
22049
|
+
throw new Error;
|
|
22050
|
+
} catch {
|
|
22051
|
+
throw new AirtableOperationError("Incomplete Airtable mutation acknowledgment", acknowledged.length ? "partial" : "uncertain", acknowledged.length ? [receipt(acknowledged)] : []);
|
|
22052
|
+
}
|
|
22053
|
+
return { receipt: receipt(acknowledged), ...kind === "delete" ? {} : { records: returned } };
|
|
21977
22054
|
}
|
|
21978
|
-
|
|
21979
|
-
|
|
21980
|
-
|
|
21981
|
-
if (!
|
|
21982
|
-
fail2("
|
|
21983
|
-
|
|
22055
|
+
function nextOffset(value, seen) {
|
|
22056
|
+
if (value === undefined)
|
|
22057
|
+
return;
|
|
22058
|
+
if (!text2(value) || seen.has(value))
|
|
22059
|
+
fail2("Malformed Airtable pagination");
|
|
22060
|
+
seen.add(value);
|
|
22061
|
+
return value;
|
|
22062
|
+
}
|
|
22063
|
+
|
|
22064
|
+
// ../../shared/airtable-transport.ts
|
|
22065
|
+
var MANAGED_CONNECTION_CAPABILITY_ENV = "ODDINGTON_MANAGED_CONNECTION_CAPABILITY";
|
|
22066
|
+
function managedConnections(capability) {
|
|
22067
|
+
const endpoint = new URL(capability.endpoint);
|
|
22068
|
+
if (endpoint.protocol !== "http:" || endpoint.hostname !== "127.0.0.1" || !endpoint.port || endpoint.pathname !== "/" || endpoint.search || endpoint.hash || endpoint.username || endpoint.password || !/^[0-9a-f-]{36}$/.test(capability.token))
|
|
22069
|
+
throw new AirtableOperationError("Airtable capability unavailable");
|
|
22070
|
+
const transport = async (name, operation, input) => {
|
|
22071
|
+
let safeInput;
|
|
22072
|
+
try {
|
|
22073
|
+
safeInput = serializable(input);
|
|
22074
|
+
} catch {
|
|
22075
|
+
safeInput = null;
|
|
22076
|
+
}
|
|
22077
|
+
try {
|
|
22078
|
+
const response = await fetch(new URL("airtable", endpoint), {
|
|
22079
|
+
method: "POST",
|
|
22080
|
+
redirect: "error",
|
|
22081
|
+
headers: { authorization: `Bearer ${capability.token}`, "content-type": "application/json" },
|
|
22082
|
+
body: JSON.stringify({ name, operation, input: safeInput }),
|
|
22083
|
+
signal: AbortSignal.timeout(125000)
|
|
22084
|
+
});
|
|
22085
|
+
const value = await response.json();
|
|
22086
|
+
if (!response.ok)
|
|
22087
|
+
throw new AirtableOperationError("Airtable operation failed; the execution retains its evidence", value.effect === "partial" || value.effect === "uncertain" ? value.effect : "unapplied", value.receipts ?? []);
|
|
22088
|
+
return value;
|
|
22089
|
+
} catch (error) {
|
|
22090
|
+
if (error instanceof AirtableOperationError)
|
|
22091
|
+
throw error;
|
|
22092
|
+
throw new AirtableOperationError("Airtable parent acknowledgment unavailable", ["records.create", "records.update", "records.delete"].includes(operation) ? "uncertain" : "unapplied");
|
|
22093
|
+
}
|
|
22094
|
+
};
|
|
22095
|
+
return createConnections(transport);
|
|
21984
22096
|
}
|
|
22097
|
+
|
|
22098
|
+
// src/connections.ts
|
|
22099
|
+
var connections = createConnections(async (name, operation, input) => {
|
|
22100
|
+
try {
|
|
22101
|
+
const managed = process.env[MANAGED_CONNECTION_CAPABILITY_ENV];
|
|
22102
|
+
if (managed) {
|
|
22103
|
+
const client = managedConnections(JSON.parse(managed)).airtable(name);
|
|
22104
|
+
if (operation === "tables.list")
|
|
22105
|
+
return await client.tables.list();
|
|
22106
|
+
return await client.records[operation.slice(8)](input);
|
|
22107
|
+
}
|
|
22108
|
+
const raw = JSON.parse(process.env[ROUTINE_CONNECTION_CREDENTIALS_ENV] ?? "[]");
|
|
22109
|
+
if (!Array.isArray(raw))
|
|
22110
|
+
throw new Error;
|
|
22111
|
+
const leases = raw.map((value) => parseConnectionLease(value));
|
|
22112
|
+
if (leases.some((lease2) => lease2.provider !== "airtable") || new Set(leases.map((lease2) => lease2.name)).size !== leases.length)
|
|
22113
|
+
throw new Error;
|
|
22114
|
+
const lease = leases.find((lease2) => lease2.name === name);
|
|
22115
|
+
if (!lease || lease.provider !== "airtable")
|
|
22116
|
+
throw new Error;
|
|
22117
|
+
return await executeAirtableOperation(lease, operation, input);
|
|
22118
|
+
} catch (error) {
|
|
22119
|
+
if (error instanceof AirtableOperationError)
|
|
22120
|
+
throw error;
|
|
22121
|
+
throw new AirtableOperationError("Airtable connection unavailable");
|
|
22122
|
+
}
|
|
22123
|
+
});
|
|
21985
22124
|
// ../../shared/delimited-text.ts
|
|
21986
22125
|
function delimiterFor(value) {
|
|
21987
22126
|
const counts = new Map([
|
|
@@ -22374,37 +22513,20 @@ function routinePublicationClient(options = {}) {
|
|
|
22374
22513
|
};
|
|
22375
22514
|
}
|
|
22376
22515
|
export {
|
|
22377
|
-
validateResponseDefinition,
|
|
22378
|
-
validateResponse,
|
|
22379
|
-
validateResolution,
|
|
22380
|
-
validateRecordViewInput,
|
|
22381
|
-
validateRecordTableInput,
|
|
22382
|
-
validateRecordSource,
|
|
22383
|
-
validateRecordReference,
|
|
22384
|
-
validatePublication,
|
|
22385
|
-
validateInboxFile,
|
|
22386
|
-
validateHandlerTransition,
|
|
22387
|
-
validateCapturedRecordTableInput,
|
|
22388
|
-
validateBlock,
|
|
22389
|
-
serializable,
|
|
22390
22516
|
selection,
|
|
22391
22517
|
routinePublicationClient,
|
|
22392
22518
|
richTextBlock,
|
|
22393
22519
|
respond,
|
|
22394
|
-
resolveInboxView,
|
|
22395
22520
|
resolveAsk,
|
|
22396
|
-
registeredViews,
|
|
22397
22521
|
recordsBlock,
|
|
22398
22522
|
recordBlock,
|
|
22399
|
-
recipientPresentation,
|
|
22400
22523
|
question,
|
|
22401
|
-
oddington,
|
|
22402
22524
|
fileBlock,
|
|
22403
22525
|
defineHandler,
|
|
22404
|
-
createInboxPublisher,
|
|
22405
22526
|
continueAsk,
|
|
22406
|
-
|
|
22527
|
+
connections,
|
|
22407
22528
|
asksBlock,
|
|
22408
22529
|
RoutineRecordWriteError,
|
|
22409
|
-
InboxContractError
|
|
22530
|
+
InboxContractError,
|
|
22531
|
+
AirtableOperationError
|
|
22410
22532
|
};
|
|
@@ -2,9 +2,16 @@ import type { AskInput, CapturedRecords, DigestInput, InboxFile, PublicationRequ
|
|
|
2
2
|
import type { FileAddOptions } from '../../../shared/inbox-files.js';
|
|
3
3
|
import type { RecordReadInput } from '../../../shared/record-capture.js';
|
|
4
4
|
import type { RecordDeleteRequest, RecordUpdateRequest, RecordWriteRequest } from '../../../shared/record-write.js';
|
|
5
|
-
export
|
|
6
|
-
export
|
|
5
|
+
export { InboxContractError, asksBlock, continueAsk, defineHandler, fileBlock, question, recordBlock, recordsBlock, resolveAsk, respond, richTextBlock, selection, } from '../../../shared/inbox-contract.js';
|
|
6
|
+
export type { AskInput, AuthoredSource, CapturedRecords, CsvDetails, DigestInput, Handler, HandlerReference, HandlerRuntime, HandlerTransition, InboxBlock, InboxFile, JsonObject, JsonValue, PresentedField, PresentedRecord, PresentedRecordSummary, PublishedAsk, PublishedDigest, PublishedView, PublicationRequest, Question, QuestionInput, QuestionResponse, RecordCreate, RecordDelete, RecordReference, RecordSource, RecordUpdate, RepositoryCapabilities, RepositoryReceipt, Resolution, ResolutionInput, ResponseDefinition, } from '../../../shared/inbox-contract.js';
|
|
7
|
+
export type { CapturedRecordTableInput, RecordField, RecordTableInput, RecordValue, RecordViewInput, TableColumn, ViewRecord, } from '../../../shared/inbox-views.js';
|
|
8
|
+
export type { FileAddOptions } from '../../../shared/inbox-files.js';
|
|
9
|
+
export type { RecordReadInput } from '../../../shared/record-capture.js';
|
|
10
|
+
export type { RecordDeleteRequest, RecordUpdateRequest, RecordWriteRequest } from '../../../shared/record-write.js';
|
|
7
11
|
export { RoutineRecordWriteError } from '../../../shared/record-write.js';
|
|
12
|
+
export { AirtableOperationError } from '../../../shared/airtable-contract.js';
|
|
13
|
+
export type { AirtableField, AirtableTable, AirtableRecord, AirtableListInput, AirtableGetInput, AirtableCreateInput, AirtableUpdateInput, AirtableDeleteInput, AirtableReceipt, AirtableWriteResult, AirtableClient, Connections } from '../../../shared/airtable-contract.js';
|
|
14
|
+
export declare const connections: import('../../../shared/airtable-contract.js').Connections;
|
|
8
15
|
export type InboxRequester = (path: string, init?: RequestInit) => Promise<Response>;
|
|
9
16
|
export type RoutinePublicationClientOptions = {
|
|
10
17
|
workspace?: string;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { JsonObject, JsonValue } from './inbox-contract.js';
|
|
2
|
+
export type AirtableField = {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
type: string;
|
|
6
|
+
options?: JsonValue;
|
|
7
|
+
};
|
|
8
|
+
export type AirtableTable = {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
primaryFieldId: string;
|
|
12
|
+
fields: AirtableField[];
|
|
13
|
+
};
|
|
14
|
+
export type AirtableRecord = {
|
|
15
|
+
id: string;
|
|
16
|
+
createdTime?: string;
|
|
17
|
+
fields: JsonObject;
|
|
18
|
+
};
|
|
19
|
+
export type AirtableListInput = {
|
|
20
|
+
table: string;
|
|
21
|
+
fields?: string[];
|
|
22
|
+
};
|
|
23
|
+
export type AirtableGetInput = {
|
|
24
|
+
table: string;
|
|
25
|
+
id: string;
|
|
26
|
+
};
|
|
27
|
+
export type AirtableCreateInput = {
|
|
28
|
+
table: string;
|
|
29
|
+
records: Array<{
|
|
30
|
+
fields: JsonObject;
|
|
31
|
+
}>;
|
|
32
|
+
};
|
|
33
|
+
export type AirtableUpdateInput = {
|
|
34
|
+
table: string;
|
|
35
|
+
records: Array<{
|
|
36
|
+
id: string;
|
|
37
|
+
expected: JsonObject;
|
|
38
|
+
set: JsonObject;
|
|
39
|
+
}>;
|
|
40
|
+
};
|
|
41
|
+
export type AirtableDeleteInput = {
|
|
42
|
+
table: string;
|
|
43
|
+
records: Array<{
|
|
44
|
+
id: string;
|
|
45
|
+
expected: JsonObject;
|
|
46
|
+
}>;
|
|
47
|
+
};
|
|
48
|
+
export type AirtableReceipt = {
|
|
49
|
+
provider: 'airtable';
|
|
50
|
+
connection: `airtable/${string}`;
|
|
51
|
+
baseId: string;
|
|
52
|
+
table: string;
|
|
53
|
+
operation: 'create' | 'update' | 'delete';
|
|
54
|
+
records: Array<{
|
|
55
|
+
id: string;
|
|
56
|
+
}>;
|
|
57
|
+
};
|
|
58
|
+
export type AirtableWriteResult = {
|
|
59
|
+
receipt: AirtableReceipt;
|
|
60
|
+
records?: AirtableRecord[];
|
|
61
|
+
};
|
|
62
|
+
export interface AirtableClient {
|
|
63
|
+
tables: {
|
|
64
|
+
list(): Promise<AirtableTable[]>;
|
|
65
|
+
};
|
|
66
|
+
records: {
|
|
67
|
+
list(input: AirtableListInput): Promise<AirtableRecord[]>;
|
|
68
|
+
get(input: AirtableGetInput): Promise<AirtableRecord>;
|
|
69
|
+
create(input: AirtableCreateInput): Promise<AirtableWriteResult>;
|
|
70
|
+
update(input: AirtableUpdateInput): Promise<AirtableWriteResult>;
|
|
71
|
+
delete(input: AirtableDeleteInput): Promise<AirtableWriteResult>;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export interface Connections {
|
|
75
|
+
airtable(name: string): AirtableClient;
|
|
76
|
+
}
|
|
77
|
+
/** Safe provider failure. An uncertain or partial mutation must never be retried blindly. */
|
|
78
|
+
export declare class AirtableOperationError extends Error {
|
|
79
|
+
readonly effect: 'unapplied' | 'partial' | 'uncertain';
|
|
80
|
+
readonly receipts: AirtableReceipt[];
|
|
81
|
+
constructor(message: string, effect?: 'unapplied' | 'partial' | 'uncertain', receipts?: AirtableReceipt[]);
|
|
82
|
+
}
|
|
@@ -188,17 +188,12 @@ export type QuestionInstance = {
|
|
|
188
188
|
export type ResponseSubmission = QuestionInstance & {
|
|
189
189
|
response: QuestionResponse;
|
|
190
190
|
};
|
|
191
|
-
export type OddingtonAssignment = {
|
|
192
|
-
type: 'oddington';
|
|
193
|
-
reason: string;
|
|
194
|
-
};
|
|
195
191
|
export type Resolution = {
|
|
196
192
|
outcome: string;
|
|
197
193
|
evidence?: JsonValue[];
|
|
198
194
|
};
|
|
199
195
|
export type HandlerTransition = {
|
|
200
196
|
type: 'continue';
|
|
201
|
-
assignTo?: OddingtonAssignment;
|
|
202
197
|
} | ({
|
|
203
198
|
type: 'resolve';
|
|
204
199
|
} & Resolution);
|
|
@@ -268,6 +263,7 @@ export type HandlerRuntime<Input extends JsonValue = JsonValue> = {
|
|
|
268
263
|
responseDefinition: ResponseDefinition;
|
|
269
264
|
questionId: string;
|
|
270
265
|
repository: RepositoryCapabilities;
|
|
266
|
+
connections: import('./airtable-contract.js').Connections;
|
|
271
267
|
};
|
|
272
268
|
export type Handler<Input extends JsonValue = JsonValue> = {
|
|
273
269
|
id: string;
|
|
@@ -322,12 +318,7 @@ export declare function validatePublication(value: unknown): Publication;
|
|
|
322
318
|
/** Structural projection only. The read boundary must check current recipient,
|
|
323
319
|
* workspace and repository/file access before returning this content. */
|
|
324
320
|
export declare function recipientPresentation(value: unknown): RecipientPublication;
|
|
325
|
-
export declare function
|
|
326
|
-
reason: string;
|
|
327
|
-
}): OddingtonAssignment;
|
|
328
|
-
export declare function continueAsk(value: {
|
|
329
|
-
assignTo?: OddingtonAssignment;
|
|
330
|
-
}): HandlerTransition;
|
|
321
|
+
export declare function continueAsk(value: Record<string, never>): HandlerTransition;
|
|
331
322
|
export declare function resolveAsk(value: Resolution): HandlerTransition;
|
|
332
323
|
/** Call at the durable transition boundary with server-derived remaining work,
|
|
333
324
|
* excluding the execution being completed. Lifecycle/effect guards also apply. */
|