ObjectStackObjectStack

Contract

Contract protocol schemas

Source: packages/spec/src/api/contract.zod.ts

TypeScript Usage

import { ApiErrorSchema, BaseResponseSchema, BatchLoadingStrategySchema, BulkRequestSchema, BulkResponseSchema, CreateRequestSchema, DataLoaderConfigSchema, DeleteResponseSchema, ExportRequestSchema, IdRequestSchema, ListRecordResponseSchema, ModificationResultSchema, QueryOptimizationConfigSchema, RecordDataSchema, SingleRecordResponseSchema, UpdateRequestSchema } from '@objectstack/spec/api';
import type { ApiError, BaseResponse, BatchLoadingStrategy, BulkRequest, BulkResponse, CreateRequest, DataLoaderConfig, DeleteResponse, ExportRequest, IdRequest, ListRecordResponse, ModificationResult, QueryOptimizationConfig, RecordData, SingleRecordResponse, UpdateRequest } from '@objectstack/spec/api';

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

ApiError

Properties

PropertyTypeRequiredDescription
codeEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | 'INVALID_FORMAT' | 'VALUE_TOO_LONG' | 'VALUE_TOO_SHORT' | 'VALUE_OUT_OF_RANGE' | … +291 more>Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)
declaredCodestringoptionalThe producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112)
messagestringReadable error message
userMessagestringoptionalProducer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message.
categorystringoptionalError category (e.g. validation, authorization)
httpStatusintegeroptionalHTTP status of the response carrying this error
detailsanyoptionalAdditional error context (e.g. field validation errors)
requestIdstringoptionalRequest ID for tracking

