ObjectStackObjectStack

Data Engine

Data Engine protocol schemas

Data Engine Protocol

Defines the standard interface for data persistence engines in ObjectStack. This protocol abstracts the underlying storage mechanism (SQL, NoSQL, API, Memory), allowing the ObjectQL engine to execute standardized CRUD and Aggregation operations regardless of where the data resides.

The Data Engine acts as the "Driver" layer in the Hexagonal Architecture.

Source: packages/spec/src/data/data-engine.zod.ts

TypeScript Usage

import { BaseEngineOptionsSchema, DataEngineAggregateOptionsSchema, DataEngineAggregateRequestSchema, DataEngineCountOptionsSchema, DataEngineCountRequestSchema, DataEngineDeleteOptionsSchema, DataEngineDeleteRequestSchema, DataEngineExecuteRequestSchema, DataEngineFilterSchema, DataEngineFindOneRequestSchema, DataEngineFindRequestSchema, DataEngineInsertOptionsSchema, DataEngineInsertRequestSchema, DataEngineQueryOptionsSchema, DataEngineRequestSchema, DataEngineSortSchema, DataEngineUpdateOptionsSchema, DataEngineUpdateRequestSchema, DataEngineVectorFindRequestSchema, DroppedFieldsEventSchema, EngineAggregateOptionsSchema, EngineCountOptionsSchema, EngineDeleteOptionsSchema, EngineQueryOptionsSchema, EngineUpdateOptionsSchema } from '@objectstack/spec/data';
import type { BaseEngineOptions, DataEngineAggregateOptions, DataEngineCountOptions, DataEngineDeleteOptions, DataEngineExecuteRequest, DataEngineFilter, DataEngineInsertOptions, DataEngineInsertRequest, DataEngineQueryOptions, DataEngineRequest, DataEngineSort, DataEngineUpdateOptions, DataEngineVectorFindRequest, DroppedFieldsEvent, EngineAggregateOptions, EngineCountOptions, EngineDeleteOptions, EngineQueryOptions, EngineUpdateOptions } from '@objectstack/spec/data';

// Validate data
const result = BaseEngineOptionsSchema.parse(data);

BaseEngineOptions

Properties

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional

Nested Shape: BaseEngineOptions.context

PropertyTypeRequiredDescription
userIdstringoptional
actorstringoptional
attributedUserIdstringoptional
emailstringoptional
tenantIdstringoptional
timezonestringoptional
localestringoptional
currencystringoptional
positionsstring[]optional (default: [])
principalKindEnum<'human' | 'agent' | 'service' | 'guest' | 'system'>optional
audienceEnum<'internal' | 'external'>optional
postureEnum<'PLATFORM_ADMIN' | 'TENANT_ADMIN' | 'MEMBER' | 'EXTERNAL'>optionalADR-0095 D2 posture rung — PLATFORM_ADMIN crosses the tenant wall where object posture permits; TENANT_ADMIN sees all rows in the org; MEMBER gets business RLS; EXTERNAL sees only explicitly shared rows.
authGate{ code: string; message: string }optionalADR-0069 authentication-policy gate: present only while the principal is blocked from protected resources until they remediate (expired password, enforced MFA), absent for every healthy session. code is the stable machine code the client branches on (PASSWORD_EXPIRED / MFA_REQUIRED) and message is what the blocked user reads; both are required because the transport seam renders them as the 403 body. AUTHENTICATION, not authorization — it suspends access entirely rather than narrowing it, and nothing in the permission/RLS path reads it, while the allow-listed remediation endpoints stay reachable. Server-constructed only, never client-supplied; a guest/anonymous principal never carries one.
onBehalfOf{ userId: string; principalKind?: Enum<'human' | 'agent' | 'service' | 'guest' | 'system'> }optional
permissionsstring[]optional (default: [])
systemPermissionsstring[]optional
tabPermissionsRecord<string, Enum<'visible' | 'hidden' | 'default_on' | 'default_off'>>optional
org_user_idsstring[]optional
accessible_org_idsstring[]optional
rlsMembershipRecord<string, string[]>optional
isSystembooleanoptional (default: false)
flowRunIdstringoptional
skipTriggersbooleanoptional
skipAutomationsbooleanoptional
seedReplaybooleanoptional
skipStateMachinebooleanoptional
preserveAuditbooleanoptionalHistorical import: preserve the ORIGINAL audit timeline for this write instead of stamping it "now". Opt-in and server-constructed only, never client-supplied. On the UPDATE path it admits a whitelist — the audit/timestamp family (created_at / created_by / updated_at / updated_by) plus author-declared business readonly fields — while platform-managed system columns (tenancy, generated) stay stripped. On INSERT the exemption does NOT apply: a create is stripped earlier, at the DataProtocol ingress, whose only exemption is context.isSystem, so a non-system create carrying preserveAudit still has those fields stripped and is warned (WARN) that the exemption is UPDATE-only — replaying archival readonly facts on create requires a system context. Permissions / RLS / field-level security are unaffected.
oauthScopesstring[]optional
accessTokenstringoptional
transactionanyoptional
traceIdstringoptional

DataEngineAggregateOptions

Options for DataEngine.aggregate operations

Properties

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
filterRecord<string, any> | anyoptionalData Engine query filter conditions
groupBystring[]optional
aggregations{ field: string; method: Enum<'count' | 'sum' | 'avg' | 'min' | 'max' | 'count_distinct'>; alias?: string }[]optional

Nested Shape: DataEngineAggregateOptions.context

PropertyTypeRequiredDescription
userIdstringoptional
actorstringoptional
attributedUserIdstringoptional
emailstringoptional
tenantIdstringoptional
timezonestringoptional
localestringoptional
currencystringoptional
positionsstring[]optional (default: [])
principalKindEnum<'human' | 'agent' | 'service' | 'guest' | 'system'>optional
audienceEnum<'internal' | 'external'>optional
postureEnum<'PLATFORM_ADMIN' | 'TENANT_ADMIN' | 'MEMBER' | 'EXTERNAL'>optionalADR-0095 D2 posture rung — PLATFORM_ADMIN crosses the tenant wall where object posture permits; TENANT_ADMIN sees all rows in the org; MEMBER gets business RLS; EXTERNAL sees only explicitly shared rows.
authGate{ code: string; message: string }optionalADR-0069 authentication-policy gate: present only while the principal is blocked from protected resources until they remediate (expired password, enforced MFA), absent for every healthy session. code is the stable machine code the client branches on (PASSWORD_EXPIRED / MFA_REQUIRED) and message is what the blocked user reads; both are required because the transport seam renders them as the 403 body. AUTHENTICATION, not authorization — it suspends access entirely rather than narrowing it, and nothing in the permission/RLS path reads it, while the allow-listed remediation endpoints stay reachable. Server-constructed only, never client-supplied; a guest/anonymous principal never carries one.
onBehalfOf{ userId: string; principalKind?: Enum<'human' | 'agent' | 'service' | 'guest' | 'system'> }optional
permissionsstring[]optional (default: [])
systemPermissionsstring[]optional
tabPermissionsRecord<string, Enum<'visible' | 'hidden' | 'default_on' | 'default_off'>>optional
org_user_idsstring[]optional
accessible_org_idsstring[]optional
rlsMembershipRecord<string, string[]>optional
isSystembooleanoptional (default: false)
flowRunIdstringoptional
skipTriggersbooleanoptional
skipAutomationsbooleanoptional
seedReplaybooleanoptional
skipStateMachinebooleanoptional
preserveAuditbooleanoptionalHistorical import: preserve the ORIGINAL audit timeline for this write instead of stamping it "now". Opt-in and server-constructed only, never client-supplied. On the UPDATE path it admits a whitelist — the audit/timestamp family (created_at / created_by / updated_at / updated_by) plus author-declared business readonly fields — while platform-managed system columns (tenancy, generated) stay stripped. On INSERT the exemption does NOT apply: a create is stripped earlier, at the DataProtocol ingress, whose only exemption is context.isSystem, so a non-system create carrying preserveAudit still has those fields stripped and is warned (WARN) that the exemption is UPDATE-only — replaying archival readonly facts on create requires a system context. Permissions / RLS / field-level security are unaffected.
oauthScopesstring[]optional
accessTokenstringoptional
transactionanyoptional
traceIdstringoptional

DataEngineAggregateRequest

Properties

PropertyTypeRequiredDescription
method'aggregate'
objectstring
query{ context?: object; where?: Record<string, any> | any; groupBy?: (string | object)[]; aggregations?: object[]; … }

