realitydb 1.9.0 → 2.0.1

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 (3) hide show
  1. package/LICENSE +80 -21
  2. package/dist/index.js +544 -65
  3. package/package.json +1 -1
package/LICENSE CHANGED
@@ -1,21 +1,80 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 emkwambe
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ Business Source License 1.1
2
+
3
+ Parameters
4
+
5
+ Licensor: Mpingo Systems Ltd (Eddy Mkwambe)
6
+ Licensed Work: RealityDB Sandbox
7
+ The Licensed Work is (c) 2026 Mpingo Systems Ltd
8
+ Additional Use Grant: You may make use of the Licensed Work, provided that
9
+ you do not use the Licensed Work for a Synthetic Data
10
+ Service, SQL Learning Service, or Database Simulation
11
+ Service.
12
+
13
+ A "Synthetic Data Service" is a commercial offering
14
+ that allows third parties to access the functionality
15
+ of the Licensed Work by generating synthetic database
16
+ records, test data, or simulation environments.
17
+
18
+ A "SQL Learning Service" is a commercial offering that
19
+ allows third parties to practice SQL queries against
20
+ auto-graded challenges using functionality substantially
21
+ derived from the Licensed Work.
22
+
23
+ A "Database Simulation Service" is a commercial offering
24
+ that allows third parties to create or access simulated
25
+ database environments with lifecycle-coherent data using
26
+ functionality substantially derived from the Licensed Work.
27
+
28
+ You may use the Licensed Work for internal testing,
29
+ development, education, research, and any non-competing
30
+ commercial purpose without restriction.
31
+
32
+ Change Date: March 31, 2030
33
+ Change License: Apache License, Version 2.0
34
+
35
+ For information about alternative licensing arrangements for the Licensed Work,
36
+ please contact: licensing@realitydb.dev
37
+
38
+ Notice
39
+
40
+ Business Source License 1.1 (the "License")
41
+
42
+ The Licensor hereby grants you the right to copy, modify, create derivative
43
+ works, redistribute, and make non-production use of the Licensed Work. The
44
+ Licensor may make an Additional Use Grant, above, permitting limited production
45
+ use.
46
+
47
+ Effective on the Change Date, or the fourth anniversary of the first publicly
48
+ available distribution of a specific version of the Licensed Work under this
49
+ License, whichever comes first, the Licensor hereby grants you rights under
50
+ the terms of the Change License, and the rights granted in the paragraph
51
+ above terminate.
52
+
53
+ If your use of the Licensed Work does not comply with the requirements
54
+ currently in effect as described in this License, you must purchase a
55
+ commercial license from the Licensor, its affiliated entities, or authorized
56
+ resellers, or you must refrain from using the Licensed Work.
57
+
58
+ All copies of the original and modified Licensed Work, and derivative works
59
+ of the Licensed Work, are subject to this License. This License applies
60
+ separately for each version of the Licensed Work and the Change Date may vary
61
+ for each version of the Licensed Work released by Licensor.
62
+
63
+ You must conspicuously display this License on each original or modified copy
64
+ of the Licensed Work. If you receive the Licensed Work in original or
65
+ modified form from a third party, the terms and conditions set forth in this
66
+ License apply to your use of that work.
67
+
68
+ Any use of the Licensed Work in violation of this License will automatically
69
+ terminate your rights under this License for the current and all other
70
+ versions of the Licensed Work.
71
+
72
+ This License does not grant you any right in any trademark or logo of
73
+ Licensor or its affiliates (provided that you may use a trademark or logo of
74
+ Licensor as expressly required by this License).
75
+
76
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
77
+ AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
78
+ EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
79
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
80
+ TITLE.
package/dist/index.js CHANGED
@@ -23297,23 +23297,42 @@ function escapeCsvField(value) {
23297
23297
  // ../../packages/generators/dist/exporters/sql.js
23298
23298
  var import_promises3 = require("fs/promises");
23299
23299
  var import_node_path3 = require("path");
23300
- async function exportToSql(dataset, outputDir, tableOrder) {
23300
+ async function exportToSql(dataset, outputDir, tableOrder, options) {
23301
23301
  await (0, import_promises3.mkdir)(outputDir, { recursive: true });
23302
+ const batchSize = options?.batchSize ?? 50;
23302
23303
  const lines = [];
23303
- lines.push("-- Generated by DataBox");
23304
+ lines.push("-- =================================================================");
23305
+ lines.push("-- Generated by RealityDB");
23306
+ if (options?.templateName) {
23307
+ lines.push(`-- Template: ${options.templateName}`);
23308
+ }
23304
23309
  lines.push(`-- Seed: ${dataset.seed}`);
23305
23310
  lines.push(`-- Generated at: ${dataset.generatedAt}`);
23311
+ lines.push("-- =================================================================");
23312
+ lines.push("");
23313
+ if (options?.ddl) {
23314
+ lines.push("-- --- SCHEMA ---------------------------------------------------------");
23315
+ lines.push("");
23316
+ lines.push(options.ddl);
23317
+ lines.push("");
23318
+ }
23319
+ lines.push("-- --- DATA -----------------------------------------------------------");
23306
23320
  lines.push("");
23307
23321
  for (const tableName of tableOrder) {
23308
23322
  const table = dataset.tables.get(tableName);
23309
23323
  if (!table || table.rows.length === 0)
23310
23324
  continue;
23311
23325
  const quotedColumns = table.columns.map((c) => `"${c}"`).join(", ");
23312
- for (const row of table.rows) {
23313
- const values = table.columns.map((col) => escapeSqlValue(row[col])).join(", ");
23314
- lines.push(`INSERT INTO "${tableName}" (${quotedColumns}) VALUES (${values});`);
23326
+ for (let i = 0; i < table.rows.length; i += batchSize) {
23327
+ const batch = table.rows.slice(i, i + batchSize);
23328
+ const valueRows = batch.map((row) => {
23329
+ const values = table.columns.map((col) => escapeSqlValue(row[col])).join(", ");
23330
+ return ` (${values})`;
23331
+ });
23332
+ lines.push(`INSERT INTO "${tableName}" (${quotedColumns}) VALUES`);
23333
+ lines.push(valueRows.join(",\n") + ";");
23334
+ lines.push("");
23315
23335
  }
23316
- lines.push("");
23317
23336
  }
23318
23337
  const filePath = (0, import_node_path3.join)(outputDir, "seed.sql");
23319
23338
  await (0, import_promises3.writeFile)(filePath, lines.join("\n"), "utf-8");
@@ -24692,10 +24711,10 @@ function exportRealityPack(dataset, plan, schema, options) {
24692
24711
  }))
24693
24712
  };
24694
24713
  const packDataset = { tables: {} };
24695
- for (const [tableName, table] of dataset.tables) {
24714
+ for (const [tableName, table] of Array.from(dataset.tables.entries())) {
24696
24715
  packDataset.tables[tableName] = {
24697
- columns: table.columns,
24698
- rows: table.rows,
24716
+ columns: [...table.columns],
24717
+ rows: table.rows.map((row) => ({ ...row })),
24699
24718
  rowCount: table.rowCount
24700
24719
  };
24701
24720
  }
@@ -27940,8 +27959,8 @@ function getSimulationProfile(name) {
27940
27959
  var saasTemplate = {
27941
27960
  name: "saas",
27942
27961
  version: "2.0",
27943
- description: "SaaS subscription business with organizations, users, plans, invoices, and payments",
27944
- targetTables: ["organizations", "users", "plans", "subscriptions", "invoices", "payments"],
27962
+ description: "SaaS platform with 10 tables: organizations, users, plans, features, plan_features, subscriptions, invoices, payments, sessions, events",
27963
+ targetTables: ["organizations", "users", "plans", "features", "plan_features", "subscriptions", "invoices", "payments", "sessions", "events"],
27945
27964
  tableConfigs: /* @__PURE__ */ new Map([
27946
27965
  ["organizations", {
27947
27966
  tableName: "organizations",
@@ -28098,7 +28117,7 @@ var saasTemplate = {
28098
28117
  {
28099
28118
  columnName: "trial_ends_at",
28100
28119
  strategy: { kind: "timestamp", options: { mode: "past" } },
28101
- description: "Nullable \u2014 null for non-trial subscriptions"
28120
+ description: "Nullable \xC3\u0192\xC2\xA2\xC3\xA2\xE2\u20AC\u0161\xC2\xAC\xC3\xA2\xE2\u201A\xAC\xC2\x9D null for non-trial subscriptions"
28102
28121
  },
28103
28122
  {
28104
28123
  columnName: "current_period_start",
@@ -28116,7 +28135,7 @@ var saasTemplate = {
28116
28135
  {
28117
28136
  columnName: "canceled_at",
28118
28137
  strategy: { kind: "timestamp", options: { mode: "past" } },
28119
- description: "Nullable \u2014 should be null for active/trialing subscriptions"
28138
+ description: "Nullable \xC3\u0192\xC2\xA2\xC3\xA2\xE2\u20AC\u0161\xC2\xAC\xC3\xA2\xE2\u201A\xAC\xC2\x9D should be null for active/trialing subscriptions"
28120
28139
  }
28121
28140
  ]
28122
28141
  }],
@@ -28157,7 +28176,7 @@ var saasTemplate = {
28157
28176
  {
28158
28177
  columnName: "paid_at",
28159
28178
  strategy: { kind: "timestamp", options: { mode: "past" } },
28160
- description: "Nullable \u2014 null for unpaid invoices"
28179
+ description: "Nullable \xC3\u0192\xC2\xA2\xC3\xA2\xE2\u20AC\u0161\xC2\xAC\xC3\xA2\xE2\u201A\xAC\xC2\x9D null for unpaid invoices"
28161
28180
  },
28162
28181
  {
28163
28182
  columnName: "created_at",
@@ -28214,7 +28233,7 @@ var saasTemplate = {
28214
28233
  weights: [0.35, 0.3, 0.2, 0.15]
28215
28234
  }
28216
28235
  },
28217
- description: "Nullable \u2014 null for successful payments"
28236
+ description: "Nullable \xC3\u0192\xC2\xA2\xC3\xA2\xE2\u20AC\u0161\xC2\xAC\xC3\xA2\xE2\u201A\xAC\xC2\x9D null for successful payments"
28218
28237
  },
28219
28238
  {
28220
28239
  columnName: "paid_at",
@@ -28222,6 +28241,50 @@ var saasTemplate = {
28222
28241
  strategy: { kind: "timestamp", options: { mode: "past" } }
28223
28242
  }
28224
28243
  ]
28244
+ }],
28245
+ ["features", {
28246
+ tableName: "features",
28247
+ matchPattern: ["features", "*feature*"],
28248
+ rowCountMultiplier: 0.02,
28249
+ columnOverrides: [
28250
+ { columnName: "name", strategy: { kind: "text", options: { mode: "short" } } },
28251
+ { columnName: "category", strategy: { kind: "enum", options: { values: ["core", "analytics", "security", "integration"], weights: [0.35, 0.25, 0.2, 0.2] } } },
28252
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
28253
+ ]
28254
+ }],
28255
+ ["plan_features", {
28256
+ tableName: "plan_features",
28257
+ matchPattern: ["plan_features", "*plan_feature*"],
28258
+ rowCountMultiplier: 0.06,
28259
+ columnOverrides: [
28260
+ { columnName: "enabled", strategy: { kind: "boolean" } },
28261
+ { columnName: "limit_value", strategy: { kind: "integer", options: { min: 0, max: 1e4 } } },
28262
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
28263
+ ]
28264
+ }],
28265
+ ["sessions", {
28266
+ tableName: "sessions",
28267
+ matchPattern: ["sessions", "*session*"],
28268
+ rowCountMultiplier: 8,
28269
+ columnOverrides: [
28270
+ { columnName: "started_at", strategy: { kind: "timestamp", options: { mode: "past" } } },
28271
+ { columnName: "ended_at", strategy: { kind: "timestamp", options: { mode: "recent" } } },
28272
+ { columnName: "duration_seconds", strategy: { kind: "integer", options: { min: 10, max: 14400 } } },
28273
+ { columnName: "ip_address", strategy: { kind: "text", options: { mode: "short" } } },
28274
+ { columnName: "user_agent", strategy: { kind: "text" } },
28275
+ { columnName: "country", strategy: { kind: "enum", options: { values: ["US", "UK", "CA", "DE", "FR", "AU", "IN", "BR"], weights: [0.5, 0.1, 0.08, 0.06, 0.05, 0.04, 0.04, 0.03] } } },
28276
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
28277
+ ]
28278
+ }],
28279
+ ["events", {
28280
+ tableName: "events",
28281
+ matchPattern: ["events", "*event*"],
28282
+ rowCountMultiplier: 10,
28283
+ columnOverrides: [
28284
+ { columnName: "event_type", strategy: { kind: "enum", options: { values: ["page_view", "button_click", "api_call", "feature_use", "error", "search"], weights: [0.4, 0.2, 0.15, 0.12, 0.05, 0.08] } } },
28285
+ { columnName: "event_name", strategy: { kind: "text", options: { mode: "short" } } },
28286
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "recent" } } }
28287
+ ]
28225
28288
  }]
28226
28289
  ])
28227
28290
  };
