@dooer/dooer-test-env 1.18.1 → 1.19.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 (3) hide show
  1. package/lib/copy.js +15 -3
  2. package/package.json +1 -1
  3. package/readme.md +25 -9
package/lib/copy.js CHANGED
@@ -94,17 +94,29 @@ async function copyOrganization({ organizationId, sourceEnv, targetEnv, reason,
94
94
  )
95
95
  console.log(` new organization id: ${chalk.bold(detail.organizationId)}`)
96
96
 
97
+ // Parent rows carried alongside the organization: global rows (workflow definitions, system messages)
98
+ // that its rows point at and the target may not have, because a target's base DB is a point-in-time
99
+ // snapshot while the source keeps moving. Only what was MISSING is inserted.
100
+ const refs = detail.references
101
+ if (refs && refs.rowsInArtifact) {
102
+ console.log(
103
+ ` referenced rows carried: ${refs.rowsInArtifact} from ${refs.tables.length} table(s)` +
104
+ `, ${refs.rowsInserted} inserted (the rest the target already had)`
105
+ )
106
+ }
107
+
97
108
  // Rows whose foreign key points at a parent that did not come with them. The load runs with FK
98
109
  // enforcement off (no ordering of 336 tables satisfies every constraint), and Postgres never re-checks,
99
110
  // so these commit silently — they have to be told, or the copy looks clean when it is not.
100
111
  if (detail.danglingRows) {
101
- console.log(chalk.yellow(`\n ${detail.danglingRows} row(s) reference a parent that was not copied:`))
112
+ console.log(chalk.yellow(`\n ${detail.danglingRows} row(s) still reference a parent that is not there:`))
102
113
  for (const d of detail.dangling || []) {
103
114
  console.log(` ${String(d.rows).padStart(6)} ${d.from} → ${d.to} (${d.cause})`)
104
115
  }
105
116
  console.log(
106
- ' The copy is complete and usable; these references dangle. `parent table not in schema map`\n' +
107
- ' means the map does not carry that parent it will recur on every copy until the map changes.'
117
+ ' The copy is complete and usable; these references dangle. Reference closure carries the parents\n' +
118
+ ' a copied row points at, so anything left here is a table it deliberately does not carry\n' +
119
+ ' (service_accounts.users / companies) or one it could not follow — see the warnings above.'
108
120
  )
109
121
  }
110
122
  console.log(` audit: export ${exported.id} (${source.name}), import ${imported.id} (${target.name})\n`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dooer/dooer-test-env",
3
- "version": "1.18.1",
3
+ "version": "1.19.0",
4
4
  "description": "Run the whole Dooer backend locally (staging DB minus customers), copy/purge customers between environments, and shred — one CLI.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": "Dooer/cli-dooer-test-env",
package/readme.md CHANGED
@@ -262,19 +262,35 @@ npx @dooer/dooer-test-env@latest customer purge --org <orgId> --local --execute
262
262
  documents in the bucket; the command says so in its own output. Removing them needs bucket credentials the
263
263
  service does not hold today.
264
264
 
265
- **Dangling references.** The load runs with foreign-key enforcement off no ordering of 336 tables
266
- satisfies every constraint on the way in — and Postgres never re-checks afterwards, so a copied row whose
267
- parent is missing would otherwise commit silently. The import checks the rows it wrote and reports any
268
- that dangle:
265
+ **Referenced rows come with the org.** A copied row can point at a row in a table the schema map does not
266
+ copy: `service_workflow.run` is org-scoped and copied, but the workflow `definition` it points at is
267
+ global and is not. The target usually has those already every environment's base DB descends from the
268
+ same production dump — but a base DB is a point-in-time snapshot while the source keeps moving. Production
269
+ has 819 workflow definitions; a base DB built in April 2023 has 689, so a 2026 org's runs pointed at
270
+ versions that were not there.
271
+
272
+ So the export carries the exact parent rows the org's rows reach, transitively (a carried `definition`
273
+ points at a `workflow` in turn), and the import inserts only what is missing:
269
274
 
270
275
  ```
271
- 44 row(s) reference a parent that was not copied:
272
- 42 service_workflow.run.definitionId → service_workflow.definition (parent table not in schema map)
273
- 2 service_accounts.systemMessageRecipient.systemMessageId → service_accounts.systemMessage
276
+ referenced rows carried: 9 from 2 table(s), 8 inserted (the rest the target already had)
274
277
  ```
275
278
 
276
- `parent table not in schema map` means the map does not carry that parent at all, so it recurs on every
277
- copy until the map changes. The copy is complete and usable; those references dangle.
279
+ Their ids are **not** remapped the copied rows reference them verbatim.
280
+
281
+ Two tables are never carried: **`service_accounts.users`** (it would move real names and personnummer from
282
+ the source environment into the target — exactly what a live→staging copy must not do) and
283
+ **`service_accounts.companies`** (the organization itself). The closure is capped at 50,000 rows, and both
284
+ the cap and any composite-key foreign key it declines to follow are reported as warnings.
285
+
286
+ Anything that still dangles is reported after the load, because the bulk load runs with foreign-key
287
+ enforcement off and Postgres never re-checks afterwards — so a dangling row would otherwise commit
288
+ silently:
289
+
290
+ ```
291
+ 44 row(s) still reference a parent that is not there:
292
+ 42 service_workflow.run.definitionId → service_workflow.definition
293
+ ```
278
294
 
279
295
  Naming: with **neither `--name` nor `--target`** the copy creates a new org that **keeps the source org's
280
296
  own name** (its `short_name` still gets a unique suffix). Pass `--name` to rename it, or `--target <uuid>`