@oneaddress/setup 1.1.4 → 1.1.5

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.
Files changed (2) hide show
  1. package/dist/index.js +125 -71
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -835,7 +835,7 @@ var _R = ["\u2588\u2588\u2588\u2588\u2588\u2588 ", "\u2588\u2588 \u2588\u2588"
835
835
  var _S = [" \u2588\u2588\u2588\u2588\u2588\u2588", "\u2588\u2588 ", "\u2588\u2588 ", " \u2588\u2588\u2588\u2588\u2588 ", " \u2588\u2588", " \u2588\u2588", "\u2588\u2588\u2588\u2588\u2588\u2588 "];
836
836
  var ONE_ROWS = Array.from({ length: 7 }, (_2, i) => [_O[i], _N[i], _E[i]].join(" "));
837
837
  var ADDR_ROWS = Array.from({ length: 7 }, (_2, i) => [_A[i], _D2[i], _D2[i], _R[i], _E[i], _S[i], _S[i]].join(" "));
838
- var WIZARD_VERSION = true ? "1.1.4" : "?";
838
+ var WIZARD_VERSION = true ? "1.1.5" : "?";
839
839
  function printHeader() {
840
840
  const BAR_LEN = 85;
841
841
  const TOP_BAR = fn("\u250C") + dm("\u2500".repeat(BAR_LEN)) + fn("\u2510");
@@ -927,27 +927,81 @@ dist/
927
927
  },
928
928
  "devDependencies": {
929
929
  "@types/express": "^4.17.0",
930
- "@types/node": "^20.0.0",
930
+ "@types/node": "^22.5.0",
931
931
  "tsup": "^8.0.0",
932
932
  "tsx": "^4.0.0",
933
933
  "typescript": "^5.0.0"
934
934
  },
935
- "engines": { "node": ">=18" }
935
+ "engines": { "node": ">=22.5" }
936
936
  }
937
+ `
938
+ },
939
+ {
940
+ name: "src/db.ts",
941
+ content: `/**
942
+ * SQLite database \u2014 initialised automatically on first run.
943
+ *
944
+ * Uses the Node.js built-in \`node:sqlite\` module (available in Node 22.5+).
945
+ * No extra packages or native compilation required.
946
+ *
947
+ * Two tables:
948
+ * addresses \u2014 one row per customer, holds their latest address
949
+ * address_history \u2014 append-only audit trail of every change
950
+ *
951
+ * The database file lives at DB_PATH (default: data.db in the project root).
952
+ * Change the location with the DB_PATH environment variable.
953
+ */
954
+ import { DatabaseSync } from 'node:sqlite';
955
+ import { join } from 'node:path';
956
+
957
+ const DB_PATH = process.env.DB_PATH ?? join(process.cwd(), 'data.db');
958
+
959
+ const db = new DatabaseSync(DB_PATH);
960
+
961
+ // WAL mode \u2014 better performance for concurrent reads
962
+ db.exec("PRAGMA journal_mode = WAL");
963
+
964
+ db.exec(\`
965
+ CREATE TABLE IF NOT EXISTS addresses (
966
+ email TEXT PRIMARY KEY,
967
+ name TEXT NOT NULL DEFAULT '',
968
+ address TEXT NOT NULL DEFAULT '{}',
969
+ dispatch_id TEXT,
970
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
971
+ );
972
+
973
+ CREATE TABLE IF NOT EXISTS address_history (
974
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
975
+ email TEXT NOT NULL,
976
+ name TEXT NOT NULL DEFAULT '',
977
+ address TEXT NOT NULL DEFAULT '{}',
978
+ dispatch_id TEXT,
979
+ recorded_at TEXT NOT NULL DEFAULT (datetime('now'))
980
+ );
981
+ \`);
982
+
983
+ console.log(\`[db] SQLite database ready \u2192 \${DB_PATH}\`);
984
+
985
+ export default db;
937
986
  `
938
987
  },