Nested Shape: DataEngineAggregateRequest.query

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
whereRecord<string, any> | anyoptional
groupBy(string | { field: string; dateGranularity?: Enum<'day' | 'week' | 'month' | 'quarter' | 'year'>; alias?: string })[]optionalGROUP BY targets (strings or {field, dateGranularity?} objects for date bucketing)
aggregations{ function: Enum<'count' | 'sum' | 'avg' | 'min' | 'max' | 'count_distinct'>; field?: string; alias: string; filter?: any }[]optional
havinganyoptionalHAVING — filter over the aggregated rows (aggregation aliases + groupBy projections); applied engine-side after aggregation
timezonestringoptional
filterRecord<string, any> | anyoptionalData Engine query filter conditions

DataEngineCountOptions

Options for DataEngine.count operations

Properties

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
filterRecord<string, any> | anyoptionalData Engine query filter conditions

Nested Shape: DataEngineCountOptions.context

PropertyTypeRequiredDescription
userIdstringoptional
actorstringoptional
attributedUserIdstringoptional
emailstringoptional
tenantIdstringoptional
timezonestringoptional
localestringoptional
currencystringoptional
positionsstring[]optional (default: [])
principalKindEnum<'human' | 'agent' | 'service' | 'guest' | 'system'>optional
audienceEnum<'internal' | 'external'>optional
postureEnum<'PLATFORM_ADMIN' | 'TENANT_ADMIN' | 'MEMBER' | 'EXTERNAL'>optionalADR-0095 D2 posture rung — PLATFORM_ADMIN crosses the tenant wall where object posture permits; TENANT_ADMIN sees all rows in the org; MEMBER gets business RLS; EXTERNAL sees only explicitly shared rows.
authGate{ code: string; message: string }optionalADR-0069 authentication-policy gate: present only while the principal is blocked from protected resources until they remediate (expired password, enforced MFA), absent for every healthy session. code is the stable machine code the client branches on (PASSWORD_EXPIRED / MFA_REQUIRED) and message is what the blocked user reads; both are required because the transport seam renders them as the 403 body. AUTHENTICATION, not authorization — it suspends access entirely rather than narrowing it, and nothing in the permission/RLS path reads it, while the allow-listed remediation endpoints stay reachable. Server-constructed only, never client-supplied; a guest/anonymous principal never carries one.
onBehalfOf{ userId: string; principalKind?: Enum<'human' | 'agent' | 'service' | 'guest' | 'system'> }optional
permissionsstring[]optional (default: [])
systemPermissionsstring[]optional
tabPermissionsRecord<string, Enum<'visible' | 'hidden' | 'default_on' | 'default_off'>>optional
org_user_idsstring[]optional
accessible_org_idsstring[]optional
rlsMembershipRecord<string, string[]>optional
isSystembooleanoptional (default: false)
flowRunIdstringoptional
skipTriggersbooleanoptional
skipAutomationsbooleanoptional
seedReplaybooleanoptional
skipStateMachinebooleanoptional
preserveAuditbooleanoptionalHistorical import: preserve the ORIGINAL audit timeline for this write instead of stamping it "now". Opt-in and server-constructed only, never client-supplied. On the UPDATE path it admits a whitelist — the audit/timestamp family (created_at / created_by / updated_at / updated_by) plus author-declared business readonly fields — while platform-managed system columns (tenancy, generated) stay stripped. On INSERT the exemption does NOT apply: a create is stripped earlier, at the DataProtocol ingress, whose only exemption is context.isSystem, so a non-system create carrying preserveAudit still has those fields stripped and is warned (WARN) that the exemption is UPDATE-only — replaying archival readonly facts on create requires a system context. Permissions / RLS / field-level security are unaffected.
oauthScopesstring[]optional
accessTokenstringoptional
transactionanyoptional
traceIdstringoptional

DataEngineCountRequest

Properties

PropertyTypeRequiredDescription
method'count'
objectstring
query{ context?: object; where?: Record<string, any> | any; filter?: Record<string, any> | any }optional

Nested Shape: DataEngineCountRequest.query

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
whereRecord<string, any> | anyoptional
filterRecord<string, any> | anyoptionalData Engine query filter conditions

DataEngineDeleteOptions

Options for DataEngine.delete operations

Properties

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
filterRecord<string, any> | anyoptionalData Engine query filter conditions
multibooleanoptional (default: false)

Nested Shape: DataEngineDeleteOptions.context

PropertyTypeRequiredDescription
userIdstringoptional
actorstringoptional
attributedUserIdstringoptional
emailstringoptional
tenantIdstringoptional
timezonestringoptional
localestringoptional
currencystringoptional
positionsstring[]optional (default: [])
principalKindEnum<'human' | 'agent' | 'service' | 'guest' | 'system'>optional
audienceEnum<'internal' | 'external'>optional
postureEnum<'PLATFORM_ADMIN' | 'TENANT_ADMIN' | 'MEMBER' | 'EXTERNAL'>optionalADR-0095 D2 posture rung — PLATFORM_ADMIN crosses the tenant wall where object posture permits; TENANT_ADMIN sees all rows in the org; MEMBER gets business RLS; EXTERNAL sees only explicitly shared rows.
authGate{ code: string; message: string }optionalADR-0069 authentication-policy gate: present only while the principal is blocked from protected resources until they remediate (expired password, enforced MFA), absent for every healthy session. code is the stable machine code the client branches on (PASSWORD_EXPIRED / MFA_REQUIRED) and message is what the blocked user reads; both are required because the transport seam renders them as the 403 body. AUTHENTICATION, not authorization — it suspends access entirely rather than narrowing it, and nothing in the permission/RLS path reads it, while the allow-listed remediation endpoints stay reachable. Server-constructed only, never client-supplied; a guest/anonymous principal never carries one.
onBehalfOf{ userId: string; principalKind?: Enum<'human' | 'agent' | 'service' | 'guest' | 'system'> }optional
permissionsstring[]optional (default: [])
systemPermissionsstring[]optional
tabPermissionsRecord<string, Enum<'visible' | 'hidden' | 'default_on' | 'default_off'>>optional
org_user_idsstring[]optional
accessible_org_idsstring[]optional
rlsMembershipRecord<string, string[]>optional
isSystembooleanoptional (default: false)
flowRunIdstringoptional
skipTriggersbooleanoptional
skipAutomationsbooleanoptional
seedReplaybooleanoptional
skipStateMachinebooleanoptional
preserveAuditbooleanoptionalHistorical import: preserve the ORIGINAL audit timeline for this write instead of stamping it "now". Opt-in and server-constructed only, never client-supplied. On the UPDATE path it admits a whitelist — the audit/timestamp family (created_at / created_by / updated_at / updated_by) plus author-declared business readonly fields — while platform-managed system columns (tenancy, generated) stay stripped. On INSERT the exemption does NOT apply: a create is stripped earlier, at the DataProtocol ingress, whose only exemption is context.isSystem, so a non-system create carrying preserveAudit still has those fields stripped and is warned (WARN) that the exemption is UPDATE-only — replaying archival readonly facts on create requires a system context. Permissions / RLS / field-level security are unaffected.
oauthScopesstring[]optional
accessTokenstringoptional
transactionanyoptional
traceIdstringoptional

DataEngineDeleteRequest

Properties

PropertyTypeRequiredDescription
method'delete'
objectstring
idstring | numberoptionalID for single delete, or use where in options
options{ context?: object; where?: Record<string, any> | any; multi?: boolean; filter?: Record<string, any> | any }optional

Nested Shape: DataEngineDeleteRequest.options

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
whereRecord<string, any> | anyoptional
multibooleanoptional (default: false)
filterRecord<string, any> | anyoptionalData Engine query filter conditions

DataEngineExecuteRequest

Properties

PropertyTypeRequiredDescription
method'execute'
commandany
optionsRecord<string, any>optional

DataEngineFilter

Data Engine query filter conditions

Union Options

This schema accepts one of the following structures:

Option 1

Type: Record<string, any>


Option 2

Reference: any



DataEngineFindOneRequest

Properties

PropertyTypeRequiredDescription
method'findOne'
objectstring
query{ context?: object; where?: Record<string, any> | any; fields?: string[]; orderBy?: object[]; … }optional