@@ -28230,8 +28293,8 @@ var saasTemplate = {
28230
28293
  var ecommerceTemplate = {
28231
28294
  name: "ecommerce",
28232
28295
  version: "2.0",
28233
- description: "E-commerce store with categories, products, orders, and reviews",
28234
- targetTables: ["customers", "categories", "products", "orders", "order_items", "reviews"],
28296
+ description: "E-commerce platform with 12 tables: customers, categories, products, orders, order_items, reviews, sessions, cart_items, payments, refunds, shipments, disputes",
28297
+ targetTables: ["customers", "categories", "products", "orders", "order_items", "reviews", "sessions", "cart_items", "payments", "refunds", "shipments", "disputes"],
28235
28298
  tableConfigs: /* @__PURE__ */ new Map([
28236
28299
  ["customers", {
28237
28300
  tableName: "customers",
@@ -28340,7 +28403,7 @@ var ecommerceTemplate = {
28340
28403
  {
28341
28404
  columnName: "compare_at_price_cents",
28342
28405
  strategy: { kind: "money", options: { min: 0, max: 2e5 } },
28343
- description: "Nullable \u2014 original price before discount"
28406
+ description: "Nullable \xC3\xA2\xE2\u201A\xAC\xE2\u20AC\x9D original price before discount"
28344
28407
  },
28345
28408
  {
28346
28409
  columnName: "brand",
@@ -28426,13 +28489,13 @@ var ecommerceTemplate = {
28426
28489
  columnName: "shipped_at",
28427
28490
  matchPattern: ["shipped_at", "ship_date"],
28428
28491
  strategy: { kind: "timestamp", options: { mode: "past" } },
28429
- description: "Nullable \u2014 null for pending/processing orders"
28492
+ description: "Nullable \xC3\xA2\xE2\u201A\xAC\xE2\u20AC\x9D null for pending/processing orders"
28430
28493
  },
28431
28494
  {
28432
28495
  columnName: "delivered_at",
28433
28496
  matchPattern: ["delivered_at", "delivery_date"],
28434
28497
  strategy: { kind: "timestamp", options: { mode: "past" } },
28435
- description: "Nullable \u2014 null for non-delivered orders"
28498
+ description: "Nullable \xC3\xA2\xE2\u201A\xAC\xE2\u20AC\x9D null for non-delivered orders"
28436
28499
  }
28437
28500
  ]
28438
28501
  }],
@@ -28484,6 +28547,76 @@ var ecommerceTemplate = {
28484
28547
  strategy: { kind: "timestamp", options: { mode: "past" } }
28485
28548
  }
28486
28549
  ]
28550
+ }],
28551
+ ["sessions", {
28552
+ tableName: "sessions",
28553
+ matchPattern: ["sessions", "*session*"],
28554
+ rowCountMultiplier: 8,
28555
+ columnOverrides: [
28556
+ { columnName: "started_at", strategy: { kind: "timestamp", options: { mode: "past" } } },
28557
+ { columnName: "ended_at", strategy: { kind: "timestamp", options: { mode: "recent" } } },
28558
+ { columnName: "page_count", strategy: { kind: "integer", options: { min: 1, max: 50 } } },
28559
+ { columnName: "device_type", strategy: { kind: "enum", options: { values: ["desktop", "mobile", "tablet"], weights: [0.45, 0.4, 0.15] } } },
28560
+ { columnName: "utm_source", strategy: { kind: "enum", options: { values: ["direct", "google", "email", "facebook", "organic"], weights: [0.35, 0.25, 0.15, 0.1, 0.15] } } },
28561
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
28562
+ ]
28563
+ }],
28564
+ ["cart_items", {
28565
+ tableName: "cart_items",
28566
+ matchPattern: ["cart_items", "*cart*"],
28567
+ rowCountMultiplier: 8,
28568
+ columnOverrides: [
28569
+ { columnName: "quantity", strategy: { kind: "integer", options: { min: 1, max: 5 } } },
28570
+ { columnName: "price_cents", strategy: { kind: "integer", options: { min: 499, max: 99999 } } },
28571
+ { columnName: "added_at", strategy: { kind: "timestamp", options: { mode: "past" } } },
28572
+ { columnName: "converted", strategy: { kind: "boolean" } },
28573
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
28574
+ ]
28575
+ }],
28576
+ ["payments", {
28577
+ tableName: "payments",
28578
+ matchPattern: ["payments", "*payment*"],
28579
+ rowCountMultiplier: 3,
28580
+ columnOverrides: [
28581
+ { columnName: "amount_cents", strategy: { kind: "integer", options: { min: 499, max: 2e5 } } },
28582
+ { columnName: "method", strategy: { kind: "enum", options: { values: ["credit_card", "debit_card", "paypal", "apple_pay", "klarna"], weights: [0.55, 0.15, 0.15, 0.1, 0.05] } } },
28583
+ { columnName: "status", strategy: { kind: "enum", options: { values: ["succeeded", "failed", "pending", "refunded"], weights: [0.8, 0.12, 0.03, 0.05] } } },
28584
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
28585
+ ]
28586
+ }],
28587
+ ["refunds", {
28588
+ tableName: "refunds",
28589
+ matchPattern: ["refunds", "*refund*"],
28590
+ rowCountMultiplier: 0.4,
28591
+ columnOverrides: [
28592
+ { columnName: "amount_cents", strategy: { kind: "integer", options: { min: 499, max: 1e5 } } },
28593
+ { columnName: "reason", strategy: { kind: "enum", options: { values: ["defective", "wrong_item", "changed_mind", "late_delivery", "duplicate"], weights: [0.25, 0.2, 0.3, 0.15, 0.1] } } },
28594
+ { columnName: "status", strategy: { kind: "enum", options: { values: ["processed", "pending", "denied"], weights: [0.7, 0.2, 0.1] } } },
28595
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
28596
+ ]
28597
+ }],
28598
+ ["shipments", {
28599
+ tableName: "shipments",
28600
+ matchPattern: ["shipments", "*shipment*"],
28601
+ rowCountMultiplier: 3,
28602
+ columnOverrides: [
28603
+ { columnName: "carrier", strategy: { kind: "enum", options: { values: ["fedex", "ups", "usps", "dhl"], weights: [0.3, 0.3, 0.25, 0.15] } } },
28604
+ { columnName: "status", strategy: { kind: "enum", options: { values: ["delivered", "in_transit", "out_for_delivery", "exception", "label_created"], weights: [0.5, 0.25, 0.1, 0.1, 0.05] } } },
28605
+ { columnName: "shipped_at", strategy: { kind: "timestamp", options: { mode: "past" } } },
28606
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
28607
+ ]
28608
+ }],
28609
+ ["disputes", {
28610
+ tableName: "disputes",
28611
+ matchPattern: ["disputes", "*dispute*"],
28612
+ rowCountMultiplier: 0.05,
28613
+ columnOverrides: [
28614
+ { columnName: "reason", strategy: { kind: "enum", options: { values: ["fraud", "not_received", "not_as_described", "duplicate", "other"], weights: [0.2, 0.3, 0.25, 0.1, 0.15] } } },
28615
+ { columnName: "status", strategy: { kind: "enum", options: { values: ["open", "under_review", "won", "lost"], weights: [0.15, 0.25, 0.35, 0.25] } } },
28616
+ { columnName: "amount_cents", strategy: { kind: "integer", options: { min: 499, max: 5e4 } } },
28617
+ { columnName: "evidence_submitted", strategy: { kind: "boolean" } },
28618
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
28619
+ ]
28487
28620
  }]
28488
28621
  ])