Allowed Values: ApiError.code

  • VALIDATION_ERROR
  • INVALID_FIELD
  • MISSING_REQUIRED_FIELD
  • INVALID_FORMAT
  • VALUE_TOO_LONG
  • VALUE_TOO_SHORT
  • VALUE_OUT_OF_RANGE
  • INVALID_REFERENCE
  • DUPLICATE_VALUE
  • INVALID_QUERY
  • INVALID_FILTER
  • INVALID_SORT
  • MAX_RECORDS_EXCEEDED
  • UNAUTHENTICATED
  • INVALID_CREDENTIALS
  • EXPIRED_TOKEN
  • INVALID_TOKEN
  • SESSION_EXPIRED
  • MFA_REQUIRED
  • EMAIL_NOT_VERIFIED
  • PERMISSION_DENIED
  • INSUFFICIENT_PRIVILEGES
  • FIELD_NOT_ACCESSIBLE
  • RECORD_NOT_ACCESSIBLE
  • LICENSE_REQUIRED
  • IP_RESTRICTED
  • TIME_RESTRICTED
  • RESOURCE_NOT_FOUND
  • OBJECT_NOT_FOUND
  • RECORD_NOT_FOUND
  • FIELD_NOT_FOUND
  • ENDPOINT_NOT_FOUND
  • RESOURCE_CONFLICT
  • CONCURRENT_MODIFICATION
  • DELETE_RESTRICTED
  • DUPLICATE_RECORD
  • LOCK_CONFLICT
  • METHOD_NOT_ALLOWED
  • PRECONDITION_REQUIRED
  • RATE_LIMIT_EXCEEDED
  • QUOTA_EXCEEDED
  • CONCURRENT_LIMIT_EXCEEDED
  • INTERNAL_ERROR
  • DATABASE_ERROR
  • TIMEOUT
  • SERVICE_UNAVAILABLE
  • NOT_IMPLEMENTED
  • EXTERNAL_SERVICE_ERROR
  • INTEGRATION_ERROR
  • WEBHOOK_DELIVERY_FAILED
  • ACCOUNT_LOCKED
  • ACTION_DISABLED
  • ALREADY_REVERTED
  • AMBIGUOUS_MATCH
  • ANALYTICS_QUERY_FAILED
  • APPROVAL_ACTIONS_FAILED
  • APPROVAL_APPROVE_FAILED
  • APPROVAL_COMMENT_FAILED
  • APPROVAL_REASSIGN_FAILED
  • APPROVAL_RECALL_FAILED
  • APPROVAL_REJECT_FAILED
  • APPROVAL_REMIND_FAILED
  • APPROVAL_REQUEST_GET_FAILED
  • APPROVAL_REQUEST_INFO_FAILED
  • APPROVAL_REQUEST_LIST_FAILED
  • APPROVAL_RESUBMIT_FAILED
  • APPROVAL_REVISE_FAILED
  • ASYNC_NOT_SUPPORTED
  • ATTACHMENT_DELETE_DENIED
  • ATTACHMENT_DOWNLOAD_DENIED
  • ATTACHMENT_PARENT_ACCESS
  • AUDIENCE_NOT_ALLOWED
  • AUDIT_TYPE_NOT_CANONICAL
  • AUTH_CONFIG_ERROR
  • AUTH_REQUIRED
  • AUTOMATION_UNSCOPED_RUN_DATA_ACCESS
  • BATCH_ABORTED
  • BATCH_NOT_ATOMIC
  • BATCH_TOO_LARGE
  • BATCH_UNRESOLVED_REF
  • BLANK_MATCH_KEY
  • CLONE_DISABLED
  • CLOUD_FETCH_FAILED
  • CLOUD_UNCONFIGURED
  • COMMIT_NOT_FOUND
  • CONCURRENT_UPDATE
  • CONFLICT
  • CONFLICTING_MAPPING
  • CONNECTOR_UPSTREAM_UNAVAILABLE
  • CREATE_FAILED
  • CUBE_NOT_FOUND
  • DATASET_INVALID
  • DATASOURCE_ADMIN_ERROR
  • DELEGABLE_SCOPE_FAILED
  • DELIVERY_NEVER_SENT
  • DELIVERY_NOT_ELIGIBLE
  • DESTRUCTIVE_CHANGE
  • DEVICE_CODE_FAILED
  • DOMAIN_VERIFICATION_DISABLED
  • DOMAIN_VERIFICATION_FAILED
  • DRIVER_UNAVAILABLE
  • DUPLICATE_REQUEST
  • ELIGIBILITY_UNEVALUABLE
  • EMAIL_DOMAIN_NOT_ALLOWED
  • EMAIL_SEND_FAILED
  • EMAIL_SERVICE_REQUIRED
  • ENQUEUE_FAILED
  • ENVIRONMENT_BIND_FAILED
  • ENVIRONMENT_NOT_FOUND
  • ENV_ACCESS_DENIED
  • ERR_AUTONUMBER_COLLISION
  • ERR_BULK_RESULT_MISMATCH
  • ERR_CROSS_DATASOURCE_TRANSACTION_WRITE
  • ERR_DATASOURCE_UNAVAILABLE
  • ERR_DRIVER_CONNECT
  • ERR_FILE_CONSTRAINT
  • ERR_FILE_REFERENCE_COPY
  • ERR_HOOK_TARGET_REBIND
  • ERR_READONLY_FIELD_REJECTED
  • ERR_SUMMARY_RECOMPUTE
  • ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED
  • ERR_TRANSACTION_UNSUPPORTED
  • EXECUTION_ERROR
  • EXPIRED_OR_REVOKED
  • EXPIRY_IN_PAST
  • EXPIRY_TOO_LONG
  • EXPLAIN_FAILED
  • EXPORT_NOT_PERMITTED
  • EXTERNAL_DATASOURCE_ERROR
  • EXTERNAL_IMPORT_ERROR
  • EXTERNAL_SCHEMA_MISMATCH
  • EXTERNAL_SCHEMA_MODE_VIOLATION
  • EXTERNAL_WRITE_FORBIDDEN
  • FEEDS_DISABLED
  • FIELD_VISIBILITY_UNRESOLVED
  • FILES_DISABLED
  • FILE_DOWNLOAD_DENIED
  • FILE_FIELD_BULK_WRITE_REFUSED
  • FILE_NOT_FOUND
  • FILTER_TOKEN_UNKNOWN
  • FILTER_TOKEN_UNRESOLVED
  • FLOW_CONVERSION_CONFLICT
  • FLOW_DISABLED
  • FLOW_FAILED
  • FLOW_INPUT_SCHEMA_INVALID
  • FLOW_NO_START_NODE
  • FORBIDDEN
  • FORM_NOT_FOUND
  • FORM_RESOLVE_FAILED
  • IMPORT_JOB_CREATE_FAILED
  • IMPORT_ROW_FAILED
  • INTERNAL
  • INVALID_EMAIL
  • INVALID_EXPIRY
  • INVALID_METADATA
  • INVALID_OR_EXPIRED
  • INVALID_PHONE
  • INVALID_REQUEST
  • INVALID_RESUME_TOKEN
  • INVALID_SIGNAL
  • INVALID_SIGNATURE
  • INVALID_STATE
  • INVITE_EMAIL_FAILED
  • INVITE_REQUIRES_EMAIL
  • INVITE_SMS_FAILED
  • IP_NOT_ALLOWED
  • ITEM_LOCKED
  • LAST_LOCAL_CREDENTIAL
  • LOOKUP_NOT_PUBLIC
  • LOOKUP_TARGET_MISSING
  • MANIFEST_CONFLICT
  • MAPPING_FORMAT_MISMATCH
  • MAPPING_FORMAT_UNSUPPORTED
  • MAPPING_NOT_FOUND
  • MAPPING_TARGET_MISMATCH
  • MARKETPLACE_PROXY_FAILED
  • MARKETPLACE_STORAGE_FAILED
  • MARKETPLACE_UNAVAILABLE
  • METADATA_BRANCH
  • METADATA_CONFLICT
  • METADATA_NOT_FOUND
  • METADATA_SCHEMA_INVALID
  • NAMESPACE_PREFIX
  • NEEDS_PASSWORD
  • NODE_FAILURE
  • NOTHING_TO_PURGE
  • NOT_ATTEMPTED
  • NOT_CREATABLE
  • NOT_FOUND
  • NOT_OVERRIDABLE
  • NOT_UNDOABLE
  • NO_DRAFT
  • NO_EXECUTOR
  • NO_IDENTITY
  • NO_MATCH
  • NO_PENDING_VERIFICATION
  • OAUTH_REGISTER_FAILED
  • OBJECT_API_DISABLED
  • OBJECT_API_METHOD_NOT_ALLOWED
  • OBJECT_OVERLAY_PACKAGE_MISMATCH
  • OBJECT_PACKAGE_DISABLED
  • OPENAPI_UNAVAILABLE
  • OS_PROTOCOL_INCOMPATIBLE
  • PACKAGE_DELETE_FAILED
  • PACKAGE_DELETE_PARTIAL
  • PACKAGE_MANIFEST_INVALID
  • PACKAGE_PUBLISH_FAILED
  • PASSWORD_ALREADY_SET
  • PASSWORD_EXPIRED
  • PASSWORD_POLICY_VIOLATION
  • PASSWORD_REUSE
  • PAYLOAD_TOO_LARGE
  • PERMISSION_NOT_ALLOWED
  • PHONE_NOT_ENABLED
  • PLUGIN_INSTALL_FAILED
  • PLUGIN_MANIFEST_INVALID
  • PLUGIN_REGISTER_FAILED
  • PROJECT_MEMBERSHIP_REQUIRED
  • PROJECT_NOT_FOUND
  • PROJECT_PROVISIONING
  • PROJECT_PROVISIONING_FAILED
  • QUERY_OBJECT_MISMATCH
  • RAW_SQL_UNSUPPORTED
  • READ_BACK_FAILED
  • READ_SCOPE_COMPILE_FAILED
  • RECORD_GONE
  • RECORD_LOCKED
  • RECORD_NOT_ELIGIBLE
  • REGISTRY_TYPE_NOT_CANONICAL
  • REPORTS_LIST_FAILED
  • REPORT_DELETE_FAILED
  • REPORT_GET_FAILED
  • REPORT_NOT_FOUND
  • REPORT_RUN_FAILED
  • REPORT_SAVE_FAILED
  • REPORT_SCHEDULE_FAILED
  • REQUEST_NOT_FOUND
  • RESEED_NO_ROWS
  • RESEED_SKIPPED
  • RESUME_FAILED
  • RESUME_IN_PROGRESS
  • RESUME_TARGET_LOST
  • ROLLED_BACK
  • ROUTE_NOT_FOUND
  • RULE_DEFINE_FAILED
  • RULE_DELETE_FAILED
  • RULE_EVALUATE_FAILED
  • RULE_GET_FAILED
  • RULE_LIST_FAILED
  • RULE_NOT_FOUND
  • RUN_NOT_FOUND
  • SAML_REGISTER_FAILED
  • SCHEDULES_LIST_FAILED
  • SCHEDULE_DELETE_FAILED
  • SELF_REGISTRATION_CLOSED
  • SETTINGS_ACTION_FAILED
  • SETTINGS_CRYPTO_UNAVAILABLE
  • SETTINGS_ENGINE_NOT_BOUND
  • SETTINGS_FORBIDDEN
  • SETTINGS_LOCKED
  • SETTINGS_UNKNOWN_KEY
  • SETTINGS_UNKNOWN_NAMESPACE
  • SETTINGS_VALIDATION
  • SHARES_LIST_FAILED
  • SHARE_GRANT_FAILED
  • SHARE_REVOKE_FAILED
  • SHARING_NOT_ENABLED
  • SIGN_IN_REQUIRED
  • SQL_DIALECT_EMISSION_UNSUPPORTED
  • SSO_REGISTER_FAILED
  • SSO_REGISTER_FORBIDDEN
  • STORED_TYPE_NOT_CANONICAL
  • STORE_UNAVAILABLE
  • SUGGESTION_CONFIRM_FAILED
  • SUGGESTION_DISMISS_FAILED
  • SUGGESTION_LIST_FAILED
  • SUGGESTION_NOT_FOUND
  • SUGGESTION_STATE
  • SUMMARY_RECOMPUTE_FAILED
  • TENANT_SCOPE_REQUIRED
  • THROTTLED
  • UNAUTHORIZED
  • UNIQUE_SCOPE_CONFIRMATION_REQUIRED
  • UNIQUE_VIOLATION
  • UNKNOWN_KEY
  • UNKNOWN_NAMESPACE
  • UNSUPPORTED
  • UNSUPPORTED_QUERY_PARAM
  • UNSUPPORTED_TRANSFORM
  • UPDATE_ID_MISMATCH
  • UPLOAD_SESSION_EXPIRED
  • UPLOAD_SESSION_NOT_FOUND
  • USER_ALREADY_EXISTS
  • VALIDATION_FAILED
  • VERSION_NOT_FOUND
  • VERSION_NOT_RESTORABLE
  • WRITABLE_PACKAGE_REQUIRED
  • WRONG_PASSWORD