Nested Shape: DataEngineFindOneRequest.query

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
whereRecord<string, any> | anyoptional
fieldsstring[]optional
orderBy{ field: string; order?: Enum<'asc' | 'desc'> }[]optional
limitnumberoptional
offsetnumberoptional
topnumberoptional
cursorneveroptional[REMOVED] query.cursor was removed in @objectstack/spec 17 (ADR-0049) — no driver ever implemented keyset pagination, so the cursor was accepted and ignored and every page came back identical (a caller looping "until hasMore is false" never terminates). Delete the key; QueryBuilder.cursor() was removed with it. Express the keyset as an ordinary where predicate on your sort key — where: { created_at: { $gt: last.created_at } } with the matching orderBy — which every driver executes with canonicalised comparands. A first-class cursor, if ever built, will be a response-minted opaque token, not this caller-built record.
searchstring | { query: string; fields?: string[]; fuzzy?: boolean; operator?: Enum<'and' | 'or'>; … }optional
searchFieldsstring[]optional
expandRecord<string, { object: string; fields?: string[]; where?: any; search?: string | object; … }>optional
distinctneveroptional[REMOVED] query.distinct was removed in @objectstack/spec 17 (ADR-0049 / ADR-0078) — no driver ever rendered SELECT DISTINCT; the flag's only observable effect was MIS-WIRED: the REST list path treated a distinct query as not countable and silently degraded total/hasMore to a page-local estimate while still returning duplicate rows. Delete the key; QueryBuilder.distinct() was removed with it, and the count suppression is gone (total is truthful again). For unique values of one column use the SQL/memory drivers' distinct(object, field) door; for unique combinations, groupBy; for a deduplicated count, the count_distinct aggregation.
filterRecord<string, any> | anyoptionalData Engine query filter conditions
selectstring[]optional
sortRecord<string, Enum<'asc' | 'desc'>> | Record<string, 1 | -1> | { field: string; order?: Enum<'asc' | 'desc'> }[]optionalSort order definition
skipintegeroptional
populatestring[]optional

DataEngineFindRequest

Properties

PropertyTypeRequiredDescription
method'find'
objectstring
query{ context?: object; where?: Record<string, any> | any; fields?: string[]; orderBy?: object[]; … }optional

Nested Shape: DataEngineFindRequest.query

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
whereRecord<string, any> | anyoptional
fieldsstring[]optional
orderBy{ field: string; order?: Enum<'asc' | 'desc'> }[]optional
limitnumberoptional
offsetnumberoptional
topnumberoptional
cursorneveroptional[REMOVED] query.cursor was removed in @objectstack/spec 17 (ADR-0049) — no driver ever implemented keyset pagination, so the cursor was accepted and ignored and every page came back identical (a caller looping "until hasMore is false" never terminates). Delete the key; QueryBuilder.cursor() was removed with it. Express the keyset as an ordinary where predicate on your sort key — where: { created_at: { $gt: last.created_at } } with the matching orderBy — which every driver executes with canonicalised comparands. A first-class cursor, if ever built, will be a response-minted opaque token, not this caller-built record.
searchstring | { query: string; fields?: string[]; fuzzy?: boolean; operator?: Enum<'and' | 'or'>; … }optional
searchFieldsstring[]optional
expandRecord<string, { object: string; fields?: string[]; where?: any; search?: string | object; … }>optional
distinctneveroptional[REMOVED] query.distinct was removed in @objectstack/spec 17 (ADR-0049 / ADR-0078) — no driver ever rendered SELECT DISTINCT; the flag's only observable effect was MIS-WIRED: the REST list path treated a distinct query as not countable and silently degraded total/hasMore to a page-local estimate while still returning duplicate rows. Delete the key; QueryBuilder.distinct() was removed with it, and the count suppression is gone (total is truthful again). For unique values of one column use the SQL/memory drivers' distinct(object, field) door; for unique combinations, groupBy; for a deduplicated count, the count_distinct aggregation.
filterRecord<string, any> | anyoptionalData Engine query filter conditions
selectstring[]optional
sortRecord<string, Enum<'asc' | 'desc'>> | Record<string, 1 | -1> | { field: string; order?: Enum<'asc' | 'desc'> }[]optionalSort order definition
skipintegeroptional
populatestring[]optional

DataEngineInsertOptions

Options for DataEngine.insert operations

Properties

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
returningbooleanoptional (default: true)

Nested Shape: DataEngineInsertOptions.context

PropertyTypeRequiredDescription
userIdstringoptional
actorstringoptional
attributedUserIdstringoptional
emailstringoptional
tenantIdstringoptional
timezonestringoptional
localestringoptional
currencystringoptional
positionsstring[]optional (default: [])
principalKindEnum<'human' | 'agent' | 'service' | 'guest' | 'system'>optional
audienceEnum<'internal' | 'external'>optional
postureEnum<'PLATFORM_ADMIN' | 'TENANT_ADMIN' | 'MEMBER' | 'EXTERNAL'>optionalADR-0095 D2 posture rung — PLATFORM_ADMIN crosses the tenant wall where object posture permits; TENANT_ADMIN sees all rows in the org; MEMBER gets business RLS; EXTERNAL sees only explicitly shared rows.
authGate{ code: string; message: string }optionalADR-0069 authentication-policy gate: present only while the principal is blocked from protected resources until they remediate (expired password, enforced MFA), absent for every healthy session. code is the stable machine code the client branches on (PASSWORD_EXPIRED / MFA_REQUIRED) and message is what the blocked user reads; both are required because the transport seam renders them as the 403 body. AUTHENTICATION, not authorization — it suspends access entirely rather than narrowing it, and nothing in the permission/RLS path reads it, while the allow-listed remediation endpoints stay reachable. Server-constructed only, never client-supplied; a guest/anonymous principal never carries one.
onBehalfOf{ userId: string; principalKind?: Enum<'human' | 'agent' | 'service' | 'guest' | 'system'> }optional
permissionsstring[]optional (default: [])
systemPermissionsstring[]optional
tabPermissionsRecord<string, Enum<'visible' | 'hidden' | 'default_on' | 'default_off'>>optional
org_user_idsstring[]optional
accessible_org_idsstring[]optional
rlsMembershipRecord<string, string[]>optional
isSystembooleanoptional (default: false)
flowRunIdstringoptional
skipTriggersbooleanoptional
skipAutomationsbooleanoptional
seedReplaybooleanoptional
skipStateMachinebooleanoptional
preserveAuditbooleanoptionalHistorical import: preserve the ORIGINAL audit timeline for this write instead of stamping it "now". Opt-in and server-constructed only, never client-supplied. On the UPDATE path it admits a whitelist — the audit/timestamp family (created_at / created_by / updated_at / updated_by) plus author-declared business readonly fields — while platform-managed system columns (tenancy, generated) stay stripped. On INSERT the exemption does NOT apply: a create is stripped earlier, at the DataProtocol ingress, whose only exemption is context.isSystem, so a non-system create carrying preserveAudit still has those fields stripped and is warned (WARN) that the exemption is UPDATE-only — replaying archival readonly facts on create requires a system context. Permissions / RLS / field-level security are unaffected.
oauthScopesstring[]optional
accessTokenstringoptional
transactionanyoptional
traceIdstringoptional

DataEngineInsertRequest

Properties

PropertyTypeRequiredDescription
method'insert'
objectstring
dataRecord<string, any> | Record<string, any>[]
options{ context?: object; returning?: boolean }optionalOptions for DataEngine.insert operations

DataEngineQueryOptions

Query options for IDataEngine.find() operations

Properties

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
filterRecord<string, any> | anyoptionalData Engine query filter conditions
selectstring[]optional
sortRecord<string, Enum<'asc' | 'desc'>> | Record<string, 1 | -1> | { field: string; order: Enum<'asc' | 'desc'> }[]optionalSort order definition
limitintegeroptional
skipintegeroptional
topintegeroptional
populatestring[]optional

Nested Shape: DataEngineQueryOptions.context

PropertyTypeRequiredDescription
userIdstringoptional
actorstringoptional
attributedUserIdstringoptional
emailstringoptional
tenantIdstringoptional
timezonestringoptional
localestringoptional
currencystringoptional
positionsstring[]optional (default: [])
principalKindEnum<'human' | 'agent' | 'service' | 'guest' | 'system'>optional
audienceEnum<'internal' | 'external'>optional
postureEnum<'PLATFORM_ADMIN' | 'TENANT_ADMIN' | 'MEMBER' | 'EXTERNAL'>optionalADR-0095 D2 posture rung — PLATFORM_ADMIN crosses the tenant wall where object posture permits; TENANT_ADMIN sees all rows in the org; MEMBER gets business RLS; EXTERNAL sees only explicitly shared rows.
authGate{ code: string; message: string }optionalADR-0069 authentication-policy gate: present only while the principal is blocked from protected resources until they remediate (expired password, enforced MFA), absent for every healthy session. code is the stable machine code the client branches on (PASSWORD_EXPIRED / MFA_REQUIRED) and message is what the blocked user reads; both are required because the transport seam renders them as the 403 body. AUTHENTICATION, not authorization — it suspends access entirely rather than narrowing it, and nothing in the permission/RLS path reads it, while the allow-listed remediation endpoints stay reachable. Server-constructed only, never client-supplied; a guest/anonymous principal never carries one.
onBehalfOf{ userId: string; principalKind?: Enum<'human' | 'agent' | 'service' | 'guest' | 'system'> }optional
permissionsstring[]optional (default: [])
systemPermissionsstring[]optional
tabPermissionsRecord<string, Enum<'visible' | 'hidden' | 'default_on' | 'default_off'>>optional
org_user_idsstring[]optional
accessible_org_idsstring[]optional
rlsMembershipRecord<string, string[]>optional
isSystembooleanoptional (default: false)
flowRunIdstringoptional
skipTriggersbooleanoptional
skipAutomationsbooleanoptional
seedReplaybooleanoptional
skipStateMachinebooleanoptional
preserveAuditbooleanoptionalHistorical import: preserve the ORIGINAL audit timeline for this write instead of stamping it "now". Opt-in and server-constructed only, never client-supplied. On the UPDATE path it admits a whitelist — the audit/timestamp family (created_at / created_by / updated_at / updated_by) plus author-declared business readonly fields — while platform-managed system columns (tenancy, generated) stay stripped. On INSERT the exemption does NOT apply: a create is stripped earlier, at the DataProtocol ingress, whose only exemption is context.isSystem, so a non-system create carrying preserveAudit still has those fields stripped and is warned (WARN) that the exemption is UPDATE-only — replaying archival readonly facts on create requires a system context. Permissions / RLS / field-level security are unaffected.
oauthScopesstring[]optional
accessTokenstringoptional
transactionanyoptional
traceIdstringoptional