939
988
  {
940
989
  name: "src/store.ts",
941
990
  content: `/**
942
991
  * \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
943
- * \u2551 src/store.ts \u2014 YOUR DATABASE INTEGRATION LIVES HERE \u2551
992
+ * \u2551 src/store.ts \u2014 DATABASE INTEGRATION \u2551
993
+ * \u2551 \u2551
994
+ * \u2551 Uses Node.js built-in SQLite (node:sqlite) \u2014 no install \u2551
995
+ * \u2551 or native compilation needed. Requires Node.js 22.5+. \u2551
996
+ * \u2551 The database file is created automatically as data.db \u2551
944
997
  * \u2551 \u2551
945
- * \u2551 Implement the two functions below using whatever database, \u2551
946
- * \u2551 ORM, or API your organisation already uses. \u2551
947
- * \u2551 server.ts calls them after verifying and decrypting each event \u2551
998
+ * \u2551 To use a different database (Postgres, MySQL, etc.): \u2551
999
+ * \u2551 Replace the db calls in saveAddress and verifyAddress below. \u2551
1000
+ * \u2551 server.ts calls these after verifying and decrypting each event \u2551
948
1001
  * \u2551 \u2014 the protocol layer is handled for you, never touch it. \u2551
949
1002
  * \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
950
1003
  */
1004
+ import db from './db.js';
951
1005
 
952
1006
  export type Address = Record<string, unknown>;
953
1007
 
@@ -961,47 +1015,34 @@ export type VerifyResult = 'match' | 'mismatch' | 'not_found';
961
1015
  /**
962
1016
  * Called when a consumer updates their address (address.updated event).
963
1017
  *
964
- * The \`address\` object is already decrypted \u2014 fields vary by country but
965
- * typically include: street, suburb, state, postcode, country.
966
- *
967
- * Persist this to your database (update the customer's address record,
968
- * publish to a queue, call an internal API \u2014 whatever your system needs).
1018
+ * Upserts the address into the \`addresses\` table and appends a row
1019
+ * to \`address_history\` for the audit trail.
969
1020
  */
970
1021
  export async function saveAddress(customer: Customer, address: Address): Promise<void> {
971
- // \u2500\u2500\u2500 TODO: implement \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
972
- //
973
- // Examples:
974
- //
975
- // Prisma:
976
- // await prisma.customer.update({
977
- // where: { email: customer.email },
978
- // data: { address: JSON.stringify(address) },
979
- // });
980
- //
981
- // Postgres.js:
982
- // await sql\`
983
- // UPDATE customers SET address = \${sql.json(address)}
984
- // WHERE email = \${customer.email}
985
- // \`;
986
- //
987
- // HTTP / CRM:
988
- // await fetch('https://your-crm.internal/addresses', {
989
- // method: 'PUT',
990
- // body: JSON.stringify({ email: customer.email, address }),
991
- // });
992
- //
993
- // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
994
-
995
- console.log('[store] saveAddress \u2014 implement this function to persist the address');
996
- console.log('[store] customer:', customer.email);
997
- console.log('[store] address: ', JSON.stringify(address));
1022
+ const addressJson = JSON.stringify(address);
1023
+
1024
+ db.prepare(\`
1025
+ INSERT INTO addresses (email, name, address, updated_at)
1026
+ VALUES ($email, $name, $address, datetime('now'))
1027
+ ON CONFLICT(email) DO UPDATE SET
1028
+ name = excluded.name,
1029
+ address = excluded.address,
1030
+ updated_at = excluded.updated_at
1031
+ \`).run({ email: customer.email, name: customer.name, address: addressJson });
1032
+
1033
+ db.prepare(\`
1034
+ INSERT INTO address_history (email, name, address, recorded_at)
1035
+ VALUES ($email, $name, $address, datetime('now'))
1036
+ \`).run({ email: customer.email, name: customer.name, address: addressJson });
1037
+
1038
+ console.log(\`[store] Saved address for \${customer.email}:\`, address);
998
1039
  }
999
1040
 
1000
1041
  /**
1001
1042
  * Called during an address verification check (address.verify event).
1002
1043
  *
1003
- * The \`address\` object is already decrypted. Compare it against whatever
1004
- * you have on file for this customer and return the appropriate result.
1044
+ * Looks up the customer by email and compares the incoming (decrypted)
1045
+ * address against what is stored in the database.
1005
1046
  *
1006
1047
  * Return values:
1007
1048
  * 'match' \u2014 the address matches your records exactly
@@ -1009,19 +1050,20 @@ export async function saveAddress(customer: Customer, address: Address): Promise
1009
1050
  * 'not_found' \u2014 you have no record for this customer at all
1010
1051
  */
1011
1052
  export async function verifyAddress(customer: Customer, address: Address): Promise<VerifyResult> {
1012
- // \u2500\u2500\u2500 TODO: implement \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1013
- //
1014
- // Example:
1015
- // const record = await prisma.customer.findUnique({ where: { email: customer.email } });
1016
- // if (!record) return 'not_found';
1017
- // const stored = JSON.parse(record.address ?? '{}') as Address;
1018
- // const match = Object.entries(address).every(([k, v]) => stored[k] === v);
1019
- // return match ? 'match' : 'mismatch';
1020
- //
1021
- // \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1022
-
1023
- console.log('[store] verifyAddress \u2014 returning stub "match". Implement this function.');
1024
- return 'match';
1053
+ const row = db.prepare(\`SELECT address FROM addresses WHERE email = ?\`)
1054
+ .get(customer.email) as { address: string } | undefined;
1055
+
1056
+ if (!row) {
1057
+ console.log(\`[store] verifyAddress \u2192 not_found (\${customer.email} has no record)\`);
1058
+ return 'not_found';
1059
+ }
1060
+
1061
+ const stored = JSON.parse(row.address) as Address;
1062
+ const isMatch = Object.entries(address).every(([k, v]) => stored[k] === v);
1063
+ const result = isMatch ? 'match' : 'mismatch';
1064
+
1065
+ console.log(\`[store] verifyAddress \u2192 \${result} for \${customer.email}\`);
1066
+ return result;
1025
1067
  }
1026
1068
  `
1027
1069
  },