BaseResponse

Properties

PropertyTypeRequiredDescription
successbooleanOperation success status
error{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }optionalError details if success is false
meta{ timestamp: string; duration?: number; requestId?: string; traceId?: string }optionalResponse metadata

Nested Shape: BaseResponse.error

PropertyTypeRequiredDescription
codeEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)
declaredCodestringoptionalThe producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112)
messagestringReadable error message
userMessagestringoptionalProducer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message.
categorystringoptionalError category (e.g. validation, authorization)
httpStatusintegeroptionalHTTP status of the response carrying this error
detailsanyoptionalAdditional error context (e.g. field validation errors)
requestIdstringoptionalRequest ID for tracking

BatchLoadingStrategy

Properties

PropertyTypeRequiredDescription
strategyEnum<'dataloader' | 'windowed' | 'prefetch'>Batch loading strategy type
windowMsnumberoptionalCollection window duration in milliseconds (for windowed strategy)
prefetchDepthintegeroptionalDepth of relation prefetching (for prefetch strategy)
associationLoadingEnum<'lazy' | 'eager' | 'batch'>optional (default: "batch")How to load related associations

BulkRequest

Properties

PropertyTypeRequiredDescription
recordsRecord<string, any>[]Array of records to process
allOrNonebooleanoptional (default: true)If true, rollback entire transaction on any failure