DataEngineRequest

Virtual ObjectQL Request Protocol

Union Options

This schema accepts one of the following structures:

Option 1

Properties

PropertyTypeRequiredDescription
method'find'
objectstring
query{ context?: object; where?: Record<string, any> | any; fields?: string[]; orderBy?: object[]; … }optional

Nested Shape: DataEngineRequest[method='find'].query

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
whereRecord<string, any> | anyoptional
fieldsstring[]optional
orderBy{ field: string; order?: Enum<'asc' | 'desc'> }[]optional
limitnumberoptional
offsetnumberoptional
topnumberoptional
cursorneveroptional[REMOVED] query.cursor was removed in @objectstack/spec 17 (ADR-0049) — no driver ever implemented keyset pagination, so the cursor was accepted and ignored and every page came back identical (a caller looping "until hasMore is false" never terminates). Delete the key; QueryBuilder.cursor() was removed with it. Express the keyset as an ordinary where predicate on your sort key — where: { created_at: { $gt: last.created_at } } with the matching orderBy — which every driver executes with canonicalised comparands. A first-class cursor, if ever built, will be a response-minted opaque token, not this caller-built record.
searchstring | { query: string; fields?: string[]; fuzzy?: boolean; operator?: Enum<'and' | 'or'>; … }optional
searchFieldsstring[]optional
expandRecord<string, { object: string; fields?: string[]; where?: any; search?: string | object; … }>optional
distinctneveroptional[REMOVED] query.distinct was removed in @objectstack/spec 17 (ADR-0049 / ADR-0078) — no driver ever rendered SELECT DISTINCT; the flag's only observable effect was MIS-WIRED: the REST list path treated a distinct query as not countable and silently degraded total/hasMore to a page-local estimate while still returning duplicate rows. Delete the key; QueryBuilder.distinct() was removed with it, and the count suppression is gone (total is truthful again). For unique values of one column use the SQL/memory drivers' distinct(object, field) door; for unique combinations, groupBy; for a deduplicated count, the count_distinct aggregation.
filterRecord<string, any> | anyoptionalData Engine query filter conditions
selectstring[]optional
sortRecord<string, Enum<'asc' | 'desc'>> | Record<string, 1 | -1> | { field: string; order?: Enum<'asc' | 'desc'> }[]optionalSort order definition
skipintegeroptional
populatestring[]optional

Option 2

Properties

PropertyTypeRequiredDescription
method'findOne'
objectstring
query{ context?: object; where?: Record<string, any> | any; fields?: string[]; orderBy?: object[]; … }optional

Nested Shape: DataEngineRequest[method='findOne'].query

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
whereRecord<string, any> | anyoptional
fieldsstring[]optional
orderBy{ field: string; order?: Enum<'asc' | 'desc'> }[]optional
limitnumberoptional
offsetnumberoptional
topnumberoptional
cursorneveroptional[REMOVED] query.cursor was removed in @objectstack/spec 17 (ADR-0049) — no driver ever implemented keyset pagination, so the cursor was accepted and ignored and every page came back identical (a caller looping "until hasMore is false" never terminates). Delete the key; QueryBuilder.cursor() was removed with it. Express the keyset as an ordinary where predicate on your sort key — where: { created_at: { $gt: last.created_at } } with the matching orderBy — which every driver executes with canonicalised comparands. A first-class cursor, if ever built, will be a response-minted opaque token, not this caller-built record.
searchstring | { query: string; fields?: string[]; fuzzy?: boolean; operator?: Enum<'and' | 'or'>; … }optional
searchFieldsstring[]optional
expandRecord<string, { object: string; fields?: string[]; where?: any; search?: string | object; … }>optional
distinctneveroptional[REMOVED] query.distinct was removed in @objectstack/spec 17 (ADR-0049 / ADR-0078) — no driver ever rendered SELECT DISTINCT; the flag's only observable effect was MIS-WIRED: the REST list path treated a distinct query as not countable and silently degraded total/hasMore to a page-local estimate while still returning duplicate rows. Delete the key; QueryBuilder.distinct() was removed with it, and the count suppression is gone (total is truthful again). For unique values of one column use the SQL/memory drivers' distinct(object, field) door; for unique combinations, groupBy; for a deduplicated count, the count_distinct aggregation.
filterRecord<string, any> | anyoptionalData Engine query filter conditions
selectstring[]optional
sortRecord<string, Enum<'asc' | 'desc'>> | Record<string, 1 | -1> | { field: string; order?: Enum<'asc' | 'desc'> }[]optionalSort order definition
skipintegeroptional
populatestring[]optional

Option 3

Properties

PropertyTypeRequiredDescription
method'insert'
objectstring
dataRecord<string, any> | Record<string, any>[]
options{ context?: object; returning?: boolean }optionalOptions for DataEngine.insert operations

Option 4

Properties

PropertyTypeRequiredDescription
method'update'
objectstring
dataRecord<string, any>
idstring | numberoptionalID for single update, or use where in options
options{ context?: object; where?: Record<string, any> | any; multi?: boolean; returning?: boolean; … }optional

Nested Shape: DataEngineRequest[method='update'].options

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
whereRecord<string, any> | anyoptional
upsertneveroptional[REMOVED] update.options.upsert was removed in @objectstack/spec 17 (ADR-0049) — it was declared and allowlisted but never implemented: no engine or driver path ever read it, so { upsert: true } was accepted and silently dropped and the update stayed a plain update. Delete the key. Express create-if-absent explicitly: a by-id update whose id names no row throws RECORD_NOT_FOUND (the by-id not-found gate) rather than inserting, so read the row first (findOne) and call insert or update on what you find. A first-class upsert, if ever built, must reconcile with that gate by design rather than through this silent flag.
multibooleanoptional (default: false)
returningbooleanoptional (default: false)
filterRecord<string, any> | anyoptionalData Engine query filter conditions

Option 5

Properties

PropertyTypeRequiredDescription
method'delete'
objectstring
idstring | numberoptionalID for single delete, or use where in options
options{ context?: object; where?: Record<string, any> | any; multi?: boolean; filter?: Record<string, any> | any }optional

Nested Shape: DataEngineRequest[method='delete'].options

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
whereRecord<string, any> | anyoptional
multibooleanoptional (default: false)
filterRecord<string, any> | anyoptionalData Engine query filter conditions

Option 6

Properties

PropertyTypeRequiredDescription
method'count'
objectstring
query{ context?: object; where?: Record<string, any> | any; filter?: Record<string, any> | any }optional

Nested Shape: DataEngineRequest[method='count'].query

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
whereRecord<string, any> | anyoptional
filterRecord<string, any> | anyoptionalData Engine query filter conditions

Option 7

Properties

PropertyTypeRequiredDescription
method'aggregate'
objectstring
query{ context?: object; where?: Record<string, any> | any; groupBy?: (string | object)[]; aggregations?: object[]; … }

Nested Shape: DataEngineRequest[method='aggregate'].query

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
whereRecord<string, any> | anyoptional
groupBy(string | { field: string; dateGranularity?: Enum<'day' | 'week' | 'month' | 'quarter' | 'year'>; alias?: string })[]optionalGROUP BY targets (strings or {field, dateGranularity?} objects for date bucketing)
aggregations{ function: Enum<'count' | 'sum' | 'avg' | 'min' | 'max' | 'count_distinct'>; field?: string; alias: string; filter?: any }[]optional
havinganyoptionalHAVING — filter over the aggregated rows (aggregation aliases + groupBy projections); applied engine-side after aggregation
timezonestringoptional
filterRecord<string, any> | anyoptionalData Engine query filter conditions