@@ -1211,26 +1253,39 @@ npm run dev # hot-reload via tsx
1211
1253
  Webhook endpoint: \`POST http://localhost:3001/webhook\`
1212
1254
  Health check: \`GET http://localhost:3001/health\`
1213
1255
 
1256
+ A SQLite database (\`data.db\`) is created automatically the first time the server starts.
1257
+ To view it, open \`data.db\` with any SQLite client (e.g. [DB Browser for SQLite](https://sqlitebrowser.org/)).
1258
+
1214
1259
  ## Architecture
1215
1260
 
1216
1261
  | File | Role |
1217
1262
  |------|------|
1218
1263
  | \`src/server.ts\` | **Protocol layer \u2014 do not edit.** HMAC verification, timestamp replay protection, ECDH decryption, deduplication. |
1219
- | \`src/store.ts\` | **Your integration \u2014 implement this.** Two functions called after every event. Wire up your database, ORM, or internal API here. |
1220
-
1221
- ## Implementing src/store.ts
1264
+ | \`src/db.ts\` | **SQLite setup.** Opens (or creates) \`data.db\` and defines the schema. Change \`DB_PATH\` env var to move the file. |
1265
+ | \`src/store.ts\` | **Your integration.** \`saveAddress\` and \`verifyAddress\` \u2014 wired to SQLite out of the box. Swap for Postgres/MySQL/etc. when ready. |
1222
1266
 
1223
- After running the wizard, open \`src/store.ts\` and implement:
1267
+ ## Database tables
1224
1268
 
1225
- \`\`\`typescript
1226
- // Called on address.updated \u2014 persist the decrypted address
1227
- async function saveAddress(customer, address): Promise<void>
1269
+ \`\`\`sql
1270
+ -- Current address per customer (upserted on every address.updated event)
1271
+ addresses(email, name, address JSON, updated_at)
1228
1272
 
1229
- // Called on address.verify \u2014 return 'match' | 'mismatch' | 'not_found'
1230
- async function verifyAddress(customer, address): Promise<VerifyResult>
1273
+ -- Full history of every address change (append-only audit trail)
1274
+ address_history(id, email, name, address JSON, recorded_at)
1231
1275
  \`\`\`
1232
1276
 
1233
- The file has commented examples for Prisma, Postgres.js, and plain HTTP calls.
1277
+ ## Swapping to a production database
1278
+
1279
+ Open \`src/store.ts\` and replace the \`db\` calls with your ORM/driver of choice:
1280
+
1281
+ \`\`\`typescript
1282
+ // Prisma example:
1283
+ await prisma.customer.upsert({
1284
+ where: { email: customer.email },
1285
+ update: { address: JSON.stringify(address) },
1286
+ create: { email: customer.email, name: customer.name, address: JSON.stringify(address) },
1287
+ });
1288
+ \`\`\`
1234
1289
 
1235
1290
  ## Events
1236
1291
 
@@ -3059,9 +3114,8 @@ function stopServer() {
3059
3114
  if (serverProcess) {
3060
3115
  try {
3061
3116
  if (process.platform === "win32" && serverProcess.pid) {
3062
- (0, import_node_child_process2.spawn)("taskkill", ["/F", "/T", "/PID", String(serverProcess.pid)], {
3063
- stdio: "ignore",
3064
- detached: true
3117
+ (0, import_node_child_process2.spawnSync)("taskkill", ["/F", "/T", "/PID", String(serverProcess.pid)], {
3118
+ stdio: "ignore"
3065
3119
  });
3066
3120
  } else {
3067
3121
  serverProcess.kill("SIGTERM");
@@ -3335,7 +3389,7 @@ function validatePkcs8Pem(pem) {
3335
3389
  }
3336
3390
  async function main() {
3337
3391
  printHeader();
3338
- we("OneAddress Partner Setup \u2014 v2");
3392
+ we("OneAddress Partner Setup");
3339
3393
  v2.info("We will:");
3340
3394
  v2.info(" 1. Collect your credentials from the Partner Portal");
3341
3395
  v2.info(" 2. Scaffold a working webhook server for your platform");
@@ -3530,9 +3584,9 @@ async function main() {
3530
3584
  `Files in: ${outDir}`
3531
3585
  ].join("\n");
3532
3586
  const nextSteps = [
3533
- "1. Open src/store.ts and wire up saveAddress() + verifyAddress() to your database",
3534
- "2. Update your address at app.oneaddress.io to trigger a live address.updated event",
3535
- "3. For production: deploy to a permanent server and update the webhook URL in the portal",
3587
+ "1. Trigger a live event: update your address at app.oneaddress.io\n \u2192 src/store.ts will save it to data.db automatically",
3588
+ "2. View your database: open data.db with DB Browser for SQLite\n (free download at sqlitebrowser.org)",
3589
+ "3. For production: swap src/store.ts for your real database (Postgres, MySQL, etc.)\n then deploy and update the webhook URL in the portal",
3536
3590
  "4. Questions? partners.oneaddress.io or partners@oneaddress.io"
3537
3591
  ].join("\n\n");
3538
3592
  ye(summary, "Setup summary");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneaddress/setup",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "description": "Interactive setup wizard for OneAddress partner webhook integrations",
5
5
  "main": "dist/index.js",
6
6
  "bin": {