BulkResponse

Properties

PropertyTypeRequiredDescription
successbooleanOperation success status
error{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }optionalError details if success is false
meta{ timestamp: string; duration?: number; requestId?: string; traceId?: string }optionalResponse metadata
data{ id?: string; success: boolean; errors?: object[]; index?: number; … }[]Results for each item in the batch

Nested Shape: BulkResponse.error

PropertyTypeRequiredDescription
codeEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)
declaredCodestringoptionalThe producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112)
messagestringReadable error message
userMessagestringoptionalProducer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message.
categorystringoptionalError category (e.g. validation, authorization)
httpStatusintegeroptionalHTTP status of the response carrying this error
detailsanyoptionalAdditional error context (e.g. field validation errors)
requestIdstringoptionalRequest ID for tracking

Nested Shape: BulkResponse.data[number]

PropertyTypeRequiredDescription
idstringoptionalRecord ID if processed
successboolean
errors{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }[]optional
indexnumberoptionalIndex in original request
dataanyoptionalResult data (e.g. created record)

CreateRequest

Properties

PropertyTypeRequiredDescription
dataRecord<string, any>Record data to insert

DataLoaderConfig

Properties

PropertyTypeRequiredDescription
maxBatchSizeintegeroptional (default: 100)Maximum number of keys per batch load
batchScheduleFnEnum<'microtask' | 'timeout' | 'manual'>optional (default: "microtask")Scheduling strategy for collecting batch keys
cacheEnabledbooleanoptional (default: true)Enable per-request result caching
cacheKeyFnstringoptionalName or identifier of the cache key function
cacheTtlnumberoptionalCache time-to-live in seconds (0 = no expiration)
coalesceRequestsbooleanoptional (default: true)Deduplicate identical requests within a batch window
maxConcurrencyintegeroptionalMaximum parallel batch requests

