@happyvertical/smrt-core 0.40.69 → 0.41.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/AGENTS.md +29 -4
- package/README.md +20 -1
- package/agents/change-feed.md +1 -1
- package/agents/query-bounds.md +45 -0
- package/agents/schema-paths.md +786 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +5 -3
- package/dist/cascade.d.ts +120 -0
- package/dist/cascade.d.ts.map +1 -0
- package/dist/cascade.js +430 -0
- package/dist/cascade.js.map +1 -0
- package/dist/change-feed.d.ts +34 -2
- package/dist/change-feed.d.ts.map +1 -1
- package/dist/change-feed.js +52 -11
- package/dist/change-feed.js.map +1 -1
- package/dist/class.d.ts +36 -3
- package/dist/class.d.ts.map +1 -1
- package/dist/class.js +87 -9
- package/dist/class.js.map +1 -1
- package/dist/collection-cache.js +0 -0
- package/dist/collection-cache.js.map +1 -1
- package/dist/collection.d.ts +130 -2
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +290 -57
- package/dist/collection.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/database.d.ts +8 -0
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +16 -8
- package/dist/database.js.map +1 -1
- package/dist/db-errors.d.ts +105 -0
- package/dist/db-errors.d.ts.map +1 -0
- package/dist/db-errors.js +382 -0
- package/dist/db-errors.js.map +1 -0
- package/dist/decorators/index.d.ts +80 -6
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +102 -12
- package/dist/decorators/index.js.map +1 -1
- package/dist/dispatch/bus.d.ts.map +1 -1
- package/dist/dispatch/bus.js +4 -3
- package/dist/dispatch/bus.js.map +1 -1
- package/dist/dispatch/collections/Dispatches.d.ts.map +1 -1
- package/dist/dispatch/collections/Dispatches.js +19 -4
- package/dist/dispatch/collections/Dispatches.js.map +1 -1
- package/dist/dispatch/types.d.ts +5 -0
- package/dist/dispatch/types.d.ts.map +1 -1
- package/dist/embedded-write-queue.d.ts +46 -0
- package/dist/embedded-write-queue.d.ts.map +1 -0
- package/dist/embedded-write-queue.js +66 -0
- package/dist/embedded-write-queue.js.map +1 -0
- package/dist/embeddings/storage.d.ts +7 -0
- package/dist/embeddings/storage.d.ts.map +1 -1
- package/dist/embeddings/storage.js +29 -12
- package/dist/embeddings/storage.js.map +1 -1
- package/dist/errors.d.ts +31 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +34 -2
- package/dist/errors.js.map +1 -1
- package/dist/generators/changes-route.d.ts.map +1 -1
- package/dist/generators/changes-route.js +6 -3
- package/dist/generators/changes-route.js.map +1 -1
- package/dist/generators/mcp-runtime-template.d.ts +8 -0
- package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
- package/dist/generators/mcp-runtime-template.js +38 -4
- package/dist/generators/mcp-runtime-template.js.map +1 -1
- package/dist/generators/mcp.d.ts +16 -0
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +41 -3
- package/dist/generators/mcp.js.map +1 -1
- package/dist/generators/rest.d.ts +22 -0
- package/dist/generators/rest.d.ts.map +1 -1
- package/dist/generators/rest.js +34 -3
- package/dist/generators/rest.js.map +1 -1
- package/dist/hierarchical.js +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -5
- package/dist/interceptors.d.ts +21 -0
- package/dist/interceptors.d.ts.map +1 -1
- package/dist/interceptors.js +27 -1
- package/dist/interceptors.js.map +1 -1
- package/dist/manifest/generator.d.ts.map +1 -1
- package/dist/manifest/generator.js +4 -7
- package/dist/manifest/generator.js.map +1 -1
- package/dist/manifest/static-manifest.js +10 -10
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest/store.js.map +1 -1
- package/dist/manifest.json +19 -19
- package/dist/migrations/differ.d.ts +211 -9
- package/dist/migrations/differ.d.ts.map +1 -1
- package/dist/migrations/differ.js +613 -50
- package/dist/migrations/differ.js.map +1 -1
- package/dist/migrations/generator.d.ts +31 -4
- package/dist/migrations/generator.d.ts.map +1 -1
- package/dist/migrations/generator.js +49 -5
- package/dist/migrations/generator.js.map +1 -1
- package/dist/migrations/index.d.ts +4 -2
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +6 -3
- package/dist/migrations/minor-units.d.ts +162 -0
- package/dist/migrations/minor-units.d.ts.map +1 -0
- package/dist/migrations/minor-units.js +381 -0
- package/dist/migrations/minor-units.js.map +1 -0
- package/dist/migrations/orchestrate.js +35 -6
- package/dist/migrations/orchestrate.js.map +1 -1
- package/dist/migrations/sqlite-rebuild.d.ts +142 -0
- package/dist/migrations/sqlite-rebuild.d.ts.map +1 -0
- package/dist/migrations/sqlite-rebuild.js +514 -0
- package/dist/migrations/sqlite-rebuild.js.map +1 -0
- package/dist/migrations/tracker.d.ts +114 -1
- package/dist/migrations/tracker.d.ts.map +1 -1
- package/dist/migrations/tracker.js +331 -16
- package/dist/migrations/tracker.js.map +1 -1
- package/dist/migrations/types.d.ts +19 -4
- package/dist/migrations/types.d.ts.map +1 -1
- package/dist/migrations.js +6 -3
- package/dist/object.d.ts +142 -10
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +196 -41
- package/dist/object.js.map +1 -1
- package/dist/postgres-timeouts.d.ts +240 -0
- package/dist/postgres-timeouts.d.ts.map +1 -0
- package/dist/postgres-timeouts.js +204 -0
- package/dist/postgres-timeouts.js.map +1 -0
- package/dist/query-bounds.d.ts +101 -0
- package/dist/query-bounds.d.ts.map +1 -0
- package/dist/query-bounds.js +177 -0
- package/dist/query-bounds.js.map +1 -0
- package/dist/registry/class-registration.d.ts.map +1 -1
- package/dist/registry/class-registration.js +3 -1
- package/dist/registry/class-registration.js.map +1 -1
- package/dist/registry/manifest-field-merge.d.ts +12 -0
- package/dist/registry/manifest-field-merge.d.ts.map +1 -1
- package/dist/registry/manifest-field-merge.js +14 -2
- package/dist/registry/manifest-field-merge.js.map +1 -1
- package/dist/registry/schema-builder.d.ts +22 -1
- package/dist/registry/schema-builder.d.ts.map +1 -1
- package/dist/registry/schema-builder.js +205 -165
- package/dist/registry/schema-builder.js.map +1 -1
- package/dist/registry/types.d.ts +35 -3
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry.d.ts +41 -46
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +61 -83
- package/dist/registry.js.map +1 -1
- package/dist/scanner/manifest-generator.d.ts +45 -0
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +92 -28
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/scanner/types.d.ts +5 -0
- package/dist/scanner/types.d.ts.map +1 -1
- package/dist/scanner/types.js.map +1 -1
- package/dist/schema/conflict-target.d.ts +104 -0
- package/dist/schema/conflict-target.d.ts.map +1 -0
- package/dist/schema/conflict-target.js +129 -0
- package/dist/schema/conflict-target.js.map +1 -0
- package/dist/schema/ddl/base-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/base-strategy.js +2 -2
- package/dist/schema/ddl/base-strategy.js.map +1 -1
- package/dist/schema/ddl/duckdb-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/duckdb-strategy.js +2 -1
- package/dist/schema/ddl/duckdb-strategy.js.map +1 -1
- package/dist/schema/ddl/postgres-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/postgres-strategy.js +12 -1
- package/dist/schema/ddl/postgres-strategy.js.map +1 -1
- package/dist/schema/generator.d.ts +307 -6
- package/dist/schema/generator.d.ts.map +1 -1
- package/dist/schema/generator.js +510 -87
- package/dist/schema/generator.js.map +1 -1
- package/dist/schema/index-utils.d.ts +120 -0
- package/dist/schema/index-utils.d.ts.map +1 -1
- package/dist/schema/index-utils.js +242 -1
- package/dist/schema/index-utils.js.map +1 -1
- package/dist/schema/index.d.ts +3 -0
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +4 -1
- package/dist/schema/live-parity.d.ts +90 -0
- package/dist/schema/live-parity.d.ts.map +1 -0
- package/dist/schema/live-parity.js +602 -0
- package/dist/schema/live-parity.js.map +1 -0
- package/dist/schema/manifest-schema.d.ts +121 -0
- package/dist/schema/manifest-schema.d.ts.map +1 -0
- package/dist/schema/manifest-schema.js +267 -0
- package/dist/schema/manifest-schema.js.map +1 -0
- package/dist/schema/schema-aggregator.d.ts +24 -10
- package/dist/schema/schema-aggregator.d.ts.map +1 -1
- package/dist/schema/schema-aggregator.js +35 -90
- package/dist/schema/schema-aggregator.js.map +1 -1
- package/dist/schema/system-table-shapes.d.ts +65 -0
- package/dist/schema/system-table-shapes.d.ts.map +1 -0
- package/dist/schema/system-table-shapes.js +187 -0
- package/dist/schema/system-table-shapes.js.map +1 -0
- package/dist/schema/types.d.ts +103 -4
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/utils.d.ts +2 -1
- package/dist/schema/utils.d.ts.map +1 -1
- package/dist/schema/utils.js +5 -3
- package/dist/schema/utils.js.map +1 -1
- package/dist/schema.js +4 -1
- package/dist/smrt-knowledge.json +20 -8
- package/dist/sync/apply.d.ts.map +1 -1
- package/dist/sync/apply.js +9 -16
- package/dist/sync/apply.js.map +1 -1
- package/dist/system/compatibility.d.ts +42 -0
- package/dist/system/compatibility.d.ts.map +1 -1
- package/dist/system/compatibility.js +182 -9
- package/dist/system/compatibility.js.map +1 -1
- package/dist/system/index.d.ts +1 -0
- package/dist/system/index.d.ts.map +1 -1
- package/dist/system/index.js +3 -2
- package/dist/system/retention.d.ts +237 -0
- package/dist/system/retention.d.ts.map +1 -0
- package/dist/system/retention.js +497 -0
- package/dist/system/retention.js.map +1 -0
- package/dist/system/schema.d.ts +100 -15
- package/dist/system/schema.d.ts.map +1 -1
- package/dist/system/schema.js +81 -45
- package/dist/system/schema.js.map +1 -1
- package/dist/system/types.d.ts +0 -2
- package/dist/system/types.d.ts.map +1 -1
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js +1 -0
- package/dist/testing/database.js.map +1 -1
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +4 -9
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +71 -5
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/web-collections.d.ts.map +1 -1
- package/dist/vite-plugin/web-collections.js +6 -4
- package/dist/vite-plugin/web-collections.js.map +1 -1
- package/package.json +5 -5
package/AGENTS.md
CHANGED
|
@@ -17,6 +17,7 @@ subsystem you are editing. This file keeps what holds across all of them.
|
|
|
17
17
|
| `src/change-feed.ts` | the adapter-agnostic change-observation spine — `_smrt_changes`, cursors, table versions, generated `_changes` routes, retention | [agents/change-feed.md](agents/change-feed.md) |
|
|
18
18
|
| `src/change-signals.ts` + the generated `_events` SSE route | the push companion to the change feed — the signal bus, cross-replica fan-out, the SSE route, and its documented gaps | [agents/change-signals.md](agents/change-signals.md) |
|
|
19
19
|
| `src/generators/` + `src/vite-plugin/web-collections.ts` | REST/CLI/MCP/web-collection generation, the `manifestHash` emission sites, and generated conditional-GET / ETag v2 semantics | [agents/generators.md](agents/generators.md) |
|
|
20
|
+
| `src/schema/` | the five `SchemaGenerator` entry points, which two reach production, why schema drift stayed invisible, and the #2382 index/tenancy rules | [agents/schema-paths.md](agents/schema-paths.md) |
|
|
20
21
|
|
|
21
22
|
## SmrtObject Lifecycle
|
|
22
23
|
|
|
@@ -25,6 +26,7 @@ subsystem you are editing. This file keeps what holds across all of them.
|
|
|
25
26
|
- `initialize()`: loads field initializers, applies option values (options override initializers), loads from DB if id/slug provided
|
|
26
27
|
- `save()`: upsert with STI validation, interceptor execution, auto-embeddings. Persisted objects (`isPersisted` — set by DB hydration and successful saves) upsert on `['id']` so natural-key edits (e.g. slug renames) update in place; new objects upsert on the natural-key conflict columns for ingestion-style dedup (#1472)
|
|
27
28
|
- `is(criteria)` / `do(instructions)` / `describe()`: AI operations via function calling. They inject the object's own `toPublicJSON()` (sensitive fields stripped) as a "content body" so the model reasons over the instance. Options: `includeData: false` skips injection (for callers that already curate the relevant fields into the instruction); `maxDataLength` overrides the truncation budget. Neither key is forwarded to `ai.message()`. (#1567)
|
|
29
|
+
- `save()` error contract (#2366): unique/PK violation → `ValidationError` `VALIDATION_UNIQUE_CONSTRAINT`, NOT NULL → `VALIDATION_REQUIRED_FIELD`, both on the first attempt on every adapter; any other database failure → `DatabaseError` with the driver error on `cause`
|
|
28
30
|
- `getSlug()`: auto-generates from name → title → label → id
|
|
29
31
|
- `loadRelated(fieldName)`: lazy-loads relationships (cached in `_loadedRelationships` Map)
|
|
30
32
|
|
|
@@ -74,7 +76,7 @@ references). Re-adding either requires the query builder to support it first;
|
|
|
74
76
|
`src/__tests__/issue-2276-where-contract.test.ts` executes every accepted
|
|
75
77
|
operator against a database to keep the two in step.
|
|
76
78
|
|
|
77
|
-
STI child collections auto-filter by `_meta_type`.
|
|
79
|
+
STI child collections auto-filter by `_meta_type`. Query bounds — `LIMIT 1` on `get()`, the `limit`/`offset` parser, the `orderBy` whitelist and sensitive/permission refusals, and the deterministic generated-list ordering (#2367) — are in [agents/query-bounds.md](agents/query-bounds.md).
|
|
78
80
|
|
|
79
81
|
## Bounded Collection Read Plans
|
|
80
82
|
|
|
@@ -97,7 +99,7 @@ Two persistence primitives every `SmrtObject`/`SmrtCollection` inherits — load
|
|
|
97
99
|
|
|
98
100
|
## @smrt() Decorator Options
|
|
99
101
|
|
|
100
|
-
Key options: `tableName`, `tableStrategy` ('cti'|'sti'), `conflictColumns`, `api`/`mcp`/`cli` (generation config), `ai` (callable methods), `hooks` (beforeSave/afterSave/beforeDelete/afterDelete), `embeddings` (auto-generate), `tenantScoped`, `agent`, `ui` (`{ icon, label, description }` — nav/help hints round-tripped through the manifest as plain data; `description` is the object-level seed for form-level help, #2046).
|
|
102
|
+
Key options: `tableName`, `tableStrategy` ('cti'|'sti'), `conflictColumns`, `indexes` (declared multi-column indexes, #2357 — see "Schema paths"), `api`/`mcp`/`cli` (generation config), `ai` (callable methods), `hooks` (beforeSave/afterSave/beforeDelete/afterDelete), `embeddings` (auto-generate), `tenantScoped`, `agent`, `ui` (`{ icon, label, description }` — nav/help hints round-tripped through the manifest as plain data; `description` is the object-level seed for form-level help, #2046).
|
|
101
103
|
|
|
102
104
|
Registration sets `SMRT_TABLE_NAME` static property (survives minification).
|
|
103
105
|
|
|
@@ -203,13 +205,36 @@ byte/provenance/path/content drift, skips scans and manifest writes, and still
|
|
|
203
205
|
generates routes, types, registration, and virtual modules. Omit it for normal
|
|
204
206
|
local development and watch mode.
|
|
205
207
|
|
|
208
|
+
## Schema paths (#2382)
|
|
209
|
+
|
|
210
|
+
Production DDL comes from the **manifest** paths
|
|
211
|
+
(`generateSTISchemaFromManifest`/`generateCTISchemaFromManifest`, selected in
|
|
212
|
+
`src/scanner/manifest-generator.ts` → registered `schema` → `db:migrate`). The
|
|
213
|
+
**registry** paths feed `getTestDatabase()` and emit foreign-key indexes
|
|
214
|
+
production never gets: the suite runs on a richer schema than it ships.
|
|
215
|
+
|
|
216
|
+
- Change column/index emission on every shipping path, proven by the path-parity
|
|
217
|
+
test `src/schema/schema-path-parity.test.ts` (#2359; index rules in the module doc). A "same as migrations" comment is a claim to check.
|
|
218
|
+
- Every new query predicate ships with its index, or a reason it doesn't.
|
|
219
|
+
- Numeric types, uuid casts, conflict targets, timestamps, migrations: run the
|
|
220
|
+
`test:postgres` lane — SQLite affinity accepts what PostgreSQL rejects.
|
|
221
|
+
- Read `dist/manifest.json`/regenerated schemas for what a decorator produced;
|
|
222
|
+
count across all packages instead of sampling.
|
|
223
|
+
- Tenant scoping is whole-path: every unique constraint and conflict target on a
|
|
224
|
+
tenant-scoped table carries the tenant column, and every read path — not only
|
|
225
|
+
`list()` — is interceptor-aware.
|
|
226
|
+
- Rolling indexes out is part of the change: a bulk `CREATE INDEX` batch needs
|
|
227
|
+
the bounded, concurrent migrate path (#2362, Gotchas), or it takes production
|
|
228
|
+
down on deploy.
|
|
229
|
+
|
|
206
230
|
## Gotchas
|
|
207
231
|
|
|
208
232
|
- **Filesystem support is a lazy boundary (#1979)**: `SmrtClass` acquires `options.fs` adapters via `createFilesystemAdapter()` (`src/filesystem-loader.ts`), never a static `@happyvertical/files` import — the files SDK statically pulls @aws-sdk/client-s3 and reaches googleapis, and a static edge here would land it in every downstream SSR bundle. Node/tsx/vite-dev runtimes resolve it on first use; fully-bundled deployments import `@happyvertical/smrt-core/filesystem` at startup. Use `importOptionalDependency()` (`src/lazy-external.ts`) for any similar optional heavyweight dependency.
|
|
209
233
|
- **Never override toJSON()** — handles STI discriminator + meta field extraction. Use `transformJSON()`
|
|
210
234
|
- **Property init order**: TypeScript initializers run first, then `initialize()` applies option values (options win)
|
|
211
|
-
- **No runtime schema creation**: application tables must be prepared explicitly via migrations/tooling; runtime only
|
|
212
|
-
- **
|
|
235
|
+
- **No runtime schema creation**: application tables must be prepared explicitly via migrations/tooling; runtime verification is `tableExists()` only (`src/schema/table-verifier.ts`) — no column, type, or index check
|
|
236
|
+
- **PostgreSQL migrate batches are always time-bounded (#2362)**: `MigrationTracker.applyAll({ atomic: true })` emits `SET LOCAL lock_timeout`/`statement_timeout` before any DDL, so a batch blocked on one table cannot hold its earlier locks indefinitely. `postgresSafe: true` adds concurrent-index mode — non-index DDL commits atomically, then index DDL runs `CONCURRENTLY` on a session pinned via `db.acquireSession()` (a pooled `db.query` would not keep the `SET` and the DDL on one connection). That mode is deliberately **not atomic**: unfinished index migrations are recorded `failed`, not `running`, and their `error_message` carries a `[smrt: concurrent-index phase 1 committed]` marker so a reconciling re-run resumes at the index build instead of replaying committed DDL. INVALID indexes are found via `pg_index.indisvalid` (`pg_indexes` reports them as present) and dropped before rebuild. Operational detail: `packages/cli/AGENTS.md`.
|
|
237
|
+
- **Retry logic is transient-only (#2366)**: `db.get()`/`db.upsert()` retry 4× total (initial + 3), but `ErrorUtils.withRetry` classifies via the cause chain (`src/db-errors.ts`) and rethrows deterministic failures immediately — constraint violations, bad input syntax, missing tables, aborted PG tx (`25P02`). `@happyvertical/sql` stringifies the driver text into `context.originalError`, so **never match `error.message`**; use `classifyDatabaseError()` / `isUniqueViolationError()` / `isAbortedTransactionError()`.
|
|
213
238
|
- **Field caching**: `_cachedFields` populated during `Collection.create()` — eliminates async `getFields()` per query
|
|
214
239
|
- **Smart cloning**: arrays/objects shallow-cloned in property init to prevent aliasing (Issue #22)
|
|
215
240
|
- **Table verification cache**: `isTableVerified(dbUrl, tableName)` avoids redundant `tableExists()` calls
|
package/README.md
CHANGED
|
@@ -425,7 +425,26 @@ Every `SmrtObject`/`SmrtCollection` can persist learned knowledge via `remember(
|
|
|
425
425
|
| `AIError` | AI provider failures |
|
|
426
426
|
| `ValidationError` | Field/object validation failures |
|
|
427
427
|
| `RuntimeError` | General runtime failures |
|
|
428
|
-
| `ErrorUtils` |
|
|
428
|
+
| `ErrorUtils` | Retry policy (`withRetry`, `isRetryable`) plus sanitization helpers |
|
|
429
|
+
|
|
430
|
+
### Database error classification
|
|
431
|
+
|
|
432
|
+
Driver errors reach the model layer wrapped by `@happyvertical/sql`, which
|
|
433
|
+
stringifies the driver text into `context.originalError` — so the constraint
|
|
434
|
+
wording never appears on `error.message`. Classify through the cause chain
|
|
435
|
+
instead of matching messages.
|
|
436
|
+
|
|
437
|
+
| Export | Description |
|
|
438
|
+
|--------|------------|
|
|
439
|
+
| `classifyDatabaseError` | Walks the cause chain and returns the kind plus `deterministic` / `retryable` |
|
|
440
|
+
| `classifyDialectMessage` | Matches a single raw dialect message (the DuckDB fallback) |
|
|
441
|
+
| `isUniqueViolationError` | Unique or primary-key violation anywhere in the chain |
|
|
442
|
+
| `isNotNullViolationError` | NOT NULL violation anywhere in the chain |
|
|
443
|
+
| `isAbortedTransactionError` | Statement issued inside an aborted PostgreSQL transaction (`25P02`) |
|
|
444
|
+
| `isDeterministicDatabaseError` | A retry cannot change the outcome |
|
|
445
|
+
| `isTransientDatabaseError` | Contention or availability; a retry may succeed |
|
|
446
|
+
| `DatabaseErrorKind` | Union of classification kinds |
|
|
447
|
+
| `DatabaseErrorClassification` | Structured result of `classifyDatabaseError` |
|
|
429
448
|
|
|
430
449
|
### Tools (AI Function Calling)
|
|
431
450
|
|
package/agents/change-feed.md
CHANGED
|
@@ -13,4 +13,4 @@ Adapter-agnostic change-observation spine (`src/change-feed.ts`) — the server
|
|
|
13
13
|
- `getChangesSince(db, { since, tables?, tenantId?, limit? }) → { changes, cursor, resyncRequired?, resyncCursor? }`: strictly monotonic cursor; polling with returned cursors misses no committed change and never repeats one. A cursor that cannot be served incrementally — pruned below the retained `[floor..horizon]` run, or foreign/ahead of the horizon — gets `resyncRequired: true` with empty `changes`, an unadvanced `cursor`, and `resyncCursor` set to the current horizon so clients can full-refetch then resume incrementally; detection runs on the UNFILTERED log so `tables`/`tenantId` filters never trigger or mask it. `getTenantScopedChangesSince()` resolves tenant via the DispatchBus resolver hook (fail-closed: tenancy on + no context → global rows only; tenant `T` sees `T` + global rows, never another tenant).
|
|
14
14
|
- `getTableVersion(db, table) → number`: the per-table change version (`MAX(seq)` for the table, replica-stable — no per-process divergence), the ETag source for zero-query conditional GETs (#1765). Advances on any framework write to the table (CRUD and sync-apply, which all `save()`/`delete()`). A table with no retained entry of its own falls back to the global horizon (never a resettable low value) so an all-pruned table cannot false-304 a stale client; only 0 when the feed is empty.
|
|
15
15
|
- Generated `_changes` routes: REST (`GET {basePath}/_changes`, requires `authMiddleware`, otherwise 401 — per-model `api.public` does NOT apply) and SvelteKit (`{routesDir}/_changes/+server.ts`, requires an authenticated principal on `locals`; opt out via `sveltekit.changesRoute.enabled: false`). Query params: `since`, `tables` (comma-separated), `limit`. Responses stay HTTP 200 in the resync state — `resyncRequired` is protocol state, not an error, and `resyncCursor` is the resume cursor after the client completes a full refetch.
|
|
16
|
-
- Retention: `pruneChangeFeed(db, { maxAgeMs?, maxRows? })` —
|
|
16
|
+
- Retention: `pruneChangeFeed(db, { maxAgeMs?, maxRows?, dryRun? })` — scheduled since #2375 by `runRetentionSweep()` (30-day default), so nothing needs to call it directly; `dryRun` counts the same predicate instead of deleting. Pruning deletes oldest-first and always retains the newest entry (a non-empty feed is never emptied), which is what makes pruned-cursor detection provable and keeps caught-up consumers polling normally. Raw-SQL writes are invisible to the feed (same documented gap as the #1499 cache); `bumpChangeFeed(db, { table, rowId? })` is the manual escape hatch.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<!-- Module doc for packages/core/AGENTS.md. Linked from the Modules table there. -->
|
|
2
|
+
|
|
3
|
+
# Query bounds (#2367)
|
|
4
|
+
|
|
5
|
+
`src/query-bounds.ts` is the single parser every generated read surface uses for
|
|
6
|
+
`limit`/`offset`. A bound is a non-negative integer or it is a client error:
|
|
7
|
+
malformed input raises a 400-typed `QueryBoundsError` (a `ValidationError`, so
|
|
8
|
+
`withRetry` never retries it and `normalizeTypedHttpError` renders it as a
|
|
9
|
+
structured 400) instead of reaching the driver as `LIMIT NaN`. Oversized pages
|
|
10
|
+
are **clamped** to `MAX_LIST_LIMIT` (1000) rather than rejected, and an explicit
|
|
11
|
+
`0` means zero rows — it is not folded into `DEFAULT_LIST_LIMIT` (50).
|
|
12
|
+
|
|
13
|
+
- `collection.get()` / `findOne()` / `findById()` emit `LIMIT 1`.
|
|
14
|
+
- `collection.list()` validates `limit`/`offset` but applies **no implicit
|
|
15
|
+
default**: it is the framework's bulk-read primitive and relationship,
|
|
16
|
+
junction, hierarchy and `listByIds()` callers all expect every matching row, so
|
|
17
|
+
a framework-wide default would truncate correct queries. Applications opt in
|
|
18
|
+
per collection with `defaultListLimit` / `maxListLimit` (validated at
|
|
19
|
+
construction). The generated surfaces enforce the ceiling on their own
|
|
20
|
+
untrusted input regardless.
|
|
21
|
+
- `orderBy` runs the same rail as `where` (#1540) and `select` (#1902): terms are
|
|
22
|
+
checked against the field whitelist and refused for `@field({ sensitive: true })`
|
|
23
|
+
and `@field({ readPermission })` columns — ordering is a comparison, and
|
|
24
|
+
`?orderBy=api_secret&limit=1` is an oracle over a column the request may neither
|
|
25
|
+
filter on nor project — and for fields that are registered but **not
|
|
26
|
+
column-backed** — `oneToMany`/`manyToMany`/`meta`/`transient`, plus the
|
|
27
|
+
`id`/`slug`/`context` system columns on a custom-primary-key class, which the
|
|
28
|
+
schema generator omits — all of which otherwise reach the driver as
|
|
29
|
+
`no such column` and surface as a 500. The whitelist is skipped only for
|
|
30
|
+
manifest-less inline test classes (#869), exactly as `where` skips it. `where`
|
|
31
|
+
still whitelists those omitted system columns; that is a pre-existing gap of
|
|
32
|
+
the same family, not closed here because `where: { id }` is on internal
|
|
33
|
+
hydration paths.
|
|
34
|
+
- Every generated list surface (REST, SvelteKit, MCP, and the emitted stdio MCP
|
|
35
|
+
runtime) pages with `ORDER BY created_at DESC, <pk> ASC` unless the caller
|
|
36
|
+
supplies `orderBy` — `LIMIT`/`OFFSET` with no ordering is not pagination, and
|
|
37
|
+
`created_at` alone still ties. `<pk>` follows a declared
|
|
38
|
+
`@field({ primaryKey: true })` (read from `_meta.primaryKey` in manifests)
|
|
39
|
+
because custom-primary-key classes have no synthetic `id` column. The stdio
|
|
40
|
+
runtime gets the per-object ordering baked in via `RuntimeOptions.listOrderBy`;
|
|
41
|
+
it cannot resolve a primary key on its own. Index: #2363.
|
|
42
|
+
- `listByIds()` chunks its `IN` list at `IN_LIST_CHUNK_SIZE` (900), like the
|
|
43
|
+
relationship/junction/hierarchy loaders.
|
|
44
|
+
|
|
45
|
+
Keyset pagination is deliberately out of scope.
|