@ultimat3/entity 29.4.0 → 30.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.
- package/CLAUDE.md +66 -96
- package/README.md +118 -8
- package/package.json +15 -5
- package/src/append-only.ts +7 -0
- package/src/batch-read.ts +12 -4
- package/src/bulk-write-send.ts +49 -0
- package/src/bulk-write.ts +48 -11
- package/src/column.ts +29 -4
- package/src/counter.ts +109 -0
- package/src/cross-tenant.ts +13 -0
- package/src/cursor.ts +17 -1
- package/src/database.ts +16 -10
- package/src/describe-tenancy.ts +25 -0
- package/src/describe.ts +144 -3
- package/src/entity-error.ts +33 -0
- package/src/entity-parse.ts +89 -0
- package/src/entity-patch.ts +42 -0
- package/src/entity.ts +90 -76
- package/src/errors.ts +8 -0
- package/src/history-actor.ts +22 -0
- package/src/history.ts +86 -0
- package/src/index.ts +20 -1
- package/src/jit-preload.ts +35 -25
- package/src/lifecycle.ts +100 -0
- package/src/lock-key.ts +27 -0
- package/src/lock-unsupported.ts +11 -0
- package/src/memory-aggregates.ts +99 -0
- package/src/memory-batch.ts +69 -0
- package/src/memory-journal.ts +68 -0
- package/src/memory-lifecycle.ts +106 -0
- package/src/memory-lock.ts +144 -0
- package/src/memory-observed.ts +121 -0
- package/src/memory-repo.ts +199 -186
- package/src/memory-same-tenant.ts +83 -0
- package/src/memory-seek.ts +44 -0
- package/src/memory-transactor.ts +94 -0
- package/src/memory-unique.ts +19 -4
- package/src/n-plus-one.ts +28 -10
- package/src/numeric-compare.ts +43 -0
- package/src/pg-aggregates.ts +185 -0
- package/src/pg-driver.ts +79 -166
- package/src/pg-lock.ts +50 -0
- package/src/pg-row.ts +29 -2
- package/src/pg-sql-aggregate.ts +31 -0
- package/src/pg-sql.ts +17 -1
- package/src/query.ts +26 -105
- package/src/read-aggregates.ts +138 -0
- package/src/read-capture.ts +22 -0
- package/src/registry.ts +44 -1
- package/src/repo.ts +34 -0
- package/src/retain.ts +71 -0
- package/src/row-observer.ts +22 -4
- package/src/row-security-settings.ts +20 -0
- package/src/sealed-repo.ts +15 -0
- package/src/seed.ts +43 -7
- package/src/series-unsupported.ts +11 -0
- package/src/table-locks.ts +41 -0
- package/src/test-driver.ts +90 -0
- package/src/time-bucket.ts +147 -0
- package/src/touch.ts +33 -0
- package/src/types.ts +20 -0
- package/src/view-entity.ts +78 -0
- package/src/view-issue.ts +47 -0
- package/src/view-repo.ts +38 -0
- package/src/view.ts +41 -27
package/src/view.ts
CHANGED
|
@@ -3,10 +3,16 @@
|
|
|
3
3
|
// already describe. Values are validated by the entity's own column parsers; an unknown key is a
|
|
4
4
|
// declaration-time failure, not a surprise on the first request.
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
6
|
+
import {
|
|
7
|
+
describeValue,
|
|
8
|
+
type SchemaNode,
|
|
9
|
+
type StandardIssue,
|
|
10
|
+
type StandardSchemaV1,
|
|
11
|
+
} from '@ultimat3/schema';
|
|
12
|
+
import { viewKeyUnknown } from './entity-error';
|
|
8
13
|
import { invariantViolated } from './errors';
|
|
9
14
|
import type { AnyColumn, ColumnMap } from './types';
|
|
15
|
+
import { columnIssue, missingIssue } from './view-issue';
|
|
10
16
|
|
|
11
17
|
/** What `bigint()` puts on a row: the digits, as a string — `columns-data.ts` parses by this. */
|
|
12
18
|
const BIGINT_PATTERN = '^-?\\d+$';
|
|
@@ -104,11 +110,7 @@ export const viewFor = <Row, K extends keyof Row & string>(
|
|
|
104
110
|
const picked: readonly (readonly [K, AnyColumn])[] = keys.map((key) => {
|
|
105
111
|
const column = columns[key];
|
|
106
112
|
if (column === undefined) {
|
|
107
|
-
throw
|
|
108
|
-
entityName,
|
|
109
|
-
'view',
|
|
110
|
-
`$view(['${key}']) names no column — pick from: ${Object.keys(columns).join(', ')}`,
|
|
111
|
-
);
|
|
113
|
+
throw viewKeyUnknown(entityName, key, Object.keys(columns));
|
|
112
114
|
}
|
|
113
115
|
return [key, column] as const;
|
|
114
116
|
});
|
|
@@ -120,47 +122,59 @@ export const viewFor = <Row, K extends keyof Row & string>(
|
|
|
120
122
|
properties: Object.fromEntries(picked.map(([key, column]) => [key, columnNode(column)])),
|
|
121
123
|
};
|
|
122
124
|
|
|
123
|
-
const
|
|
125
|
+
const nodes = node.properties ?? {};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Every picked key through its own column's parser, each refusal an issue AT ITS KEY: a form
|
|
129
|
+
* binds `path: ['title']` to the control, where one wrapped `EntityError` line at `path: ''`
|
|
130
|
+
* bound to nothing. All keys are read, so a form shows every refused field at once.
|
|
131
|
+
*/
|
|
132
|
+
const check = (
|
|
133
|
+
value: unknown,
|
|
134
|
+
): { readonly value: Pick<Row, K> } | { readonly issues: readonly StandardIssue[] } => {
|
|
124
135
|
if (typeof value !== 'object' || value === null) {
|
|
125
136
|
// Shape, never content — the same renderer `columns.ts` uses, for the same reason: a
|
|
126
137
|
// view issue is folded into `X_BODY_INVALID` and reaches the caller and the log line.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
138
|
+
return {
|
|
139
|
+
issues: [
|
|
140
|
+
{
|
|
141
|
+
path: [],
|
|
142
|
+
message: `expected an object, got ${describeValue(value)}`,
|
|
143
|
+
rule: 'type',
|
|
144
|
+
params: { type: 'object' },
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
};
|
|
132
148
|
}
|
|
133
149
|
const input = value as Readonly<Record<string, unknown>>;
|
|
134
150
|
const projected = {} as Record<K, unknown>;
|
|
151
|
+
const issues: StandardIssue[] = [];
|
|
135
152
|
for (const [key, column] of picked) {
|
|
136
153
|
const given = input[key];
|
|
137
154
|
if (given === undefined || given === null) {
|
|
138
155
|
// No default is filled in: a view projects a row that already exists, so an absent
|
|
139
156
|
// required column is missing data, never a value the projection may invent.
|
|
140
|
-
if (column.$meta.notNull)
|
|
141
|
-
projected[key] = null;
|
|
157
|
+
if (column.$meta.notNull) issues.push(missingIssue(key));
|
|
158
|
+
else projected[key] = null;
|
|
142
159
|
continue;
|
|
143
160
|
}
|
|
144
|
-
|
|
161
|
+
try {
|
|
162
|
+
projected[key] = column.$parse(given);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
issues.push(columnIssue(key, column, nodes[key] ?? { kind: 'unknown' }, error));
|
|
165
|
+
}
|
|
145
166
|
}
|
|
146
167
|
// Every picked key went through its own column's parser, so this is the derived projection.
|
|
147
|
-
return projected as Pick<Row, K
|
|
168
|
+
return issues.length > 0 ? { issues } : { value: projected as Pick<Row, K> };
|
|
148
169
|
};
|
|
149
170
|
|
|
150
171
|
return {
|
|
151
172
|
'~standard': {
|
|
152
173
|
version: 1,
|
|
153
174
|
vendor: 'ultimate',
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
} catch (error) {
|
|
158
|
-
// The entity's rule, for the same reason: `instanceof` and `String()` are both reads of
|
|
159
|
-
// a caught value, and a throwable that fights being read turned a rejected projection
|
|
160
|
-
// into an uncatchable `TypeError`. `renderThrowable` is total.
|
|
161
|
-
return { issues: [{ message: renderThrowable(error) }] };
|
|
162
|
-
}
|
|
163
|
-
},
|
|
175
|
+
// Never throws: `columnIssue` reads a caught value through `renderThrowable`, which is
|
|
176
|
+
// total — `instanceof` and `String()` are reads a hostile throwable refuses.
|
|
177
|
+
validate: check,
|
|
164
178
|
},
|
|
165
179
|
// `node` is the duck-typed key `nodeOf()` reads — the same one every `t.*` schema carries.
|
|
166
180
|
node,
|