@ultimat3/entity
Advanced tools
+5
-5
| { | ||
| "name": "@ultimat3/entity", | ||
| "version": "16.0.0", | ||
| "version": "17.0.0", | ||
| "description": "A table + its domain type + invariants the database also enforces", | ||
@@ -34,7 +34,7 @@ "license": "MIT", | ||
| "dependencies": { | ||
| "@ultimat3/core": "16.0.0", | ||
| "@ultimat3/db": "16.0.0", | ||
| "@ultimat3/schema": "16.0.0", | ||
| "@ultimat3/time": "16.0.0" | ||
| "@ultimat3/core": "17.0.0", | ||
| "@ultimat3/db": "17.0.0", | ||
| "@ultimat3/schema": "17.0.0", | ||
| "@ultimat3/time": "17.0.0" | ||
| } | ||
| } |
+17
-1
@@ -5,2 +5,3 @@ // The entity layer's stable error codes. Each factory produces the exact command | ||
| import { registerErrorCodes, UltimateError } from '@ultimat3/core'; | ||
| import { shellInertIdentifier } from '@ultimat3/db'; | ||
@@ -297,2 +298,12 @@ /** Codes this package declares and owns. */ | ||
| /** | ||
| * The contract's pinned wording. Mirror of `@ultimat3/db`'s `dbDrift()` (`drift-errors.ts`) — | ||
| * keep in sync; both screen through the same `shellInertIdentifier`, so the two lines are one | ||
| * text on both sides of the tier seam, and `errors.test.ts` asserts it rather than asking. | ||
| * | ||
| * The column is the CATALOG's, so it is data, and `x db gen "add C"` puts it inside SHELL DOUBLE | ||
| * QUOTES where `$(…)` and a backtick substitute before `x` is reached at all. The argument is a | ||
| * migration DESCRIPTION, not an identifier, so no quoted form makes a hostile name safe to pass — | ||
| * a refused one is left OUT of the command rather than escaped into it, and read off `cause`. | ||
| */ | ||
| export const dbDrift = (tableName: string, columnName: string): EntityError => | ||
@@ -302,3 +313,8 @@ new EntityError({ | ||
| cause: `table "${tableName}" has column "${columnName}" not present in any migration`, | ||
| fix: `x db gen "add ${columnName}"`, | ||
| fix: | ||
| shellInertIdentifier(columnName) === null | ||
| ? 'x db gen "add the column named in this error" # its name carries a backtick, a ' + | ||
| 'dollar sign, a quote, a backslash or whitespace, so it is in the cause and not in ' + | ||
| 'this command' | ||
| : `x db gen "add ${columnName}"`, | ||
| }); | ||
@@ -305,0 +321,0 @@ |
+6
-4
@@ -91,4 +91,4 @@ // Single responsibility: turn repository arguments into the `QueryPlan` a driver executes. | ||
| */ | ||
| export const assertPageSize = (entityName: string, rows: number): void => { | ||
| if (Number.isSafeInteger(rows) && rows >= 1 && rows <= MAX_PAGE_SIZE) return; | ||
| export const assertFinitePageSize = (entityName: string, rows: number): number => { | ||
| if (Number.isSafeInteger(rows) && rows >= 1 && rows <= MAX_PAGE_SIZE) return rows; | ||
| throw new EntityError({ | ||
@@ -112,3 +112,2 @@ code: 'X_INVARIANT_VIOLATED', | ||
| export const planFor = <Row>(entity: EntityCore<Row>, args: FindManyArgs): QueryPlan => { | ||
| if (args.limit !== undefined) assertPageSize(entity.$name, args.limit); | ||
| const orderBy = totalOrder(entity, args.orderBy ?? []); | ||
@@ -124,3 +123,6 @@ assertSeekable(entity, orderBy); | ||
| orderBy, | ||
| limit: args.limit ?? DEFAULT_PAGE_SIZE, | ||
| // Screened on the RESOLVED page, so `findMany({ limit })` straight at the repository — a | ||
| // generated client, a query, a third-party driver's caller — cannot route around the rule the | ||
| // chain's own `limit()` applies. | ||
| limit: assertFinitePageSize(entity.$name, args.limit ?? DEFAULT_PAGE_SIZE), | ||
| ...(args.cursor === undefined || args.cursor === null ? {} : { cursor: args.cursor }), | ||
@@ -127,0 +129,0 @@ ...(args.select === undefined ? {} : { select: args.select }), |
+3
-3
@@ -12,3 +12,3 @@ // The chainable read. Every chain terminates in a cursor page — `page()` returns rows plus the | ||
| import { searchUndeclared } from './feature-errors'; | ||
| import { assertPageSize, DEFAULT_PAGE_SIZE, namedColumns } from './plan'; | ||
| import { assertFinitePageSize, DEFAULT_PAGE_SIZE, namedColumns } from './plan'; | ||
| import type { RelatedTables } from './preload'; | ||
@@ -309,3 +309,3 @@ import { preloaded } from './preload'; | ||
| limit: (rows) => { | ||
| assertPageSize(entity.$name, rows); | ||
| assertFinitePageSize(entity.$name, rows); | ||
| return next({ limit: rows }); | ||
@@ -405,3 +405,3 @@ }, | ||
| // The page that will actually run, so an unnamed one still reads as the bound it has. | ||
| limit: state.limit ?? DEFAULT_PAGE_SIZE, | ||
| limit: assertFinitePageSize(entity.$name, state.limit ?? DEFAULT_PAGE_SIZE), | ||
| ...(state.cursor === null ? {} : { cursor: state.cursor }), | ||
@@ -408,0 +408,0 @@ // The projection actually sent, preload keys included: a plan that is safe to log is only |
Sorry, the diff of this file is too big to display
706572
0.21%10646
0.17%+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated