docs(namespace): the d.ts JSDoc wave — the sealed field-addressing law on the full find + aggregation surface, present-tense, with the refusal semantics and migration note inline (comment-only; verified zero code lines changed)
Some checks failed
CI / Node 22 (push) Failing after 7m32s
CI / Node 24 (push) Failing after 7m28s
CI / Bun (latest) (push) Successful in 12m14s

This commit is contained in:
David Snelling 2026-08-03 15:11:24 -07:00
parent 5502abcdd8
commit fcb24ab627

View file

@ -498,6 +498,43 @@ export interface UpdateRelationParams<T = any> {
* - **Graph:** `connected` for relationship traversal (via GraphAdjacencyIndex) * - **Graph:** `connected` for relationship traversal (via GraphAdjacencyIndex)
* *
* See also: [Query Operators](../../docs/QUERY_OPERATORS.md) for all `where` operators. * See also: [Query Operators](../../docs/QUERY_OPERATORS.md) for all `where` operators.
*
* @remarks
* **Field-addressing law.** Governs every query-surface field name `where`
* and `orderBy` on this interface, plus `AggregateSource.where` and
* `AggregateDefinition.groupBy` in the aggregation engine:
*
* 1. A bare name (e.g. `'level'`, `'rank'`, `'score'`) always means the
* caller's own metadata field it reads `entity.metadata.<name>`. There
* is no fallback to an engine-internal field of the same name and no
* priority resolution between the two; metadata wins unconditionally.
* 2. `system.<field>` reaches an engine scalar, explicitly, and only for
* these ten: `id`, `type`, `subtype`, `createdAt`, `updatedAt`,
* `confidence`, `weight`, `visibility`, `service`, `createdBy`.
* 3. `vector`, `connections`, `level` (the engine-internal node field a
* different thing from a user metadata field also named `level`),
* `data`, and `_rev` are invisible plumbing: neither spelling can
* address them from a query surface.
* 4. `metadata.<field>` is the explicit spelling of the bare form and means
* exactly the same thing as rule 1.
* 5. A name that matches none of the above most often a bare name that
* collides with one of the ten system-scalar names in rule 2 REFUSES
* with a typed {@link UnresolvableFieldError} naming both candidates,
* e.g. `no metadata field 'createdAt' — did you mean system.createdAt or
* metadata.createdAt?`. The same loud-refusal principle covers whole
* options: the previously accepted-and-silently-ignored `cursor`,
* `includeRelations`, and `writeOnly` now throw
* {@link UnsupportedFindOptionError} instead of doing nothing.
* 6. **Ordering contract** (identical on the pure-JS engine and the native
* accelerator): rows missing or `null` on the `orderBy` field sort LAST
* in BOTH `asc` and `desc` order and are never dropped from the result;
* ties break by `id` ascending.
*
* Migration note: a call site written against the old rule e.g.
* `orderBy: 'createdAt'` or `where: { visibility: 'internal' }` meaning the
* engine scalar now refuses instead of silently reading the wrong field.
* The thrown error names the exact fix (`system.createdAt`). A loud
* refusal with the fix in hand beats a silent behavior flip.
*/ */
export interface FindParams<T = any> { export interface FindParams<T = any> {
// Vector Intelligence // Vector Intelligence
@ -516,7 +553,18 @@ export interface FindParams<T = any> {
* `{ exists: true }`, `{ missing: true }`) use `where: { subtype: { …operators… } }`. * `{ exists: true }`, `{ missing: true }`) use `where: { subtype: { …operators… } }`.
*/ */
subtype?: string | string[] subtype?: string | string[]
/** Metadata filters using BFO operators (e.g., `{ year: { greaterThan: 2020 } }`) */ /**
* Metadata filters using BFO operators (e.g., `{ year: { greaterThan: 2020 } }`).
* Field names follow the field-addressing law see the `@remarks` on
* {@link FindParams}: a bare key is always the caller's metadata field;
* an engine scalar needs the explicit `system.<field>` form.
*
* @example
* ```typescript
* await brain.find({ where: { level: { greaterThan: 5 } } }) // metadata.level
* await brain.find({ where: { 'system.visibility': 'internal' } }) // engine scalar
* ```
*/
where?: Partial<T> where?: Partial<T>
// Visibility // Visibility
@ -548,29 +596,49 @@ export interface FindParams<T = any> {
// Control options // Control options
limit?: number // Max results (default: 10) limit?: number // Max results (default: 10)
offset?: number // Skip N results offset?: number // Skip N results
/**
* @deprecated Not implemented. Passing `cursor` throws
* {@link UnsupportedFindOptionError} it used to be accepted and
* silently ignored, which masked that no cursor pagination ever ran. Use
* `offset` / `limit` until cursor pagination ships.
*/
cursor?: string // Cursor-based pagination cursor?: string // Cursor-based pagination
// Sorting // Sorting
/** /**
* Field to sort by. User metadata fields sort by their stored values * Field to sort by. Follows the field-addressing law (see the `@remarks`
* including natural names like `level`, `rank`, or `score` (an engine-internal * on {@link FindParams}): a bare name (`'level'`, `'rank'`, `'score'`, )
* field can never shadow your metadata; fixed 2026-08 after a production * always sorts by that metadata field; the ten engine scalars sort only
* report). System timestamps (`createdAt`, `updatedAt`) sort by entity age. * via the explicit `system.<field>` form (e.g. `'system.createdAt'`); a
* name that resolves to neither throws {@link UnresolvableFieldError}
* naming the fix.
* *
* Ordering contract (identical on the pure-JS engine and the native * Ordering contract (identical on the pure-JS engine and the native
* accelerator): entities missing the field sort LAST in both directions * accelerator): rows missing or `null` on this field sort LAST in BOTH
* they are never dropped from the result; ties break deterministically. * `asc` and `desc` order and are never dropped from the result; ties
* break by `id` ascending.
* *
* NOTE the field-addressing law is changing (announced 2026-08): bare * @example
* names will mean user metadata ALWAYS, and system fields will be reached * ```typescript
* explicitly as `system.<field>` (e.g. `system.createdAt`), with typed * await brain.find({ orderBy: 'level', order: 'desc' }) // metadata.level
* refusals for unresolvable names. Until that release, bare `createdAt` * await brain.find({ orderBy: 'system.createdAt', order: 'desc' }) // engine scalar
* and friends keep resolving to the system fields as documented above. * ```
*/ */
orderBy?: string orderBy?: string
/**
* Sort direction: `'asc'` (default) or `'desc'`. Per the ordering
* contract on `orderBy`, rows missing/`null` on the sorted field sort
* LAST in both directions `order` never moves them to the front.
*/
order?: 'asc' | 'desc' // Sort direction: 'asc' (default) or 'desc' order?: 'asc' | 'desc' // Sort direction: 'asc' (default) or 'desc'
// Advanced options // Advanced options
/**
* @deprecated Not implemented. Passing `includeRelations` throws
* {@link UnsupportedFindOptionError} it used to be accepted and
* silently ignored, so no relationships were ever attached. Fetch
* relationships separately via `brain.related()`.
*/
includeRelations?: boolean // Include entity relationships includeRelations?: boolean // Include entity relationships
excludeVFS?: boolean // Exclude VFS entities from results (default: false - VFS included) excludeVFS?: boolean // Exclude VFS entities from results (default: false - VFS included)
service?: string // Multi-tenancy filter service?: string // Multi-tenancy filter
@ -603,6 +671,11 @@ export interface FindParams<T = any> {
} }
// Performance options // Performance options
/**
* @deprecated Not implemented. Passing `writeOnly` throws
* {@link UnsupportedFindOptionError} it used to be accepted and
* silently ignored, so validation was never actually skipped.
*/
writeOnly?: boolean // Skip validation for high-speed ingestion writeOnly?: boolean // Skip validation for high-speed ingestion
// Aggregation // Aggregation
@ -1352,7 +1425,10 @@ export type GroupByDimension =
export interface AggregateSource { export interface AggregateSource {
/** Filter by entity type(s) */ /** Filter by entity type(s) */
type?: NounType | NounType[] type?: NounType | NounType[]
/** Metadata filter (same syntax as find({ where })) */ /**
* Metadata filter same syntax and field-addressing law as find()'s
* `where` (see the `@remarks` on {@link FindParams}).
*/
where?: Record<string, unknown> where?: Record<string, unknown>
/** Multi-tenancy service filter */ /** Multi-tenancy service filter */
service?: string service?: string
@ -1366,7 +1442,11 @@ export interface AggregateDefinition {
name: string name: string
/** Which entities contribute to this aggregate */ /** Which entities contribute to this aggregate */
source: AggregateSource source: AggregateSource
/** Dimensions to group by */ /**
* Dimensions to group by field names follow the same field-addressing
* law as find()'s `where` / `orderBy` (see the `@remarks` on
* {@link FindParams}).
*/
groupBy: GroupByDimension[] groupBy: GroupByDimension[]
/** Named metrics to compute */ /** Named metrics to compute */
metrics: Record<string, AggregateMetricDef> metrics: Record<string, AggregateMetricDef>
@ -1425,16 +1505,25 @@ export interface AggregateGroupState {
export interface AggregateQueryParams { export interface AggregateQueryParams {
/** Name of the aggregate to query */ /** Name of the aggregate to query */
name: string name: string
/** Filter aggregate groups by their key values */ /**
* Filter aggregate groups by their key values same field-addressing
* law as find() (see the `@remarks` on {@link FindParams}).
*/
where?: Record<string, unknown> where?: Record<string, unknown>
/** /**
* Filter groups by their computed METRIC values (SQL HAVING). Same BFO operators as * Filter groups by their computed METRIC values (SQL HAVING). Same BFO operators as
* `where`, but applied to the derived metric results plus `count`, e.g. * `where`, but applied to the derived metric results plus `count`, e.g.
* `{ revenue: { greaterThan: 1000 } }`. Evaluated per group (O(groups), independent of * `{ revenue: { greaterThan: 1000 } }`. Evaluated per group (O(groups), independent of
* entity count), before sort/pagination. * entity count), before sort/pagination. Metric names and `count` are looked up
* directly, not field-addressed; a group-KEY field used here follows the same
* field-addressing law as find() (see the `@remarks` on {@link FindParams}).
*/ */
having?: Record<string, unknown> having?: Record<string, unknown>
/** Sort by metric name or group key field */ /**
* Sort by metric name (a key from `metrics`, looked up directly) or by a
* group key field a group key field follows the same field-addressing
* law as find()'s `orderBy` (see the `@remarks` on {@link FindParams}).
*/
orderBy?: string orderBy?: string
/** Sort direction */ /** Sort direction */
order?: 'asc' | 'desc' order?: 'asc' | 'desc'