Option 8

Properties

PropertyTypeRequiredDescription
method'execute'
commandany
optionsRecord<string, any>optional

Option 9

Properties

PropertyTypeRequiredDescription
method'vectorFind'
objectstring
vectornumber[]
whereRecord<string, any> | anyoptional
fieldsstring[]optional
limitintegeroptional (default: 5)
thresholdnumberoptional


DataEngineSort

Sort order definition

Union Options

This schema accepts one of the following structures:

Option 1

Type: Record<string, Enum<'asc' | 'desc'>>


Option 2

Type: Record<string, 1 | -1>


Option 3

Type: { field: string; order: Enum<'asc' | 'desc'> }[]



DataEngineUpdateOptions

Options for DataEngine.update operations

Properties

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
filterRecord<string, any> | anyoptionalData Engine query filter conditions
upsertneveroptional[REMOVED] update.options.upsert was removed in @objectstack/spec 17 (ADR-0049) — it was declared and allowlisted but never implemented: no engine or driver path ever read it, so { upsert: true } was accepted and silently dropped and the update stayed a plain update. Delete the key. Express create-if-absent explicitly: a by-id update whose id names no row throws RECORD_NOT_FOUND (the by-id not-found gate) rather than inserting, so read the row first (findOne) and call insert or update on what you find. A first-class upsert, if ever built, must reconcile with that gate by design rather than through this silent flag.
multibooleanoptional (default: false)
returningbooleanoptional (default: false)

Nested Shape: DataEngineUpdateOptions.context

PropertyTypeRequiredDescription
userIdstringoptional
actorstringoptional
attributedUserIdstringoptional
emailstringoptional
tenantIdstringoptional
timezonestringoptional
localestringoptional
currencystringoptional
positionsstring[]optional (default: [])
principalKindEnum<'human' | 'agent' | 'service' | 'guest' | 'system'>optional
audienceEnum<'internal' | 'external'>optional
postureEnum<'PLATFORM_ADMIN' | 'TENANT_ADMIN' | 'MEMBER' | 'EXTERNAL'>optionalADR-0095 D2 posture rung — PLATFORM_ADMIN crosses the tenant wall where object posture permits; TENANT_ADMIN sees all rows in the org; MEMBER gets business RLS; EXTERNAL sees only explicitly shared rows.
authGate{ code: string; message: string }optionalADR-0069 authentication-policy gate: present only while the principal is blocked from protected resources until they remediate (expired password, enforced MFA), absent for every healthy session. code is the stable machine code the client branches on (PASSWORD_EXPIRED / MFA_REQUIRED) and message is what the blocked user reads; both are required because the transport seam renders them as the 403 body. AUTHENTICATION, not authorization — it suspends access entirely rather than narrowing it, and nothing in the permission/RLS path reads it, while the allow-listed remediation endpoints stay reachable. Server-constructed only, never client-supplied; a guest/anonymous principal never carries one.
onBehalfOf{ userId: string; principalKind?: Enum<'human' | 'agent' | 'service' | 'guest' | 'system'> }optional
permissionsstring[]optional (default: [])
systemPermissionsstring[]optional
tabPermissionsRecord<string, Enum<'visible' | 'hidden' | 'default_on' | 'default_off'>>optional
org_user_idsstring[]optional
accessible_org_idsstring[]optional
rlsMembershipRecord<string, string[]>optional
isSystembooleanoptional (default: false)
flowRunIdstringoptional
skipTriggersbooleanoptional
skipAutomationsbooleanoptional
seedReplaybooleanoptional
skipStateMachinebooleanoptional
preserveAuditbooleanoptionalHistorical import: preserve the ORIGINAL audit timeline for this write instead of stamping it "now". Opt-in and server-constructed only, never client-supplied. On the UPDATE path it admits a whitelist — the audit/timestamp family (created_at / created_by / updated_at / updated_by) plus author-declared business readonly fields — while platform-managed system columns (tenancy, generated) stay stripped. On INSERT the exemption does NOT apply: a create is stripped earlier, at the DataProtocol ingress, whose only exemption is context.isSystem, so a non-system create carrying preserveAudit still has those fields stripped and is warned (WARN) that the exemption is UPDATE-only — replaying archival readonly facts on create requires a system context. Permissions / RLS / field-level security are unaffected.
oauthScopesstring[]optional
accessTokenstringoptional
transactionanyoptional
traceIdstringoptional

DataEngineUpdateRequest

Properties

PropertyTypeRequiredDescription
method'update'
objectstring
dataRecord<string, any>
idstring | numberoptionalID for single update, or use where in options
options{ context?: object; where?: Record<string, any> | any; multi?: boolean; returning?: boolean; … }optional

Nested Shape: DataEngineUpdateRequest.options

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
whereRecord<string, any> | anyoptional
upsertneveroptional[REMOVED] update.options.upsert was removed in @objectstack/spec 17 (ADR-0049) — it was declared and allowlisted but never implemented: no engine or driver path ever read it, so { upsert: true } was accepted and silently dropped and the update stayed a plain update. Delete the key. Express create-if-absent explicitly: a by-id update whose id names no row throws RECORD_NOT_FOUND (the by-id not-found gate) rather than inserting, so read the row first (findOne) and call insert or update on what you find. A first-class upsert, if ever built, must reconcile with that gate by design rather than through this silent flag.
multibooleanoptional (default: false)
returningbooleanoptional (default: false)
filterRecord<string, any> | anyoptionalData Engine query filter conditions

DataEngineVectorFindRequest

Properties

PropertyTypeRequiredDescription
method'vectorFind'
objectstring
vectornumber[]
whereRecord<string, any> | anyoptional
fieldsstring[]optional
limitintegeroptional (default: 5)
thresholdnumberoptional

DroppedFieldsEvent

A write-path strip event: caller-supplied fields legally dropped from the payload

Properties

PropertyTypeRequiredDescription
objectstringObject the write targeted (resolved object name)
fieldsstring[]Caller-supplied field names the engine removed from the write payload
reasonEnum<'readonly' | 'readonly_when' | 'primary_key'>Why the fields were dropped: static readonly, a TRUE readonlyWhen predicate, or the primary-key strip of a payload id the engine ruled is not an identifier

EngineAggregateOptions

QueryAST-aligned options for DataEngine.aggregate operations

Properties

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
whereRecord<string, any> | anyoptional
groupBy(string | { field: string; dateGranularity?: Enum<'day' | 'week' | 'month' | 'quarter' | 'year'>; alias?: string })[]optionalGROUP BY targets (strings or {field, dateGranularity?} objects for date bucketing)
aggregations{ function: Enum<'count' | 'sum' | 'avg' | 'min' | 'max' | 'count_distinct'>; field?: string; alias: string; filter?: any }[]optional
havinganyoptionalHAVING — filter over the aggregated rows (aggregation aliases + groupBy projections); applied engine-side after aggregation
timezonestringoptional

Nested Shape: EngineAggregateOptions.context

PropertyTypeRequiredDescription
userIdstringoptional
actorstringoptional
attributedUserIdstringoptional
emailstringoptional
tenantIdstringoptional
timezonestringoptional
localestringoptional
currencystringoptional
positionsstring[]optional (default: [])
principalKindEnum<'human' | 'agent' | 'service' | 'guest' | 'system'>optional
audienceEnum<'internal' | 'external'>optional
postureEnum<'PLATFORM_ADMIN' | 'TENANT_ADMIN' | 'MEMBER' | 'EXTERNAL'>optionalADR-0095 D2 posture rung — PLATFORM_ADMIN crosses the tenant wall where object posture permits; TENANT_ADMIN sees all rows in the org; MEMBER gets business RLS; EXTERNAL sees only explicitly shared rows.
authGate{ code: string; message: string }optionalADR-0069 authentication-policy gate: present only while the principal is blocked from protected resources until they remediate (expired password, enforced MFA), absent for every healthy session. code is the stable machine code the client branches on (PASSWORD_EXPIRED / MFA_REQUIRED) and message is what the blocked user reads; both are required because the transport seam renders them as the 403 body. AUTHENTICATION, not authorization — it suspends access entirely rather than narrowing it, and nothing in the permission/RLS path reads it, while the allow-listed remediation endpoints stay reachable. Server-constructed only, never client-supplied; a guest/anonymous principal never carries one.
onBehalfOf{ userId: string; principalKind?: Enum<'human' | 'agent' | 'service' | 'guest' | 'system'> }optional
permissionsstring[]optional (default: [])
systemPermissionsstring[]optional
tabPermissionsRecord<string, Enum<'visible' | 'hidden' | 'default_on' | 'default_off'>>optional
org_user_idsstring[]optional
accessible_org_idsstring[]optional
rlsMembershipRecord<string, string[]>optional
isSystembooleanoptional (default: false)
flowRunIdstringoptional
skipTriggersbooleanoptional
skipAutomationsbooleanoptional
seedReplaybooleanoptional
skipStateMachinebooleanoptional
preserveAuditbooleanoptionalHistorical import: preserve the ORIGINAL audit timeline for this write instead of stamping it "now". Opt-in and server-constructed only, never client-supplied. On the UPDATE path it admits a whitelist — the audit/timestamp family (created_at / created_by / updated_at / updated_by) plus author-declared business readonly fields — while platform-managed system columns (tenancy, generated) stay stripped. On INSERT the exemption does NOT apply: a create is stripped earlier, at the DataProtocol ingress, whose only exemption is context.isSystem, so a non-system create carrying preserveAudit still has those fields stripped and is warned (WARN) that the exemption is UPDATE-only — replaying archival readonly facts on create requires a system context. Permissions / RLS / field-level security are unaffected.
oauthScopesstring[]optional
accessTokenstringoptional
transactionanyoptional
traceIdstringoptional

