@emulates/airtable 0.1.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -2
- package/DISCOVERY.md +1 -1
- package/README.md +1 -1
- package/dist/{chunk-2JTI4AMV.js → chunk-DNNJI6DA.js} +54 -34
- package/dist/chunk-DNNJI6DA.js.map +7 -0
- package/dist/{chunk-L6HHUISL.js → chunk-UCQPFSFJ.js} +3 -3
- package/dist/{chunk-L6HHUISL.js.map → chunk-UCQPFSFJ.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.js +1 -1
- package/dist/server.js +2 -2
- package/package.json +5 -5
- package/dist/chunk-2JTI4AMV.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
# Changelog — @emulates/airtable
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 1.0.0 (2026-10-07)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### ⚠️ Breaking changes
|
|
6
|
+
|
|
7
|
+
- point the repo at crvouga/emulates ([cdb5e53](https://github.com/crvouga/emulates/commit/cdb5e536ed8c1f52345e3984f089001c25fdb08a))
|
|
8
|
+
|
|
9
|
+
### Fixes and improvements
|
|
10
|
+
|
|
11
|
+
- format the emulates records query ([8698872](https://github.com/crvouga/emulates/commit/8698872509d06002be5a8fcdc85d79f8462320c3))
|
|
6
12
|
|
|
7
13
|
### Dependencies
|
|
8
14
|
|
|
9
15
|
- `@emulates/sqlite`
|
|
16
|
+
|
|
17
|
+
## 0.1.1 (2026-10-06)
|
|
18
|
+
|
|
19
|
+
Initial release.
|
package/DISCOVERY.md
CHANGED
|
@@ -47,7 +47,7 @@ designed for assertions and diagnosis by consuming test suites.
|
|
|
47
47
|
|
|
48
48
|
## Report a mismatch or missing capability
|
|
49
49
|
|
|
50
|
-
Follow the [agent reporting contract](https://github.com/crvouga/
|
|
50
|
+
Follow the [agent reporting contract](https://github.com/crvouga/emulates/blob/main/docs/REPORTING_ISSUES.md). Include package version,
|
|
51
51
|
operation/command, a minimal redacted request, actual emulator result, expected oracle result or vendor
|
|
52
52
|
documentation, and whether the mismatch appears in the matrix. Never include keys, tokens,
|
|
53
53
|
customer data, prompts, PHI, card data, or unredacted recordings.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @emulates/airtable
|
|
2
2
|
|
|
3
|
-
> Part of [Emulates](https://github.com/crvouga/
|
|
3
|
+
> Part of [Emulates](https://github.com/crvouga/emulates): high-fidelity, in-process emulators for APIs and databases.
|
|
4
4
|
|
|
5
5
|
WIP Airtable OAuth and forms-integration emulator. Only synthetic fixtures belong here.
|
|
6
6
|
|
|
@@ -33,7 +33,7 @@ var CORE_MIGRATIONS = [
|
|
|
33
33
|
{
|
|
34
34
|
id: "20260322_core_records_sequences",
|
|
35
35
|
sql: `
|
|
36
|
-
CREATE TABLE IF NOT EXISTS
|
|
36
|
+
CREATE TABLE IF NOT EXISTS emulates_records (
|
|
37
37
|
namespace TEXT NOT NULL,
|
|
38
38
|
collection TEXT NOT NULL,
|
|
39
39
|
id TEXT NOT NULL,
|
|
@@ -41,9 +41,9 @@ var CORE_MIGRATIONS = [
|
|
|
41
41
|
value TEXT NOT NULL,
|
|
42
42
|
PRIMARY KEY (namespace, collection, id)
|
|
43
43
|
);
|
|
44
|
-
CREATE INDEX IF NOT EXISTS
|
|
45
|
-
ON
|
|
46
|
-
CREATE TABLE IF NOT EXISTS
|
|
44
|
+
CREATE INDEX IF NOT EXISTS emulates_records_seq
|
|
45
|
+
ON emulates_records (namespace, collection, seq);
|
|
46
|
+
CREATE TABLE IF NOT EXISTS emulates_sequences (
|
|
47
47
|
namespace TEXT NOT NULL,
|
|
48
48
|
name TEXT NOT NULL,
|
|
49
49
|
kind TEXT NOT NULL,
|
|
@@ -53,13 +53,33 @@ var CORE_MIGRATIONS = [
|
|
|
53
53
|
`
|
|
54
54
|
}
|
|
55
55
|
];
|
|
56
|
+
var LEGACY_TABLES = [
|
|
57
|
+
["emulators_records", "emulates_records"],
|
|
58
|
+
["emulators_sequences", "emulates_sequences"]
|
|
59
|
+
];
|
|
60
|
+
var LEGACY_INDEXES = [["emulators_records_seq", "emulates_records_seq"]];
|
|
61
|
+
var namesOf = (sqlite, type) => new Set(sqlite.prepare("SELECT name FROM sqlite_master WHERE type = ?").all(type).map((row) => row.name));
|
|
56
62
|
var migrateCore = (sqlite) => {
|
|
57
63
|
migrate(sqlite, CORE_MIGRATIONS);
|
|
64
|
+
const tables = namesOf(sqlite, "table");
|
|
65
|
+
const indexes = namesOf(sqlite, "index");
|
|
66
|
+
sqlite.transaction(() => {
|
|
67
|
+
for (const [from, to] of LEGACY_TABLES) {
|
|
68
|
+
if (tables.has(from) && !tables.has(to))
|
|
69
|
+
sqlite.exec(`ALTER TABLE ${from} RENAME TO ${to}`);
|
|
70
|
+
}
|
|
71
|
+
for (const [from, to] of LEGACY_INDEXES) {
|
|
72
|
+
if (!indexes.has(from) || indexes.has(to))
|
|
73
|
+
continue;
|
|
74
|
+
sqlite.exec(`DROP INDEX ${from}`);
|
|
75
|
+
sqlite.exec(`CREATE INDEX ${to} ON emulates_records (namespace, collection, seq)`);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
58
78
|
};
|
|
59
79
|
var clearNamespace = (sqlite, namespace) => {
|
|
60
80
|
sqlite.transaction(() => {
|
|
61
|
-
sqlite.prepare("DELETE FROM
|
|
62
|
-
sqlite.prepare("DELETE FROM
|
|
81
|
+
sqlite.prepare("DELETE FROM emulates_records WHERE namespace = ?").run(namespace);
|
|
82
|
+
sqlite.prepare("DELETE FROM emulates_sequences WHERE namespace = ?").run(namespace);
|
|
63
83
|
});
|
|
64
84
|
};
|
|
65
85
|
|
|
@@ -74,9 +94,9 @@ var Collection = class {
|
|
|
74
94
|
this.collectionName = collectionName;
|
|
75
95
|
}
|
|
76
96
|
bumpCollectionSeq() {
|
|
77
|
-
const row = this.sqlite.prepare("SELECT value FROM
|
|
97
|
+
const row = this.sqlite.prepare("SELECT value FROM emulates_sequences WHERE namespace = ? AND name = ? AND kind = 'collection'").get(this.namespace, this.collectionName);
|
|
78
98
|
const next = (row?.value ?? 0) + 1;
|
|
79
|
-
this.sqlite.prepare(`INSERT INTO
|
|
99
|
+
this.sqlite.prepare(`INSERT INTO emulates_sequences (namespace, name, kind, value) VALUES (?, ?, 'collection', ?)
|
|
80
100
|
ON CONFLICT(namespace, name, kind) DO UPDATE SET value = excluded.value`).run(this.namespace, this.collectionName, next);
|
|
81
101
|
return next;
|
|
82
102
|
}
|
|
@@ -84,13 +104,13 @@ var Collection = class {
|
|
|
84
104
|
return this.sqlite.transaction(() => this.bumpCollectionSeq());
|
|
85
105
|
}
|
|
86
106
|
get(id) {
|
|
87
|
-
const row = this.sqlite.prepare("SELECT value FROM
|
|
107
|
+
const row = this.sqlite.prepare("SELECT value FROM emulates_records WHERE namespace = ? AND collection = ? AND id = ?").get(this.namespace, this.collectionName, id);
|
|
88
108
|
if (!row)
|
|
89
109
|
return void 0;
|
|
90
110
|
return JSON.parse(row.value).value;
|
|
91
111
|
}
|
|
92
112
|
has(id) {
|
|
93
|
-
const row = this.sqlite.prepare("SELECT 1 AS ok FROM
|
|
113
|
+
const row = this.sqlite.prepare("SELECT 1 AS ok FROM emulates_records WHERE namespace = ? AND collection = ? AND id = ?").get(this.namespace, this.collectionName, id);
|
|
94
114
|
return row !== void 0;
|
|
95
115
|
}
|
|
96
116
|
/** Insert a new record, assigning it the next sequence number. */
|
|
@@ -98,7 +118,7 @@ var Collection = class {
|
|
|
98
118
|
return this.sqlite.transaction(() => {
|
|
99
119
|
const seq = this.bumpCollectionSeq();
|
|
100
120
|
const stored = { seq, value };
|
|
101
|
-
this.sqlite.prepare(`INSERT INTO
|
|
121
|
+
this.sqlite.prepare(`INSERT INTO emulates_records (namespace, collection, id, seq, value)
|
|
102
122
|
VALUES (?, ?, ?, ?, ?)
|
|
103
123
|
ON CONFLICT(namespace, collection, id) DO UPDATE SET seq = excluded.seq, value = excluded.value`).run(this.namespace, this.collectionName, id, seq, JSON.stringify(stored));
|
|
104
124
|
return stored;
|
|
@@ -107,25 +127,25 @@ var Collection = class {
|
|
|
107
127
|
/** Replace an existing record's value, keeping its position. */
|
|
108
128
|
update(id, value) {
|
|
109
129
|
return this.sqlite.transaction(() => {
|
|
110
|
-
const row = this.sqlite.prepare("SELECT seq, value FROM
|
|
130
|
+
const row = this.sqlite.prepare("SELECT seq, value FROM emulates_records WHERE namespace = ? AND collection = ? AND id = ?").get(this.namespace, this.collectionName, id);
|
|
111
131
|
if (!row)
|
|
112
132
|
return void 0;
|
|
113
133
|
const stored = { seq: row.seq, value };
|
|
114
|
-
this.sqlite.prepare("UPDATE
|
|
134
|
+
this.sqlite.prepare("UPDATE emulates_records SET value = ? WHERE namespace = ? AND collection = ? AND id = ?").run(JSON.stringify(stored), this.namespace, this.collectionName, id);
|
|
115
135
|
return stored;
|
|
116
136
|
});
|
|
117
137
|
}
|
|
118
138
|
delete(id) {
|
|
119
|
-
const result = this.sqlite.prepare("DELETE FROM
|
|
139
|
+
const result = this.sqlite.prepare("DELETE FROM emulates_records WHERE namespace = ? AND collection = ? AND id = ?").run(this.namespace, this.collectionName, id);
|
|
120
140
|
return result.changes > 0;
|
|
121
141
|
}
|
|
122
142
|
/** How many records the collection holds, without reading them. */
|
|
123
143
|
count() {
|
|
124
|
-
const row = this.sqlite.prepare("SELECT COUNT(*) AS n FROM
|
|
144
|
+
const row = this.sqlite.prepare("SELECT COUNT(*) AS n FROM emulates_records WHERE namespace = ? AND collection = ?").get(this.namespace, this.collectionName);
|
|
125
145
|
return Number(row?.n ?? 0);
|
|
126
146
|
}
|
|
127
147
|
list(options = {}) {
|
|
128
|
-
const rows = this.sqlite.prepare("SELECT id, seq, value FROM
|
|
148
|
+
const rows = this.sqlite.prepare("SELECT id, seq, value FROM emulates_records WHERE namespace = ? AND collection = ?").all(this.namespace, this.collectionName);
|
|
129
149
|
const out = [];
|
|
130
150
|
for (const row of rows) {
|
|
131
151
|
const stored = JSON.parse(row.value);
|
|
@@ -174,9 +194,9 @@ var IdSequence = class {
|
|
|
174
194
|
}
|
|
175
195
|
next(prefix, length = 14) {
|
|
176
196
|
return this.sqlite.transaction(() => {
|
|
177
|
-
const row = this.sqlite.prepare("SELECT value FROM
|
|
197
|
+
const row = this.sqlite.prepare("SELECT value FROM emulates_sequences WHERE namespace = ? AND name = ? AND kind = 'id'").get(this.namespace, prefix);
|
|
178
198
|
const value = (row?.value ?? 0) + 1;
|
|
179
|
-
this.sqlite.prepare(`INSERT INTO
|
|
199
|
+
this.sqlite.prepare(`INSERT INTO emulates_sequences (namespace, name, kind, value) VALUES (?, ?, 'id', ?)
|
|
180
200
|
ON CONFLICT(namespace, name, kind) DO UPDATE SET value = excluded.value`).run(this.namespace, prefix, value);
|
|
181
201
|
return `${prefix}${opaqueToken(`${this.salt}:${prefix}:${value}`, length)}`;
|
|
182
202
|
});
|
|
@@ -881,7 +901,7 @@ var serviceRoute = (template) => {
|
|
|
881
901
|
offset = match.index + match[0].length;
|
|
882
902
|
}
|
|
883
903
|
expression += escapePattern(segment.slice(offset));
|
|
884
|
-
return `:
|
|
904
|
+
return `:emulatesSegment${index}{${expression}}`;
|
|
885
905
|
}).join("/");
|
|
886
906
|
return {
|
|
887
907
|
path,
|
|
@@ -1024,7 +1044,7 @@ var json = (status, body) => new Response(JSON.stringify(body), {
|
|
|
1024
1044
|
status,
|
|
1025
1045
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
1026
1046
|
});
|
|
1027
|
-
var adminError = (status, message) => json(status, { error: { type: "
|
|
1047
|
+
var adminError = (status, message) => json(status, { error: { type: "emulates_admin", message } });
|
|
1028
1048
|
var UNITS = {
|
|
1029
1049
|
ms: 1,
|
|
1030
1050
|
s: 1e3,
|
|
@@ -1919,18 +1939,18 @@ var adminUiRoutes = (service, ui, adminPrefix = ADMIN_PREFIX) => {
|
|
|
1919
1939
|
// ../core/dist/snapshot.js
|
|
1920
1940
|
var snapshotNamespace = (sqlite, namespace) => ({
|
|
1921
1941
|
namespace,
|
|
1922
|
-
records: sqlite.prepare("SELECT collection, id, seq, value FROM
|
|
1923
|
-
sequences: sqlite.prepare("SELECT name, kind, value FROM
|
|
1942
|
+
records: sqlite.prepare("SELECT collection, id, seq, value FROM emulates_records WHERE namespace = ? ORDER BY collection, seq").all(namespace),
|
|
1943
|
+
sequences: sqlite.prepare("SELECT name, kind, value FROM emulates_sequences WHERE namespace = ? ORDER BY name, kind").all(namespace)
|
|
1924
1944
|
});
|
|
1925
1945
|
var restoreNamespace = (sqlite, namespace, snapshot) => {
|
|
1926
1946
|
sqlite.transaction(() => {
|
|
1927
|
-
sqlite.prepare("DELETE FROM
|
|
1928
|
-
sqlite.prepare("DELETE FROM
|
|
1929
|
-
const record = sqlite.prepare("INSERT INTO
|
|
1947
|
+
sqlite.prepare("DELETE FROM emulates_records WHERE namespace = ?").run(namespace);
|
|
1948
|
+
sqlite.prepare("DELETE FROM emulates_sequences WHERE namespace = ?").run(namespace);
|
|
1949
|
+
const record = sqlite.prepare("INSERT INTO emulates_records (namespace, collection, id, seq, value) VALUES (?, ?, ?, ?, ?)");
|
|
1930
1950
|
for (const row of snapshot.records) {
|
|
1931
1951
|
record.run(namespace, row.collection, row.id, row.seq, row.value);
|
|
1932
1952
|
}
|
|
1933
|
-
const sequence = sqlite.prepare("INSERT INTO
|
|
1953
|
+
const sequence = sqlite.prepare("INSERT INTO emulates_sequences (namespace, name, kind, value) VALUES (?, ?, ?, ?)");
|
|
1934
1954
|
for (const row of snapshot.sequences) {
|
|
1935
1955
|
sequence.run(namespace, row.name, row.kind, row.value);
|
|
1936
1956
|
}
|
|
@@ -2029,8 +2049,8 @@ var discoverCollections = (root) => {
|
|
|
2029
2049
|
return found;
|
|
2030
2050
|
};
|
|
2031
2051
|
var storedNames = (sqlite, namespace) => {
|
|
2032
|
-
const records = sqlite.prepare("SELECT DISTINCT collection AS name FROM
|
|
2033
|
-
const sequences = sqlite.prepare("SELECT DISTINCT name FROM
|
|
2052
|
+
const records = sqlite.prepare("SELECT DISTINCT collection AS name FROM emulates_records WHERE namespace = ?").all(namespace);
|
|
2053
|
+
const sequences = sqlite.prepare("SELECT DISTINCT name FROM emulates_sequences WHERE namespace = ? AND kind = 'collection'").all(namespace);
|
|
2034
2054
|
return [...records, ...sequences].map((row) => row.name);
|
|
2035
2055
|
};
|
|
2036
2056
|
var blankAccum = (name) => ({
|
|
@@ -2271,7 +2291,7 @@ var json3 = (status, body) => new Response(JSON.stringify(body), {
|
|
|
2271
2291
|
status,
|
|
2272
2292
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
2273
2293
|
});
|
|
2274
|
-
var adminError2 = (status, message) => json3(status, { error: { type: "
|
|
2294
|
+
var adminError2 = (status, message) => json3(status, { error: { type: "emulates_admin", message } });
|
|
2275
2295
|
var fromResult = (result, status) => result.ok ? json3(status, result.value) : adminError2(result.status, result.message);
|
|
2276
2296
|
var integerParam = (value) => {
|
|
2277
2297
|
if (value === null)
|
|
@@ -2372,14 +2392,14 @@ var stateAdminRoutes = (routes) => {
|
|
|
2372
2392
|
};
|
|
2373
2393
|
|
|
2374
2394
|
// ../core/dist/version.js
|
|
2375
|
-
var PACKAGE_VERSION = true ? "0.
|
|
2395
|
+
var PACKAGE_VERSION = true ? "1.0.0" : UNRELEASED_VERSION;
|
|
2376
2396
|
|
|
2377
2397
|
// ../core/dist/webhooks.js
|
|
2378
2398
|
var json4 = (status, body) => new Response(JSON.stringify(body), {
|
|
2379
2399
|
status,
|
|
2380
2400
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
2381
2401
|
});
|
|
2382
|
-
var adminError3 = (status, message) => json4(status, { error: { type: "
|
|
2402
|
+
var adminError3 = (status, message) => json4(status, { error: { type: "emulates_admin", message } });
|
|
2383
2403
|
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2384
2404
|
var parseEndpoint = (value) => {
|
|
2385
2405
|
if (!isRecord4(value) || typeof value.url !== "string")
|
|
@@ -2981,7 +3001,7 @@ var createRuntime = (options) => {
|
|
|
2981
3001
|
log(400);
|
|
2982
3002
|
return stamp(new Response(JSON.stringify({
|
|
2983
3003
|
error: {
|
|
2984
|
-
type: "
|
|
3004
|
+
type: "emulates_admin",
|
|
2985
3005
|
message: `${NAMESPACE_HEADER} must match ${NAMESPACE_PATTERN}`
|
|
2986
3006
|
}
|
|
2987
3007
|
}), { status: 400, headers: { "content-type": "application/json; charset=utf-8" } }));
|
|
@@ -3147,7 +3167,7 @@ var adminJson = (status, body) => new Response(JSON.stringify(body), {
|
|
|
3147
3167
|
status,
|
|
3148
3168
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
3149
3169
|
});
|
|
3150
|
-
var adminFail = (status, message) => adminJson(status, { error: { type: "
|
|
3170
|
+
var adminFail = (status, message) => adminJson(status, { error: { type: "emulates_admin", message } });
|
|
3151
3171
|
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3152
3172
|
var credentialRoutes = (registry) => ({
|
|
3153
3173
|
"GET /credentials": () => adminJson(200, {
|
|
@@ -3620,4 +3640,4 @@ export {
|
|
|
3620
3640
|
AIRTABLE_NAMESPACE,
|
|
3621
3641
|
AirtableAPI
|
|
3622
3642
|
};
|
|
3623
|
-
//# sourceMappingURL=chunk-
|
|
3643
|
+
//# sourceMappingURL=chunk-DNNJI6DA.js.map
|