@emulates/caretalk 2.3.1 → 3.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 +3 -3
- package/dist/{chunk-DE6GPQAC.js → chunk-GGJSTUWM.js} +53 -33
- package/dist/chunk-GGJSTUWM.js.map +7 -0
- package/dist/{chunk-D5BKM3F7.js → chunk-PD6GHEG3.js} +3 -3
- package/dist/{chunk-D5BKM3F7.js.map → chunk-PD6GHEG3.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-DE6GPQAC.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
# Changelog — @emulates/caretalk
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 3.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
|
+
## 2.3.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/caretalk
|
|
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
|
Stateful emulator of **CareTalk**'s external API (`/externalapi`) for test suites: client login,
|
|
6
6
|
form definitions (`GetForm`) and saved form rounds (`SavePatientForm`, the form-submission
|
|
@@ -8,7 +8,7 @@ queue's call), patient search and insert (the account backfill), states, free sl
|
|
|
8
8
|
appointments. The backend requires CareTalk keys at boot, so the emulator lets a stack boot and run
|
|
9
9
|
the CareTalk paths without the beta environment.
|
|
10
10
|
|
|
11
|
-
- Operation coverage: [SUPPORT.md](https://github.com/crvouga/
|
|
11
|
+
- Operation coverage: [SUPPORT.md](https://github.com/crvouga/emulates/blob/main/packages/service/caretalk/SUPPORT.md)
|
|
12
12
|
- The vendor publishes no spec: the contract (`openapi.yaml`) is hand-authored from the
|
|
13
13
|
consumer's zod schemas and interfaces (`caretalk.types.ts`, `caretalk-forms.type.ts`).
|
|
14
14
|
|
|
@@ -123,4 +123,4 @@ carry the API user they were issued to, so `PUT /__admin/credentials {"credentia
|
|
|
123
123
|
| `document`, `operationIds`, `supportedOperationIds` | values | The OpenAPI contract and its operation ids. |
|
|
124
124
|
| `createServer`, `serveTarget`, `DEFAULT_PORT` (`./server`) | Node | Serve over `node:http`; the `serve` CLI target (`--api-user`, `--api-key`); port 8823. |
|
|
125
125
|
|
|
126
|
-
Part of [
|
|
126
|
+
Part of [Emulates](https://github.com/crvouga/emulates).
|
|
@@ -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);
|
|
@@ -872,7 +892,7 @@ var serviceRoute = (template) => {
|
|
|
872
892
|
offset = match.index + match[0].length;
|
|
873
893
|
}
|
|
874
894
|
expression += escapePattern(segment.slice(offset));
|
|
875
|
-
return `:
|
|
895
|
+
return `:emulatesSegment${index}{${expression}}`;
|
|
876
896
|
}).join("/");
|
|
877
897
|
return {
|
|
878
898
|
path,
|
|
@@ -1015,7 +1035,7 @@ var json = (status, body) => new Response(JSON.stringify(body), {
|
|
|
1015
1035
|
status,
|
|
1016
1036
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
1017
1037
|
});
|
|
1018
|
-
var adminError = (status, message) => json(status, { error: { type: "
|
|
1038
|
+
var adminError = (status, message) => json(status, { error: { type: "emulates_admin", message } });
|
|
1019
1039
|
var UNITS = {
|
|
1020
1040
|
ms: 1,
|
|
1021
1041
|
s: 1e3,
|
|
@@ -1914,18 +1934,18 @@ var adminUiRoutes = (service, ui, adminPrefix = ADMIN_PREFIX) => {
|
|
|
1914
1934
|
// ../core/dist/snapshot.js
|
|
1915
1935
|
var snapshotNamespace = (sqlite, namespace) => ({
|
|
1916
1936
|
namespace,
|
|
1917
|
-
records: sqlite.prepare("SELECT collection, id, seq, value FROM
|
|
1918
|
-
sequences: sqlite.prepare("SELECT name, kind, value FROM
|
|
1937
|
+
records: sqlite.prepare("SELECT collection, id, seq, value FROM emulates_records WHERE namespace = ? ORDER BY collection, seq").all(namespace),
|
|
1938
|
+
sequences: sqlite.prepare("SELECT name, kind, value FROM emulates_sequences WHERE namespace = ? ORDER BY name, kind").all(namespace)
|
|
1919
1939
|
});
|
|
1920
1940
|
var restoreNamespace = (sqlite, namespace, snapshot) => {
|
|
1921
1941
|
sqlite.transaction(() => {
|
|
1922
|
-
sqlite.prepare("DELETE FROM
|
|
1923
|
-
sqlite.prepare("DELETE FROM
|
|
1924
|
-
const record = sqlite.prepare("INSERT INTO
|
|
1942
|
+
sqlite.prepare("DELETE FROM emulates_records WHERE namespace = ?").run(namespace);
|
|
1943
|
+
sqlite.prepare("DELETE FROM emulates_sequences WHERE namespace = ?").run(namespace);
|
|
1944
|
+
const record = sqlite.prepare("INSERT INTO emulates_records (namespace, collection, id, seq, value) VALUES (?, ?, ?, ?, ?)");
|
|
1925
1945
|
for (const row of snapshot.records) {
|
|
1926
1946
|
record.run(namespace, row.collection, row.id, row.seq, row.value);
|
|
1927
1947
|
}
|
|
1928
|
-
const sequence = sqlite.prepare("INSERT INTO
|
|
1948
|
+
const sequence = sqlite.prepare("INSERT INTO emulates_sequences (namespace, name, kind, value) VALUES (?, ?, ?, ?)");
|
|
1929
1949
|
for (const row of snapshot.sequences) {
|
|
1930
1950
|
sequence.run(namespace, row.name, row.kind, row.value);
|
|
1931
1951
|
}
|
|
@@ -2024,8 +2044,8 @@ var discoverCollections = (root) => {
|
|
|
2024
2044
|
return found;
|
|
2025
2045
|
};
|
|
2026
2046
|
var storedNames = (sqlite, namespace) => {
|
|
2027
|
-
const records = sqlite.prepare("SELECT DISTINCT collection AS name FROM
|
|
2028
|
-
const sequences = sqlite.prepare("SELECT DISTINCT name FROM
|
|
2047
|
+
const records = sqlite.prepare("SELECT DISTINCT collection AS name FROM emulates_records WHERE namespace = ?").all(namespace);
|
|
2048
|
+
const sequences = sqlite.prepare("SELECT DISTINCT name FROM emulates_sequences WHERE namespace = ? AND kind = 'collection'").all(namespace);
|
|
2029
2049
|
return [...records, ...sequences].map((row) => row.name);
|
|
2030
2050
|
};
|
|
2031
2051
|
var blankAccum = (name) => ({
|
|
@@ -2266,7 +2286,7 @@ var json3 = (status, body) => new Response(JSON.stringify(body), {
|
|
|
2266
2286
|
status,
|
|
2267
2287
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
2268
2288
|
});
|
|
2269
|
-
var adminError2 = (status, message) => json3(status, { error: { type: "
|
|
2289
|
+
var adminError2 = (status, message) => json3(status, { error: { type: "emulates_admin", message } });
|
|
2270
2290
|
var fromResult = (result, status) => result.ok ? json3(status, result.value) : adminError2(result.status, result.message);
|
|
2271
2291
|
var integerParam = (value) => {
|
|
2272
2292
|
if (value === null)
|
|
@@ -2367,7 +2387,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2367
2387
|
};
|
|
2368
2388
|
|
|
2369
2389
|
// ../core/dist/version.js
|
|
2370
|
-
var PACKAGE_VERSION = true ? "
|
|
2390
|
+
var PACKAGE_VERSION = true ? "3.0.0" : UNRELEASED_VERSION;
|
|
2371
2391
|
|
|
2372
2392
|
// ../core/dist/signing.js
|
|
2373
2393
|
var encoder = new TextEncoder();
|
|
@@ -2385,7 +2405,7 @@ var json4 = (status, body) => new Response(JSON.stringify(body), {
|
|
|
2385
2405
|
status,
|
|
2386
2406
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
2387
2407
|
});
|
|
2388
|
-
var adminError3 = (status, message) => json4(status, { error: { type: "
|
|
2408
|
+
var adminError3 = (status, message) => json4(status, { error: { type: "emulates_admin", message } });
|
|
2389
2409
|
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2390
2410
|
var parseEndpoint = (value) => {
|
|
2391
2411
|
if (!isRecord4(value) || typeof value.url !== "string")
|
|
@@ -2989,7 +3009,7 @@ var createRuntime = (options) => {
|
|
|
2989
3009
|
log(400);
|
|
2990
3010
|
return stamp(new Response(JSON.stringify({
|
|
2991
3011
|
error: {
|
|
2992
|
-
type: "
|
|
3012
|
+
type: "emulates_admin",
|
|
2993
3013
|
message: `${NAMESPACE_HEADER} must match ${NAMESPACE_PATTERN}`
|
|
2994
3014
|
}
|
|
2995
3015
|
}), { status: 400, headers: { "content-type": "application/json; charset=utf-8" } }));
|
|
@@ -3155,7 +3175,7 @@ var adminJson = (status, body) => new Response(JSON.stringify(body), {
|
|
|
3155
3175
|
status,
|
|
3156
3176
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
3157
3177
|
});
|
|
3158
|
-
var adminFail = (status, message) => adminJson(status, { error: { type: "
|
|
3178
|
+
var adminFail = (status, message) => adminJson(status, { error: { type: "emulates_admin", message } });
|
|
3159
3179
|
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3160
3180
|
var credentialRoutes = (registry) => ({
|
|
3161
3181
|
"GET /credentials": () => adminJson(200, {
|
|
@@ -3534,7 +3554,7 @@ var CARETALK_PRESETS = {
|
|
|
3534
3554
|
}
|
|
3535
3555
|
};
|
|
3536
3556
|
var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
3537
|
-
var adminError4 = (status, message) => json5(status, { error: { type: "
|
|
3557
|
+
var adminError4 = (status, message) => json5(status, { error: { type: "emulates_admin", message } });
|
|
3538
3558
|
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3539
3559
|
var adminRoutes = (runtime) => ({
|
|
3540
3560
|
"GET /patients": ({ namespace }) => json5(200, { patients: runtime.instance(namespace).patients() }),
|
|
@@ -4095,4 +4115,4 @@ export {
|
|
|
4095
4115
|
normalizeDate,
|
|
4096
4116
|
CareTalkAPI
|
|
4097
4117
|
};
|
|
4098
|
-
//# sourceMappingURL=chunk-
|
|
4118
|
+
//# sourceMappingURL=chunk-GGJSTUWM.js.map
|