Nested Shape: EngineAggregateOptions.groupBy[number]

PropertyTypeRequiredDescription
fieldstringField to group by
dateGranularityEnum<'day' | 'week' | 'month' | 'quarter' | 'year'>optionalBucket date values into uniform periods (day/week/month/quarter/year)
aliasstringoptionalAlias for the projected group value

Nested Shape: EngineAggregateOptions.aggregations[number]

PropertyTypeRequiredDescription
functionEnum<'count' | 'sum' | 'avg' | 'min' | 'max' | 'count_distinct'>Aggregation function
fieldstringoptionalField to aggregate (optional for COUNT(*))
aliasstringResult column alias
distinctneveroptional[REMOVED] query.aggregations[].distinct was removed in @objectstack/spec 17 (ADR-0049) — exactly ONE of the six faces that read an aggregation honoured it. The objectql in-memory fallback deduplicated the values before applying the function, while driver-sql, driver-turso, driver-mongodb, driver-memory and the service-analytics SQL builder all ignored it — so { function: 'sum', field: 'amount', distinct: true } answered a DEDUPLICATED sum when the engine fell back in memory and an ordinary sum on every SQL datasource: one query, two numbers, chosen by which backend happened to serve it. Both answers are plausible, so nothing surfaced the divergence. Delete the key. For a deduplicated COUNT the live spelling is the count_distinct aggregation function, which every SQL face compiles to COUNT(DISTINCT field) and the in-memory fallback computes identically. SUM(DISTINCT …) / AVG(DISTINCT …) get no replacement: no backend ever computed them here, and a per-row measure that needs deduplicating is a modelling problem to fix in the data, not a flag on the read.
filteranyoptionalPer-aggregation filter (SQL FILTER (WHERE …) semantics): narrows the source rows THIS aggregation reads, leaving sibling aggregations unfiltered. Enforced by engine.aggregate: lowered in memory for drivers without native conditional aggregation; a driver reached directly refuses rather than silently dropping it.

EngineCountOptions

QueryAST-aligned options for DataEngine.count operations

Properties

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
whereRecord<string, any> | anyoptional

Nested Shape: EngineCountOptions.context

PropertyTypeRequiredDescription
userIdstringoptional
actorstringoptional
attributedUserIdstringoptional
emailstringoptional
tenantIdstringoptional
timezonestringoptional
localestringoptional
currencystringoptional
positionsstring[]optional (default: [])
principalKindEnum<'human' | 'agent' | 'service' | 'guest' | 'system'>optional
audienceEnum<'internal' | 'external'>optional
postureEnum<'PLATFORM_ADMIN' | 'TENANT_ADMIN' | 'MEMBER' | 'EXTERNAL'>optionalADR-0095 D2 posture rung — PLATFORM_ADMIN crosses the tenant wall where object posture permits; TENANT_ADMIN sees all rows in the org; MEMBER gets business RLS; EXTERNAL sees only explicitly shared rows.
authGate{ code: string; message: string }optionalADR-0069 authentication-policy gate: present only while the principal is blocked from protected resources until they remediate (expired password, enforced MFA), absent for every healthy session. code is the stable machine code the client branches on (PASSWORD_EXPIRED / MFA_REQUIRED) and message is what the blocked user reads; both are required because the transport seam renders them as the 403 body. AUTHENTICATION, not authorization — it suspends access entirely rather than narrowing it, and nothing in the permission/RLS path reads it, while the allow-listed remediation endpoints stay reachable. Server-constructed only, never client-supplied; a guest/anonymous principal never carries one.
onBehalfOf{ userId: string; principalKind?: Enum<'human' | 'agent' | 'service' | 'guest' | 'system'> }optional
permissionsstring[]optional (default: [])
systemPermissionsstring[]optional
tabPermissionsRecord<string, Enum<'visible' | 'hidden' | 'default_on' | 'default_off'>>optional
org_user_idsstring[]optional
accessible_org_idsstring[]optional
rlsMembershipRecord<string, string[]>optional
isSystembooleanoptional (default: false)
flowRunIdstringoptional
skipTriggersbooleanoptional
skipAutomationsbooleanoptional
seedReplaybooleanoptional
skipStateMachinebooleanoptional
preserveAuditbooleanoptionalHistorical import: preserve the ORIGINAL audit timeline for this write instead of stamping it "now". Opt-in and server-constructed only, never client-supplied. On the UPDATE path it admits a whitelist — the audit/timestamp family (created_at / created_by / updated_at / updated_by) plus author-declared business readonly fields — while platform-managed system columns (tenancy, generated) stay stripped. On INSERT the exemption does NOT apply: a create is stripped earlier, at the DataProtocol ingress, whose only exemption is context.isSystem, so a non-system create carrying preserveAudit still has those fields stripped and is warned (WARN) that the exemption is UPDATE-only — replaying archival readonly facts on create requires a system context. Permissions / RLS / field-level security are unaffected.
oauthScopesstring[]optional
accessTokenstringoptional
transactionanyoptional
traceIdstringoptional

EngineDeleteOptions

QueryAST-aligned options for DataEngine.delete operations

Properties

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
whereRecord<string, any> | anyoptional
multibooleanoptional (default: false)

Nested Shape: EngineDeleteOptions.context

PropertyTypeRequiredDescription
userIdstringoptional
actorstringoptional
attributedUserIdstringoptional
emailstringoptional
tenantIdstringoptional
timezonestringoptional
localestringoptional
currencystringoptional
positionsstring[]optional (default: [])
principalKindEnum<'human' | 'agent' | 'service' | 'guest' | 'system'>optional
audienceEnum<'internal' | 'external'>optional
postureEnum<'PLATFORM_ADMIN' | 'TENANT_ADMIN' | 'MEMBER' | 'EXTERNAL'>optionalADR-0095 D2 posture rung — PLATFORM_ADMIN crosses the tenant wall where object posture permits; TENANT_ADMIN sees all rows in the org; MEMBER gets business RLS; EXTERNAL sees only explicitly shared rows.
authGate{ code: string; message: string }optionalADR-0069 authentication-policy gate: present only while the principal is blocked from protected resources until they remediate (expired password, enforced MFA), absent for every healthy session. code is the stable machine code the client branches on (PASSWORD_EXPIRED / MFA_REQUIRED) and message is what the blocked user reads; both are required because the transport seam renders them as the 403 body. AUTHENTICATION, not authorization — it suspends access entirely rather than narrowing it, and nothing in the permission/RLS path reads it, while the allow-listed remediation endpoints stay reachable. Server-constructed only, never client-supplied; a guest/anonymous principal never carries one.
onBehalfOf{ userId: string; principalKind?: Enum<'human' | 'agent' | 'service' | 'guest' | 'system'> }optional
permissionsstring[]optional (default: [])
systemPermissionsstring[]optional
tabPermissionsRecord<string, Enum<'visible' | 'hidden' | 'default_on' | 'default_off'>>optional
org_user_idsstring[]optional
accessible_org_idsstring[]optional
rlsMembershipRecord<string, string[]>optional
isSystembooleanoptional (default: false)
flowRunIdstringoptional
skipTriggersbooleanoptional
skipAutomationsbooleanoptional
seedReplaybooleanoptional
skipStateMachinebooleanoptional
preserveAuditbooleanoptionalHistorical import: preserve the ORIGINAL audit timeline for this write instead of stamping it "now". Opt-in and server-constructed only, never client-supplied. On the UPDATE path it admits a whitelist — the audit/timestamp family (created_at / created_by / updated_at / updated_by) plus author-declared business readonly fields — while platform-managed system columns (tenancy, generated) stay stripped. On INSERT the exemption does NOT apply: a create is stripped earlier, at the DataProtocol ingress, whose only exemption is context.isSystem, so a non-system create carrying preserveAudit still has those fields stripped and is warned (WARN) that the exemption is UPDATE-only — replaying archival readonly facts on create requires a system context. Permissions / RLS / field-level security are unaffected.
oauthScopesstring[]optional
accessTokenstringoptional
transactionanyoptional
traceIdstringoptional

