realitydb 1.9.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +207 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -31672,15 +31672,92 @@ async function captureDatabase(config, options) {
31672
31672
  foreignKeyCount: filteredFKs.length
31673
31673
  };
31674
31674
  const ddl = generateCreateTableDDL(filteredSchema);
31675
+ let aroundFilter;
31676
+ if (options.around) {
31677
+ aroundFilter = /* @__PURE__ */ new Map();
31678
+ const { column: aroundCol, value: aroundVal } = options.around;
31679
+ for (const table of filteredTables) {
31680
+ const hasCol = table.columns.some((c) => c.name === aroundCol);
31681
+ if (hasCol) {
31682
+ aroundFilter.set(table.name, { column: aroundCol, values: /* @__PURE__ */ new Set([aroundVal]) });
31683
+ }
31684
+ }
31685
+ for (const fk of filteredFKs) {
31686
+ const sourceFilter = aroundFilter.get(fk.targetTable);
31687
+ if (sourceFilter && sourceFilter.column === fk.targetColumn) {
31688
+ aroundFilter.set(fk.sourceTable, { column: fk.sourceColumn, values: sourceFilter.values });
31689
+ }
31690
+ }
31691
+ }
31692
+ const detectionsByTable = /* @__PURE__ */ new Map();
31693
+ let piiSummary;
31694
+ if (options.safe) {
31695
+ const mode = "gdpr";
31696
+ for (const table of filteredTables) {
31697
+ const tableForeignKeys = filteredFKs.filter((fk) => fk.sourceTable === table.name);
31698
+ const detections = detectTablePII(table.columns, tableForeignKeys, table.name, mode);
31699
+ detectionsByTable.set(table.name, detections);
31700
+ }
31701
+ let columnsDetected = 0;
31702
+ let tablesAffected = 0;
31703
+ const categoriesFound = /* @__PURE__ */ new Set();
31704
+ for (const [, detections] of detectionsByTable) {
31705
+ const piiCols = detections.filter((d) => d.shouldMask);
31706
+ if (piiCols.length > 0) {
31707
+ tablesAffected++;
31708
+ columnsDetected += piiCols.length;
31709
+ for (const d of piiCols) {
31710
+ categoriesFound.add(d.category);
31711
+ }
31712
+ }
31713
+ }
31714
+ piiSummary = {
31715
+ columnsDetected,
31716
+ tablesAffected,
31717
+ categoriesFound: Array.from(categoriesFound)
31718
+ };
31719
+ }
31675
31720
  const packDataset = { tables: {} };
31676
31721
  const tableDetails = [];
31677
31722
  let totalRows = 0;
