Skip to content

Latest commit

 

History

History
1547 lines (1139 loc) · 62.1 KB

File metadata and controls

1547 lines (1139 loc) · 62.1 KB

@objectstack/driver-sql

15.1.1

Patch Changes

  • @objectstack/spec@15.1.1
  • @objectstack/core@15.1.1
  • @objectstack/types@15.1.1

15.1.0

Patch Changes

  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [3fe9df1]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [4109153]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [627f225]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
  • Updated dependencies [f531a26]
    • @objectstack/spec@15.1.0
    • @objectstack/core@15.1.0
    • @objectstack/types@15.1.0

15.0.0

Patch Changes

  • Updated dependencies [28b7c28]
  • Updated dependencies [13749ec]
  • Updated dependencies [e62c233]
  • Updated dependencies [ed61c9b]
  • Updated dependencies [31d04d4]
    • @objectstack/spec@15.0.0
    • @objectstack/core@15.0.0
    • @objectstack/types@15.0.0

14.8.0

Patch Changes

  • 84650c5: Log a concise one-liner instead of the full ERR_DLOPEN_FAILED stack trace when native better-sqlite3 cannot load (an ABI / NODE_MODULE_VERSION mismatch after a Node upgrade, or the native addon was never built). The native → wasm SQLite step-down is unchanged — this only stops a handled, non-fatal fallback from reading like a fatal crash in the dev console, and points at pnpm rebuild better-sqlite3 for native speed. Any other PRAGMA failure keeps its full warning.
  • Updated dependencies [16b4bf6]
  • Updated dependencies [16b4bf6]
  • Updated dependencies [10e8983]
  • Updated dependencies [607aaf4]
  • Updated dependencies [bb71321]
    • @objectstack/spec@14.8.0
    • @objectstack/core@14.8.0
    • @objectstack/types@14.8.0

14.7.0

Patch Changes

  • Updated dependencies [d6a72eb]
  • Updated dependencies [824a395]
    • @objectstack/spec@14.7.0
    • @objectstack/types@14.7.0
    • @objectstack/core@14.7.0

14.6.0

