@fabrika/db 0.9.0-cloudflare.1 → 0.9.0-cloudflare.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fabrika/db",
3
- "version": "0.9.0-cloudflare.1",
3
+ "version": "0.9.0-cloudflare.2",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -92,9 +92,9 @@
92
92
  "test": "bun test"
93
93
  },
94
94
  "dependencies": {
95
- "@fabrika/auth-core": "0.9.0-cloudflare.1",
96
- "@fabrika/platform": "0.9.0-cloudflare.1",
97
- "@fabrika/platform-node": "0.9.0-cloudflare.1",
95
+ "@fabrika/auth-core": "0.9.0-cloudflare.2",
96
+ "@fabrika/platform": "0.9.0-cloudflare.2",
97
+ "@fabrika/platform-node": "0.9.0-cloudflare.2",
98
98
  "kysely": "0.29.6",
99
99
  "kysely-postgres-js": "5.0.1",
100
100
  "zod": "^3.25.76"
@@ -83,6 +83,11 @@ export interface IsolationWorlds {
83
83
  readonly dimension: string
84
84
  readonly own: WorldRow
85
85
  readonly other: WorldRow
86
+ /**
87
+ * Deletes both rows of the table and every row that refers to them, however deep; the containers stay. A case whose
88
+ * rows hold pinned values removes them, so the next case can pin the same values again under a unique index.
89
+ */
90
+ readonly remove: () => Promise<void>
86
91
  }
87
92
 
88
93
  /** What a test kit seeds through, over its migrated schema. */
@@ -90,9 +95,17 @@ export interface TestSeeding {
90
95
  /**
91
96
  * Seeds one row of `table` in each of two fresh chains, with every container row a table owns. `values` (column →
92
97
  * value) are written into both rows of `table`: the state an endpoint's precondition needs. A text column keeps its
93
- * marker, so a seed of one, or of a column that places the row, throws.
98
+ * marker, so a seed of one, or of a column that places the row, throws. `pins` (column → value) give a text column of
99
+ * `table` a known value instead of its marker: the value in the own row, and `<value>-other` in the other row, which
100
+ * counts among the other row's markers, so a leak of it is still found. A pin of a column that places the row, or of
101
+ * no text column, throws.
94
102
  */
95
- seedWorlds<DB, P>(tables: Tables<DB, P>, table: string, values?: Readonly<Record<string, SampleValue>>): Promise<IsolationWorlds>
103
+ seedWorlds<DB, P>(
104
+ tables: Tables<DB, P>,
105
+ table: string,
106
+ values?: Readonly<Record<string, SampleValue>>,
107
+ pins?: Readonly<Record<string, string>>,
108
+ ): Promise<IsolationWorlds>
96
109
  /**
97
110
  * Seeds one row of the application table `table`: its key, version and creator stamped, `values` (column → value)
98
111
  * as given, a text column too, every other column sampled. A value for a stamped column or a file column throws.
@@ -525,7 +538,17 @@ export function testSeeding(backend: SeedingBackend): TestSeeding {
525
538
  return { key, ...seeded }
526
539
  }
527
540
 
528
- async function seedWorld(models: TableModels, model: TableModel, world: string, seed: Readonly<Record<string, SampleValue>>): Promise<WorldRow> {
541
+ /** The value a pin gives the row of `world`: as written in the own row, distinct in the other. */
542
+ const pinnedValue = (value: string, world: string): string => (world === 'own' ? value : `${value}-${world}`)
543
+
544
+ async function seedWorld(
545
+ models: TableModels,
546
+ model: TableModel,
547
+ world: string,
548
+ seed: Readonly<Record<string, SampleValue>>,
549
+ pins: Readonly<Record<string, string>>,
550
+ inserted: InsertedRow[],
551
+ ): Promise<WorldRow> {
529
552
  const markers: string[] = []
530
553
  const values = new Map<string, string>()
531
554
  const links: readonly Container[] = model.chain
@@ -562,7 +585,20 @@ export function testSeeding(backend: SeedingBackend): TestSeeding {
562
585
  }
563
586
  assigned.set(name, value)
564
587
  }
588
+ for (const [name, value] of Object.entries(pins)) {
589
+ const column = columnOf(model.name, name)
590
+ if (assigned.has(name)) {
591
+ throw new Error(`${model.name}.${name} places the row in its scope or is seeded; a pin gives a text column of the row's own state a known value`)
592
+ }
593
+ if (!(await holdsMarker(model, model.name, column))) {
594
+ throw new Error(`${model.name}.${name} holds no text marker; a pin gives a text column a known value, a seed every other column`)
595
+ }
596
+ const pinned = pinnedValue(value, world)
597
+ assigned.set(name, pinned)
598
+ markers.push(pinned)
599
+ }
565
600
  const { row } = await insert(models, model.name, assigned, markers)
601
+ inserted.push({ table: model.name, match: new Map([[scope.key, key]]) })
566
602
  const updates: Record<string, SampleValue> = {}
567
603
  for (const name of namesOf(model.update)) {
568
604
  updates[name] = row.get(name) ?? await updateSample(model.name, name)
@@ -586,10 +622,16 @@ export function testSeeding(backend: SeedingBackend): TestSeeding {
586
622
  }
587
623
 
588
624
  return {
589
- async seedWorlds(tables, table, values = {}) {
625
+ async seedWorlds(tables, table, values = {}, pins = {}) {
590
626
  const { models, model } = scopedModel(tables, table)
591
627
  const dimension = grantDimension(models, model)
592
- return { dimension, own: await seedWorld(models, model, 'own', values), other: await seedWorld(models, model, 'other', values) }
628
+ const inserted: InsertedRow[] = []
629
+ return {
630
+ dimension,
631
+ own: await seedWorld(models, model, 'own', values, pins, inserted),
632
+ other: await seedWorld(models, model, 'other', values, pins, inserted),
633
+ remove: () => backend.remove(inserted),
634
+ }
593
635
  },
594
636
  async seedRow(tables, table, values = {}) {
595
637
  const { models, model } = scopedModel(tables, table)
@@ -69,15 +69,23 @@ export interface IsolationWorlds {
69
69
  readonly dimension: string;
70
70
  readonly own: WorldRow;
71
71
  readonly other: WorldRow;
72
+ /**
73
+ * Deletes both rows of the table and every row that refers to them, however deep; the containers stay. A case whose
74
+ * rows hold pinned values removes them, so the next case can pin the same values again under a unique index.
75
+ */
76
+ readonly remove: () => Promise<void>;
72
77
  }
73
78
  /** What a test kit seeds through, over its migrated schema. */
74
79
  export interface TestSeeding {
75
80
  /**
76
81
  * Seeds one row of `table` in each of two fresh chains, with every container row a table owns. `values` (column →
77
82
  * value) are written into both rows of `table`: the state an endpoint's precondition needs. A text column keeps its
78
- * marker, so a seed of one, or of a column that places the row, throws.
83
+ * marker, so a seed of one, or of a column that places the row, throws. `pins` (column → value) give a text column of
84
+ * `table` a known value instead of its marker: the value in the own row, and `<value>-other` in the other row, which
85
+ * counts among the other row's markers, so a leak of it is still found. A pin of a column that places the row, or of
86
+ * no text column, throws.
79
87
  */
80
- seedWorlds<DB, P>(tables: Tables<DB, P>, table: string, values?: Readonly<Record<string, SampleValue>>): Promise<IsolationWorlds>;
88
+ seedWorlds<DB, P>(tables: Tables<DB, P>, table: string, values?: Readonly<Record<string, SampleValue>>, pins?: Readonly<Record<string, string>>): Promise<IsolationWorlds>;
81
89
  /**
82
90
  * Seeds one row of the application table `table`: its key, version and creator stamped, `values` (column → value)
83
91
  * as given, a text column too, every other column sampled. A value for a stamped column or a file column throws.