31723
+ const safeMode = options.safeMode ?? "mask";
31724
+ const random = options.safe ? createSeededRandom(42) : void 0;
31725
+ const tokenPrefix = options.safe && safeMode === "tokenize" ? generateTokenPrefix() : void 0;
31678
31726
  for (const tableName of tablesToCapture) {
31679
31727
  const tableSchema = filteredTables.find((t) => t.name === tableName);
31680
31728
  if (!tableSchema)
31681
31729
  continue;
31682
31730
  const columns = tableSchema.columns.map((c) => c.name);
31683
- const rows = await readTableRows(pool, tableName, columns);
31731
+ let rows = await readTableRows(pool, tableName, columns);
31732
+ const filter = aroundFilter?.get(tableName);
31733
+ if (filter) {
31734
+ rows = rows.filter((row) => {
31735
+ const val = row[filter.column];
31736
+ return val !== null && val !== void 0 && filter.values.has(String(val));
31737
+ });
31738
+ }
31739
+ if (options.maxRows !== void 0 && rows.length > options.maxRows) {
31740
+ rows = rows.slice(0, options.maxRows);
31741
+ }
31742
+ if (options.safe) {
31743
+ const detections = detectionsByTable.get(tableName);
31744
+ if (detections) {
31745
+ const hasPII = detections.some((d) => d.shouldMask);
31746
+ if (hasPII) {
31747
+ if (safeMode === "tokenize" && tokenPrefix) {
31748
+ const { tokenizedRows } = tokenizeTableRows(rows, detections, tableName, tokenPrefix);
31749
+ rows = tokenizedRows;
31750
+ } else if (safeMode === "redact") {
31751
+ const redactDetections = detections.map((d) => d.shouldMask ? { ...d, maskStrategy: "redact" } : d);
31752
+ const { maskedRows } = maskTableRows(rows, redactDetections, random, tableName);
31753
+ rows = maskedRows;
31754
+ } else {
31755
+ const { maskedRows } = maskTableRows(rows, detections, random, tableName);
31756
+ rows = maskedRows;
31757
+ }
31758
+ }
31759
+ }
31760
+ }
31684
31761
  const rowCount = rows.length;
31685
31762
  packDataset.tables[tableName] = {
31686
31763
  columns,
@@ -31725,6 +31802,7 @@ async function captureDatabase(config, options) {
31725
31802
  }
31726
31803
  };
31727
31804
  const masked = maskConnection(config.database.connectionString);
31805
+ const safeModeValue = options.safe ? safeMode === "tokenize" ? "tokenized" : safeMode === "redact" ? "redacted" : "masked" : "raw";
31728
31806
  const pack = {
31729
31807
  format: "realitydb-pack",
31730
31808
  version: "1.0",
@@ -31736,7 +31814,9 @@ async function captureDatabase(config, options) {
31736
31814
  totalRows,
31737
31815
  tableCount: tablesToCapture.length,
31738
31816
  ddl,
31739
- capturedFrom: masked
31817
+ capturedFrom: masked,
31818
+ safeMode: safeModeValue,
31819
+ piiSummary
31740
31820
  },
31741
31821
  schema: packSchema,
31742
31822
  plan,
@@ -31751,7 +31831,8 @@ async function captureDatabase(config, options) {
31751
31831
  totalRows,
31752
31832
  tableCount: tablesToCapture.length,
31753
31833
  durationMs,
31754
- tableDetails
31834
+ tableDetails,
31835
+ piiSummary
31755
31836
  };
31756
31837
  } finally {
31757
31838
  await closeConnection(pool);
@@ -33737,6 +33818,18 @@ async function captureCommand(options) {
33737
33818
  const config = await loadConfig(options.configPath);
33738
33819
  const masked = maskConnectionString(config.database.connectionString);
33739
33820
  const tables = options.tables ? options.tables.split(",").map((t) => t.trim()).filter((t) => t.length > 0) : void 0;
33821
+ const safeMode = options.safe ? ["mask", "tokenize", "redact"].includes(options.safeMode ?? "") ? options.safeMode : "mask" : void 0;
33822
+ const maxRows = options.maxRows ? parseInt(options.maxRows, 10) : void 0;
33823
+ let around;
33824
+ if (options.around) {
33825
+ const eqIdx = options.around.indexOf("=");
33826
+ if (eqIdx > 0) {
33827
+ around = {
33828
+ column: options.around.substring(0, eqIdx),
33829
+ value: options.around.substring(eqIdx + 1)
33830
+ };
33831
+ }
33832
+ }
33740
33833
  if (!options.ci) {
33741
33834
  console.log("");
33742
33835
  console.log("RealityDB Capture");
@@ -33746,6 +33839,15 @@ async function captureCommand(options) {
33746
33839
  if (tables) {
33747
33840
  console.log(`Tables: ${tables.join(", ")}`);
33748
33841
  }
33842
+ if (options.safe) {
33843
+ console.log(`Safe mode: ${safeMode} (PII will be sanitized)`);
33844
+ }
33845
+ if (maxRows !== void 0) {
33846
+ console.log(`Max rows per table: ${maxRows}`);
33847
+ }
33848
+ if (around) {
33849
+ console.log("");
33850
+ }
33749
33851
  console.log("");
33750
33852
  console.log("Capturing...");
33751
33853
  }
@@ -33753,7 +33855,11 @@ async function captureCommand(options) {
33753
33855
  name: options.name,
33754
33856
  description: options.description,
33755
33857
  tables,
33756
- outputDir: options.output
33858
+ outputDir: options.output,
33859
+ safe: options.safe,
33860
+ safeMode,
33861
+ maxRows,
33862
+ around
33757
33863
  });
33758
33864
  const durationMs = Math.round(performance.now() - start);
33759
33865
  if (options.ci) {
@@ -33769,6 +33875,8 @@ async function captureCommand(options) {
33769
33875
  filePath: result.filePath,
33770
33876
  tableCount: result.tableCount,
33771
33877
  totalRows: result.totalRows,
33878
+ safeMode: result.pack.metadata.safeMode,
33879
+ piiSummary: result.piiSummary,
33772
33880
  tables: result.tableDetails.map((t) => ({
33773
33881
  name: t.name,
33774
33882
  rowCount: t.rowCount
@@ -33778,6 +33886,21 @@ async function captureCommand(options) {
33778
33886
  }));
33779
33887
  return;
33780
33888
  }
33889
+ if (options.safe && result.piiSummary) {
33890
+ const { columnsDetected, tablesAffected, categoriesFound } = result.piiSummary;
33891
+ if (columnsDetected > 0) {
33892
+ console.log(`PII detected: ${columnsDetected} columns across ${tablesAffected} tables. Sanitizing...`);
33893
+ console.log(` Categories: ${categoriesFound.join(", ")}`);
33894
+ console.log("");
33895
+ } else {
33896
+ console.log("No PII detected \u2014 data captured as-is.");
33897
+ console.log("");
33898
+ }
33899
+ }
33900
+ if (around) {
33901
+ console.log(`Capturing rows related to ${around.column}=${around.value} across ${result.tableCount} tables`);
33902
+ console.log("");
33903
+ }
33781
33904
  for (const table of result.tableDetails) {
33782
33905
  console.log(` ${table.name}: ${table.rowCount} rows`);
33783
33906
  }
@@ -33785,6 +33908,9 @@ async function captureCommand(options) {
33785
33908
  const sizeKb = Math.round(fileStat.size / 1024);
33786
33909
  console.log("");
33787
33910
  console.log(`Captured: ${result.filePath} (${sizeKb} KB)`);
33911
+ if (options.safe) {
33912
+ console.log(`Privacy: PII sanitized (${safeMode} mode). Safe to share.`);
33913
+ }
33788
33914
  console.log("Schema DDL included. Share this file to reproduce the environment.");
33789
33915
  console.log("");
33790
33916
  } catch (err) {
@@ -33915,6 +34041,19 @@ var VERSION9 = "0.10.0";
33915
34041
  function isUrl(input) {
33916
34042
  return input.startsWith("http://") || input.startsWith("https://");
33917
34043
  }
34044
+ function displaySafeModeStatus(safeMode) {
34045
+ if (!safeMode) {
34046
+ console.warn("Note: This pack was captured before privacy-safe mode was available");
34047
+ } else if (safeMode === "raw") {
34048
+ console.log("WARNING: This pack contains raw (unsanitized) data");
34049
+ } else if (safeMode === "masked") {
34050
+ console.log("This pack was captured with PII masking");
34051
+ } else if (safeMode === "tokenized") {
34052
+ console.log("This pack was captured with PII tokenization");
34053
+ } else if (safeMode === "redacted") {
34054
+ console.log("This pack was captured with PII redaction");
34055
+ }
34056
+ }
33918
34057
  async function loadCommand(filePath, options) {
33919
34058
  const start = performance.now();
33920
34059
  try {
@@ -33950,6 +34089,57 @@ async function loadCommand(filePath, options) {
33950
34089
  }
33951
34090
  }
33952
34091
  const pack = await loadRealityPack(localPath);
34092
+ if (options.preview) {
34093
+ const meta2 = pack.metadata;
34094
+ const safeMode2 = meta2.safeMode;
34095
+ const piiSummary = meta2.piiSummary;
34096
+ if (options.ci) {
34097
+ const tableNames = Object.keys(pack.dataset.tables);
34098
+ const tableInfo = tableNames.map((name) => ({
34099
+ name,
34100
+ rowCount: pack.dataset.tables[name].rowCount
34101
+ }));
34102
+ console.log(formatCIOutput({
34103
+ success: true,
34104
+ command: "load",
34105
+ version: VERSION9,
34106
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
34107
+ durationMs: Math.round(performance.now() - start),
34108
+ data: {
34109
+ packName: pack.metadata.name,
34110
+ safeMode: safeMode2 ?? null,
34111
+ piiSummary: piiSummary ?? null,
34112
+ tableCount: pack.metadata.tableCount,
34113
+ totalRows: pack.metadata.totalRows,
34114
+ tables: tableInfo
34115
+ }
34116
+ }));
34117
+ return;
34118
+ }
34119
+ console.log("");
34120
+ console.log("Reality Pack Preview");
34121
+ 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");
34122
+ console.log(`Name: ${pack.metadata.name}`);
34123
+ if (pack.metadata.description) {
34124
+ console.log(`Description: ${pack.metadata.description}`);
34125
+ }
34126
+ console.log(`Tables: ${pack.metadata.tableCount}`);
34127
+ console.log(`Total rows: ${pack.metadata.totalRows}`);
34128
+ console.log("");
34129
+ displaySafeModeStatus(safeMode2);
34130
+ if (piiSummary) {
34131
+ console.log(` PII columns detected: ${piiSummary.columnsDetected}`);
34132
+ console.log(` Tables affected: ${piiSummary.tablesAffected}`);
34133
+ console.log(` Categories: ${piiSummary.categoriesFound.join(", ")}`);
34134
+ }
34135
+ console.log("");
34136
+ console.log("Tables:");
34137
+ for (const [name, tableData] of Object.entries(pack.dataset.tables)) {
34138
+ console.log(` ${name}: ${tableData.rowCount} rows`);
34139
+ }
34140
+ console.log("");
34141
+ return;
34142
+ }
33953
34143
  if (options.showDdl) {
33954
34144
  const ddl = pack.metadata.ddl;
33955
34145
  if (options.ci) {
@@ -33982,6 +34172,8 @@ async function loadCommand(filePath, options) {
33982
34172
  }
33983
34173
  const config = await loadConfig(options.configPath);
33984
34174
  const masked = maskConnectionString(config.database.connectionString);
34175
+ const meta = pack.metadata;
34176
+ const safeMode = meta.safeMode;
33985
34177
  if (!options.ci) {
33986
34178
  console.log("");
33987
34179
  console.log("RealityDB Load");
@@ -34001,6 +34193,8 @@ async function loadCommand(filePath, options) {
34001
34193
  console.log("Schema DDL: included");
34002
34194
  }
34003
34195
  console.log("");
34196
+ displaySafeModeStatus(safeMode);
34197
+ console.log("");
34004
34198
  }
34005
34199
  if (!options.ci && !options.confirm) {
34006
34200
  console.error("[realitydb] Load requires --confirm flag.");
@@ -34008,6 +34202,9 @@ async function loadCommand(filePath, options) {
34008
34202
  console.error("");
34009
34203
  console.error("To view the schema DDL first:");
34010
34204
  console.error(` realitydb load ${filePath} --show-ddl`);
34205
+ console.error("");
34206
+ console.error("To preview pack contents:");
34207
+ console.error(` realitydb load ${filePath} --preview`);
34011
34208
  process.exit(1);
34012
34209
  }
34013
34210
  if (!options.ci) {
@@ -34026,6 +34223,7 @@ async function loadCommand(filePath, options) {
34026
34223
  database: masked,
34027
34224
  packName: pack.metadata.name,
34028
34225
  totalRows: result.totalRows,
34226
+ safeMode: safeMode ?? null,
34029
34227
  source: isUrl(filePath) ? filePath : void 0,
34030
34228
  tables: result.insertResult.tables.map((t) => ({
34031
34229
  name: t.tableName,
@@ -34043,7 +34241,8 @@ async function loadCommand(filePath, options) {
34043
34241
  }
34044
34242
  const totalTime = (result.durationMs / 1e3).toFixed(1);
34045
34243
  console.log("");
34046
- console.log(`Load complete. ${result.totalRows} rows in ${totalTime}s`);
34244
+ console.log(`Bug reproduction environment ready. ${result.insertResult.tables.length} tables, ${result.totalRows} rows loaded.`);
34245
+ console.log(`Load complete in ${totalTime}s`);
34047
34246
  console.log("");
34048
34247
  } catch (err) {
34049
34248
  const message = err instanceof Error ? err.message : String(err);
@@ -35664,7 +35863,7 @@ async function simulateWebhooksCommand(options) {
35664
35863
  }
35665
35864
 
35666
35865
  // src/cli.ts
35667
- var VERSION17 = "1.9.0";
35866
+ var VERSION17 = "2.0.0";
35668
35867
  function run(argv) {
35669
35868
  const program2 = new Command();
35670
35869
  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);
@@ -35766,7 +35965,7 @@ function run(argv) {
35766
35965
  const opts = program2.opts();
35767
35966
  await simulateWebhooksCommand({ ...cmdOpts, ci: opts.ci, configPath: opts.config });
35768
35967
  });
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) => {
35968
+ 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
35969
  const opts = program2.opts();
35771
35970
  await captureCommand({ ...cmdOpts, ci: opts.ci, configPath: opts.config });
35772
35971
  });
@@ -35774,7 +35973,7 @@ function run(argv) {
35774
35973
  const opts = program2.opts();
35775
35974
  await shareCommand(filePath, { ...cmdOpts, ci: opts.ci, configPath: opts.config });
35776
35975
  });
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) => {
35976
+ 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
35977
  const opts = program2.opts();
35779
35978
  await loadCommand(filePath, { ...cmdOpts, ci: opts.ci, configPath: opts.config });
35780
35979
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "realitydb",
3
- "version": "1.9.0",
3
+ "version": "2.0.0",
4
4
  "description": "Developer Reality Platform - realistic database environments from your schema",
5
5
  "license": "MIT",
6
6
  "keywords": [