You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* refactor(api): take ContainerWriteInput only at container creation.
Callers pass quantity as an option instead of flattening to ContainerData before persist.
* refactor(api): assemble container reads through one view.
List, collection detail, GET /containers/:id, and export project from the same load. ADR 0009 records the decision.
* refactor(web): split derivations bulk-import into reducer, effects, and gateway.
The page and hook become a thin shell over a testable core.
* fix(api): enforce grid occupancy, foreign keys, and atomic derivation writes
Reconnects were leaving SQLite foreign keys off, and two containers could share a grid cell. Open every connection with FK on, unique-index occupied positions, and wrap derivation writes in a transaction that survives await.
Copy file name to clipboardExpand all lines: CONTEXT.md
+5-1Lines changed: 5 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -124,9 +124,13 @@ A place in the storage hierarchy where collections are kept (e.g. freezer, shelf
124
124
_Avoid_: Site (unless referring to an external facility), storage (too vague)
125
125
126
126
**Container placement**:
127
-
Where a **Container** sits in the physical storage hierarchy — its position within a **Collection**, the collection it belongs to, and the **Location** path where that collection is stored. For tubes and wells, the immediate **Collection** is a plate or box and may include a grid position. For **Paper** containers, the immediate **Collection** is a **Sheet** (within a box or bag). Distinct from specimen provenance (**Source**, study/control context) and from container identity metadata (barcodes, **Tags**).
127
+
Where a **Container** sits in the physical storage hierarchy — its **grid position** within a **Collection**, the collection it belongs to, and the **Location** path where that collection is stored. For tubes and wells, the immediate **Collection** is a plate or box. For **Paper** containers, the immediate **Collection** is a **Sheet** (within a box or bag). Distinct from specimen provenance (**Source**, study/control context) and from container identity metadata (barcodes, **Tags**).
128
128
_Avoid_: Enrichment (implementation term); conflating placement with specimen or source lookups; placing paper directly on a box or bag without a sheet
129
129
130
+
**Grid position**:
131
+
The well or slot of a tube or well **Container** in its plate or box (e.g. A01). Optional: a tube or well may belong to a **Collection** with no grid position (legacy rows). At most one Container occupies a given grid position in a Collection; a missing position occupies no cell. Not applicable to **Paper**.
132
+
_Avoid_: Well (as the domain term); treating a missing position as occupying a slot; requiring every tube to have a position before it can exist in a Collection
Copy file name to clipboardExpand all lines: packages/api/initial_schema.sql
+4-1Lines changed: 4 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -206,6 +206,7 @@ CREATE TABLE IF NOT EXISTS micronix_tube (
206
206
barcode TEXTNOT NULL UNIQUE,
207
207
position TEXT
208
208
);--> statement-breakpoint
209
+
CREATEUNIQUE INDEXIF NOT EXISTS micronix_tube_collection_position_idx ON micronix_tube(collection_id, position) WHERE position IS NOT NULL;--> statement-breakpoint
@@ -223,6 +224,7 @@ CREATE TABLE IF NOT EXISTS cryovial_tube (
223
224
barcode TEXT,
224
225
position TEXT
225
226
);--> statement-breakpoint
227
+
CREATEUNIQUE INDEXIF NOT EXISTS cryovial_tube_collection_position_idx ON cryovial_tube(collection_id, position) WHERE position IS NOT NULL;--> statement-breakpoint
CREATEUNIQUE INDEXIF NOT EXISTS static_well_collection_position_idx ON static_well(collection_id, position) WHERE position IS NOT NULL;--> statement-breakpoint
269
272
CREATETABLEIF NOT EXISTS strain (
270
273
id INTEGERPRIMARY KEY,
271
274
name TEXTNOT NULL UNIQUE,
@@ -279,7 +282,7 @@ CREATE TABLE IF NOT EXISTS storage_type (
279
282
CREATETABLEIF NOT EXISTS schema_version (
280
283
version INTEGERNOT NULL
281
284
);--> statement-breakpoint
282
-
INSERT INTO schema_version (version) VALUES (3);--> statement-breakpoint
285
+
INSERT INTO schema_version (version) VALUES (4);--> statement-breakpoint
Copy file name to clipboardExpand all lines: packages/api/src/db/README.md
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,7 +6,9 @@ See [ADR-0003: SQLite schema evolution](../../../docs/adr/0003-sqlite-schema-evo
6
6
7
7
## Opening a database
8
8
9
-
Use `openOperationalDatabase(optionalPath?)` from the client module. It resolves the file path, enables WAL, runs schema evolution, and returns Drizzle + raw `bun:sqlite` handles. Importing the client module does **not** open a connection.
9
+
Use `openOperationalDatabase(optionalPath?)` from the client module. It resolves the file path, enables WAL, turns on foreign keys, runs schema evolution, and returns Drizzle + raw `bun:sqlite` handles. Importing the client module does **not** open a connection.
10
+
11
+
Multi-statement writes that `await` must use `withWriteTransaction` from `write-transaction.ts`. Drizzle's bun-sqlite `db.transaction(async () => …)` commits when the callback first yields; it does not await the returned Promise.
10
12
11
13
Tests should use the same entry point (e.g. `openOperationalDatabase(':memory:')` via `setupTestDatabase()`).
CREATEUNIQUE INDEXIF NOT EXISTS micronix_tube_collection_position_idx ON micronix_tube(collection_id, position) WHERE position IS NOT NULL;--> statement-breakpoint
21
+
CREATEUNIQUE INDEXIF NOT EXISTS cryovial_tube_collection_position_idx ON cryovial_tube(collection_id, position) WHERE position IS NOT NULL;--> statement-breakpoint
22
+
CREATEUNIQUE INDEXIF NOT EXISTS static_well_collection_position_idx ON static_well(collection_id, position) WHERE position IS NOT NULL;
0 commit comments