EngineQueryOptions

QueryAST-aligned query options for IDataEngine.find() operations

Properties

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
whereRecord<string, any> | anyoptional
fieldsstring[]optional
orderBy{ field: string; order: Enum<'asc' | 'desc'> }[]optional
limitnumberoptional
offsetnumberoptional
topnumberoptional
cursorneveroptional[REMOVED] query.cursor was removed in @objectstack/spec 17 (ADR-0049) — no driver ever implemented keyset pagination, so the cursor was accepted and ignored and every page came back identical (a caller looping "until hasMore is false" never terminates). Delete the key; QueryBuilder.cursor() was removed with it. Express the keyset as an ordinary where predicate on your sort key — where: { created_at: { $gt: last.created_at } } with the matching orderBy — which every driver executes with canonicalised comparands. A first-class cursor, if ever built, will be a response-minted opaque token, not this caller-built record.
searchstring | { query: string; fields?: string[]; fuzzy: boolean; operator: Enum<'and' | 'or'>; … }optional
searchFieldsstring[]optional
expandRecord<string, { object: string; fields?: string[]; where?: any; search?: string | object; … }>optional
distinctneveroptional[REMOVED] query.distinct was removed in @objectstack/spec 17 (ADR-0049 / ADR-0078) — no driver ever rendered SELECT DISTINCT; the flag's only observable effect was MIS-WIRED: the REST list path treated a distinct query as not countable and silently degraded total/hasMore to a page-local estimate while still returning duplicate rows. Delete the key; QueryBuilder.distinct() was removed with it, and the count suppression is gone (total is truthful again). For unique values of one column use the SQL/memory drivers' distinct(object, field) door; for unique combinations, groupBy; for a deduplicated count, the count_distinct aggregation.

Nested Shape: EngineQueryOptions.context

PropertyTypeRequiredDescription
userIdstringoptional
actorstringoptional
attributedUserIdstringoptional
emailstringoptional
tenantIdstringoptional
timezonestringoptional
localestringoptional
currencystringoptional
positionsstring[]optional (default: [])
principalKindEnum<'human' | 'agent' | 'service' | 'guest' | 'system'>optional
audienceEnum<'internal' | 'external'>optional
postureEnum<'PLATFORM_ADMIN' | 'TENANT_ADMIN' | 'MEMBER' | 'EXTERNAL'>optionalADR-0095 D2 posture rung — PLATFORM_ADMIN crosses the tenant wall where object posture permits; TENANT_ADMIN sees all rows in the org; MEMBER gets business RLS; EXTERNAL sees only explicitly shared rows.
authGate{ code: string; message: string }optionalADR-0069 authentication-policy gate: present only while the principal is blocked from protected resources until they remediate (expired password, enforced MFA), absent for every healthy session. code is the stable machine code the client branches on (PASSWORD_EXPIRED / MFA_REQUIRED) and message is what the blocked user reads; both are required because the transport seam renders them as the 403 body. AUTHENTICATION, not authorization — it suspends access entirely rather than narrowing it, and nothing in the permission/RLS path reads it, while the allow-listed remediation endpoints stay reachable. Server-constructed only, never client-supplied; a guest/anonymous principal never carries one.
onBehalfOf{ userId: string; principalKind?: Enum<'human' | 'agent' | 'service' | 'guest' | 'system'> }optional
permissionsstring[]optional (default: [])
systemPermissionsstring[]optional
tabPermissionsRecord<string, Enum<'visible' | 'hidden' | 'default_on' | 'default_off'>>optional
org_user_idsstring[]optional
accessible_org_idsstring[]optional
rlsMembershipRecord<string, string[]>optional
isSystembooleanoptional (default: false)
flowRunIdstringoptional
skipTriggersbooleanoptional
skipAutomationsbooleanoptional
seedReplaybooleanoptional
skipStateMachinebooleanoptional
preserveAuditbooleanoptionalHistorical import: preserve the ORIGINAL audit timeline for this write instead of stamping it "now". Opt-in and server-constructed only, never client-supplied. On the UPDATE path it admits a whitelist — the audit/timestamp family (created_at / created_by / updated_at / updated_by) plus author-declared business readonly fields — while platform-managed system columns (tenancy, generated) stay stripped. On INSERT the exemption does NOT apply: a create is stripped earlier, at the DataProtocol ingress, whose only exemption is context.isSystem, so a non-system create carrying preserveAudit still has those fields stripped and is warned (WARN) that the exemption is UPDATE-only — replaying archival readonly facts on create requires a system context. Permissions / RLS / field-level security are unaffected.
oauthScopesstring[]optional
accessTokenstringoptional
transactionanyoptional
traceIdstringoptional

Nested Shape: EngineQueryOptions.search

PropertyTypeRequiredDescription
querystringSearch query text
fieldsstring[]optionalFields to search in (if not specified, searches all text fields)
fuzzybooleanoptional (default: false)[EXPERIMENTAL — not enforced] Fuzzy matching (tolerate typos). The ADR-0061 expansion reads only query + fields; no executor receives this flag.
operatorEnum<'and' | 'or'>optional (default: "or")[EXPERIMENTAL — not enforced] Logical operator between terms. The ADR-0061 expansion applies its own term semantics; no executor receives this flag.
boostRecord<string, number>optional[EXPERIMENTAL — not enforced] Field-specific relevance boosting (field name -> boost factor). No executor scores results.
minScorenumberoptional[EXPERIMENTAL — not enforced] Minimum relevance score threshold. No executor scores results.
languagestringoptional[EXPERIMENTAL — not enforced] Language for text analysis (e.g., "en", "zh", "es"). No executor selects an analyzer.
highlightbooleanoptional (default: false)[EXPERIMENTAL — not enforced] Search result highlighting. No executor emits highlights.

Nested Shape: EngineQueryOptions.expand[string]

PropertyTypeRequiredDescription
objectstringObject name (e.g. account)
fieldsstring[]optionalFields to retrieve — names of the queried object's OWN columns. A dotted path (owner.name) is not a projection: no driver resolves one, and the ingress refuses it with 400 INVALID_FIELD. Related data is read with expand, whose nested QueryAST both filters (where) and selects (fields) the related record's columns. The projection must RETAIN the foreign-key column: fields: ['title'] with expand: 'project_id' resolves nothing, because the relation is carried by that key — add 'project_id' and it works. Where the value is wanted on the queried object itself, denormalise it onto that object (a stored field, written when the source changes), the same remedy the sort axis prescribes.
whereanyoptionalFiltering criteria (WHERE)
searchstring | { query: string; fields?: string[]; fuzzy: boolean; operator: Enum<'and' | 'or'>; … }optionalFull-text search — the query text (canonical, ADR-0061 D1), or a structured FullTextSearch configuration
searchFieldsstring[]optionalNarrow the search to these fields (server-intersected with the allowed searchable set — can only narrow, never widen; ADR-0061 D1)
orderBy{ field: string; order: Enum<'asc' | 'desc'> }[]optionalSorting instructions (ORDER BY)
limitnumberoptionalMax records to return (LIMIT)
offsetnumberoptionalRecords to skip (OFFSET)
topnumberoptionalAlias for limit (OData compatibility)
cursorneveroptional[REMOVED] query.cursor was removed in @objectstack/spec 17 (ADR-0049) — no driver ever implemented keyset pagination, so the cursor was accepted and ignored and every page came back identical (a caller looping "until hasMore is false" never terminates). Delete the key; QueryBuilder.cursor() was removed with it. Express the keyset as an ordinary where predicate on your sort key — where: { created_at: { $gt: last.created_at } } with the matching orderBy — which every driver executes with canonicalised comparands. A first-class cursor, if ever built, will be a response-minted opaque token, not this caller-built record.
joinsneveroptional[REMOVED] query.joins was removed in @objectstack/spec 17 (ADR-0049) — no engine or driver ever read it: a query carrying joins behaved exactly as if the key were absent, while its name squatted on the reserved REST parameter set. Delete the key. Related records are read through expandexpand: { owner_id: { object: 'user', fields: ['name'] } } — which the engine resolves via batch $in queries, and whose nested query selects the related record's own columns. Keep the foreign key in your own projection (fields: ['title', 'owner_id']): the relation is carried by that column, so projecting it away leaves expansion nothing to resolve. A dotted fields path is NOT a replacement — no driver ever resolved one and the ingress refuses it (400 INVALID_FIELD).
aggregations{ function: Enum<'count' | 'sum' | 'avg' | 'min' | 'max' | 'count_distinct'>; field?: string; alias: string; filter?: any }[]optionalAggregation functions
groupBy(string | { field: string; dateGranularity?: Enum<'day' | 'week' | 'month' | 'quarter' | 'year'>; alias?: string })[]optionalGROUP BY targets (strings or {field, dateGranularity?} objects for date bucketing)
havinganyoptionalHAVING — filter over the AGGREGATED rows (aggregation aliases + groupBy projections); applied engine-side after aggregation
windowFunctionsneveroptional[REMOVED] query.windowFunctions was removed in @objectstack/spec 17 (ADR-0049) — find() never applied it: no engine or driver read the key on the query path, so every OVER clause it declared was silently dropped. Delete the key. Window functions are a SQL-driver capability behind SqlDriver.findWithWindowFunctions(object, query) (embedder-level; not on the IDataDriver contract or the REST surface); request-level analytics are aggregations + groupBy.
distinctneveroptional[REMOVED] query.distinct was removed in @objectstack/spec 17 (ADR-0049 / ADR-0078) — no driver ever rendered SELECT DISTINCT; the flag's only observable effect was MIS-WIRED: the REST list path treated a distinct query as not countable and silently degraded total/hasMore to a page-local estimate while still returning duplicate rows. Delete the key; QueryBuilder.distinct() was removed with it, and the count suppression is gone (total is truthful again). For unique values of one column use the SQL/memory drivers' distinct(object, field) door; for unique combinations, groupBy; for a deduplicated count, the count_distinct aggregation.
expandRecord<string, { object: string; fields?: string[]; where?: any; search?: string | object; … }>optionalRecursive relation loading map. Keys are lookup/master_detail field names; values are nested QueryAST objects that control select (fields) and filter (where, AND-merged with the batch $in), plus further expansion on the related object. The engine resolves expand via batch $in queries (driver-agnostic) with a default max depth of 3; per-parent limit/offset/orderBy are NOT applied on this path.