28489
28622
  };
@@ -28492,7 +28625,7 @@ var ecommerceTemplate = {
28492
28625
  var educationTemplate = {
28493
28626
  name: "education",
28494
28627
  version: "2.0",
28495
- description: "K-12 school system with teachers, classes, students, grades, and attendance",
28628
+ description: "K-12 school system with 6 tables: teachers, classes, students, enrollments, grades, attendance",
28496
28629
  targetTables: ["teachers", "classes", "students", "enrollments", "grades", "attendance"],
28497
28630
  tableConfigs: /* @__PURE__ */ new Map([
28498
28631
  ["teachers", {
@@ -28762,8 +28895,8 @@ var educationTemplate = {
28762
28895
  var fintechTemplate = {
28763
28896
  name: "fintech",
28764
28897
  version: "2.0",
28765
- description: "Financial services with accounts, transactions, fraud alerts, settlements, and chargebacks",
28766
- targetTables: ["accounts", "transactions", "fraud_alerts", "settlements", "chargebacks"],
28898
+ description: "Financial platform with 10 tables: accounts, transactions, transfers, cards, authorizations, settlements, fraud_alerts, fraud_investigations, chargebacks, compliance_checks",
28899
+ targetTables: ["accounts", "transactions", "fraud_alerts", "settlements", "chargebacks", "transfers", "cards", "authorizations", "fraud_investigations", "compliance_checks"],
28767
28900
  tableConfigs: /* @__PURE__ */ new Map([
28768
28901
  ["accounts", {
28769
28902
  tableName: "accounts",
@@ -28831,7 +28964,7 @@ var fintechTemplate = {
28831
28964
  {
28832
28965
  columnName: "closed_at",
28833
28966
  strategy: { kind: "timestamp", options: { mode: "past" } },
28834
- description: "Nullable \u2014 null for active accounts"
28967
+ description: "Nullable \xC3\u0192\xC2\xA2\xC3\xA2\xE2\u20AC\u0161\xC2\xAC\xC3\xA2\xE2\u201A\xAC\xC2\x9D null for active accounts"
28835
28968
  }
28836
28969
  ]
28837
28970
  }],
@@ -28952,7 +29085,7 @@ var fintechTemplate = {
28952
29085
  {
28953
29086
  columnName: "resolved_at",
28954
29087
  strategy: { kind: "timestamp", options: { mode: "past" } },
28955
- description: "Nullable \u2014 null for open/investigating alerts"
29088
+ description: "Nullable \xC3\u0192\xC2\xA2\xC3\xA2\xE2\u20AC\u0161\xC2\xAC\xC3\xA2\xE2\u201A\xAC\xC2\x9D null for open/investigating alerts"
28956
29089
  }
28957
29090
  ]
28958
29091
  }],
@@ -28988,7 +29121,7 @@ var fintechTemplate = {
28988
29121
  {
28989
29122
  columnName: "settled_at",
28990
29123
  strategy: { kind: "timestamp", options: { mode: "past" } },
28991
- description: "Nullable \u2014 null for pending settlements"
29124
+ description: "Nullable \xC3\u0192\xC2\xA2\xC3\xA2\xE2\u20AC\u0161\xC2\xAC\xC3\xA2\xE2\u201A\xAC\xC2\x9D null for pending settlements"
28992
29125
  },
28993
29126
  {
28994
29127
  columnName: "created_at",
@@ -29032,9 +29165,70 @@ var fintechTemplate = {
29032
29165
  {
29033
29166
  columnName: "resolved_at",
29034
29167
  strategy: { kind: "timestamp", options: { mode: "past" } },
29035
- description: "Nullable \u2014 null for open/under_review chargebacks"
29168
+ description: "Nullable \xC3\u0192\xC2\xA2\xC3\xA2\xE2\u20AC\u0161\xC2\xAC\xC3\xA2\xE2\u201A\xAC\xC2\x9D null for open/under_review chargebacks"
29036
29169
  }
29037
29170
  ]
29171
+ }],
29172
+ ["transfers", {
29173
+ tableName: "transfers",
29174
+ matchPattern: ["transfers", "*transfer*"],
29175
+ rowCountMultiplier: 5,
29176
+ columnOverrides: [
29177
+ { columnName: "amount_cents", strategy: { kind: "integer", options: { min: 100, max: 5e6 } } },
29178
+ { columnName: "status", strategy: { kind: "enum", options: { values: ["completed", "pending", "failed", "cancelled"], weights: [0.75, 0.12, 0.08, 0.05] } } },
29179
+ { columnName: "reference", strategy: { kind: "text", options: { mode: "short" } } },
29180
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
29181
+ ]
29182
+ }],
29183
+ ["cards", {
29184
+ tableName: "cards",
29185
+ matchPattern: ["cards", "*card*"],
29186
+ rowCountMultiplier: 2.5,
29187
+ columnOverrides: [
29188
+ { columnName: "card_number_last_four", strategy: { kind: "text", options: { mode: "short" } } },
29189
+ { columnName: "card_type", strategy: { kind: "enum", options: { values: ["debit", "credit"], weights: [0.6, 0.4] } } },
29190
+ { columnName: "status", strategy: { kind: "enum", options: { values: ["active", "blocked", "expired", "cancelled"], weights: [0.75, 0.08, 0.12, 0.05] } } },
29191
+ { columnName: "daily_limit_cents", strategy: { kind: "integer", options: { min: 5e4, max: 1e6 } } },
29192
+ { columnName: "expires_at", strategy: { kind: "timestamp", options: { mode: "recent" } } },
29193
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
29194
+ ]
29195
+ }],
29196
+ ["authorizations", {
29197
+ tableName: "authorizations",
29198
+ matchPattern: ["authorizations", "*authorization*", "*auth*"],
29199
+ rowCountMultiplier: 8,
29200
+ columnOverrides: [
29201
+ { columnName: "merchant_name", strategy: { kind: "company_name" } },
29202
+ { columnName: "amount_cents", strategy: { kind: "integer", options: { min: 100, max: 5e5 } } },
29203
+ { columnName: "status", strategy: { kind: "enum", options: { values: ["approved", "declined", "reversed"], weights: [0.85, 0.1, 0.05] } } },
29204
+ { columnName: "decline_reason", strategy: { kind: "enum", options: { values: ["insufficient_funds", "card_blocked", "suspicious_activity", "expired_card", "limit_exceeded"], weights: [0.3, 0.2, 0.25, 0.1, 0.15] } } },
29205
+ { columnName: "authorized_at", strategy: { kind: "timestamp", options: { mode: "past" } } },
29206
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
29207
+ ]
29208
+ }],
29209
+ ["fraud_investigations", {
29210
+ tableName: "fraud_investigations",
29211
+ matchPattern: ["fraud_investigations", "*investigation*"],
29212
+ rowCountMultiplier: 0.2,
29213
+ columnOverrides: [
29214
+ { columnName: "investigator_notes", strategy: { kind: "text" } },
29215
+ { columnName: "outcome", strategy: { kind: "enum", options: { values: ["confirmed_fraud", "false_positive", "inconclusive"], weights: [0.3, 0.5, 0.2] } } },
29216
+ { columnName: "amount_recovered_cents", strategy: { kind: "integer", options: { min: 0, max: 5e5 } } },
29217
+ { columnName: "closed_at", strategy: { kind: "timestamp", options: { mode: "recent" } } },
29218
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
29219
+ ]
29220
+ }],
29221
+ ["compliance_checks", {
29222
+ tableName: "compliance_checks",
29223
+ matchPattern: ["compliance_checks", "*compliance*"],
29224
+ rowCountMultiplier: 2,
29225
+ columnOverrides: [
29226
+ { columnName: "check_type", strategy: { kind: "enum", options: { values: ["kyc_verification", "aml_screening", "pep_check", "sanctions_check"], weights: [0.35, 0.3, 0.2, 0.15] } } },
29227
+ { columnName: "result", strategy: { kind: "enum", options: { values: ["pass", "fail", "review_needed"], weights: [0.7, 0.1, 0.2] } } },
29228
+ { columnName: "notes", strategy: { kind: "text" } },
29229
+ { columnName: "performed_at", strategy: { kind: "timestamp", options: { mode: "past" } } },
29230
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
29231
+ ]
29038
29232
  }]
29039
29233
  ])
29040
29234
  };
@@ -29043,8 +29237,8 @@ var fintechTemplate = {
29043
29237
  var healthcareTemplate = {
29044
29238
  name: "healthcare",
29045
29239
  version: "2.0",
29046
- description: "Healthcare system with patients, providers, encounters, diagnoses, billing, medications, and vitals",
29047
- targetTables: ["patients", "providers", "encounters", "diagnoses", "billing", "medications", "vitals"],
29240
+ description: "Healthcare network with 13 tables: patients, providers, encounters, diagnoses, procedures, prescriptions, lab_orders, lab_results, vitals, billing, medications, insurance_claims, claim_payments",
29241
+ targetTables: ["patients", "providers", "encounters", "diagnoses", "billing", "medications", "vitals", "procedures", "prescriptions", "lab_orders", "lab_results", "insurance_claims", "claim_payments"],
29048
29242
  tableConfigs: /* @__PURE__ */ new Map([
29049
29243
  ["patients", {
29050
29244
  tableName: "patients",
@@ -29204,12 +29398,12 @@ var healthcareTemplate = {
29204
29398
  {
29205
29399
  columnName: "checked_in_at",
29206
29400
  strategy: { kind: "timestamp", options: { mode: "past" } },
29207
- description: "Nullable \u2014 null for scheduled/canceled encounters"
29401
+ description: "Nullable \xC3\xA2\xE2\u201A\xAC\xE2\u20AC\x9D null for scheduled/canceled encounters"
29208
29402
  },
29209
29403
  {
29210
29404
  columnName: "discharged_at",
29211
29405
  strategy: { kind: "timestamp", options: { mode: "past" } },
29212
- description: "Nullable \u2014 null for non-completed encounters"
29406
+ description: "Nullable \xC3\xA2\xE2\u201A\xAC\xE2\u20AC\x9D null for non-completed encounters"
29213
29407
  }
29214
29408
  ]
29215
29409
  }],
@@ -29308,7 +29502,7 @@ var healthcareTemplate = {
29308
29502
  {
29309
29503
  columnName: "paid_at",
29310
29504
  strategy: { kind: "timestamp", options: { mode: "past" } },
29311
- description: "Nullable \u2014 null for unpaid bills"
29505
+ description: "Nullable \xC3\xA2\xE2\u201A\xAC\xE2\u20AC\x9D null for unpaid bills"
29312
29506
  }
29313
29507
  ]
29314
29508
  }],
@@ -29363,7 +29557,7 @@ var healthcareTemplate = {
29363
29557
  {
29364
29558
  columnName: "end_date",
29365
29559
  strategy: { kind: "timestamp", options: { mode: "past" } },
29366
- description: "Nullable \u2014 null for ongoing medications"
29560
+ description: "Nullable \xC3\xA2\xE2\u201A\xAC\xE2\u20AC\x9D null for ongoing medications"
29367
29561
  },
29368
29562
  {
29369
29563
  columnName: "status",
@@ -29411,6 +29605,85 @@ var healthcareTemplate = {
29411
29605
  strategy: { kind: "timestamp", options: { mode: "past" } }
29412
29606
  }
29413
29607
  ]
29608
+ }],
29609
+ ["procedures", {
29610
+ tableName: "procedures",
29611
+ matchPattern: ["procedures", "*procedure*"],
29612
+ rowCountMultiplier: 5,
29613
+ columnOverrides: [
29614
+ { columnName: "cpt_code", strategy: { kind: "text", options: { mode: "short" } } },
29615
+ { columnName: "procedure_name", strategy: { kind: "text" } },
29616
+ { columnName: "status", strategy: { kind: "enum", options: { values: ["completed", "scheduled", "cancelled"], weights: [0.7, 0.2, 0.1] } } },
29617
+ { columnName: "performed_at", strategy: { kind: "timestamp", options: { mode: "past" } } },
29618
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
29619
+ ]
29620
+ }],
29621
+ ["prescriptions", {
29622
+ tableName: "prescriptions",
29623
+ matchPattern: ["prescriptions", "*prescription*"],
29624
+ rowCountMultiplier: 8,
29625
+ columnOverrides: [
29626
+ { columnName: "dosage", strategy: { kind: "text", options: { mode: "short" } } },
29627
+ { columnName: "frequency", strategy: { kind: "enum", options: { values: ["once_daily", "twice_daily", "three_times_daily", "as_needed", "weekly"], weights: [0.35, 0.25, 0.15, 0.15, 0.1] } } },
29628
+ { columnName: "duration_days", strategy: { kind: "integer", options: { min: 5, max: 365 } } },
29629
+ { columnName: "refills_remaining", strategy: { kind: "integer", options: { min: 0, max: 12 } } },
29630
+ { columnName: "status", strategy: { kind: "enum", options: { values: ["active", "completed", "cancelled", "expired"], weights: [0.45, 0.3, 0.1, 0.15] } } },
29631
+ { columnName: "prescribed_at", strategy: { kind: "timestamp", options: { mode: "past" } } },
29632
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
29633
+ ]
29634
+ }],
29635
+ ["lab_orders", {
29636
+ tableName: "lab_orders",
29637
+ matchPattern: ["lab_orders", "*lab_order*"],
29638
+ rowCountMultiplier: 4,
29639
+ columnOverrides: [
29640
+ { columnName: "test_name", strategy: { kind: "text" } },
29641
+ { columnName: "priority", strategy: { kind: "enum", options: { values: ["routine", "urgent", "stat"], weights: [0.7, 0.2, 0.1] } } },
29642
+ { columnName: "status", strategy: { kind: "enum", options: { values: ["ordered", "collected", "resulted", "cancelled"], weights: [0.15, 0.1, 0.65, 0.1] } } },
29643
+ { columnName: "ordered_at", strategy: { kind: "timestamp", options: { mode: "past" } } },
29644
+ { columnName: "resulted_at", strategy: { kind: "timestamp", options: { mode: "recent" } } },
29645
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
29646
+ ]
29647
+ }],
29648
+ ["lab_results", {
29649
+ tableName: "lab_results",
29650
+ matchPattern: ["lab_results", "*lab_result*"],
29651
+ rowCountMultiplier: 10,
29652
+ columnOverrides: [
29653
+ { columnName: "component", strategy: { kind: "text", options: { mode: "short" } } },
29654
+ { columnName: "value", strategy: { kind: "float", options: { min: 0.1, max: 500 } } },
29655
+ { columnName: "unit", strategy: { kind: "enum", options: { values: ["mg/dL", "mmol/L", "g/dL", "mEq/L", "U/L", "ng/mL", "cells/uL"], weights: [0.2, 0.15, 0.15, 0.15, 0.15, 0.1, 0.1] } } },
29656
+ { columnName: "reference_range", strategy: { kind: "text", options: { mode: "short" } } },
29657
+ { columnName: "abnormal_flag", strategy: { kind: "enum", options: { values: ["normal", "low", "high", "critical"], weights: [0.65, 0.15, 0.15, 0.05] } } },
29658
+ { columnName: "resulted_at", strategy: { kind: "timestamp", options: { mode: "recent" } } },
29659
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
29660
+ ]
29661
+ }],
29662
+ ["insurance_claims", {
29663
+ tableName: "insurance_claims",
29664
+ matchPattern: ["insurance_claims", "*claim*"],
29665
+ rowCountMultiplier: 6,
29666
+ columnOverrides: [
29667
+ { columnName: "total_charges_cents", strategy: { kind: "integer", options: { min: 5e3, max: 5e5 } } },
29668
+ { columnName: "allowed_amount_cents", strategy: { kind: "integer", options: { min: 3e3, max: 4e5 } } },
29669
+ { columnName: "patient_responsibility_cents", strategy: { kind: "integer", options: { min: 0, max: 1e5 } } },
29670
+ { columnName: "status", strategy: { kind: "enum", options: { values: ["submitted", "adjudicated", "paid", "denied", "appealed"], weights: [0.15, 0.1, 0.55, 0.12, 0.08] } } },
29671
+ { columnName: "submitted_at", strategy: { kind: "timestamp", options: { mode: "past" } } },
29672
+ { columnName: "paid_at", strategy: { kind: "timestamp", options: { mode: "recent" } } },
29673
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
29674
+ ]
29675
+ }],
29676
+ ["claim_payments", {
29677
+ tableName: "claim_payments",
29678
+ matchPattern: ["claim_payments", "*claim_payment*"],
29679
+ rowCountMultiplier: 4,
29680
+ columnOverrides: [
29681
+ { columnName: "amount_cents", strategy: { kind: "integer", options: { min: 1e3, max: 4e5 } } },
29682
+ { columnName: "payment_method", strategy: { kind: "enum", options: { values: ["eft", "check", "wire"], weights: [0.7, 0.2, 0.1] } } },
29683
+ { columnName: "status", strategy: { kind: "enum", options: { values: ["processed", "pending", "failed"], weights: [0.8, 0.12, 0.08] } } },
29684
+ { columnName: "paid_at", strategy: { kind: "timestamp", options: { mode: "recent" } } },
29685
+ { columnName: "created_at", strategy: { kind: "timestamp", options: { mode: "past" } } }
29686
+ ]
29414
29687
  }]
29415
29688
  ])
29416
29689
  };
@@ -30095,8 +30368,9 @@ function buildGenerationPlan(schema, config, timelineConfig) {
30095
30368
  for (const fk of schema.foreignKeys) {
30096
30369
  fkBySource.set(`${fk.sourceTable}.${fk.sourceColumn}`, fk);
30097
30370
  }
30371
+ const templateTargetSet = template ? new Set(template.targetTables) : null;
30098
30372
  const tables = schema.tables.map((table) => {
30099
- const tableForeignKeys = schema.foreignKeys.filter((fk) => fk.sourceTable === table.name);
30373
+ const tableForeignKeys = schema.foreignKeys.filter((fk) => fk.sourceTable === table.name && (!templateTargetSet || templateTargetSet.has(fk.targetTable)));
30100
30374
  const dependencies = tableForeignKeys.map((fk) => fk.targetTable);
30101
30375
  const uniqueDeps = [...new Set(dependencies)];
30102
30376
  const tableConfig = template && registry && templateLookupName ? registry.matchTable(templateLookupName, table.name) : null;
@@ -30162,7 +30436,7 @@ function buildGenerationPlan(schema, config, timelineConfig) {
30162
30436
  isUnique: column.isUnique
30163
30437
  };
30164
30438
  const fk = fkBySource.get(`${table.name}.${column.name}`);
30165
- if (fk) {
30439
+ if (fk && (!templateTargetSet || templateTargetSet.has(fk.targetTable))) {
30166
30440
  columnPlan.strategy = { kind: "foreign_key" };
30167
30441
  const ref = {
30168
30442
  referencedTable: fk.targetTable,
@@ -30192,7 +30466,7 @@ function buildGenerationPlan(schema, config, timelineConfig) {
30192
30466
  });
30193
30467
  }
30194
30468
  }
30195
- const rowCount = tableConfig?.rowCountMultiplier ? Math.round(defaultRowCount * tableConfig.rowCountMultiplier) : defaultRowCount;
30469
+ const rowCount = tableConfig?.rowCountMultiplier ? Math.max(1, Math.round(defaultRowCount * tableConfig.rowCountMultiplier)) : defaultRowCount;
30196
30470
  const enabled = template ? tableConfig !== null : true;
30197
30471
  const tablePlan = {
30198
30472
  tableName: table.name,
@@ -30207,26 +30481,17 @@ function buildGenerationPlan(schema, config, timelineConfig) {
30207
30481
  return tablePlan;
30208
30482
  });
30209
30483
  if (template) {
30210
- const enabledSet = new Set(tables.filter((t) => t.enabled).map((t) => t.tableName));
30211
- let changed = true;
30212
- while (changed) {
30213
- changed = false;
30214
- for (const tablePlan of tables) {
30215
- if (tablePlan.enabled) {
30216
- for (const dep of tablePlan.dependencies) {
30217
- if (!enabledSet.has(dep)) {
30218
- enabledSet.add(dep);
30219
- const depPlan = tables.find((t) => t.tableName === dep);
30220
- if (depPlan && !depPlan.enabled) {
30221
- depPlan.enabled = true;
30222
- changed = true;
30223
- }
30224
- }
30225
- }
30226
- }
30484
+ const targetSet = new Set(template.targetTables);
30485
+ for (const tablePlan of tables) {
30486
+ if (tablePlan.enabled && !targetSet.has(tablePlan.tableName)) {
30487
+ tablePlan.enabled = false;
30227
30488
  }
30228
30489
  }
30229
30490
  }
30491
+ const enabledNames = new Set(tables.filter((t) => t.enabled).map((t) => t.tableName));
30492
+ const filteredTableOrder = tableOrder.filter((name) => enabledNames.has(name));
30493
+ tableOrder.length = 0;
30494
+ tableOrder.push(...filteredTableOrder);
30230
30495
  if (timelineConfig?.enabled) {
30231
30496
  const temporalConstraints = resolveTemporalConstraints(schema, schema.foreignKeys, config.template);
30232
30497
  for (const tablePlan of tables) {
@@ -31485,9 +31750,22 @@ async function exportDataset(config, options) {
31485
31750
  case "csv":
31486
31751
  files = await exportToCsv(dataset, outputDir);
31487
31752
  break;
31488
- case "sql":
31489
- files = await exportToSql(dataset, outputDir, plan.tableOrder);
31753
+ case "sql": {
31754
+ const templateTableSet = new Set(plan.tableOrder);
31755
+ const scopedSchema = {
31756
+ ...schema,
31757
+ tables: schema.tables.filter((t) => templateTableSet.has(t.name)),
31758
+ foreignKeys: schema.foreignKeys.filter((fk) => templateTableSet.has(fk.sourceTable) && templateTableSet.has(fk.targetTable))
31759
+ };
31760
+ scopedSchema.tableCount = scopedSchema.tables.length;
31761
+ scopedSchema.foreignKeyCount = scopedSchema.foreignKeys.length;
31762
+ files = await exportToSql(dataset, outputDir, plan.tableOrder, {
31763
+ ddl: generateCreateTableDDL(scopedSchema),
31764
+ batchSize: options?.batchSize ?? 50,
31765
+ templateName: plan.config.templateName
31766
+ });
31490
31767
  break;
31768
+ }
31491
31769
  default:
31492
31770
  throw new Error(`Unknown export format: "${format}". Supported: json, csv, sql`);
31493
31771
  }
@@ -31672,15 +31950,92 @@ async function captureDatabase(config, options) {
31672
31950
  foreignKeyCount: filteredFKs.length
31673
31951
  };
31674
31952
  const ddl = generateCreateTableDDL(filteredSchema);
31953
+ let aroundFilter;
31954
+ if (options.around) {
31955
+ aroundFilter = /* @__PURE__ */ new Map();
31956
+ const { column: aroundCol, value: aroundVal } = options.around;
31957
+ for (const table of filteredTables) {
31958
+ const hasCol = table.columns.some((c) => c.name === aroundCol);
31959
+ if (hasCol) {
31960
+ aroundFilter.set(table.name, { column: aroundCol, values: /* @__PURE__ */ new Set([aroundVal]) });
31961
+ }
31962
+ }
31963
+ for (const fk of filteredFKs) {
31964
+ const sourceFilter = aroundFilter.get(fk.targetTable);
31965
+ if (sourceFilter && sourceFilter.column === fk.targetColumn) {
31966
+ aroundFilter.set(fk.sourceTable, { column: fk.sourceColumn, values: sourceFilter.values });
31967
+ }
31968
+ }
31969
+ }
31970
+ const detectionsByTable = /* @__PURE__ */ new Map();
31971
+ let piiSummary;
31972
+ if (options.safe) {
31973
+ const mode = "gdpr";
31974
+ for (const table of filteredTables) {
31975
+ const tableForeignKeys = filteredFKs.filter((fk) => fk.sourceTable === table.name);
31976
+ const detections = detectTablePII(table.columns, tableForeignKeys, table.name, mode);
31977
+ detectionsByTable.set(table.name, detections);
31978
+ }
31979
+ let columnsDetected = 0;
31980
+ let tablesAffected = 0;
31981
+ const categoriesFound = /* @__PURE__ */ new Set();
31982
+ for (const [, detections] of detectionsByTable) {
31983
+ const piiCols = detections.filter((d) => d.shouldMask);
31984
+ if (piiCols.length > 0) {
31985
+ tablesAffected++;
31986
+ columnsDetected += piiCols.length;
31987
+ for (const d of piiCols) {
31988
+ categoriesFound.add(d.category);
31989
+ }
31990
+ }
31991
+ }
31992
+ piiSummary = {
31993
+ columnsDetected,
31994
+ tablesAffected,
31995
+ categoriesFound: Array.from(categoriesFound)
31996
+ };
31997
+ }
31675
31998
  const packDataset = { tables: {} };
31676
31999
  const tableDetails = [];
31677
32000
  let totalRows = 0;
32001
+ const safeMode = options.safeMode ?? "mask";
32002
+ const random = options.safe ? createSeededRandom(42) : void 0;
32003
+ const tokenPrefix = options.safe && safeMode === "tokenize" ? generateTokenPrefix() : void 0;
31678
32004
  for (const tableName of tablesToCapture) {
31679
32005
  const tableSchema = filteredTables.find((t) => t.name === tableName);
31680
32006
  if (!tableSchema)
31681
32007
  continue;
31682
32008
  const columns = tableSchema.columns.map((c) => c.name);
31683
- const rows = await readTableRows(pool, tableName, columns);
32009
+ let rows = await readTableRows(pool, tableName, columns);
32010
+ const filter = aroundFilter?.get(tableName);
32011
+ if (filter) {
32012
+ rows = rows.filter((row) => {
32013
+ const val = row[filter.column];
32014
+ return val !== null && val !== void 0 && filter.values.has(String(val));
32015
+ });
32016
+ }
32017
+ if (options.maxRows !== void 0 && rows.length > options.maxRows) {
32018
+ rows = rows.slice(0, options.maxRows);
32019
+ }
32020
+ if (options.safe) {
32021
+ const detections = detectionsByTable.get(tableName);
32022
+ if (detections) {
32023
+ const hasPII = detections.some((d) => d.shouldMask);
32024
+ if (hasPII) {
32025
+ if (safeMode === "tokenize" && tokenPrefix) {
32026
+ const { tokenizedRows } = tokenizeTableRows(rows, detections, tableName, tokenPrefix);
32027
+ rows = tokenizedRows;
32028
+ } else if (safeMode === "redact") {
32029
+ const redactDetections = detections.map((d) => d.shouldMask ? { ...d, maskStrategy: "redact" } : d);
32030
+ const { maskedRows } = maskTableRows(rows, redactDetections, random, tableName);
32031
+ rows = maskedRows;
32032
+ } else {
32033
+ const { maskedRows } = maskTableRows(rows, detections, random, tableName);
32034
+ rows = maskedRows;
32035
+ }
32036
+ }
32037
+ }
32038
+ }
31684
32039
  const rowCount = rows.length;
31685
32040
  packDataset.tables[tableName] = {
31686
32041
  columns,
@@ -31725,6 +32080,7 @@ async function captureDatabase(config, options) {
31725
32080
  }
31726
32081
  };
31727
32082
  const masked = maskConnection(config.database.connectionString);
32083
+ const safeModeValue = options.safe ? safeMode === "tokenize" ? "tokenized" : safeMode === "redact" ? "redacted" : "masked" : "raw";
31728
32084
  const pack = {
31729
32085
  format: "realitydb-pack",
31730
32086
  version: "1.0",
@@ -31736,7 +32092,9 @@ async function captureDatabase(config, options) {
31736
32092
  totalRows,
31737
32093
  tableCount: tablesToCapture.length,
31738
32094
  ddl,
31739
- capturedFrom: masked
32095
+ capturedFrom: masked,
32096
+ safeMode: safeModeValue,
32097
+ piiSummary
31740
32098
  },
31741
32099
  schema: packSchema,
31742
32100
  plan,
@@ -31751,7 +32109,8 @@ async function captureDatabase(config, options) {
31751
32109
  totalRows,
31752
32110
  tableCount: tablesToCapture.length,
31753
32111
  durationMs,
31754
- tableDetails
32112
+ tableDetails,
32113
+ piiSummary
31755
32114
  };
31756
32115
  } finally {
31757
32116
  await closeConnection(pool);
@@ -33285,6 +33644,7 @@ async function exportCommand(options) {
33285
33644
  console.log("");
33286
33645
  console.log("Generating dataset...");
33287
33646
  }
33647
+ const batchSize = options.batchSize ? parseInt(options.batchSize, 10) : void 0;
33288
33648
  const result = await exportDataset(config, {
33289
33649
  format,
33290
33650
  outputDir,
@@ -33294,7 +33654,8 @@ async function exportCommand(options) {
33294
33654
  timeline,
33295
33655
  scenarios: scenario,
33296
33656
  scenarioIntensity,
33297
- scenarioSchedule: options.scenarioSchedule
33657
+ scenarioSchedule: options.scenarioSchedule,
33658
+ batchSize
33298
33659
  });
33299
33660
  const durationMs = Math.round(performance.now() - start);
33300
33661
  if (options.ci) {
@@ -33737,6 +34098,18 @@ async function captureCommand(options) {
33737
34098
  const config = await loadConfig(options.configPath);
33738
34099
  const masked = maskConnectionString(config.database.connectionString);
33739
34100
  const tables = options.tables ? options.tables.split(",").map((t) => t.trim()).filter((t) => t.length > 0) : void 0;
34101
+ const safeMode = options.safe ? ["mask", "tokenize", "redact"].includes(options.safeMode ?? "") ? options.safeMode : "mask" : void 0;
34102
+ const maxRows = options.maxRows ? parseInt(options.maxRows, 10) : void 0;
34103
+ let around;
34104
+ if (options.around) {
34105
+ const eqIdx = options.around.indexOf("=");
34106
+ if (eqIdx > 0) {
34107
+ around = {
34108
+ column: options.around.substring(0, eqIdx),
34109
+ value: options.around.substring(eqIdx + 1)
34110
+ };
34111
+ }
34112
+ }
33740
34113
  if (!options.ci) {
33741
34114
  console.log("");
33742
34115
  console.log("RealityDB Capture");
@@ -33746,6 +34119,15 @@ async function captureCommand(options) {
33746
34119
  if (tables) {
33747
34120
  console.log(`Tables: ${tables.join(", ")}`);
33748
34121
  }
34122
+ if (options.safe) {
34123
+ console.log(`Safe mode: ${safeMode} (PII will be sanitized)`);
34124
+ }
34125
+ if (maxRows !== void 0) {
34126
+ console.log(`Max rows per table: ${maxRows}`);
34127
+ }
34128
+ if (around) {
34129
+ console.log("");
34130
+ }
33749
34131
  console.log("");
33750
34132
  console.log("Capturing...");
33751
34133
  }
@@ -33753,7 +34135,11 @@ async function captureCommand(options) {
33753
34135
  name: options.name,
33754
34136
  description: options.description,
33755
34137
  tables,
33756
- outputDir: options.output
34138
+ outputDir: options.output,
34139
+ safe: options.safe,
34140
+ safeMode,
34141
+ maxRows,
34142
+ around
33757
34143
  });
33758
34144
  const durationMs = Math.round(performance.now() - start);
33759
34145
  if (options.ci) {
@@ -33769,6 +34155,8 @@ async function captureCommand(options) {
33769
34155
  filePath: result.filePath,
33770
34156
  tableCount: result.tableCount,
33771
34157
  totalRows: result.totalRows,
34158
+ safeMode: result.pack.metadata.safeMode,
34159
+ piiSummary: result.piiSummary,
33772
34160
  tables: result.tableDetails.map((t) => ({
33773
34161
  name: t.name,
33774
34162
  rowCount: t.rowCount
@@ -33778,6 +34166,21 @@ async function captureCommand(options) {
33778
34166
  }));
33779
34167
  return;
33780
34168
  }
34169
+ if (options.safe && result.piiSummary) {
34170
+ const { columnsDetected, tablesAffected, categoriesFound } = result.piiSummary;
34171
+ if (columnsDetected > 0) {
34172
+ console.log(`PII detected: ${columnsDetected} columns across ${tablesAffected} tables. Sanitizing...`);
34173
+ console.log(` Categories: ${categoriesFound.join(", ")}`);
34174
+ console.log("");
34175
+ } else {
34176
+ console.log("No PII detected \u2014 data captured as-is.");
34177
+ console.log("");
34178
+ }
34179
+ }
34180
+ if (around) {
34181
+ console.log(`Capturing rows related to ${around.column}=${around.value} across ${result.tableCount} tables`);
34182
+ console.log("");
34183
+ }
33781
34184
  for (const table of result.tableDetails) {
33782
34185
  console.log(` ${table.name}: ${table.rowCount} rows`);
33783
34186
  }
@@ -33785,6 +34188,9 @@ async function captureCommand(options) {
33785
34188
  const sizeKb = Math.round(fileStat.size / 1024);
33786
34189
  console.log("");
33787
34190
  console.log(`Captured: ${result.filePath} (${sizeKb} KB)`);
34191
+ if (options.safe) {
34192
+ console.log(`Privacy: PII sanitized (${safeMode} mode). Safe to share.`);
34193
+ }
33788
34194
  console.log("Schema DDL included. Share this file to reproduce the environment.");
33789
34195
  console.log("");
33790
34196
  } catch (err) {
@@ -33915,6 +34321,19 @@ var VERSION9 = "0.10.0";
33915
34321
  function isUrl(input) {
33916
34322
  return input.startsWith("http://") || input.startsWith("https://");
33917
34323
  }
34324
+ function displaySafeModeStatus(safeMode) {
34325
+ if (!safeMode) {
34326
+ console.warn("Note: This pack was captured before privacy-safe mode was available");
34327
+ } else if (safeMode === "raw") {
34328
+ console.log("WARNING: This pack contains raw (unsanitized) data");
34329
+ } else if (safeMode === "masked") {
34330
+ console.log("This pack was captured with PII masking");
34331
+ } else if (safeMode === "tokenized") {
34332
+ console.log("This pack was captured with PII tokenization");
34333
+ } else if (safeMode === "redacted") {
34334
+ console.log("This pack was captured with PII redaction");
34335
+ }
34336
+ }
33918
34337
  async function loadCommand(filePath, options) {
33919
34338
  const start = performance.now();
33920
34339
  try {
@@ -33950,6 +34369,57 @@ async function loadCommand(filePath, options) {
33950
34369
  }
33951
34370
  }
33952
34371
  const pack = await loadRealityPack(localPath);
34372
+ if (options.preview) {
34373
+ const meta2 = pack.metadata;
34374
+ const safeMode2 = meta2.safeMode;
34375
+ const piiSummary = meta2.piiSummary;
34376
+ if (options.ci) {
34377
+ const tableNames = Object.keys(pack.dataset.tables);
34378
+ const tableInfo = tableNames.map((name) => ({
34379
+ name,
34380
+ rowCount: pack.dataset.tables[name].rowCount
34381
+ }));
34382
+ console.log(formatCIOutput({
34383
+ success: true,
34384
+ command: "load",
34385
+ version: VERSION9,
34386
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
34387
+ durationMs: Math.round(performance.now() - start),
34388
+ data: {
34389
+ packName: pack.metadata.name,
34390
+ safeMode: safeMode2 ?? null,
34391
+ piiSummary: piiSummary ?? null,
34392
+ tableCount: pack.metadata.tableCount,
34393
+ totalRows: pack.metadata.totalRows,
34394
+ tables: tableInfo
34395
+ }
34396
+ }));
34397
+ return;
34398
+ }
34399
+ console.log("");
34400
+ console.log("Reality Pack Preview");
34401
+ console.log("\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");
34402
+ console.log(`Name: ${pack.metadata.name}`);
34403
+ if (pack.metadata.description) {
34404
+ console.log(`Description: ${pack.metadata.description}`);
34405
+ }
34406
+ console.log(`Tables: ${pack.metadata.tableCount}`);
34407
+ console.log(`Total rows: ${pack.metadata.totalRows}`);
34408
+ console.log("");
34409
+ displaySafeModeStatus(safeMode2);
34410
+ if (piiSummary) {
34411
+ console.log(` PII columns detected: ${piiSummary.columnsDetected}`);
34412
+ console.log(` Tables affected: ${piiSummary.tablesAffected}`);
34413
+ console.log(` Categories: ${piiSummary.categoriesFound.join(", ")}`);
34414
+ }
34415
+ console.log("");
34416
+ console.log("Tables:");
34417
+ for (const [name, tableData] of Object.entries(pack.dataset.tables)) {
34418
+ console.log(` ${name}: ${tableData.rowCount} rows`);
34419
+ }
34420
+ console.log("");
34421
+ return;
34422
+ }
33953
34423
  if (options.showDdl) {
33954
34424
  const ddl = pack.metadata.ddl;
33955
34425
  if (options.ci) {
@@ -33982,6 +34452,8 @@ async function loadCommand(filePath, options) {
33982
34452
  }
33983
34453
  const config = await loadConfig(options.configPath);
33984
34454
  const masked = maskConnectionString(config.database.connectionString);
34455
+ const meta = pack.metadata;
34456
+ const safeMode = meta.safeMode;
33985
34457
  if (!options.ci) {
33986
34458
  console.log("");
33987
34459
  console.log("RealityDB Load");
@@ -34001,6 +34473,8 @@ async function loadCommand(filePath, options) {
34001
34473
  console.log("Schema DDL: included");
34002
34474
  }
34003
34475
  console.log("");
34476
+ displaySafeModeStatus(safeMode);
34477
+ console.log("");
34004
34478
  }
34005
34479
  if (!options.ci && !options.confirm) {
34006
34480
  console.error("[realitydb] Load requires --confirm flag.");
@@ -34008,6 +34482,9 @@ async function loadCommand(filePath, options) {
34008
34482
  console.error("");
34009
34483
  console.error("To view the schema DDL first:");
34010
34484
  console.error(` realitydb load ${filePath} --show-ddl`);
34485
+ console.error("");
34486
+ console.error("To preview pack contents:");
34487
+ console.error(` realitydb load ${filePath} --preview`);
34011
34488
  process.exit(1);
34012
34489
  }
34013
34490
  if (!options.ci) {
@@ -34026,6 +34503,7 @@ async function loadCommand(filePath, options) {
34026
34503
  database: masked,
34027
34504
  packName: pack.metadata.name,
34028
34505
  totalRows: result.totalRows,
34506
+ safeMode: safeMode ?? null,
34029
34507
  source: isUrl(filePath) ? filePath : void 0,
34030
34508
  tables: result.insertResult.tables.map((t) => ({
34031
34509
  name: t.tableName,
@@ -34043,7 +34521,8 @@ async function loadCommand(filePath, options) {
34043
34521
  }
34044
34522
  const totalTime = (result.durationMs / 1e3).toFixed(1);
34045
34523
  console.log("");
34046
- console.log(`Load complete. ${result.totalRows} rows in ${totalTime}s`);
34524
+ console.log(`Bug reproduction environment ready. ${result.insertResult.tables.length} tables, ${result.totalRows} rows loaded.`);
34525
+ console.log(`Load complete in ${totalTime}s`);
34047
34526
  console.log("");
34048
34527
  } catch (err) {
34049
34528
  const message = err instanceof Error ? err.message : String(err);
@@ -35664,7 +36143,7 @@ async function simulateWebhooksCommand(options) {
35664
36143
  }
35665
36144
 
35666
36145
  // src/cli.ts
35667
- var VERSION17 = "1.9.0";
36146
+ var VERSION17 = "2.0.1";
35668
36147
  function run(argv) {
35669
36148
  const program2 = new Command();
35670
36149
  program2.name("realitydb").description("RealityDB -- Developer Reality Platform").version(VERSION17).option("--config <path>", "Path to config file").option("--ci", "CI mode: JSON output, no prompts, proper exit codes", false).option("--verbose", "Enable verbose output", false);
@@ -35687,7 +36166,7 @@ function run(argv) {
35687
36166
  const opts = program2.opts();
35688
36167
  await resetCommand({ ...cmdOpts, ci: opts.ci, configPath: opts.config });
35689
36168
  });
35690
- program2.command("export").description("Export generated data").option("--format <format>", "Output format (json|csv|sql)", "json").option("--output <dir>", "Output directory", "./.realitydb").option("--records <count>", "Number of records per table").option("--seed <number>", "Random seed for reproducibility").option("--template <name>", "Template to use").option("--timeline <duration>", 'Timeline duration (e.g., "12-months", "1-year")').option("--scenario <names>", "Scenarios to apply (comma-separated)").option("--scenario-intensity <level>", "Scenario intensity (low|medium|high)", "medium").option("--scenario-schedule <schedule>", 'Timeline-scheduled scenarios (e.g., "fraud-spike:month-6,churn-spike:month-9")').action(async (cmdOpts) => {
36169
+ program2.command("export").description("Export generated data").option("--format <format>", "Output format (json|csv|sql)", "json").option("--output <dir>", "Output directory", "./.realitydb").option("--records <count>", "Number of records per table").option("--seed <number>", "Random seed for reproducibility").option("--template <name>", "Template to use").option("--timeline <duration>", 'Timeline duration (e.g., "12-months", "1-year")').option("--scenario <names>", "Scenarios to apply (comma-separated)").option("--scenario-intensity <level>", "Scenario intensity (low|medium|high)", "medium").option("--scenario-schedule <schedule>", 'Timeline-scheduled scenarios (e.g., "fraud-spike:month-6,churn-spike:month-9")').option("--batch-size <number>", "Rows per INSERT statement for SQL format (default: 50)").action(async (cmdOpts) => {
35691
36170
  const opts = program2.opts();
35692
36171
  await exportCommand({ ...cmdOpts, ci: opts.ci, configPath: opts.config });
35693
36172
  });
@@ -35766,7 +36245,7 @@ function run(argv) {
35766
36245
  const opts = program2.opts();
35767
36246
  await simulateWebhooksCommand({ ...cmdOpts, ci: opts.ci, configPath: opts.config });
35768
36247
  });
35769
- program2.command("capture").description("Capture live database state into a Reality Pack").requiredOption("--name <name>", "Name for the captured pack").option("--description <desc>", "Pack description").option("--tables <tables>", "Comma-separated list of tables to capture").option("--output <dir>", "Output directory", ".").action(async (cmdOpts) => {
36248
+ program2.command("capture").description("Capture live database state into a Reality Pack").requiredOption("--name <name>", "Name for the captured pack").option("--description <desc>", "Pack description").option("--tables <tables>", "Comma-separated list of tables to capture").option("--output <dir>", "Output directory", ".").option("--safe", "Enable privacy-safe capture (sanitize PII before writing)").option("--safe-mode <mode>", "PII sanitization mode: mask (default), tokenize, redact", "mask").option("--max-rows <count>", "Maximum rows to capture per table").option("--around <column=value>", "Capture rows related to a specific entity (follows FK chains)").action(async (cmdOpts) => {
35770
36249
  const opts = program2.opts();
35771
36250
  await captureCommand({ ...cmdOpts, ci: opts.ci, configPath: opts.config });
35772
36251
  });
@@ -35774,7 +36253,7 @@ function run(argv) {
35774
36253
  const opts = program2.opts();
35775
36254
  await shareCommand(filePath, { ...cmdOpts, ci: opts.ci, configPath: opts.config });
35776
36255
  });
35777
- program2.command("load <file>").description("Load a Reality Pack into the database (file path or URL)").option("--confirm", "Confirm import operation").option("--show-ddl", "Show schema DDL without importing").action(async (filePath, cmdOpts) => {
36256
+ program2.command("load <file>").description("Load a Reality Pack into the database (file path or URL)").option("--confirm", "Confirm import operation").option("--show-ddl", "Show schema DDL without importing").option("--preview", "Preview pack contents without importing").action(async (filePath, cmdOpts) => {
35778
36257
  const opts = program2.opts();
35779
36258
  await loadCommand(filePath, { ...cmdOpts, ci: opts.ci, configPath: opts.config });
35780
36259
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "realitydb",
3
- "version": "1.9.0",
3
+ "version": "2.0.1",
4
4
  "description": "Developer Reality Platform - realistic database environments from your schema",
5
5
  "license": "MIT",
6
6
  "keywords": [