@oneaddress/setup 1.1.3 → 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 +130 -75
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -835,8 +835,9 @@ 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.5" : "?";
838
839
  function printHeader() {
839
- const BAR_LEN = 86;
840
+ const BAR_LEN = 85;
840
841
  const TOP_BAR = fn("\u250C") + dm("\u2500".repeat(BAR_LEN)) + fn("\u2510");
841
842
  const BOT_BAR = fn("\u2514") + dm("\u2500".repeat(BAR_LEN)) + fn("\u2518");
842
843
  const SIDE = dm("\u2502");
@@ -845,13 +846,13 @@ function printHeader() {
845
846
  console.log(` ${SIDE}${" ".repeat(BAR_LEN)}${SIDE}`);
846
847
  for (let i = 0; i < 7; i++) {
847
848
  const line = wb(ONE_ROWS[i]) + " " + ab(ADDR_ROWS[i]);
848
- console.log(` ${SIDE} ${line} ${SIDE}`);
849
+ console.log(` ${SIDE} ${line} ${SIDE}`);
849
850
  }
850
- console.log(` ${SIDE} ${fn("\u2591".repeat(81))} ${SIDE}`);
851
+ console.log(` ${SIDE} ${fn("\u2591".repeat(81))} ${SIDE}`);
851
852
  console.log(` ${SIDE}${" ".repeat(BAR_LEN)}${SIDE}`);
852
853
  const wizard = mi("Partner Setup Wizard");
853
- const version = fn("v2 \xB7 partners.oneaddress.io");
854
- const tagLine = ` ${wizard} ${version}`;
854
+ const ver = fn(`v${WIZARD_VERSION} \xB7 partners.oneaddress.io`);
855
+ const tagLine = ` ${wizard} ${ver}`;
855
856
  const padLen = BAR_LEN - 4 - stripAnsi(tagLine).length;
856
857
  console.log(` ${SIDE} ${tagLine}${" ".repeat(Math.max(0, padLen))} ${SIDE}`);
857
858
  console.log(` ${SIDE}${" ".repeat(BAR_LEN)}${SIDE}`);
@@ -926,27 +927,81 @@ dist/
926
927
  },
927
928
  "devDependencies": {
928
929
  "@types/express": "^4.17.0",
929
- "@types/node": "^20.0.0",
930
+ "@types/node": "^22.5.0",
930
931
  "tsup": "^8.0.0",
931
932
  "tsx": "^4.0.0",
932
933
  "typescript": "^5.0.0"
933
934
  },
934
- "engines": { "node": ">=18" }
935
+ "engines": { "node": ">=22.5" }
935
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;
936
986
  `
937
987
  },
938
988
  {
939
989
  name: "src/store.ts",
940
990
  content: `/**
941
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
942
- * \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
943
997
  * \u2551 \u2551
944
- * \u2551 Implement the two functions below using whatever database, \u2551
945
- * \u2551 ORM, or API your organisation already uses. \u2551
946
- * \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
947
1001
  * \u2551 \u2014 the protocol layer is handled for you, never touch it. \u2551
948
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
949
1003
  */
1004
+ import db from './db.js';
950
1005
 
951
1006
  export type Address = Record<string, unknown>;
952
1007
 
@@ -960,47 +1015,34 @@ export type VerifyResult = 'match' | 'mismatch' | 'not_found';
960
1015
  /**
961
1016
  * Called when a consumer updates their address (address.updated event).
962
1017
  *
963
- * The \`address\` object is already decrypted \u2014 fields vary by country but
964
- * typically include: street, suburb, state, postcode, country.
965
- *
966
- * Persist this to your database (update the customer's address record,
967
- * 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.
968
1020
  */
969
1021
  export async function saveAddress(customer: Customer, address: Address): Promise<void> {
970
- // \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
971
- //
972
- // Examples:
973
- //
974
- // Prisma:
975
- // await prisma.customer.update({
976
- // where: { email: customer.email },
977
- // data: { address: JSON.stringify(address) },
978
- // });
979
- //
980
- // Postgres.js:
981
- // await sql\`
982
- // UPDATE customers SET address = \${sql.json(address)}
983
- // WHERE email = \${customer.email}
984
- // \`;
985
- //
986
- // HTTP / CRM:
987
- // await fetch('https://your-crm.internal/addresses', {
988
- // method: 'PUT',
989
- // body: JSON.stringify({ email: customer.email, address }),
990
- // });
991
- //
992
- // \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
993
-
994
- console.log('[store] saveAddress \u2014 implement this function to persist the address');
995
- console.log('[store] customer:', customer.email);
996
- 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);
997
1039
  }