EngineUpdateOptions

QueryAST-aligned options for DataEngine.update operations

Properties

PropertyTypeRequiredDescription
context{ userId?: string; actor?: string; attributedUserId?: string; email?: string; … }optional
whereRecord<string, any> | anyoptional
upsertneveroptional[REMOVED] update.options.upsert was removed in @objectstack/spec 17 (ADR-0049) — it was declared and allowlisted but never implemented: no engine or driver path ever read it, so { upsert: true } was accepted and silently dropped and the update stayed a plain update. Delete the key. Express create-if-absent explicitly: a by-id update whose id names no row throws RECORD_NOT_FOUND (the by-id not-found gate) rather than inserting, so read the row first (findOne) and call insert or update on what you find. A first-class upsert, if ever built, must reconcile with that gate by design rather than through this silent flag.
multibooleanoptional (default: false)
returningbooleanoptional (default: false)

Nested Shape: EngineUpdateOptions.context

PropertyTypeRequiredDescription
userIdstringoptional
actorstringoptional
attributedUserIdstringoptional
emailstringoptional
tenantIdstringoptional
timezonestringoptional
localestringoptional
currencystringoptional
positionsstring[]optional (default: [])
principalKindEnum<'human' | 'agent' | 'service' | 'guest' | 'system'>optional
audienceEnum<'internal' | 'external'>optional
postureEnum<'PLATFORM_ADMIN' | 'TENANT_ADMIN' | 'MEMBER' | 'EXTERNAL'>optionalADR-0095 D2 posture rung — PLATFORM_ADMIN crosses the tenant wall where object posture permits; TENANT_ADMIN sees all rows in the org; MEMBER gets business RLS; EXTERNAL sees only explicitly shared rows.
authGate{ code: string; message: string }optionalADR-0069 authentication-policy gate: present only while the principal is blocked from protected resources until they remediate (expired password, enforced MFA), absent for every healthy session. code is the stable machine code the client branches on (PASSWORD_EXPIRED / MFA_REQUIRED) and message is what the blocked user reads; both are required because the transport seam renders them as the 403 body. AUTHENTICATION, not authorization — it suspends access entirely rather than narrowing it, and nothing in the permission/RLS path reads it, while the allow-listed remediation endpoints stay reachable. Server-constructed only, never client-supplied; a guest/anonymous principal never carries one.
onBehalfOf{ userId: string; principalKind?: Enum<'human' | 'agent' | 'service' | 'guest' | 'system'> }optional
permissionsstring[]optional (default: [])
systemPermissionsstring[]optional
tabPermissionsRecord<string, Enum<'visible' | 'hidden' | 'default_on' | 'default_off'>>optional
org_user_idsstring[]optional
accessible_org_idsstring[]optional
rlsMembershipRecord<string, string[]>optional
isSystembooleanoptional (default: false)
flowRunIdstringoptional
skipTriggersbooleanoptional
skipAutomationsbooleanoptional
seedReplaybooleanoptional
skipStateMachinebooleanoptional
preserveAuditbooleanoptionalHistorical import: preserve the ORIGINAL audit timeline for this write instead of stamping it "now". Opt-in and server-constructed only, never client-supplied. On the UPDATE path it admits a whitelist — the audit/timestamp family (created_at / created_by / updated_at / updated_by) plus author-declared business readonly fields — while platform-managed system columns (tenancy, generated) stay stripped. On INSERT the exemption does NOT apply: a create is stripped earlier, at the DataProtocol ingress, whose only exemption is context.isSystem, so a non-system create carrying preserveAudit still has those fields stripped and is warned (WARN) that the exemption is UPDATE-only — replaying archival readonly facts on create requires a system context. Permissions / RLS / field-level security are unaffected.
oauthScopesstring[]optional
accessTokenstringoptional
transactionanyoptional
traceIdstringoptional

On this page

TypeScript UsageBaseEngineOptionsPropertiesNested Shape: BaseEngineOptions.contextDataEngineAggregateOptionsPropertiesNested Shape: DataEngineAggregateOptions.contextDataEngineAggregateRequestPropertiesNested Shape: DataEngineAggregateRequest.queryDataEngineCountOptionsPropertiesNested Shape: DataEngineCountOptions.contextDataEngineCountRequestPropertiesNested Shape: DataEngineCountRequest.queryDataEngineDeleteOptionsPropertiesNested Shape: DataEngineDeleteOptions.contextDataEngineDeleteRequestPropertiesNested Shape: DataEngineDeleteRequest.optionsDataEngineExecuteRequestPropertiesDataEngineFilterUnion OptionsOption 1Option 2DataEngineFindOneRequestPropertiesNested Shape: DataEngineFindOneRequest.queryDataEngineFindRequestPropertiesNested Shape: DataEngineFindRequest.queryDataEngineInsertOptionsPropertiesNested Shape: DataEngineInsertOptions.contextDataEngineInsertRequestPropertiesDataEngineQueryOptionsPropertiesNested Shape: DataEngineQueryOptions.contextDataEngineRequestUnion OptionsOption 1PropertiesNested Shape: DataEngineRequest[method='find'].queryOption 2PropertiesNested Shape: DataEngineRequest[method='findOne'].queryOption 3PropertiesOption 4PropertiesNested Shape: DataEngineRequest[method='update'].optionsOption 5PropertiesNested Shape: DataEngineRequest[method='delete'].optionsOption 6PropertiesNested Shape: DataEngineRequest[method='count'].queryOption 7PropertiesNested Shape: DataEngineRequest[method='aggregate'].queryOption 8PropertiesOption 9PropertiesDataEngineSortUnion OptionsOption 1Option 2Option 3DataEngineUpdateOptionsPropertiesNested Shape: DataEngineUpdateOptions.contextDataEngineUpdateRequestPropertiesNested Shape: DataEngineUpdateRequest.optionsDataEngineVectorFindRequestPropertiesDroppedFieldsEventPropertiesEngineAggregateOptionsPropertiesNested Shape: EngineAggregateOptions.contextNested Shape: EngineAggregateOptions.groupBy[number]Nested Shape: EngineAggregateOptions.aggregations[number]EngineCountOptionsPropertiesNested Shape: EngineCountOptions.contextEngineDeleteOptionsPropertiesNested Shape: EngineDeleteOptions.contextEngineQueryOptionsPropertiesNested Shape: EngineQueryOptions.contextNested Shape: EngineQueryOptions.searchNested Shape: EngineQueryOptions.expand[string]EngineUpdateOptionsPropertiesNested Shape: EngineUpdateOptions.context