@emulates/flex 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 CHANGED
@@ -1,9 +1,19 @@
1
1
  # Changelog — @emulates/flex
2
2
 
3
- ## 2.3.1 (2026-10-06)
3
+ ## 3.0.0 (2026-10-07)
4
4
 
5
- Initial release.
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/emulators/blob/main/docs/REPORTING_ISSUES.md). Include package version,
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/flex
2
2
 
3
- > Part of [Emulates](https://github.com/crvouga/emulators): high-fidelity, in-process emulators for APIs and databases.
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 the **Flex** (withflex.com) HSA/FSA payments API for test suites: products
6
6
  (answered from a recorded catalog corpus), checkout sessions in `payment` (one-time),
@@ -10,7 +10,7 @@ refunds, the **hosted checkout page**, and the Svix-signed webhooks Flex posts b
10
10
  milliseconds: the page is local, and the signed webhook reaches the app as soon as the card is
11
11
  accepted.
12
12
 
13
- - Operation coverage: [SUPPORT.md](https://github.com/crvouga/emulators/blob/main/packages/service/flex/SUPPORT.md)
13
+ - Operation coverage: [SUPPORT.md](https://github.com/crvouga/emulates/blob/main/packages/service/flex/SUPPORT.md)
14
14
  - Flex publishes no machine-readable spec: the contract (`openapi.yaml`) is hand-authored from
15
15
  the wire shapes our consumer reads and writes (`B/billing/flex/`), and every field its zod
16
16
  schemas require is served. Subscription mode, `price_data.recurring` and the subscription
@@ -231,4 +231,4 @@ so product names are synthesised.
231
231
  | `document`, `operationIds`, `supportedOperationIds` | values | The vendored OpenAPI contract and its operation ids. |
232
232
  | `createServer`, `serveTarget`, `DEFAULT_PORT` (`./server`) | Node | Serve over `node:http` (expiry ticks every 100 ms); the `serve` CLI target (`--webhook-url`, `--webhook-secret`, `--public-url`, `--event-naming`); port 8792. |
233
233
 
234
- Part of [emulators](https://github.com/crvouga/emulators).
234
+ 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 emulators_records (
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 emulators_records_seq
45
- ON emulators_records (namespace, collection, seq);
46
- CREATE TABLE IF NOT EXISTS emulators_sequences (
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 emulators_records WHERE namespace = ?").run(namespace);
62
- sqlite.prepare("DELETE FROM emulators_sequences WHERE namespace = ?").run(namespace);
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 emulators_sequences WHERE namespace = ? AND name = ? AND kind = 'collection'").get(this.namespace, this.collectionName);
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 emulators_sequences (namespace, name, kind, value) VALUES (?, ?, 'collection', ?)
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 emulators_records WHERE namespace = ? AND collection = ? AND id = ?").get(this.namespace, this.collectionName, id);
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 emulators_records WHERE namespace = ? AND collection = ? AND id = ?").get(this.namespace, this.collectionName, id);
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 emulators_records (namespace, collection, id, seq, value)
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 emulators_records WHERE namespace = ? AND collection = ? AND id = ?").get(this.namespace, this.collectionName, id);
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 emulators_records SET value = ? WHERE namespace = ? AND collection = ? AND id = ?").run(JSON.stringify(stored), this.namespace, this.collectionName, id);
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 emulators_records WHERE namespace = ? AND collection = ? AND id = ?").run(this.namespace, this.collectionName, id);
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 emulators_records WHERE namespace = ? AND collection = ?").get(this.namespace, this.collectionName);
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 emulators_records WHERE namespace = ? AND collection = ?").all(this.namespace, this.collectionName);
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 emulators_sequences WHERE namespace = ? AND name = ? AND kind = 'id'").get(this.namespace, prefix);
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 emulators_sequences (namespace, name, kind, value) VALUES (?, ?, 'id', ?)
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 `:emulatorsSegment${index}{${expression}}`;
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: "emulators_admin", message } });
1047
+ var adminError = (status, message) => json(status, { error: { type: "emulates_admin", message } });
1028
1048
  var UNITS = {
1029
1049
  ms: 1,
1030
1050
  s: 1e3,
@@ -1983,18 +2003,18 @@ var adminUiRoutes = (service, ui, adminPrefix = ADMIN_PREFIX) => {
1983
2003
  // ../core/dist/snapshot.js
1984
2004
  var snapshotNamespace = (sqlite, namespace) => ({
1985
2005
  namespace,
1986
- records: sqlite.prepare("SELECT collection, id, seq, value FROM emulators_records WHERE namespace = ? ORDER BY collection, seq").all(namespace),
1987
- sequences: sqlite.prepare("SELECT name, kind, value FROM emulators_sequences WHERE namespace = ? ORDER BY name, kind").all(namespace)
2006
+ records: sqlite.prepare("SELECT collection, id, seq, value FROM emulates_records WHERE namespace = ? ORDER BY collection, seq").all(namespace),
2007
+ sequences: sqlite.prepare("SELECT name, kind, value FROM emulates_sequences WHERE namespace = ? ORDER BY name, kind").all(namespace)
1988
2008
  });
1989
2009
  var restoreNamespace = (sqlite, namespace, snapshot) => {
1990
2010
  sqlite.transaction(() => {
1991
- sqlite.prepare("DELETE FROM emulators_records WHERE namespace = ?").run(namespace);
1992
- sqlite.prepare("DELETE FROM emulators_sequences WHERE namespace = ?").run(namespace);
1993
- const record = sqlite.prepare("INSERT INTO emulators_records (namespace, collection, id, seq, value) VALUES (?, ?, ?, ?, ?)");
2011
+ sqlite.prepare("DELETE FROM emulates_records WHERE namespace = ?").run(namespace);
2012
+ sqlite.prepare("DELETE FROM emulates_sequences WHERE namespace = ?").run(namespace);
2013
+ const record = sqlite.prepare("INSERT INTO emulates_records (namespace, collection, id, seq, value) VALUES (?, ?, ?, ?, ?)");
1994
2014
  for (const row of snapshot.records) {
1995
2015
  record.run(namespace, row.collection, row.id, row.seq, row.value);
1996
2016
  }
1997
- const sequence = sqlite.prepare("INSERT INTO emulators_sequences (namespace, name, kind, value) VALUES (?, ?, ?, ?)");
2017
+ const sequence = sqlite.prepare("INSERT INTO emulates_sequences (namespace, name, kind, value) VALUES (?, ?, ?, ?)");
1998
2018
  for (const row of snapshot.sequences) {
1999
2019
  sequence.run(namespace, row.name, row.kind, row.value);
2000
2020
  }
@@ -2093,8 +2113,8 @@ var discoverCollections = (root) => {
2093
2113
  return found;
2094
2114
  };
2095
2115
  var storedNames = (sqlite, namespace) => {
2096
- const records = sqlite.prepare("SELECT DISTINCT collection AS name FROM emulators_records WHERE namespace = ?").all(namespace);
2097
- const sequences = sqlite.prepare("SELECT DISTINCT name FROM emulators_sequences WHERE namespace = ? AND kind = 'collection'").all(namespace);
2116
+ const records = sqlite.prepare("SELECT DISTINCT collection AS name FROM emulates_records WHERE namespace = ?").all(namespace);
2117
+ const sequences = sqlite.prepare("SELECT DISTINCT name FROM emulates_sequences WHERE namespace = ? AND kind = 'collection'").all(namespace);
2098
2118
  return [...records, ...sequences].map((row) => row.name);
2099
2119
  };
2100
2120
  var blankAccum = (name) => ({
@@ -2335,7 +2355,7 @@ var json3 = (status, body) => new Response(JSON.stringify(body), {
2335
2355
  status,
2336
2356
  headers: { "content-type": "application/json; charset=utf-8" }
2337
2357
  });
2338
- var adminError2 = (status, message) => json3(status, { error: { type: "emulators_admin", message } });
2358
+ var adminError2 = (status, message) => json3(status, { error: { type: "emulates_admin", message } });
2339
2359
  var fromResult = (result, status) => result.ok ? json3(status, result.value) : adminError2(result.status, result.message);
2340
2360
  var integerParam = (value) => {
2341
2361
  if (value === null)
@@ -2436,7 +2456,7 @@ var stateAdminRoutes = (routes) => {
2436
2456
  };
2437
2457
 
2438
2458
  // ../core/dist/version.js
2439
- var PACKAGE_VERSION = true ? "2.3.1" : UNRELEASED_VERSION;
2459
+ var PACKAGE_VERSION = true ? "3.0.0" : UNRELEASED_VERSION;
2440
2460
 
2441
2461
  // ../core/dist/signing.js
2442
2462
  var encoder = new TextEncoder();
@@ -2813,7 +2833,7 @@ var json4 = (status, body) => new Response(JSON.stringify(body), {
2813
2833
  status,
2814
2834
  headers: { "content-type": "application/json; charset=utf-8" }
2815
2835
  });
2816
- var adminError3 = (status, message) => json4(status, { error: { type: "emulators_admin", message } });
2836
+ var adminError3 = (status, message) => json4(status, { error: { type: "emulates_admin", message } });
2817
2837
  var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2818
2838
  var parseEndpoint = (value) => {
2819
2839
  if (!isRecord4(value) || typeof value.url !== "string")
@@ -3417,7 +3437,7 @@ var createRuntime = (options) => {
3417
3437
  log(400);
3418
3438
  return stamp(new Response(JSON.stringify({
3419
3439
  error: {
3420
- type: "emulators_admin",
3440
+ type: "emulates_admin",
3421
3441
  message: `${NAMESPACE_HEADER} must match ${NAMESPACE_PATTERN}`
3422
3442
  }
3423
3443
  }), { status: 400, headers: { "content-type": "application/json; charset=utf-8" } }));
@@ -3583,7 +3603,7 @@ var adminJson = (status, body) => new Response(JSON.stringify(body), {
3583
3603
  status,
3584
3604
  headers: { "content-type": "application/json; charset=utf-8" }
3585
3605
  });
3586
- var adminFail = (status, message) => adminJson(status, { error: { type: "emulators_admin", message } });
3606
+ var adminFail = (status, message) => adminJson(status, { error: { type: "emulates_admin", message } });
3587
3607
  var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3588
3608
  var credentialRoutes = (registry) => ({
3589
3609
  "GET /credentials": () => adminJson(200, {
@@ -5047,7 +5067,7 @@ var FLEX_PRESETS = {
5047
5067
  }
5048
5068
  };
5049
5069
  var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
5050
- var adminError4 = (status, message) => json5(status, { error: { type: "emulators_admin", message } });
5070
+ var adminError4 = (status, message) => json5(status, { error: { type: "emulates_admin", message } });
5051
5071
  var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5052
5072
  var parseSettings = (body) => {
5053
5073
  if (!isRecord5(body)) return "expected a JSON object";
@@ -6134,4 +6154,4 @@ export {
6134
6154
  FlexAPI,
6135
6155
  isNextActionType
6136
6156
  };
6137
- //# sourceMappingURL=chunk-O6YIBDG6.js.map
6157
+ //# sourceMappingURL=chunk-4QSJ4FCG.js.map