998
1040
 
999
1041
  /**
1000
1042
  * Called during an address verification check (address.verify event).
1001
1043
  *
1002
- * The \`address\` object is already decrypted. Compare it against whatever
1003
- * 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.
1004
1046
  *
1005
1047
  * Return values:
1006
1048
  * 'match' \u2014 the address matches your records exactly
@@ -1008,19 +1050,20 @@ export async function saveAddress(customer: Customer, address: Address): Promise
1008
1050
  * 'not_found' \u2014 you have no record for this customer at all
1009
1051
  */
1010
1052
  export async function verifyAddress(customer: Customer, address: Address): Promise<VerifyResult> {
1011
- // \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
1012
- //
1013
- // Example:
1014
- // const record = await prisma.customer.findUnique({ where: { email: customer.email } });
1015
- // if (!record) return 'not_found';
1016
- // const stored = JSON.parse(record.address ?? '{}') as Address;
1017
- // const match = Object.entries(address).every(([k, v]) => stored[k] === v);
1018
- // return match ? 'match' : 'mismatch';
1019
- //
1020
- // \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
1021
-
1022
- console.log('[store] verifyAddress \u2014 returning stub "match". Implement this function.');
1023
- 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;
1024
1067
  }
1025
1068
  `
1026
1069
  },
@@ -1210,26 +1253,39 @@ npm run dev # hot-reload via tsx
1210
1253
  Webhook endpoint: \`POST http://localhost:3001/webhook\`
1211
1254
  Health check: \`GET http://localhost:3001/health\`
1212
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
+
1213
1259
  ## Architecture
1214
1260
 
1215
1261
  | File | Role |
1216
1262
  |------|------|
1217
1263
  | \`src/server.ts\` | **Protocol layer \u2014 do not edit.** HMAC verification, timestamp replay protection, ECDH decryption, deduplication. |
1218
- | \`src/store.ts\` | **Your integration \u2014 implement this.** Two functions called after every event. Wire up your database, ORM, or internal API here. |
1219
-
1220
- ## 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. |
1221
1266
 
1222
- After running the wizard, open \`src/store.ts\` and implement:
1267
+ ## Database tables
1223
1268
 
1224
- \`\`\`typescript
1225
- // Called on address.updated \u2014 persist the decrypted address
1226
- 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)
1227
1272
 
1228
- // Called on address.verify \u2014 return 'match' | 'mismatch' | 'not_found'
1229
- 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)
1230
1275
  \`\`\`
1231
1276
 
1232
- 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
+ \`\`\`
1233
1289
 
1234
1290
  ## Events
1235
1291
 
@@ -3058,9 +3114,8 @@ function stopServer() {
3058
3114
  if (serverProcess) {
3059
3115
  try {
3060
3116
  if (process.platform === "win32" && serverProcess.pid) {
3061
- (0, import_node_child_process2.spawn)("taskkill", ["/F", "/T", "/PID", String(serverProcess.pid)], {
3062
- stdio: "ignore",
3063
- detached: true
3117
+ (0, import_node_child_process2.spawnSync)("taskkill", ["/F", "/T", "/PID", String(serverProcess.pid)], {
3118
+ stdio: "ignore"
3064
3119
  });
3065
3120
  } else {
3066
3121
  serverProcess.kill("SIGTERM");
@@ -3334,7 +3389,7 @@ function validatePkcs8Pem(pem) {
3334
3389
  }
3335
3390
  async function main() {
3336
3391
  printHeader();
3337
- we("OneAddress Partner Setup \u2014 v2");
3392
+ we("OneAddress Partner Setup");
3338
3393
  v2.info("We will:");
3339
3394
  v2.info(" 1. Collect your credentials from the Partner Portal");
3340
3395
  v2.info(" 2. Scaffold a working webhook server for your platform");
@@ -3529,9 +3584,9 @@ async function main() {
3529
3584
  `Files in: ${outDir}`
3530
3585
  ].join("\n");
3531
3586
  const nextSteps = [
3532
- "1. Open src/store.ts and wire up saveAddress() + verifyAddress() to your database",
3533
- "2. Update your address at app.oneaddress.io to trigger a live address.updated event",
3534
- "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",
3535
3590
  "4. Questions? partners.oneaddress.io or partners@oneaddress.io"
3536
3591
  ].join("\n\n");
3537
3592
  ye(summary, "Setup summary");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneaddress/setup",
3
- "version": "1.1.3",
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": {