DeleteResponse

Properties

PropertyTypeRequiredDescription
successbooleanOperation success status
error{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }optionalError details if success is false
meta{ timestamp: string; duration?: number; requestId?: string; traceId?: string }optionalResponse metadata
idstringID of the deleted record

Nested Shape: DeleteResponse.error

PropertyTypeRequiredDescription
codeEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)
declaredCodestringoptionalThe producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112)
messagestringReadable error message
userMessagestringoptionalProducer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message.
categorystringoptionalError category (e.g. validation, authorization)
httpStatusintegeroptionalHTTP status of the response carrying this error
detailsanyoptionalAdditional error context (e.g. field validation errors)
requestIdstringoptionalRequest ID for tracking

ExportRequest

Properties

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.

Nested Shape: ExportRequest.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: ExportRequest.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.

Nested Shape: ExportRequest.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: ExportRequest.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.

IdRequest

Properties

PropertyTypeRequiredDescription
idstringRecord ID

ListRecordResponse

Properties

PropertyTypeRequiredDescription
successbooleanOperation success status
error{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }optionalError details if success is false
meta{ timestamp: string; duration?: number; requestId?: string; traceId?: string }optionalResponse metadata
dataRecord<string, any>[]Array of matching records
pagination{ total?: number; limit?: number; offset?: number; cursor?: string; … }Pagination info

Nested Shape: ListRecordResponse.error

PropertyTypeRequiredDescription
codeEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)
declaredCodestringoptionalThe producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112)
messagestringReadable error message
userMessagestringoptionalProducer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message.
categorystringoptionalError category (e.g. validation, authorization)
httpStatusintegeroptionalHTTP status of the response carrying this error
detailsanyoptionalAdditional error context (e.g. field validation errors)
requestIdstringoptionalRequest ID for tracking