Patch Changes

  • ce6d151: fix(driver-sql): fail-loud on unknown filter operators; real IS NULL / IS NOT NULL; $not support (#2704)

    The SQL driver used to forward any filter operator it didn't recognise straight to Knex. On a null comparand that silently compiled to a whole-table match, so a permission/assignment-scoped list view could leak every row (e.g. an is_null / is_empty operator from the client). It also had no real null-check: field = null never renders IS NULL in SQL.

    This change makes the driver:

    • Render null predicates as real SQL — is_null / isnull / is_empty (and the not-null variants) → IS NULL / IS NOT NULL, unified with equals + null; != nullIS NOT NULL.
    • Support the full spec operator set plus client alias spellings across both filter shapes (array [field, op, value] and object {field: {$op: value}}): $between, $startsWith, $endsWith, $notContains, $null, $exists, and the logical $not (a negated sub-condition, matching driver-mongodb / driver-memory — CEL !expr permission scopes compile to it).
    • LIKE-escape contains / startsWith / endsWith values with an explicit ESCAPE '\' so % / _ in user input can't widen the match.
    • Throw on a genuinely unknown operator in both paths instead of silently passing it through — no more silent whole-table results.

    @objectstack/spec recognises the client alias operator spellings (isnull / is_empty / …) in VALID_AST_OPERATORS and maps them to $null so the array-AST → object-filter conversion is consistent with the driver.

  • Updated dependencies [609cb13]

  • Updated dependencies [ce6d151]

    • @objectstack/spec@14.6.0
    • @objectstack/core@14.6.0
    • @objectstack/types@14.6.0

14.5.0

Patch Changes

  • Updated dependencies [526805e]
  • Updated dependencies [d79ca07]
  • Updated dependencies [33ebd34]
  • Updated dependencies [c044f08]
  • Updated dependencies [01274eb]
    • @objectstack/spec@14.5.0
    • @objectstack/core@14.5.0
    • @objectstack/types@14.5.0

14.4.0

Minor Changes

  • 7953832: ADR-0057 data lifecycle P1–P4 (#2786): platform-generated data is now bounded by construction.

    • P1 — contract: new lifecycle object property (class: record | audit | telemetry | transient | event + retention / ttl / storage(rotation) / archive / reclaim), enforced by the platform-owned LifecycleService registered by ObjectQLPlugin (default-on; disable via OS_LIFECYCLE_DISABLED=1 or plugin lifecycle.enabled=false). The Reaper batch-deletes rows past retention.maxAge / ttl under a system context and reclaims space (SqlDriver.reclaimSpace() → SQLite PRAGMA incremental_vacuum). Non-record classes must declare a bounding policy (parse-time invariant + spec-liveness gate + dogfood storage-growth gate).
    • P2 — rotation: storage: { strategy: 'rotation', shards, unit } physically time-shards the table on SQLite — writes land in the current shard, reads go through a UNION-ALL view under the base name, expiry is an O(1) DROP of shards past the window. A legacy table is adopted as the first shard on upgrade. Other dialects fall back to an equivalent age-based reap.
    • P3 — separation + Archiver: registering a datasource named telemetry routes telemetry/event/audit objects to it (opt-in by existence; transient deliberately stays on the primary). Audit objects with archive declared get retain → archive → delete once the archive datasource exists; without it rows are retained, never dropped unarchived.
    • P4 — governance: new lifecycle settings namespace — runtime enable switch, per-object retention overrides (tenant-scoped: regulated tenants set years, dev sets days), per-object/per-class row quotas and growth alerts (observe-and-alert only).

    Behavior change: 11 platform objects now carry lifecycle declarations and their telemetry is bounded by default — sys_activity 14d (rotated), sys_audit_log 90d hot → archive (retained forever until an archive datasource is registered), sys_metadata_audit 365d → archive, sys_job_run / sys_automation_run / sys_http_delivery 30d, notification pipeline (sys_notification, delivery, receipt, inbox) 90d, sys_device_code expires_at + 1d. Extend windows per environment/tenant via the lifecycle.retention_overrides setting.

Patch Changes

  • Updated dependencies [7953832]
  • Updated dependencies [82e745e]
  • Updated dependencies [f3035bd]
  • Updated dependencies [82c0d94]
  • Updated dependencies [7449476]
    • @objectstack/spec@14.4.0
    • @objectstack/core@14.4.0
    • @objectstack/types@14.4.0

14.3.0

Patch Changes

  • Updated dependencies [2a71f48]
  • Updated dependencies [02f6af4]
  • Updated dependencies [c1064f1]
    • @objectstack/spec@14.3.0
    • @objectstack/core@14.3.0
    • @objectstack/types@14.3.0

14.2.0

Patch Changes

  • Updated dependencies [ac8f029]
  • Updated dependencies [4ab9958]
    • @objectstack/spec@14.2.0
    • @objectstack/core@14.2.0
    • @objectstack/types@14.2.0

14.1.0

Patch Changes

  • Updated dependencies [5a8465f]
  • Updated dependencies [7f8620b]
  • Updated dependencies [82ba3a6]
    • @objectstack/spec@14.1.0
    • @objectstack/core@14.1.0
    • @objectstack/types@14.1.0

14.0.0

Patch Changes

  • afa8115: Three permission-runtime fixes found dogfooding the ADR-0090 showcase zoo:

    #2734 — driver tenant wall hid every global row. applyTenantScope used strict organization_id = :tenantId equality, so any caller with an active org (every logged-in admin) saw ZERO rows in the org-less platform tables (sys_position, sys_permission_set, sys_business_unit — Setup → Access Control rendered empty on a fresh deployment) and none of the first-boot seeds (stamped before the default org exists). The scope is now (organization_id = :tenantId OR organization_id IS NULL): a NULL tenant column marks a GLOBAL/platform row that belongs to no other tenant; rows stamped with a DIFFERENT org stay invisible exactly as before.

    #2735 — bulkCreate skipped write-side marshaling. The batch insert path (the common case for seeds/imports since #2678) handed raw object values (location/json/array fields) to the SQLite binder — "Wrong API use: tried to bind a value of an unknown type" — silently failing whole seed batches (showcase accounts/tasks/field-zoo seeded zero rows). bulkCreate now runs each row through the same formatInput + applyWriteColumnMap + timestamp-stamp sequence as create(), and decodes the read-back the same way.

    #2737 — count()/aggregate() ignored injected read filters. engine.count and engine.aggregate built a LOCAL ast inside the executor, discarding the RLS/OWD filters the security and sharing middlewares inject into opCtx.ast.whereGET /data/:object returned scoped records with an UNSCOPED total (a row-count oracle over invisible records, broken pagination). Both now carry their ast on the opCtx exactly like find().

  • Updated dependencies [0a8e685]

  • Updated dependencies [afa8115]

  • Updated dependencies [80f12ca]

  • Updated dependencies [e2fa074]

  • Updated dependencies [23c8668]

  • Updated dependencies [29f017d]

  • Updated dependencies [216fa9a]

  • Updated dependencies [6c22b12]

    • @objectstack/spec@14.0.0
    • @objectstack/core@14.0.0
    • @objectstack/types@14.0.0

13.0.0

Patch Changes

  • Updated dependencies [6d83431]
  • Updated dependencies [01917c2]
  • Updated dependencies [b271691]
  • Updated dependencies [a5a1e41]
  • Updated dependencies [466adf6]
  • Updated dependencies [57b89b4]
  • Updated dependencies [5be00c3]
  • Updated dependencies [466adf6]
  • Updated dependencies [2bee609]
  • Updated dependencies [fc7e7f7]
    • @objectstack/spec@13.0.0
    • @objectstack/core@13.0.0
    • @objectstack/types@13.0.0

12.6.0

Patch Changes

  • 21420d9: Seed loader and data-import now route bulk writes through the engine's array-form insert() (one round-trip per batch, with parent-deduplicated summary recompute) instead of one insert()/createData() call per record, and both retry transient driver errors instead of silently dropping the row (#2678).

    A new shared helper, bulkWrite (@objectstack/core), batches rows through a caller-supplied batch-write function, retries a whole-batch transient failure (network blip / timeout) with exponential backoff, and degrades to per-row writes (each itself retried) when a batch fails for a non-transient reason — so one bad row can't drop the other N-1. withTransientRetry wraps a single write (e.g. an update) with the same retry behavior.

    • SeedLoaderService.loadDataset() (@objectstack/metadata-protocol) buffers insert-mode records and flushes them in batches of 200 via the engine's array insert(). Datasets with a self-referencing field (e.g. employee.manager_id -> employee) keep the historical per-record write path, since a later record may need an earlier one's freshly-assigned id.
    • runImport() (@objectstack/rest) buffers create-resolved rows and flushes them via protocol.createManyData() when the protocol supports it, falling back to the original per-row createData() call otherwise. Protocol.createManyData (@objectstack/metadata-protocol) now forwards context to engine.insert() like createData already did, so tenant-scoped bulk creates work correctly.

    Previously, a 1000-row seed or import into an object with a rollup summary issued 1000+ round-trips and up to 1000 summary recomputes; a single transient network error on any one row silently dropped it with no retry (the 2026-07-06 HotCRM first-boot incident). A bulkCreate-capable driver now sees roughly ceil(N/batch) writes, and a transient error is retried before a row is ever reported as failed.

    Fix (@objectstack/driver-sql): SqlDriver.bulkCreate() never generated a client-side id for a row missing one, unlike create() — a latent gap that this change is the first to exercise at scale (a bulk-inserted row without a driver-native id default silently landed with id: NULL). bulkCreate() now mirrors create()'s id/_id normalization per row.

  • Updated dependencies [6cebf22]

  • Updated dependencies [21420d9]

    • @objectstack/spec@12.6.0
    • @objectstack/core@12.6.0
    • @objectstack/types@12.6.0

12.5.0

Patch Changes

  • Updated dependencies [8b3d363]
    • @objectstack/spec@12.5.0
    • @objectstack/core@12.5.0
    • @objectstack/types@12.5.0

12.4.0

Patch Changes

  • Updated dependencies [60dc3ba]
    • @objectstack/spec@12.4.0
    • @objectstack/core@12.4.0
    • @objectstack/types@12.4.0

12.3.0

Patch Changes

  • Updated dependencies [e7eceec]
    • @objectstack/spec@12.3.0
    • @objectstack/core@12.3.0
    • @objectstack/types@12.3.0

12.2.0

Patch Changes

  • Updated dependencies [fce8ff4]
  • Updated dependencies [3962023]
  • Updated dependencies [2bb193d]
  • Updated dependencies [0426d27]
  • Updated dependencies [da807f7]
  • Updated dependencies [4f5b791]
    • @objectstack/spec@12.2.0
    • @objectstack/core@12.2.0
    • @objectstack/types@12.2.0

12.1.0

Patch Changes

  • Updated dependencies [93e6d02]
    • @objectstack/spec@12.1.0
    • @objectstack/core@12.1.0
    • @objectstack/types@12.1.0

12.0.0

Patch Changes

  • Updated dependencies [a8df396]
  • Updated dependencies [e695fe0]
  • Updated dependencies [7c09621]
  • Updated dependencies [7709db4]
  • Updated dependencies [2082109]
  • Updated dependencies [7c09621]
  • Updated dependencies [9860de4]
  • Updated dependencies [069c205]
    • @objectstack/spec@12.0.0
    • @objectstack/core@12.0.0
    • @objectstack/types@12.0.0

11.10.0

Patch Changes

  • Updated dependencies [6a9397e]
  • Updated dependencies [c0efe5d]
    • @objectstack/spec@11.10.0
    • @objectstack/core@11.10.0
    • @objectstack/types@11.10.0

11.9.0

Patch Changes

  • 8d87930: Fix a connection-pool deadlock when the first auto_number write after process start goes through a transaction (e.g. POST /api/v1/batch, which wraps every operation in one ql.transaction(...)).

    The sequence-counter table (_objectstack_sequences) was created lazily on the first autonumber INSERT via a bare this.knex.schema.* call that asks the pool for a second connection. On SQLite (better-sqlite3, pool max=1) the open batch transaction already holds the only connection, so the acquire blocked until Knex: Timeout acquiring a connection. Postgres/MySQL are exposed to the same pool-exhaustion deadlock under concurrent cold first-writes.

    Fixes:

    • initObjects now pre-creates the counter table up front, outside any data transaction, so the first write never runs DDL (primary fix).
    • The lazy fallback (ensureSequencesTable) now runs its DDL on the caller's own transaction on SQLite instead of grabbing a second connection. It deliberately does not route DDL through the caller's transaction on MySQL, where DDL would implicitly commit the caller's in-flight transaction.
    • Added a dev/test guard (assertBareKnexSafe): on SQLite, issuing a bare this.knex query while a transaction holds the single pooled connection now fails fast with an actionable error instead of hanging until the opaque Knex: Timeout acquiring a connection. No-op in production and on non-SQLite dialects, so it adds no runtime cost on the hot path — it just turns this whole class of "forgot to thread the transaction through" bug into an immediate, self-explaining failure at the call site.
  • Updated dependencies [d3595d9]

    • @objectstack/spec@11.9.0
    • @objectstack/core@11.9.0
    • @objectstack/types@11.9.0

11.8.0

Patch Changes

  • @objectstack/spec@11.8.0
  • @objectstack/core@11.8.0
  • @objectstack/types@11.8.0

11.7.0

Patch Changes

  • Updated dependencies [5178906]
    • @objectstack/spec@11.7.0
    • @objectstack/core@11.7.0
    • @objectstack/types@11.7.0

11.6.0

Patch Changes

  • @objectstack/spec@11.6.0
  • @objectstack/core@11.6.0
  • @objectstack/types@11.6.0

11.5.0

Patch Changes

  • Updated dependencies [6ee4f04]
  • Updated dependencies [c1e3a65]
    • @objectstack/spec@11.5.0
    • @objectstack/core@11.5.0
    • @objectstack/types@11.5.0

11.4.0

Patch Changes

  • Updated dependencies [5821c51]
  • Updated dependencies [a0fce3f]
    • @objectstack/spec@11.4.0
    • @objectstack/core@11.4.0
    • @objectstack/types@11.4.0

11.3.0

Patch Changes

  • Updated dependencies [58e8e31]
  • Updated dependencies [b4a5df0]
    • @objectstack/spec@11.3.0
    • @objectstack/core@11.3.0
    • @objectstack/types@11.3.0

11.2.0

Patch Changes

  • Updated dependencies [d0f4b13]
  • Updated dependencies [302bdab]
    • @objectstack/spec@11.2.0
    • @objectstack/core@11.2.0
    • @objectstack/types@11.2.0

11.1.0

Patch Changes

  • Updated dependencies [ce0b4f6]
  • Updated dependencies [9ccfcd6]
  • Updated dependencies [ecf193f]
  • Updated dependencies [51bec81]
  • Updated dependencies [3e593a7]
  • Updated dependencies [fdb41c0]
  • Updated dependencies [63d5403]
    • @objectstack/core@11.1.0
    • @objectstack/spec@11.1.0
    • @objectstack/types@11.1.0

11.0.0

Minor Changes

  • d980f0d: feat: add a first-class user field type (person picker)

    A new user field type — the equivalent of Airtable's Collaborator / Notion's Person / Salesforce's Lookup(User). Authored as Field.user({ ... }); use { multiple: true } for collaborators/watchers and { defaultValue: 'current_user' } to auto-fill the acting user on create.

    Why a distinct type rather than telling authors to Field.lookup('sys_user'): selecting a person is table-stakes, but the value is in modelling discoverability — a "User" entry in the Studio/AI field palette instead of requiring authors (and AI) to know to reference the internal sys_user system object — plus current_user defaults and a user-search picker. Storage and runtime are unchanged.

    Deliberately NOT a new storage primitive. user is a semantic specialization of lookup with the target fixed to sys_user: it shares the exact lookup code path — same FK string column (multiple ⇒ JSON), same $expand resolution, same indexing — so referential integrity and fresh display names come for free, and nothing is re-implemented. An existing Field.lookup('sys_user') is therefore equivalent at the storage layer (zero data migration to adopt Field.user).

    Ownership semantics are unchanged: the existing owner_id convention + plugin-security auto-stamp/RLS still apply. A declarative owner flag is a possible future follow-up; intentionally not added here to avoid a second field type for what is a system role (rationale: keep the FieldType surface lean — see related ADR-0059 freeze discipline).

    Changes: FieldType gains 'user' + Field.user() builder; the SQL/Mongo drivers treat user exactly like lookup; the engine resolves $expand for user fields and honours a new defaultValue: 'current_user' token (resolved app-side from the execution context, mirroring the NOW() convention); kanban group-by and symbolic seed references accept user; approvals enrich user references. The public API surface is unchanged (additive enum member).

Patch Changes

  • 98a1535: Fix: store SQLite created_at/updated_at in one canonical, timezone-explicit format (ADR-0074)

    The two SQLite write paths disagreed on the audit-timestamp format. INSERT fell back to the column default CURRENT_TIMESTAMP ('YYYY-MM-DD HH:MM:SS') while UPDATE stamped toISOString().replace('T',' ').replace('Z','') ('YYYY-MM-DD HH:MM:SS.mmm') — both timezone-naive, space-separated strings that Date.parse reads as local time. On a non-UTC runtime a stored UTC wall-clock silently shifted by the host offset; e.g. the objectos kernel freshness probe compared a shifted updated_at against an absolute builtAtMs and never evicted (publishes/installs/config toggles didn't take effect until the LRU TTL expired).

    create / bulkCreate / upsert / update now stamp a single canonical ISO-8601 instant with an explicit Z (new Date().toISOString()) — matching the caller-stamped paths (sys_metadata, the service outboxes) and Postgres/MySQL's native now(). Because the stamp is applied app-side (not via the column default), existing tenant databases are fixed immediately, not just freshly created tables. formatOutput additionally repairs any legacy/raw zone-naive audit timestamp to the same format on read (idempotent), so old rows read back unambiguously without a data migration. upsert now treats created_at as insert-only — a conflicting merge never overwrites it.

    Postgres/MySQL are unaffected (they store a real zone-aware TIMESTAMP).

  • bc22a89: Fix: present Field.time as a wall-clock time-of-day on read (SQLite)

    Field.time is a tz-naive time-of-day, not an instant (#2004). A defaultValue: 'NOW()' time column historically took the full SQLite CURRENT_TIMESTAMP default, so a defaulted/legacy row read back a full 'YYYY-MM-DD HH:MM:SS' timestamp instead of a time-of-day.

    formatOutput now repairs a Field.time value to just its time portion (toTimeOnly): a legacy full timestamp — or a full ISO value that leaked into the column — is sliced to HH:MM[:SS[.fff]], while a value already stored as a bare time-of-day is left untouched. This is a deliberately NARROW, read-only normalization with no write/filter counterpart, so it introduces no write/read asymmetry and preserves exact round-trips for bare time-of-day values (e.g. the field-zoo f_time guard). Runs for every dialect (a native TIME column already returns a time-of-day, so it is a no-op there).

    Completes the temporal-field read normalization alongside #2346: datetime folds to a canonical ISO-8601-Z instant, date to YYYY-MM-DD, and time to a wall-clock time-of-day.

  • 8a7e9f1: Fix: canonical storage + presentation for user-declared NOW()-default temporal fields on SQLite (ADR-0074 follow-up)

    A user-declared Field.datetime (or date/time) with defaultValue: 'NOW()' took the knex.fn.now()CURRENT_TIMESTAMP column default on SQLite, storing a timezone-naive, space-separated 'YYYY-MM-DD HH:MM:SS' (no millis, no zone). Date.parse reads such a zone-less string as local time, so the stored UTC wall-clock shifted by the host offset on a non-UTC runtime — the same class of bug ADR-0074 fixed for the builtin created_at/updated_at audit columns, but left scoped out for user fields. Worse, the same column mixed storage: an explicit JS Date is bound by better-sqlite3 as INTEGER epoch ms, while an omitted value took the naive TEXT default — so one column held both INTEGER ms and naive TEXT.

    This fix, SQLite-only:

    • DDL default → canonical. The NOW() default now emits a per-type canonical via strftime: datetime → ISO-8601 with explicit Z (strftime('%Y-%m-%dT%H:%M:%fZ','now'), e.g. 2026-06-26T10:34:13.891Z, matching new Date().toISOString()); date → YYYY-MM-DD; time → HH:MM:SS.fff time-of-day (not a full timestamp).
    • Read → uniform instant. formatOutput folds every Field.datetime storage form — INTEGER epoch ms, canonical ISO-Z, and legacy naive CURRENT_TIMESTAMP TEXT — to one canonical ISO-8601-Z instant (normalizeSqliteDatetimeOutput), interpreting a naive wall-clock as UTC. Idempotent on already-zone-explicit values; total on null/unparseable. This transparently repairs existing rows on read (a DDL default only governs newly-created columns), so no data migration is needed — mirroring the Field.date/numeric read-repairs already in place.

    Applied as DDL-default + read-normalization, NOT app-side write stamping (the inverse of ADR-0074's audit-column fix): the read path already repairs existing-table rows transparently, and an explicit Date is bound as INTEGER epoch ms regardless of any write stamp, so stamping wouldn't make on-disk storage uniform anyway — the INTEGER-vs-TEXT split is inherent to SQLite and resolved at the read boundary. This keeps the hot insert/upsert/bulk paths untouched.

    The analytics SQL-bucketing path (strftime, bypasses formatOutput) is unchanged: ISO-Z TEXT buckets identically to the old naive TEXT. Postgres/MySQL keep native now() (a real zone-aware TIMESTAMP) and are entirely unaffected.

    Generalizes ADR-0074's repairNaiveUtcAuditTimestamp by also folding the INTEGER epoch-ms storage form; the two read-repairs can be unified once both land.

  • Updated dependencies [ab5718a]

  • Updated dependencies [4845c12]

  • Updated dependencies [c1a754a]

  • Updated dependencies [6fbe91f]

  • Updated dependencies [715d667]

  • Updated dependencies [5eef4cf]

  • Updated dependencies [72759e1]

  • Updated dependencies [6c4fbd9]

  • Updated dependencies [ef3ed67]

  • Updated dependencies [cd51229]

  • Updated dependencies [7697a0e]

  • Updated dependencies [e7e04f1]

  • Updated dependencies [cfd5ac4]

  • Updated dependencies [2be5c1f]

  • Updated dependencies [ad143ce]

  • Updated dependencies [5c4a8c8]

  • Updated dependencies [3afaeed]

  • Updated dependencies [795b6d1]

  • Updated dependencies [8801c02]

  • Updated dependencies [3d04e06]

  • Updated dependencies [4a84c98]

  • Updated dependencies [c715d25]

  • Updated dependencies [aa33b02]

  • Updated dependencies [d980f0d]

  • Updated dependencies [a658523]

  • Updated dependencies [82ff91c]

  • Updated dependencies [638f472]

    • @objectstack/spec@11.0.0
    • @objectstack/types@11.0.0
    • @objectstack/core@11.0.0

10.3.0

Patch Changes

  • 5ba52b0: fix(driver-sql): honor tenancy.enabled:false in driver org-scoping

    The driver auto-detects organization_id as a tenant-isolation column and, when the caller passes DriverOptions.tenantId, scopes reads/updates/deletes to that tenant (and injects the column on inserts). The implicit column-detection fallback ignored an explicit tenancy.enabled === false, so a platform-global object that opts out of tenancy but carries an optional organization_id FK (e.g. sys_license) was still org-scoped — an authenticated caller's active-org tenantId then hid every NULL-org / cross-org row. The opt-out is now honored in a single shared computeTenantField() used by both initObjects and registerExternalObject (which had drifted). Covers TursoDriver (extends SqlDriver). Genuine org-scoped objects are unaffected.

    • @objectstack/spec@10.3.0
    • @objectstack/core@10.3.0

10.2.0

Patch Changes

  • Updated dependencies [b496498]
    • @objectstack/spec@10.2.0
    • @objectstack/core@10.2.0

10.1.0

Minor Changes

  • 517dad9: Schema drift detection + os migrate for non-additive metadata changes (#2186).

    The metadata→DB schema sync was additive-only: it created tables and added columns but never altered/dropped existing ones, so relaxing required, changing a type/length, or dropping a field silently diverged from an existing database. The physical column won at write time, surfacing a misleading organization_id is required 400 even though /meta reported the field optional.

    • driver-sql — the SQL driver now detects managed-schema drift (metadata is the source of truth) and categorises each divergence safe / needs_confirm / destructive. initObjects warns once per divergence with an actionable hint. A new opt-in SqlDriverConfig.autoMigrate: 'safe' auto-applies the loosening subset (relax NOT NULL, widen varchar) so an existing dev DB self-heals on restart — never destructive, force-disabled under NODE_ENV=production. New public methods detectManagedDrift() / applyMigrationEntries(). SQLite reconciles via the official table-rebuild (copy → swap), preserving data; Postgres/MySQL alter in place.
    • cli — new os migrate plan (dry-run, categorised diff) and os migrate apply (--allow-destructive for drops/tightenings, confirm gate, --json). os dev/serve now pass autoMigrate: 'safe' in dev only.
    • rest — a NOT NULL violation that reaches the driver (metadata validation already passed) now carries a drift-aware hint pointing at os migrate, instead of only the misleading "field is required" message. The VALIDATION_FAILED / fields envelope is unchanged for back-compat.

Patch Changes

  • Updated dependencies [49da36e]
  • Updated dependencies [ac79f16]
    • @objectstack/spec@10.1.0
    • @objectstack/core@10.1.0

10.0.0

Patch Changes

  • 92db3e5: feat(driver-sql): honor external.columnMap on federated (external) objects (ADR-0015).

    When a federated object declares external.columnMap ({ remoteColumn -> localField }), the SQL driver now translates queries to the physical remote columns: WHERE and ORDER BY map local fields to remote columns (value coercion stays keyed by the local field), formatOutput renames remote-column keys back to local field names on read, and write payloads are key-remapped. Managed objects and external objects without a columnMap are unchanged (the resolver falls back to the existing per-site behavior).

  • 2a1b16b: fix(ADR-0015): honor external.remoteName / external.remoteSchema on the federation read path.

    The query path previously resolved an external object's physical table from the object name, ignoring its external binding — so a federated object bound to a differently-named remote table failed with no such table, and ADR-0015's own wh_ordermart.fact_orders example was unqueryable. The SQL driver now resolves the remote table (remoteName, plus remoteSchema via .withSchema() on pg/mysql) and registers external objects' read-coercion metadata without DDL (SqlDriver.registerExternalObject, routed from the engine/plugin schema-sync). The managed path is unchanged. See ADR-0015 §18.

  • Updated dependencies [d7ff626]

  • Updated dependencies [2a1b16b]

  • Updated dependencies [e16f2a8]

  • Updated dependencies [e411a82]

  • Updated dependencies [a581385]

  • Updated dependencies [d5f6d29]

  • Updated dependencies [220ce5b]

  • Updated dependencies [3efe334]

  • Updated dependencies [feead7e]

  • Updated dependencies [6ca20b3]

  • Updated dependencies [5f875fe]

  • Updated dependencies [b469950]

    • @objectstack/spec@10.0.0
    • @objectstack/core@10.0.0

9.11.0

Minor Changes

  • 36138c7: feat(autonumber): date, {field} and per-scope counter reset for autonumber formats

    autonumberFormat previously only understood a single {0000} sequence slot — everything else was a fixed literal prefix on one global counter. Real MES/eHR record numbers need three more token classes, so the format is now tokenized by a shared pure renderer in @objectstack/spec (parseAutonumberFormat / renderAutonumber) that the engine fallback and the SQL driver both call, so they emit byte-identical numbers (#1603 parity):

    • Date tokens{YYYY} {YY} {MM} {DD} {YYYYMMDD} resolve the calendar day in the request's business timezone (ExecutionContext.timezone, ADR-0053; UTC fallback), threaded through the new DriverOptions.timezone.
    • {field} interpolation{section}{island_zone}{000} substitutes record field values into the prefix.
    • Per-scope counter reset — the counter's scope is the rendered prefix before the sequence slot, so AD{YYYYMMDD}{0000} resets daily, {section}{island_zone}{000} numbers per group, and {plan_no}{000} numbers per parent — all from one mechanism, no separate reset config.

    Fixed-prefix formats like CASE-{0000} render an empty scope and keep their single global counter, so existing sequences are unchanged. The persistent _objectstack_sequences table is keyed by a key_hash (SHA-256 of object, tenant_id, field, scope) — a single 64-char primary key that keys every dialect uniformly, stays within MySQL's utf8mb4 index-length limit (four raw columns would not), and lets scope be a generous non-indexed column. Deployments with an older table (3-column, or an interim scope column) are migrated in place on first use, carrying existing counters to scope=''.

    Guardrails:

    • Empty interpolated field is a hard error, not a silent mis-number. A {field} token whose value is missing at create time would render to an empty prefix and collapse the record into the wrong counter scope. Both the SQL driver and the engine fallback now refuse to generate and throw a clear error naming the empty field (shared missingFieldValues helper).
    • Build-time lint (@objectstack/cli compile). autonumber formats are checked against the object's fields: a {field} token naming a non-existent field (or the autonumber field itself) fails the build; a token naming an optional field emits an advisory warning to mark it required: true.
    • Migration fails safe. If a legacy table cannot be migrated to the key_hash shape, fixed-prefix sequences keep working via the legacy key and a per-scope write raises an actionable error instead of corrupting counters.
    • Long {field} scopes are supported (e.g. a long {plan_no}): the non-indexed scope column and hashed key remove the old varchar/PK length ceiling.

    Notes on inherent semantics (documented, not bugs):

    • The counter scope IS the rendered prefix. When two records' tokens render to the same prefix string (e.g. {a}{b} for ('AB','C') and ('A','BC')) they also render the same visible number, so they share one counter to stay unique — the remedy for genuinely-distinct groups is an unambiguous format (a delimiter literal between variable tokens).
    • The sequence pad width is a MINIMUM; past it the number grows ({000}1000), it never wraps — matching mainstream autonumber semantics.

Patch Changes

  • Updated dependencies [e7f6539]
  • Updated dependencies [2365d07]
  • Updated dependencies [6595b53]
  • Updated dependencies [fa8964d]
  • Updated dependencies [36138c7]
  • Updated dependencies [a8e4f3b]
  • Updated dependencies [4c213c2]
  • Updated dependencies [2afb612]
    • @objectstack/spec@9.11.0
    • @objectstack/core@9.11.0

9.10.0

Patch Changes

  • db02bd5: Fix dashboard time-series charts / "last N months" KPIs that filter or group by a Field.datetime column silently returning "No rows".

    The analytics NativeSQLStrategy compiles dashboard relative-date tokens ({12_months_ago}, {today}, …) to ISO date strings and binds them directly into raw SQL, bypassing the driver's own filter coercion. Under better-sqlite3 a Field.datetime column is stored as an INTEGER epoch (ms), so assessed_at >= '2025-06-18' became a TEXT-vs-INTEGER affinity compare that is always false — an empty result even though the rows exist. Field.date columns store ISO TEXT and were unaffected.

    The strategy now coerces a temporal comparand to the column's on-disk storage form via a new optional StrategyContext.coerceTemporalFilterValue hook, wired to the driver's public SqlDriver.temporalFilterValue (the single source of truth for the storage convention). Coercion is dialect-correct: SQLite Field.datetime → epoch ms; Field.date text and native-timestamp dialects (Postgres/MySQL) are left unchanged, so Postgres is never handed an epoch integer. Applied to gte/lte/gt/lt/equals, in/notIn, and the dateRange/timeDimension BETWEEN path.

  • d9508d1: fix(driver-sql): make numeric-scalar type fidelity self-heal on legacy SQLite columns

    The #2025 fix mapped rating/slider/progress to numeric columns, but SQLite never alters a column's type in place and the schema reconciler only adds missing columns — so a column created before that fix keeps its TEXT affinity and would still read back '4' instead of 4 forever.

    A read-side numeric coercion (the new numericFields registry, single-sourced from NUMERIC_SCALAR_TYPES) now coerces numeric-looking stored strings back to numbers on read, mirroring how dateFields already repairs legacy timestamp-typed Field.date rows. The fidelity no longer depends on column affinity alone; null and genuinely non-numeric legacy values are left intact rather than turned into 0/NaN.

  • 1d352d3: fix(driver-sql): round-trip rating/slider/toggle/progress with type fidelity

    rating/slider/toggle/progress had no case in the DDL column-type switch, so they fell to default → table.string (TEXT affinity). SQLite then coerced the written value to a string — rating: 4 read back '4', toggle: true read back '1' — so the value persisted but the JS type leaked on read. On a low-code platform where field types are author-driven, a field that silently returns the wrong type is a runtime-fidelity trap the static gates and value-loss tests don't catch.

    • rating/slider/progress now map to a REAL (numeric) column.
    • toggle maps to a boolean column and is registered in the boolean read-coercion path, so stored 1/0 come back as real JS booleans.
    • The object-valued record/video/audio types are folded into the shared JSON_COLUMN_TYPES source, and the DDL default case now derives JSON-vs-string from that set, so the column-type switch and isJsonField (the read-side deserializer) can no longer drift.
  • Updated dependencies [db02bd5]

  • Updated dependencies [641675d]

  • Updated dependencies [94e9040]

  • Updated dependencies [1f88fd9]

  • Updated dependencies [1f88fd9]

    • @objectstack/spec@9.10.0
    • @objectstack/core@9.10.0

9.9.1

Patch Changes

  • @objectstack/spec@9.9.1
  • @objectstack/core@9.9.1

9.9.0

Minor Changes

  • bfa3102: fix: array-valued field types persist, and Field.time accepts time-of-day — two field-type runtime gaps found driving the showcase field-zoo (which had no seed data, so neither was ever exercised).

    Array/object fields broke every write (driver-sql). multiselect / checkboxes / tags / repeater / vector were absent from the SQL driver's JSON-field classification, so their array values reached the better-sqlite3 binder un-serialized and threw "SQLite3 can only bind numbers, strings, bigints, buffers, and null" — a 500 on insert/update for common field types (even task.labels on a normal object). The DDL column-type switch and isJsonField had drifted into two separate lists; they now share one JSON_COLUMN_TYPES source that includes the array/object types, so these columns are created as JSON and round-trip as arrays/objects. A formatInput safety net additionally serializes any stray array/object value so an unclassified field degrades to a stored string instead of crashing.

    Field.time rejected every valid value (objectql). The validator reused the date/datetime branch (Date.parse), which is NaN for any bare time string — so a time field could never accept 14:30 or 09:05:30. time now validates a time-of-day (HH:MM / HH:MM:SS, optional fractional seconds and Z/offset) and still accepts a full ISO datetime; date/datetime are unchanged.

    Verified live on app-showcase: the full field-zoo specimen (all input-able field types) now persists and round-trips. Regression tests added for both.

Patch Changes

  • 796f0d6: fix(driver-sql): Field.date is now stored and returned as a tz-naive YYYY-MM-DD calendar day (ADR-0053 Phase 1)

    A Field.date ("close date", "due date", "birthday") is semantically a timezone-naive calendar day, but the SQL driver was treating it as an instant: formatInput wrote the value verbatim (keeping any time component, so dev.db held close_date = "2026-07-15T17:24:56.533Z"), while the filter layer (coerceFilterValue) already normalized the comparand to date-only YYYY-MM-DD. That write/filter asymmetry meant a date-equality filter — close_date == '2026-07-15', expires_on: { $in: [...] }, or a daysFromNow(n)-style comparand — compared "2026-07-15T17:24Z" against "2026-07-15" and silently matched nothing.

    This patch aligns the write/read boundary with the date-only contract the filter already enforced:

    • Write (formatInput): every Field.date value (a JS Date, a full-ISO string, or an already date-only string) collapses to YYYY-MM-DD before insert/update. A Date collapses to its UTC calendar day, matching coerceFilterValue.
    • Read (formatOutput): Field.date values are returned as YYYY-MM-DD, slicing any stored time component. This transparently repairs legacy rows that were written as a full timestamp, so date-equality works without a data migration. Read normalization now runs on the find path for every dialect (previously only findOne), matching the new behaviour.
    • The truncation logic is shared by the filter, write and read paths via a single toDateOnly helper, so all three agree on what a date is.

    Field.datetime is unchanged — it keeps full-instant (UTC millisecond) semantics.

    Out of scope (ADR-0053 Phase 2): timezone-aware today()/daysFromNow()/daysAgo(), an org/user reference timezone, and datetime render-time TZ. See ADR-0053 and issue #1928.

  • Updated dependencies [84249a4]

  • Updated dependencies [11af299]

  • Updated dependencies [d5774b5]

  • Updated dependencies [134043a]

  • Updated dependencies [90108e0]

  • Updated dependencies [9afeb2d]

  • Updated dependencies [6bec07e]

  • Updated dependencies [601cc11]

  • Updated dependencies [575448d]

    • @objectstack/spec@9.9.0
    • @objectstack/core@9.9.0

9.8.0

Patch Changes

  • Updated dependencies [97c55b3]
  • Updated dependencies [1b1f490]
    • @objectstack/spec@9.8.0
    • @objectstack/core@9.8.0

9.7.0

Patch Changes

  • @objectstack/spec@9.7.0
  • @objectstack/core@9.7.0

9.6.0

Patch Changes

  • Updated dependencies [d1e930a]
  • Updated dependencies [71578f2]
  • Updated dependencies [5e3a301]
  • Updated dependencies [5db2742]
    • @objectstack/spec@9.6.0
    • @objectstack/core@9.6.0

9.5.1

Patch Changes

  • Updated dependencies [ee72aae]
    • @objectstack/spec@9.5.1
    • @objectstack/core@9.5.1

9.5.0

Patch Changes

  • Updated dependencies [d08551c]
  • Updated dependencies [707aeed]
  • Updated dependencies [7a103d4]
  • Updated dependencies [4b01250]
    • @objectstack/spec@9.5.0
    • @objectstack/core@9.5.0

9.4.0

Patch Changes

  • b678d8c: fix(driver-sql): an unknown $select column must not zero the result set

    find() swallowed any "no such column" error into an empty array. A projected $select naming a column the table lacks (e.g. a generic list view auto-requesting status/due_date/image on an object without them) then made the WHOLE query return zero rows — reading to the UI as "no records exist" while the data was actually there: a silent data-loss footgun.

    When the failure comes from the projection, retry once with SELECT * so the real rows still come back (the phantom field is simply absent from each row). Non-projection errors (unknown table, etc.) still surface as before. This driver backstop holds even when the engine's unknown-field filter cannot fire because the object's schema is not populated in the registry (notably the cloud multi-tenant runtime).

  • Updated dependencies [060467a]

  • Updated dependencies [0856476]

  • Updated dependencies [b678d8c]

  • Updated dependencies [b678d8c]

  • Updated dependencies [b678d8c]

    • @objectstack/spec@9.4.0
    • @objectstack/core@9.4.0

9.3.0

Patch Changes

  • Updated dependencies [1ada658]
  • Updated dependencies [3219191]
  • Updated dependencies [290f631]
  • Updated dependencies [50b7b47]
  • Updated dependencies [f15d6f6]
  • Updated dependencies [f8684ea]
  • Updated dependencies [b4765be]
    • @objectstack/spec@9.3.0
    • @objectstack/core@9.3.0

9.2.0

Patch Changes

  • Updated dependencies [2f57b75]
  • Updated dependencies [2f57b75]
    • @objectstack/spec@9.2.0
    • @objectstack/core@9.2.0

9.1.0

Patch Changes

  • Updated dependencies [b9062c9]
    • @objectstack/spec@9.1.0
    • @objectstack/core@9.1.0

9.0.1

Patch Changes

  • Updated dependencies [1817845]
    • @objectstack/spec@9.0.1
    • @objectstack/core@9.0.1

9.0.0

Patch Changes

  • Updated dependencies [4c3f693]
  • Updated dependencies [0bf39f1]
  • Updated dependencies [f533f42]
  • Updated dependencies [1c83ee8]
    • @objectstack/spec@9.0.0
    • @objectstack/core@9.0.0

8.0.1

Patch Changes

  • @objectstack/spec@8.0.1
  • @objectstack/core@8.0.1

8.0.0

Minor Changes

  • b990b89: fix(autonumber): one owner for autonumber generation — the persistent driver sequence (#1603)

    Autonumber values were generated in TWO places: the SQL driver's persistent, atomic _objectstack_sequences table AND a non-persistent in-memory counter in the ObjectQL engine. Because the engine pre-filled the field BEFORE calling the driver, the driver always saw a value already set and skipped — so the persistent sequence was effectively dead code, and a multi-instance / post-restart deployment could mint duplicate numbers from the in-memory counter.

    This makes generation single-owner:

    • @objectstack/specDriverCapabilities gains an optional autonumber flag: "driver natively generates persistent autonumber/sequence values".

    • @objectstack/driver-sql — advertises supports.autonumber = true. bulkCreate() now fills autonumber fields too (previously only create() / upsert() did), so bulk inserts also draw from the persistent sequence. Field parsing now honors either the spec-canonical autonumberFormat key OR the format shorthand (both appear in metadata).

    • @objectstack/objectql — when the driver advertises native autonumber support, the engine NO LONGER pre-fills (it defers entirely to the persistent driver sequence as the single source of truth). For drivers without native support (memory, mongodb) the in-memory fallback is unchanged. The fallback also now reads either autonumberFormat or format. Record-validation exempts autonumber fields from the required check — the value is runtime-owned and assigned after validation, so a required record number is never rejected as "missing".

    No metadata changes required. Existing data is respected: the driver bootstraps each sequence from the current max numeric tail on first use.

Patch Changes

  • 1e8b680: fix(security): close four P0 launch-readiness findings

    • plugin-auth (P0-1): generateSecret() now throws (fails boot) when no OS_AUTH_SECRET is set and NODE_ENV==='production', instead of silently falling back to a predictable dev-secret-<timestamp> (session forgery). The dev/test fallback is unchanged.
    • plugin-security (P0-2): the permission-resolution catch now fails closed — it logs at ERROR and throws PermissionDeniedError rather than return next(). A degraded metadata service can no longer let every authenticated request bypass RBAC/RLS. System operations still bypass as before.
    • driver-sql (P0-3): the contains / $contains operator now escapes LIKE metacharacters (% / _ / \) in the user value and binds an explicit ESCAPE '\', so a value of % matches literally instead of every row (filter bypass). Correct across SQLite/MySQL/Postgres.
    • driver-mongodb (P0-4): the field-operator translator now rejects unknown $-operators instead of passing them through, blocking $where / $function / $expr (server-side JS execution / query-intent bypass). All legitimate ObjectQL operators remain allowlisted.

    +12 regression tests across the four packages.

  • Updated dependencies [a46c017]

  • Updated dependencies [b990b89]

  • Updated dependencies [99111ec]

  • Updated dependencies [d5a8161]

  • Updated dependencies [5cf1f1b]

  • Updated dependencies [9ef89d4]

  • Updated dependencies [3306d2f]

  • Updated dependencies [c262301]

  • Updated dependencies [bc44195]

  • Updated dependencies [9e2e229]

    • @objectstack/spec@8.0.0
    • @objectstack/core@8.0.0

7.9.0

Patch Changes

  • @objectstack/spec@7.9.0
  • @objectstack/core@7.9.0

7.8.0

Patch Changes

  • Updated dependencies [06f2bbb]
  • Updated dependencies [36719db]
  • Updated dependencies [424ab26]
    • @objectstack/spec@7.8.0
    • @objectstack/core@7.8.0

7.7.0

Patch Changes

  • 764c747: fix(metadata): home the metadata-storage objects in metadata-core and register them from ObjectQL

    Standalone "host config" apps boot without @objectstack/metadata's MetadataPlugin, so nobody registered the metadata-storage objects (sys_metadata, _history, _audit, sys_view_definition) into ObjectQL — their tables were never schema-synced and ObjectQL's own protocol (loadMetaFromDb / getMetaItems) failed with no such table: sys_metadata on every read.

    • Move the four storage-object definitions from @objectstack/platform-objects/metadata to @objectstack/metadata-core (the lowest package shared by their real consumers); platform-objects/metadata now re-exports them for back-compat.
    • ObjectQLPlugin registers these objects itself (gated on environmentId === undefined, mirroring restoreMetadataFromDb) so their tables always sync on platform/standalone kernels.
    • Gate the SQL driver's tenant-audit warning on actual multi-tenant mode — organization_id now exists on every table, so column presence alone no longer implies "tenant-scoped"; single-tenant boots no longer spam the warning for system writes.
  • Updated dependencies [b391955]

  • Updated dependencies [f06b64e]

  • Updated dependencies [023bf93]

    • @objectstack/spec@7.7.0
    • @objectstack/core@7.7.0

7.6.0

Patch Changes

  • Updated dependencies [955d4c8]
  • Updated dependencies [c4a4cbd]
  • Updated dependencies [b046ec2]
  • Updated dependencies [2170ad9]
  • Updated dependencies [02d6359]
  • Updated dependencies [7648242]
  • Updated dependencies [8fa1e7f]
  • Updated dependencies [55866f5]
  • Updated dependencies [60f9c45]
    • @objectstack/spec@7.6.0
    • @objectstack/core@7.6.0

7.5.0

Patch Changes

  • @objectstack/spec@7.5.0
  • @objectstack/core@7.5.0

7.4.1

Patch Changes

  • @objectstack/spec@7.4.1
  • @objectstack/core@7.4.1

7.4.0

Minor Changes

  • 24c9013: fix(driver-sql): materialize declared object-level indexes (#1459)

    The SQL driver synced columns and field-level unique, but silently dropped object-level declared indexes (ObjectSchema.indexes: [{ fields, unique }]). As a result several documented multi-column UNIQUE / dedup guarantees were never enforced at the DB level — a fresh dev --fresh sqlite DB showed only primary-key autoindexes.

    initObjects now materializes declared indexes (syncDeclaredIndexes) after the table is created/altered:

    • single- and multi-column indexes, including UNIQUE
    • NULL-distinct semantics (the cross-dialect default), so multiple NULL rows stay insertable while non-NULL duplicates are rejected — matching the convergence-on-conflict pattern the messaging pipeline relies on
    • idempotent: deterministic, length-bounded index names + per-dialect existing-index introspection (sqlite/pg/mysql); "already exists" races are absorbed
    • indexes referencing a non-materialized (virtual formula) column are skipped with a warning instead of failing sync

    The indexes driver capability flag is now true.

  • 2faf9f2: External Datasource Federation (ADR-0015) — Phase 1.

    Adds the spec foundation and the DDL gate for federating mature external databases without ObjectStack ever mutating their schema:

    • Datasource.schemaMode (managed | external | validate-only) and Datasource.external settings, with a cross-field invariant.
    • Object.external binding (remote table/schema, writability, column map).
    • Shared error contract: ExternalSchemaMismatchError, ExternalWriteForbiddenError, ExternalSchemaModeViolationError (stable codes) + structured SchemaDiffEntry rendering.
    • driver-sql DDL gate: schema-mutating DDL (initObjects/syncSchema/ dropTable) is rejected when schemaMode !== 'managed'.

    All changes are additive and backward-compatible (schemaMode defaults to 'managed').

Patch Changes

  • Updated dependencies [23c7107]
  • Updated dependencies [c72daad]
  • Updated dependencies [f115182]
  • Updated dependencies [2faf9f2]
  • Updated dependencies [2faf9f2]
  • Updated dependencies [2faf9f2]
  • Updated dependencies [58b450b]
  • Updated dependencies [82eb6cf]
  • Updated dependencies [13d8653]
  • Updated dependencies [ff3d006]
  • Updated dependencies [5e831de]
    • @objectstack/spec@7.4.0
    • @objectstack/core@7.4.0

7.3.0

Patch Changes

  • Updated dependencies [5e7c554]
    • @objectstack/spec@7.3.0
    • @objectstack/core@7.3.0

7.2.1

Patch Changes

  • @objectstack/spec@7.2.1
  • @objectstack/core@7.2.1

7.2.0

Patch Changes

  • @objectstack/spec@7.2.0
  • @objectstack/core@7.2.0

7.1.0

Patch Changes

  • Updated dependencies [47a92f4]
    • @objectstack/spec@7.1.0
    • @objectstack/core@7.1.0

7.0.0

Patch Changes

  • Updated dependencies [74470ad]
  • Updated dependencies [d29617e]
  • Updated dependencies [dc72172]
    • @objectstack/spec@7.0.0
    • @objectstack/core@7.0.0

6.9.0

Patch Changes

  • @objectstack/spec@6.9.0
  • @objectstack/core@6.9.0

6.8.1

Patch Changes

  • @objectstack/spec@6.8.1
  • @objectstack/core@6.8.1

6.8.0

Patch Changes

  • Updated dependencies [6e88f77]
  • Updated dependencies [c8b9f57]
    • @objectstack/spec@6.8.0
    • @objectstack/core@6.8.0

6.7.1

Patch Changes

  • @objectstack/spec@6.7.1
  • @objectstack/core@6.7.1

6.7.0

Patch Changes

  • 4944f3a: Promote native database client packages so npm consumers can boot without manual installs.

    • better-sqlite3 is now an optionalDependency (prebuilt binaries cover the common case), so npx @objectstack/cli start boots a default SQLite database out-of-the-box.
    • pg, mysql2, sqlite3, and tedious are declared as optional peerDependencies (peerDependenciesMeta.optional = true), removing install warnings while keeping the loader-on-demand pattern.

    Fixes: Knex: Cannot find module 'better-sqlite3' on fresh npm install @objectstack/cli followed by objectstack start.

  • Updated dependencies [430067b]

  • Updated dependencies [4f9e9d4]

    • @objectstack/spec@6.7.0
    • @objectstack/core@6.7.0

6.6.0

Patch Changes

  • Updated dependencies [a49cfc2]
    • @objectstack/spec@6.6.0
    • @objectstack/core@6.6.0

6.5.1

Patch Changes

  • @objectstack/spec@6.5.1
  • @objectstack/core@6.5.1

6.5.0

Patch Changes

  • @objectstack/spec@6.5.0
  • @objectstack/core@6.5.0

6.4.0

Patch Changes

  • Updated dependencies [f8651cc]
  • Updated dependencies [f8651cc]
  • Updated dependencies [0bf6f9a]
    • @objectstack/spec@6.4.0
    • @objectstack/core@6.4.0

6.3.0

Patch Changes

  • @objectstack/spec@6.3.0
  • @objectstack/core@6.3.0

6.2.0

Patch Changes

  • Updated dependencies [b4c74a9]
    • @objectstack/spec@6.2.0
    • @objectstack/core@6.2.0

6.1.1

Patch Changes

  • @objectstack/spec@6.1.1
  • @objectstack/core@6.1.1

6.1.0

Patch Changes

  • Updated dependencies [93c0589]
    • @objectstack/spec@6.1.0
    • @objectstack/core@6.1.0

6.0.0

Patch Changes

  • Updated dependencies [629a716]
  • Updated dependencies [dbc4f7d]
  • Updated dependencies [944f187]
    • @objectstack/spec@6.0.0
    • @objectstack/core@6.0.0

5.2.0

Patch Changes

  • Updated dependencies [bab2b20]
  • Updated dependencies [fa011d8]
  • Updated dependencies [b806f58]
    • @objectstack/spec@5.2.0
    • @objectstack/core@5.2.0

5.1.0

Patch Changes

  • Updated dependencies [75f4ee6]
  • Updated dependencies [823d559]
    • @objectstack/spec@5.1.0
    • @objectstack/core@5.1.0

5.0.0

Patch Changes

  • Updated dependencies [2f9073a]
    • @objectstack/spec@5.0.0
    • @objectstack/core@5.0.0

4.2.0

Patch Changes

  • Updated dependencies [2869891]
    • @objectstack/spec@4.2.0
    • @objectstack/core@4.2.0

4.1.1

Patch Changes

  • @objectstack/spec@4.1.1
  • @objectstack/core@4.1.1

4.1.0

Minor Changes

  • 0cc0374: feat(driver-sql): tenant-isolated auto_number sequences backed by a persistent counter table

    Breaking nothing; new behaviour is opt-in via object schema.

    The SQL driver now generates auto_number / autonumber field values via a dedicated _objectstack_sequences table keyed by (object, tenant_id, field) instead of scanning the data table for the current MAX on every insert.

    Highlights:

    • Tenant isolation. Objects with an organization_id field get a separate counter per organization. Two tenants creating contracts at the same time both legitimately observe CTR-0001, CTR-0002, … in their own namespaces — they no longer interleave or skip numbers.
    • Tenant resolution. Source order: row[organization_id]DriverOptions.tenantId__global__ sentinel for org-less objects (e.g. setup-side singletons share one counter).
    • Bootstrap from existing data. On the first reservation in a new (object, tenant, field) tuple, the driver seeds last_value from the current per-tenant MAX so legacy/seeded records keep their position and downstream inserts pick up monotonically (gaps are tolerated).
    • Atomic increment. Each reservation runs in a transaction with SELECT … FOR UPDATE (where the dialect supports it) and a single UPDATE of last_value. Tested with 25 concurrent inserts in one tenant producing 25 distinct sequence values.
    • Caller overrides honoured. A row that already has an explicit value for the auto_number field is left untouched, and the sequence bootstrap respects that value so future reservations advance past it.
    • Dual spelling. Both type: 'auto_number' (snake) and type: 'autonumber' (the spec factory output) are recognised.

    Migration notes:

    • The first time the driver handles an auto_number insert, it creates the _objectstack_sequences table automatically — no manual DDL.
    • Pre-existing data is not renumbered. Gaps introduced by older cross-tenant logic (where a tenant's number could "jump" because it inherited another tenant's MAX) remain in place; subsequent inserts continue from MAX + 1 in the affected tenant.
  • 5b878d9: Generate auto_number / autonumber field values on insert. The driver parses the field's format template (e.g. CTR-{0000}) to extract the prefix and pad-width, then scans existing rows with the same prefix and emits prefix + padded(maxN + 1) for any row that omits the field.

    Note: per-call MAX+1 — not atomic across concurrent writers. Fine for seed-data and low-write demo loads; production deployments should layer a dedicated sequence table.

  • f0b3972: Driver-level tenant isolation for objects with organization_id.

    SqlDriver now auto-applies a WHERE organization_id = :tenantId predicate on every read/update/delete and auto-injects the column on insert when the caller passes options.tenantId and the object schema declares an organization_id field. bulkCreate, bulkDelete, updateMany, deleteMany, count and aggregate are all scoped.

    ObjectQL's engine now threads ExecutionContext.tenantId into the driver options for every CRUD entry point (including expandRelatedRecords), so a tenant-scoped session can no longer cross tenants — even through lookup expansion or count fallbacks.

    Backward compatible: callers that omit tenantId (system tasks, seed scripts) keep getting unscoped behaviour. Explicit organization_id on an insert row always wins over the contextual tenantId so admin tooling can still target a specific tenant.

    13 new tests in sql-driver-tenant-scope.test.ts verify cross-tenant find/findOne/update/delete/count/bulkCreate/updateMany/deleteMany isolation, the unscoped admin path, and that global objects (no organization_id) are not scoped.

  • 0e63f2f: Declarative tenant scoping + audit warn for missing tenantId.

    SqlDriver now reads obj.tenancy.tenantField first when picking the tenant column for an object, falling back to the implicit organization_id detection so legacy objects keep working without a spec migration. Set tenancy: { enabled: true, strategy: 'shared', tenantField: 'workspace_id' } on any object to use a custom column.

    Writes (create, update, delete, bulkCreate, bulkDelete, updateMany, deleteMany, upsert) that target a tenant-scoped object without options.tenantId now emit one [tenant-audit] warning per {object}:{op} so missing-context bugs surface in CI/logs instead of silently writing globally. The engine auto-silences when ExecutionContext.isSystem === true (boot-time seeds, kernel mirrors). Callers can opt out per-call with options.bypassTenantAudit = true or globally with OS_TENANT_AUDIT=0.

    Driver README now documents the full scope/bypass matrix and the audit warning.

    Three new tests cover the declared-tenant-field path, the audit throttle, and the bypass flag.

Patch Changes

  • 5683206: Document the tenant-isolation bypass on raw execute() (both SqlDriver.execute() and engine.execute()). The behaviour is unchanged — execute() has always passed commands through verbatim — but the JSDoc now spells out the security contract so callers know they must inline WHERE organization_id = ? themselves or restrict raw execution to genuinely global statements (migrations, control-plane tables).
  • Updated dependencies [2108c30]
  • Updated dependencies [23db640]
    • @objectstack/spec@4.1.0
    • @objectstack/core@4.1.0

4.0.5

Patch Changes

  • 15e0df6: chore: unify all package versions to a single patch release
  • Updated dependencies [15e0df6]
    • @objectstack/spec@4.0.5
    • @objectstack/core@4.0.5

4.0.4

Patch Changes

  • Updated dependencies [326b66b]
    • @objectstack/spec@4.0.4
    • @objectstack/core@4.0.4

4.0.3

Patch Changes

  • @objectstack/spec@4.0.3
  • @objectstack/core@4.0.3

4.0.2

Patch Changes

  • 5f659e9: fix ai
  • Updated dependencies [5f659e9]
    • @objectstack/spec@4.0.2
    • @objectstack/core@4.0.2

3.3.2

Patch Changes

  • Updated dependencies [f08ffc3]
  • Updated dependencies [e0b0a78]
    • @objectstack/spec@4.0.0
    • @objectstack/core@4.0.0

3.3.1

Patch Changes

  • @objectstack/spec@3.3.1
  • @objectstack/core@3.3.1

3.3.0

Minor Changes

  • 814a6c4: sql driver

Patch Changes

  • @objectstack/spec@3.3.0
  • @objectstack/core@3.3.0