Nested Shape: ListRecordResponse.pagination

PropertyTypeRequiredDescription
totalnumberoptionalTotal matching records count
limitnumberoptionalPage size
offsetnumberoptionalPage offset
cursorstringoptionalCursor for next page
nextCursorstringoptionalNext cursor for pagination
hasMorebooleanAre there more pages?

ModificationResult

Properties

PropertyTypeRequiredDescription
idstringoptionalRecord ID if processed
successboolean
errors{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }[]optional
indexnumberoptionalIndex in original request
dataanyoptionalResult data (e.g. created record)

Nested Shape: ModificationResult.errors[number]

PropertyTypeRequiredDescription
codeEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)
declaredCodestringoptionalThe producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112)
messagestringReadable error message
userMessagestringoptionalProducer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message.
categorystringoptionalError category (e.g. validation, authorization)
httpStatusintegeroptionalHTTP status of the response carrying this error
detailsanyoptionalAdditional error context (e.g. field validation errors)
requestIdstringoptionalRequest ID for tracking

QueryOptimizationConfig

Properties

PropertyTypeRequiredDescription
preventNPlusOnebooleanEnable N+1 query detection and prevention
dataLoader{ maxBatchSize: integer; batchScheduleFn: Enum<'microtask' | 'timeout' | 'manual'>; cacheEnabled: boolean; cacheKeyFn?: string; … }optionalDataLoader batch loading configuration
batchStrategy{ strategy: Enum<'dataloader' | 'windowed' | 'prefetch'>; windowMs?: number; prefetchDepth?: integer; associationLoading: Enum<'lazy' | 'eager' | 'batch'> }optionalBatch loading strategy configuration
maxQueryDepthintegerMaximum depth for nested relation queries
queryComplexityLimitnumberoptionalMaximum allowed query complexity score
enableQueryPlanbooleanoptional (default: false)Log query execution plans for debugging

Nested Shape: QueryOptimizationConfig.dataLoader

PropertyTypeRequiredDescription
maxBatchSizeintegeroptional (default: 100)Maximum number of keys per batch load
batchScheduleFnEnum<'microtask' | 'timeout' | 'manual'>optional (default: "microtask")Scheduling strategy for collecting batch keys
cacheEnabledbooleanoptional (default: true)Enable per-request result caching
cacheKeyFnstringoptionalName or identifier of the cache key function
cacheTtlnumberoptionalCache time-to-live in seconds (0 = no expiration)
coalesceRequestsbooleanoptional (default: true)Deduplicate identical requests within a batch window
maxConcurrencyintegeroptionalMaximum parallel batch requests

Nested Shape: QueryOptimizationConfig.batchStrategy

PropertyTypeRequiredDescription
strategyEnum<'dataloader' | 'windowed' | 'prefetch'>Batch loading strategy type
windowMsnumberoptionalCollection window duration in milliseconds (for windowed strategy)
prefetchDepthintegeroptionalDepth of relation prefetching (for prefetch strategy)
associationLoadingEnum<'lazy' | 'eager' | 'batch'>optional (default: "batch")How to load related associations

RecordData

Key-value map of record data

Type: Record<string, any>


SingleRecordResponse

Properties

PropertyTypeRequiredDescription
successbooleanOperation success status
error{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }optionalError details if success is false
meta{ timestamp: string; duration?: number; requestId?: string; traceId?: string }optionalResponse metadata
dataRecord<string, any>The requested or modified record

Nested Shape: SingleRecordResponse.error

PropertyTypeRequiredDescription
codeEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)
declaredCodestringoptionalThe producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112)
messagestringReadable error message
userMessagestringoptionalProducer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message.
categorystringoptionalError category (e.g. validation, authorization)
httpStatusintegeroptionalHTTP status of the response carrying this error
detailsanyoptionalAdditional error context (e.g. field validation errors)
requestIdstringoptionalRequest ID for tracking

UpdateRequest

Properties

PropertyTypeRequiredDescription
dataRecord<string, any>Partial record data to update

On this page