-
211425e: fix(objectql): return the real
total/hasMorefromfindData(#2212)ObjectStackProtocolImplementation.findDatapreviously returned placeholder pagination metadata:totalwas always the page length andhasMorewas alwaysfalse. Front-end tables therefore believed every result set was a single page and never requested records past the first batch (e.g. row 51+ was unreachable).For a normal limited query it now runs
engine.count()over the samewhereto get the true match total and deriveshasMorefromoffset + page length < total.engine.count()only honorswhere, sosearch/distinctqueries skip the count and fall back to a page-local estimate (a full page implies there may be more) instead of reporting a wrong total. Unlimited queries return the full set, whose length already is the total. The aggregate/group branch now reports the full group count astotalwithhasMorereflecting whether the client-side slice dropped any groups.- @objectstack/spec@10.3.0
- @objectstack/core@10.3.0
- @objectstack/types@10.3.0
- @objectstack/metadata-core@10.3.0
- @objectstack/formula@10.3.0
- Updated dependencies [b496498]
- @objectstack/spec@10.2.0
- @objectstack/core@10.2.0
- @objectstack/formula@10.2.0
- @objectstack/metadata-core@10.2.0
- @objectstack/types@10.2.0
- Updated dependencies [49da36e]
- Updated dependencies [ac79f16]
- @objectstack/spec@10.1.0
- @objectstack/core@10.1.0
- @objectstack/formula@10.1.0
- @objectstack/metadata-core@10.1.0
- @objectstack/types@10.1.0
-
e16f2a8: BREAKING: the system object
sys_departmentis renamed tosys_business_unit— object + member table (sys_department_member→sys_business_unit_member), fields, and i18n — with no compatibility alias. Any deployment holdingsys_departmentrows, or metadata that references the object by name (lookups, list views, queries, sharing/approval scopes), must migrate tosys_business_unit. A renamed shipped system object is a breaking change to the platform's public data surface, so this lands as a major. Verified per ADR-0059's pre-publish hotcrm gate: no published downstream consumer references the old name.ADR-0057 — ERP authorization core. Adds permission-grant access DEPTH (
own/own_and_reports/unit/unit_and_below/org), renamessys_department→sys_business_unit(no aliases — see BREAKING above), introduces the platform-ownedsys_user_roleassignment, and seeds stack-declaredroles/sharingRulesintosys_role/sys_sharing_ruleat boot (closes #2077). Hierarchy-relative scopes are delegated to a pluggableIHierarchyScopeResolver(open edition fails closed to owner-only;defineStackerrors withoutrequires: ['hierarchy-security']). Also fixes a latent over-grant whereengine.find({ filter })was ignored (driver readswhere) — normalizedfilter→wherein the engine.
-
2a1b16b: fix(ADR-0015): honor
external.remoteName/external.remoteSchemaon the federation read path.The query path previously resolved an external object's physical table from the object name, ignoring its
externalbinding — so a federated object bound to a differently-named remote table failed withno such table, and ADR-0015's ownwh_order→mart.fact_ordersexample was unqueryable. The SQL driver now resolves the remote table (remoteName, plusremoteSchemavia.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. -
3efe334: Honor a nested
wherefilter insideexpandon lookup/master_detail expansion.The expand post-processor batch-loads related records with an
id $in [...]query but never merged the nested QueryASTwhere, so a documentedexpand: { rel: { where: {...} } }filter was silently ignored and every related record came back. The nested filter is now AND-merged into the batch query via an explicit$andgroup ({ $and: [{ id: { $in } }, nestedAST.where] }) — robust against a nested filter that itself keysidor uses a top-level$or/$and, where a shallow spread would clobber or reorder the constraint.limit/offset/orderByremain intentionally not honored on the expand path: it batch-loads every parent's related records in one$inquery and re-keys them per parent by foreign key, so a per-parent page size or ordering can't be expressed there. Docs and the schemadescribe()are updated to match, with a guard test assertinglimit/offsetare not pushed into the expand query. -
Updated dependencies [d7ff626]
-
Updated dependencies [2a1b16b]
-
Updated dependencies [e16f2a8]
-
Updated dependencies [cfd86ce]
-
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]
-
Updated dependencies [48a307a]
-
Updated dependencies [25fc0e4]
- @objectstack/spec@10.0.0
- @objectstack/formula@10.0.0
- @objectstack/core@10.0.0
- @objectstack/metadata-core@10.0.0
- @objectstack/types@10.0.0
-
36138c7: feat(autonumber): date, {field} and per-scope counter reset for autonumber formats
autonumberFormatpreviously 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 newDriverOptions.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_sequencestable is keyed by akey_hash(SHA-256 ofobject, 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 letsscopebe a generous non-indexed column. Deployments with an older table (3-column, or an interimscopecolumn) are migrated in place on first use, carrying existing counters toscope=''.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 (sharedmissingFieldValueshelper). - Build-time lint (
@objectstack/cli compile).autonumberformats 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 itrequired: true. - Migration fails safe. If a legacy table cannot be migrated to the
key_hashshape, 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-indexedscopecolumn 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.
- Date tokens —
- 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
- @objectstack/formula@9.11.0
- @objectstack/metadata-core@9.11.0
- @objectstack/types@9.11.0
-
1f88fd9: Add a transaction boundary to sandboxed hook/action bodies:
ctx.api.transaction(async () => { … }). Everyctx.apiread/write inside the callback runs in one driver transaction — committed when the callback returns, rolled back if it throws (or if the body leaves the transaction open at timeout). Guarded by the newapi.transactioncapability.- spec: new
api.transactioncapability token onHookBodyCapability. - objectql:
ScopedContextgains discretebeginTransaction()/commitTransaction(handle)/rollbackTransaction(handle)primitives. The handle is threaded explicitly through a child context (resolveTxhonors it ahead of the ambienttxStore), because the sandbox drives the body across many host event-loop turns where AsyncLocalStorage context does not survive. Degrades to non-transactional execution when the driver has no transaction support. - runtime: the QuickJS runner wires
ctx.api.transactionover three deferred-promise host leaves (begin/commit/rollback), routes in-transaction ops through the tx-scoped context, and rolls back a transaction the body left open before disposing the VM.
- spec: new
-
e2b5324: feat(ownership): auto-provision a canonical
owner_idand hand seeded records to the first adminOwnership is now correct-by-default instead of opt-in — closing the gap where seeded demo data ended up owned by nobody a human can log in as (so "My" views, owner reports and owner notifications were empty out of the box) and where author-written objects silently shipped with no working ownership at all.
-
applySystemFields(objectql) now auto-injects a canonical, reassignableowner_idlookup (→sys_user) on user-authored business objects, alongside the existing tenant/audit fields. Unlike the audit*_bylookups it is NOT readonly — ownership transfers. Withheld formanagedBy/sys_*tables and for objects that opt out viaownership: 'org' | 'none'(Dataverse-style). The safe default direction: forgetting the opt-out leaves a harmless spare column, whereas the old opt-IN model let authors ship objects with broken ownership. Once present, the existing machinery engages automatically (insert auto-stamp, owner-scoped RLS, owner-keyed views/reports). -
claimSeedOwnership(plugin-security), invoked frombootstrapPlatformAdminright after the first human is promoted to platform admin, transfers ownership of seeded rows (owner_idNULL orusr_system) to that admin. The ownership twin of org-scoping'sclaimOrphanOrgRows. Idempotent; skipsmanagedBy/sys_*. Authors write plain seed records (noowner_id) and the platform — not the author — performs the handoff, so there is nothing to remember or mistype. -
usr_systemis never minted (runtime + objectql). The seed loader bindsos.userto a NULL identity, socelos.user.id``resolves to NULL at seed time (the owning admin does not exist yet) and the row seeds NULL-owned — then the handoff above fills it. The runtime'sensureSeedIdentity(the only code that inserted a`usr_system`row) is removed.`SystemUserId.SYSTEM`survives only as a reserved id so legacy DBs' exclusion guards / ownership handoff still recognize a pre-existing row.`os.org`is unaffected (derived from`organizationId`).
Also hardens
bootstrapPlatformAdminagainst a latent dts typecheck error (defensive read of the untypeddescriptionon seed permission sets). -
-
fd07027: fix(analytics): make organization timezone actually drive date-dimension bucketing (ADR-0053 Phase 2, #1982)
Date-bucketed analytics silently ignored the reference timezone end-to-end. Three independent seams were broken:
- service-analytics —
NativeSQLStrategy(priority 10) won every cube/dataset query on a SQL driver, but it groups by the raw column (nodate_trunc) and ignorestimezone, so a date dimension never bucketed (one row per raw timestamp) and a non-UTC zone was dropped. It now declines queries that carry atimeDimensions[].granularity, handing them toObjectQLStrategy→engine.aggregate(native bucketing when UTC-safe, uniform in-memory bucketing when non-UTC). - objectql — the in-memory
countaggregation treated the*count-all sentinel (the Cubecountmeasure / a fieldless datasetcount, both compiled tosql: '*') as a column name, counting non-null of a non-existent property →0for every bucket. The driver'sCOUNT(*)masked it; the in-memory path (non-UTC date buckets,driver-rest/driver-memory) returned zeros.*is now counted as all rows. - rest —
resolveExecCtxnever resolved the localization timezone/locale, so/analytics/dataset/queryalways ran withtimezone: 'UTC'. It now resolves them through thesettingsservice (honouring the 4-tier cascade incl. theOS_LOCALIZATION_TIMEZONEenv override), mirroring the dispatcher path.
- service-analytics —
-
Updated dependencies [db02bd5]
-
Updated dependencies [641675d]
-
Updated dependencies [1f88fd9]
-
Updated dependencies [94e9040]
-
Updated dependencies [1f88fd9]
-
Updated dependencies [1f88fd9]
- @objectstack/spec@9.10.0
- @objectstack/formula@9.10.0
- @objectstack/core@9.10.0
- @objectstack/metadata-core@9.10.0
- @objectstack/types@9.10.0
- @objectstack/spec@9.9.1
- @objectstack/core@9.9.1
- @objectstack/types@9.9.1
- @objectstack/metadata-core@9.9.1
- @objectstack/formula@9.9.1
-
44c5348: fix: two runtime gaps found by driving the CRM example end-to-end.
Delete of a parent with a required-FK child no longer fails with a misleading " is required" error.
cascadeDeleteRelationsdefaulted alookupFK toset_null; for a required FK that issued an UPDATE clearing the column, which the child's validator rejected with a400 "<field> is required"naming a field that isn't even on the object being deleted (e.g. deleting acrm_accountwith opportunities →"account is required"). A required FK can't be nulled, so a defaultedset_nullnow escalates torestrict: the delete is refused with a clear409 DELETE_RESTRICTEDcarrying the dependent object + count ("Cannot delete crm_account (…): 4 dependent crm_opportunity record(s) reference it via account … set deleteBehavior:'cascade'"). Explicitcascade/restrictand optional (nullable) lookups are unchanged.Removed the hardcoded
POST /data/lead/:id/convertendpoint +convertLeadprotocol method. It hardcoded bare object names (lead/account/contact/opportunity) and a fixed Salesforce field mapping into the framework runtime, so it was unreachable by any real (namespaced) app —/data/crm_lead/:id/convert404s, and the literalleadobject doesn't exist. Lead conversion is an app concern modeled correctly as a flow (the CRM ships acrm_convert_lead_wizardscreen flow); baking a CRM-specific workflow into the framework was false surface. Untested, undocumented, unused by the example. Removed. -
601cc11: feat(analytics): timezone-aware date bucketing (ADR-0053 Phase 2)
Analytics day/week/month/quarter/year buckets now resolve on a reference timezone's calendar days, so a row near a tz day-boundary lands in the bucket a user in that zone would expect — identically on SQLite and Postgres.
Per ADR-0053 decision D2, bucketing is done in-memory, uniformly for non-UTC zones rather than emitting dialect-specific
date_trunc … AT TIME ZONE(SQLite has no tz database and MySQL needs tz tables loaded, so splitting by dialect would shift bucket boundaries for the same data).engine.aggregate({ timezone })therefore forces the in-memory aggregation path when a non-UTC reference tz is set — the date-rangewherestill goes to the driver, so only matching rows are fetched. UTC / unset keeps the native driver fast path unchanged.- New shared
calendarPartsInTz/calendarPartsInTzOrUtcutil in@objectstack/core(DST-safe viaIntl.DateTimeFormat, never hand-rolled offset math; falls back to UTC for an unset/'UTC'/invalid zone). EngineAggregateOptionsand the analyticsexecuteAggregatebridge /ObjectQLStrategythread the reference timezone (sourced from the dataset selection /ExecutionContext) through toapplyInMemoryAggregation→bucketDateValue, and the draft-preview evaluator'sbucketDate.formatDateBucket(dimension labels) stays UTC-only by design: it re-labels values that were already bucketed upstream, so re-applying a timezone there would shift a correct bucket by a day.
- New shared
-
d99a75a: feat(formula): timezone-aware
today()/daysFromNow()/daysAgo()(ADR-0053 Phase 2)These are now calendar-day functions resolved in a reference timezone, threaded from
ExecutionContext.timezone(#1978) throughEvalContext.timezoneinto the CEL stdlib. Each returns the reference-tz calendar day expressed as a UTC-midnightDate(ADR-0053 decision D1) — the one representation consistent with howField.datestrings hydrate, how the SQL driver normalizes date filters, and how Phase 1 stores dates. Sorecord.close_date == daysFromNow(30)now matches in-memory too, not just at the storage boundary. The timezone calculation usesIntl.DateTimeFormat(DST-safe; no hand-rolled offset math).⚠️ Behavior change:daysFromNow(n)/daysAgo(n)previously kept the wall-clock time ofnow(e.g.daysFromNow(30)at10:00Z→…T10:00:00Z). They now drop the time and return the calendar day at midnight (…T00:00:00Z) — the ADR-0053 "defect #3" fix.today()is unchanged at UTC (it already truncated to start-of-day). For a genuine sub-day offset use the documented escape hatchnow() + duration("Nh").With no reference timezone configured the zone resolves to
UTC, sotoday()is byte-for-byte unchanged; only thedaysFromNow/daysAgomidnight-truncation differs from before.objectqlthreadsexecCtx.timezoneinto read-time formula evaluation (applyFormulaPlan) and default-value expressions (applyFieldDefaults).Part of #1980. (Consuming a non-UTC reference timezone end-to-end also needs the
localizationsettings manifest noted in #1978.)
-
bfa3102: fix: array-valued field types persist, and
Field.timeaccepts 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/vectorwere 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 (eventask.labelson a normal object). The DDL column-type switch andisJsonFieldhad drifted into two separate lists; they now share oneJSON_COLUMN_TYPESsource that includes the array/object types, so these columns are created as JSON and round-trip as arrays/objects. AformatInputsafety net additionally serializes any stray array/object value so an unclassified field degrades to a stored string instead of crashing.Field.timerejected every valid value (objectql). The validator reused the date/datetime branch (Date.parse), which isNaNfor any bare time string — so atimefield could never accept14:30or09:05:30.timenow validates a time-of-day (HH:MM/HH:MM:SS, optional fractional seconds andZ/offset) and still accepts a full ISO datetime;date/datetimeare 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.
-
67c29ee: fix(objectql): thread execution context into read-time formula evaluation
applyFormulaPlan— which computesField.formulavirtual fields after afind/findOne— evaluated each expression with only{ record }. So a formula usingnow()/today()ran against a fresh wall-clock read on every evaluation (no determinism), and a formula referencing the caller (os.user.id,os.org.id) faulted and fell back tonullbecause the user/org were never in scope.It now builds the eval context the same way
applyFieldDefaultsalready does: anowsnapshot pinned once per operation (every row and every formula field in one read observes the same instant) plusos.user/os.orgresolved from theExecutionContext. Read-time formulas behave consistently with default-value expressions, and computed fields can reference the caller.This is independent of timezone; it is the read-path prerequisite for ADR-0053 Phase 2 (#1980 will additionally thread
timezonehere onceExecutionContext.timezoneexists). -
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 [d99a75a]
-
Updated dependencies [575448d]
- @objectstack/spec@9.9.0
- @objectstack/core@9.9.0
- @objectstack/formula@9.9.0
- @objectstack/metadata-core@9.9.0
- @objectstack/types@9.9.0
-
76ac582: engine: accept execution context via the trailing
optionsargument on the read methods (find/findOne/count/aggregate), aligning them with the write methods (insert/update).Previously reads took context only inside the query (
query.context) while writes took it in a trailingoptions.context. The same{ context }object was therefore correct as the 3rd argument toinsertbut silently dropped as the 3rd argument tofind— a recurring footgun where an intendedisSystembypass just vanished (e.g. control-plane reads returning empty once org-scoping hooks were added). Now "execution context goes in the trailingoptionsargument" is a single rule across reads and writes.query.contextremains fully supported; when both are supplied,options.contextwins. -
884bf2f: feat: record clone — wire the
object.enable.clonecapability to a real runtime (previously a parsed-but-dead flag).- objectql: new
protocol.cloneData({ object, id, overrides?, context? })— reads the source record, drops engine-owned columns (id+ auditcreated_at/created_by/updated_at/updated_by, plussystem-flagged,autonumber,formulaandsummaryfields) so the insert path re-derives them, applies calleroverrideslast, and inserts the copy. Shallow by design (duplicates the record's own fields, not its child records). Gated byschema.enable.clone: default-on, an explicitenable.clone === falsethrows403 CLONE_DISABLED. - rest: new
POST /api/v1/data/:object/:id/clone(201 →{ object, id, sourceId, record }). Optional body{ overrides }(or a bare field map) overrides copied values, e.g. a newnameor a cleared unique field. Honors the same auth +enable.apiEnabled/apiMethodsgates as the rest of the data surface;enable.clone === false→ 403.
Reclassifies
object.enable.clonedead → livein the spec liveness ledger. - objectql: new
- Updated dependencies [c17d2c8]
- Updated dependencies [97c55b3]
- Updated dependencies [1b1f490]
- @objectstack/formula@9.8.0
- @objectstack/spec@9.8.0
- @objectstack/core@9.8.0
- @objectstack/metadata-core@9.8.0
- @objectstack/types@9.8.0
- Updated dependencies [82c7438]
- Updated dependencies [417b6ac]
- Updated dependencies [ff0a87a]
- @objectstack/formula@9.7.0
- @objectstack/spec@9.7.0
- @objectstack/core@9.7.0
- @objectstack/types@9.7.0
- @objectstack/metadata-core@9.7.0
-
71578f2: feat(book): documentation navigation as a
bookelement — spine + derived membership (ADR-0046 §6)Adds the
bookmetadata element: a navigation spine (ordered groups +audience+ identity) whose membership is derived by rule (includeglob/tag) plus optional per-docorder/group, never a central array. This keeps AI authoring create-and-forget (no central-array read-modify-write) and runtime overlay merge-safe (RFC 7396 treats arrays atomically).BookSchema+resolveBookTree()derived-membership resolver +defineBook()+ additivedoc.order/doc.group.- Register
bookas a render-time metadata type (allowOrgOverride: true); wire it through the runtime type enumerations (PLURAL_TO_SINGULAR, engine registration, artifact field map, type-schema map). - REST
GET /meta/book/:name/treeresolves the tree; read-layeraudiencegating (public≡ anonymous;org/{profile}require sign-in).
-
b04b7e3: fix(objectql): validate a declared required
organization_id/tenant_idinstead of silently skipping it by name (#1592)validateRecordskipped required-checks for any field literally namedorganization_id/tenant_id. That's correct only for the engine-INJECTED tenant column (already markedsystem: true, skipped via provenance). A genuinely DECLARED required business field with that name — e.g.sys_team'sorganization_idlookup, on amanagedBy: 'better-auth'table where the column is NOT injected — was silently bypassed and reached the driver as NULL (a DB constraint error instead of a clean400 required). Removed the two names from the by-name skip set; injected columns remain skipped viadef.system/def.readonly. -
d13df3f: fix(objectql):
record.<field> == nullvalidation fires on insert when the field is omitted (#1871)A
script/cross_fieldvalidation predicate likerecord.due_date == nulldid not fire on insert when the optional field was omitted entirely from the payload — the CELrecordscope lacked the key, sorecord.x == nullsaw a missing key (not null) and silently couldn't match. It worked on update (the prior record supplies the field) and when the field was explicitlynull.Fix: on insert, default declared-but-absent schema fields to
nullin the rule evaluation scope, so an omitted optional reads asnull— matching an explicitnulland the update path. -
Updated dependencies [d1e930a]
-
Updated dependencies [71578f2]
-
Updated dependencies [bb00a50]
-
Updated dependencies [5e3a301]
-
Updated dependencies [5db2742]
- @objectstack/spec@9.6.0
- @objectstack/formula@9.6.0
- @objectstack/core@9.6.0
- @objectstack/metadata-core@9.6.0
- @objectstack/types@9.6.0
- Updated dependencies [ee72aae]
- @objectstack/spec@9.5.1
- @objectstack/core@9.5.1
- @objectstack/formula@9.5.1
- @objectstack/metadata-core@9.5.1
- @objectstack/types@9.5.1
- Updated dependencies [d08551c]
- Updated dependencies [707aeed]
- Updated dependencies [7a103d4]
- Updated dependencies [4b01250]
- @objectstack/spec@9.5.0
- @objectstack/core@9.5.0
- @objectstack/formula@9.5.0
- @objectstack/metadata-core@9.5.0
- @objectstack/types@9.5.0
-
0856476: feat(metadata): package-scoped single-item resolution via
?package=(ADR-0048)A single-item metadata GET (
/meta/:type/:name?package=<id>) now resolves package-scoped (prefer-local): when two installed packages ship an item of the sametype/name, the requester's own package wins. Previously only the list endpoint was package-aware; a single-item fetch was context-free, so a cross-package collision always resolved to whichever package registered first.The fix threads
packageIdend-to-end:@objectstack/rest— the cacheable single-item path calledgetMetaItemCached(ETag keyed on type+name only) and dropped?package=. A?package=read now bypasses that cache and takes the disambiguatinggetMetaItem(type, name, packageId)path, so two same-named items never share one cache entry.@objectstack/objectql—protocol.getMetaItemforwardspackageIdto the overlay query (sys_metadata.package_id),MetadataFacade.get, andregistry.getItem;MetadataFacade.getgained an optionalcurrentPackageId.@objectstack/runtime— the parallel HTTP dispatcher threads?package=too.
This lets the doc viewer (
/apps/:packageId/docs/:name) resolve one doc scoped to its app, sodocnames no longer need a namespace prefix for uniqueness (the prefix becomes a recommended convention, likepage/dashboard/report);doc.zoddoc-comments updated accordingly. -
fef38ec: feat(metadata): package-scoped customization overlays (ADR-0048 #1824)
A
sys_metadatacustomization overlay is now keyed by(type, name, organization_id, package_id), so two installed packages shipping an item of the sametype/namecan each carry their own overlay. Previously the overlay uniqueness key was(type, name, organization_id)— physically one row per name — so customizing one package's item shadowed both, and a package-scoped read fell back to whichever row existed.- Index:
idx_sys_metadata_overlay_active/…_draftnow includepackage_id. The runtime migration (ensureOverlayIndex) usesCOALESCE(package_id, '')so package-less (global) overlays stay unique among themselves (a plain unique index treats NULLs as distinct). DROP-then-CREATE, idempotent; existing rows migrate safely (the old key already guaranteed one row per(type, name, org)). - Write:
SysMetadataRepository.whereFor/put/getscope the upsert to the requested package, so a save bound to package B no longer finds and overwrites package A's same-name overlay. A package-less save (packageIdnull) targets the global row. - Read:
getMetaItem/getMetaItemLayeredoverlay lookups already prefer the package-scoped row; the fallback now resolves only the global (package_id IS NULL) overlay, never a different package's row. Package-less readers are unchanged (match-any, back-compat).
Verified live against a real collision (two packages each shipping
page/showcase_task_workbench): two overlay rows coexist, and?package=single reads + the?layers=trueStudio editor view each return that package's own overlay; the unique index migrated in place.Known follow-up: the unscoped list (
GET /meta/:typewith no?package=) still dedupes by bare name, so when two packages both carry an overlay on the same name the list collapses them — the per-package single-item and editor paths are unaffected. Tracked for the list-dedup-by-name work. - Index:
-
b678d8c: feat(objectql): ADR-0048 Phase 1+2 — namespace install gate + package-scoped resolution
Phase 1 — install-time namespace gate.
SchemaRegistry.installPackagerefuses a package whosemanifest.namespaceis already owned by a DIFFERENT installed package (newNamespaceConflictError), making explicit and early the constraint the table layer already enforces implicitly. Same-package reinstall and shareable platform namespaces (base/system/sys) are exempt;OS_METADATA_COLLISION=warndowngrades to a warning.Phase 2 — prefer-local resolution, pivoted to ADR-0048 option A (package id as the routing key).
getItem(type, name, currentPackageId?)prefers${currentPackageId}:${name}before any cross-package fallback (ADR-0005 overlay precedence and backward compatibility unchanged);getApp(name, currentPackageId?)resolves prefer-local by package id. Because package ids are globally unique, package-scoped resolution always disambiguates two distinct packages — so the old per-item CROSS-package throw (and the now-deadMetadataCollisionError,findOtherPackageOwner,SYS_METADATA_OWNER, …) is retired; two different-namespace packages legitimately coexist on the same bare name.collisionPolicynow governs only the Phase 1 namespace gate. -
b678d8c: feat(objectql): opt-in
sys_metadatahydration for isolated project kernelsBoot Phase-2 hydration (
restoreMetadataFromDb→loadMetaFromDb, which registers objects WITH their fields into theSchemaRegistry) was gated onenvironmentId === undefined, assuming every project kernel sources its metadata from a remote artifact / control-plane proxy. That is untrue for an isolated, proxy-free project kernel that persists its OWNsys_metadatalocally (the cloud single-env tenant runtime): objects created at runtime there never re-entered the registry after a restart, soregistry.getObject(name)returned nothing and every registry consumer silently degraded (notably theengine.findunknown-$selectguard, which then let an unknown projected column zero the result set).Adds an explicit
hydrateMetadataFromDbplugin option (defaultfalse, so the control-plane/proxy path is untouched). When set, hydration runs even withenvironmentIddefined — safe because each engine now owns its registry instance andloadMetaFromDbalready tolerates a missing table.
-
c1dfe34: fix(metadata): keep each colliding item's own
_packageIdprovenance (ADR-0048)When two installed packages ship an item of the same
type/name, the single-item and list reads grafted the artifact protection envelope from a first-match artifact lookup (lookupArtifactItem(type, name)), so the second package's item inherited the FIRST package's_packageId. The frontend prefer-local resolution (dashboard/report/page) filters the unscoped list by_packageId, so this mislabel made it resolve a collision to the wrong package (or fail to find the local item entirely).getMetaItemnow scopes the artifact lookup torequest.packageId.getMetaItemsscopes the per-item decorate to the requested package (when the whole list is package-scoped) else to each item's own_packageId.
getItemordering is unchanged — a bare-key runtime/DB overlay still takes ADR-0005 precedence over the packaged item (clarifying comment added). An env-wide (package-less) overlay of a name that collides across packages remains inherently ambiguous by schema (sys_metadatais unique ontype+name+org, not package); pure-artifact collisions (the marketplace default) now resolve and list correctly per package. -
3e675f6: fix(metadata): package-scope the layered (Studio editor) read via
?package=(ADR-0048)The
?layers=truesingle-item read (the Studio metadata editor's 3-state code/overlay/effective view) ignoredpackageId, so editing one of two same-named items from different packages resolved ambiguously (first match).protocol.getMetaItemLayerednow threadspackageIdinto the code layer (metadataService.get+lookupArtifactItem+registry.getItem) and thesys_metadataoverlay query (package_idprefer-local).registry.getArtifactItem(type, name, currentPackageId?)andlookupArtifactItemgained the optional package-scope hint.rest-serverthreads?package=into the layered branch.
This completes the per-route package-scoped resolution audit: the runtime render surface (dashboard/report/page/doc) was already scoped; this closes the Studio editor (
/apps/:appName/metadata/:type/:name). Frontend counterpart sends?package=from the metadata list row's owning package. -
b678d8c: fix(objectql): seed reference resolution falls back to matching by
idSeedLoaderService.resolveFromDatabaseonly matched a reference value against the target's natural-key field. A seed that wires a lookup to a REAL existing record by its internal id — e.g. a people field (approver/applicant → user) pointed at the current user — dangled tonullwhen that id is not a UUID/ObjectId (so the caller'slooksLikeInternalIdguard did not short-circuit) and is not the target's natural key.Adds an id fallback: when the natural-key lookup finds nothing, try resolving the value as the target's
id. Safe — an id either exists or it doesn't, so there is no risk of a false natural-key match, and it is tenant-scoped like the primary lookup. -
Updated dependencies [060467a]
-
Updated dependencies [0856476]
-
Updated dependencies [fef38ec]
-
Updated dependencies [b678d8c]
-
Updated dependencies [b678d8c]
-
Updated dependencies [b678d8c]
- @objectstack/spec@9.4.0
- @objectstack/metadata-core@9.4.0
- @objectstack/core@9.4.0
- @objectstack/formula@9.4.0
- @objectstack/types@9.4.0
-
1ada658: ADR-0046 P1: package documentation as metadata. New
docmetadata element — flat Markdown files undersrc/docs/*.mdcompile intodocs: DocSchema[]on the stack and register like any other metadata.- spec:
DocSchema({ name, label?, content }) insystem/,StackDefinition.docs,docinMetadataTypeSchema+ type registry (inert data, runtime-creatable) + canonical schema map,docs → docplural mapping. - cli:
os buildcollects flatsrc/docs/*.md(frontmattertitle:/first#heading → label) and enforces the ADR lint — flat directory, namespace-prefixed snake_case names, namespace required when docs ship, MDX/image ban, same-package relative-link resolution. Same rules surface inos lint. - objectql:
docsjoins the generic metadata registration loop (manifest + nested plugins). - runtime: docs count as app payload;
GET /metadata/doclist responses omitcontentby default (?include=contentopts in) so unbounded manuals stay off hot paths.
- spec:
-
6259882: ADR-0048: cross-package metadata collision detection. Bare-named generic metadata (
page,dashboard,flow,app,action,doc, …) carries no package coordinate in the registry key (org/type/name), so two installed packages defining the same(type, name)would silently shadow each other at read time (getItemreturns whichever the registry iterates first). The kernel only prefix-validates object names, leaving these types unguarded.SchemaRegistry.registerItemnow refuses a cross-package base-layer collision — a realpackageIdregistering a(type, name)already owned by a different real package — with aMetadataCollisionErrornaming both packages and the type/name.ObjectQL.registerAppand the nested-plugin loop delegate to it, so manifest and plugin metadata are both covered.Legitimate same-key writes are unaffected: same-package reloads, runtime/DB overlays (ADR-0005, bare-key or
sys_metadata-sentinel rows), object ownership/extension, and nav contributions all pass through. Policy iserrorby default; setcollisionPolicy: 'warn'(orOS_METADATA_COLLISION=warn) to downgrade during a deliberate migration. -
b10aa78: Metadata registered through the metadata-service path now carries package provenance.
loadMetadataFromServiceandMetadataFacade.registerpass each item's own_packageIdthrough toregistry.registerItemsoapplyProtectionstamps_packageId/_provenance: 'package'(never a synthetic id —isArtifactBacked()write authorization keys off_packageId). NewMetadataPluginOptions.packageIdlets hosts running the filesystem scanner declare the owning package id for scanned source-file metadata, closing the same gap for hand-wired kernels. GET /api/v1/meta/:type consumers (e.g. objectui NavigationSyncEffect) can now distinguish package-shipped items from user-authored rows without name heuristics.
- 2796a1f: Fix metadata registry pollution: a packaged artifact's protection envelope (
_lock/_packageId/_provenance) survives overlay hydration and reset (ADR-0010 §3.3). GET-list hydration used to register the sys_metadata overlay body under the registry's plain key, shadowing the artifact — a_lock: fullapp read back as unlocked after PUT+GET, and DELETE (reset) left the stale shadow in place until restart. Envelope readers now resolve through the shadow-immuneSchemaRegistry.getArtifactItem(), hydration grafts the artifact envelope onto the overlay body (overlay content wins, artifact protection wins), and reset heals the registry viaremoveRuntimeShadow()— including self-healing on a no-op DELETE. - 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
- @objectstack/formula@9.3.0
- @objectstack/metadata-core@9.3.0
- @objectstack/types@9.3.0
- Updated dependencies [2f57b75]
- Updated dependencies [2f57b75]
- @objectstack/spec@9.2.0
- @objectstack/core@9.2.0
- @objectstack/formula@9.2.0
- @objectstack/metadata-core@9.2.0
- @objectstack/types@9.2.0
- Updated dependencies [b9062c9]
- @objectstack/spec@9.1.0
- @objectstack/core@9.1.0
- @objectstack/formula@9.1.0
- @objectstack/metadata-core@9.1.0
- @objectstack/types@9.1.0
- Updated dependencies [1817845]
- @objectstack/spec@9.0.1
- @objectstack/core@9.0.1
- @objectstack/formula@9.0.1
- @objectstack/metadata-core@9.0.1
- @objectstack/types@9.0.1
- Updated dependencies [4c3f693]
- Updated dependencies [0bf39f1]
- Updated dependencies [f533f42]
- Updated dependencies [1c83ee8]
- @objectstack/spec@9.0.0
- @objectstack/core@9.0.0
- @objectstack/formula@9.0.0
- @objectstack/metadata-core@9.0.0
- @objectstack/types@9.0.0
- @objectstack/spec@8.0.1
- @objectstack/core@8.0.1
- @objectstack/types@8.0.1
- @objectstack/metadata-core@8.0.1
- @objectstack/formula@8.0.1
-
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_sequencestable 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/spec—DriverCapabilitiesgains an optionalautonumberflag: "driver natively generates persistent autonumber/sequence values". -
@objectstack/driver-sql— advertisessupports.autonumber = true.bulkCreate()now fills autonumber fields too (previously onlycreate()/upsert()did), so bulk inserts also draw from the persistent sequence. Field parsing now honors either the spec-canonicalautonumberFormatkey OR theformatshorthand (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 eitherautonumberFormatorformat. Record-validation exemptsautonumberfields from therequiredcheck — 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.
-
-
99111ec: Field-level conditional rules (CEL):
visibleWhen/readonlyWhen/requiredWhen, enforced server-side.Add three CEL-predicate field props (over
record) evaluated on both sides. Spec:visibleWhen/readonlyWhen/requiredWhen(requiredWhencanonical;conditionalRequiredkept as a back-compat alias). Server (objectql): the validator now enforcesrequiredWhen/conditionalRequiredover the merged record (so the rule can't be bypassed by a direct API write), and the update path ignores writes to a field whosereadonlyWhenis TRUE (keeps the persisted value).needsPriorRecordaccounts for conditional fields so the prior record is fetched on update. -
9e2e229: feat(objectql): compute roll-up
summaryfields server-sideThe
summaryfield type was declared in the spec but never computed — its value stayed empty. ObjectQL now recomputes roll-up summaries automatically: a parent field whosesummaryOperationsaggregates (count/sum/min/max/avg) a field across child records is recalculated whenever a child is inserted, updated, or deleted.-
@objectstack/spec—summaryOperationsgains an optionalrelationshipField(the child→parent FK). When omitted the engine auto-detects it from the child'slookup/master_detailfield whosereferencepoints back at the parent; set it explicitly only when the child has more than one such reference. -
@objectstack/objectql— afterafterInsert/afterUpdate/afterDeleteon a child object, the engine finds the affected parent (from the child's FK, plus the prior FK on update/delete so a re-parented child updates both), re-aggregates the child collection, and writes the result onto the parent's summary field. It runs in the caller's execution context, so when a transaction is open (e.g. the cross-object/api/v1/batch) the rollup commits atomically with the child writes. A small index of child→summary descriptors is built lazily from the registry and invalidated on package registration.
Empty collections roll up to
0forcount/sumandnullformin/max/avg. This lets master-detail forms stop computing parent totals on the client — the server is now the single source of truth. -
-
345e189: Robust multi-write transactions (ADR-0034).
engine.transaction()now establishes an ambient transaction (AsyncLocalStorage) so every data operation during the callback — including internal reads performed while a write runs — binds to the active transaction's connection instead of asking the pool for another one and deadlocking on SQLite's single-connection pool. Adds a cross-object transactional batch endpoint (POST /api/v1/data/batch) with intra-batch{ $ref: <opIndex> }parent references, so a parent and its children can be created atomically in one transaction.
-
e6374b5: fix(objectql): master_detail cascade delete + autonumber generation
deletenow applies referential delete behavior for incoming relations:master_detailcascades to children (the parent owns the child lifecycle; only an explicitrestrictdeviates),lookuphonors itsdeleteBehavior(defaultset_null). Recurses for grandchildren, depth-guarded, single-id deletes. Previously deleting a parent left its children orphaned.insertnow generates values for emptyautonumberfields before required-validation (max+1, seeded perobject.field, honorsautonumberFormat). Previously a required autonumber was rejected as "missing" and autonumber fields were never populated.
-
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
- @objectstack/formula@8.0.0
- @objectstack/metadata-core@8.0.0
- @objectstack/types@8.0.0
-
ac1fc4c: feat(metadata): optional storage teardown on delete so "publish to preview" leaves no orphan table
Object storage was create-only:
publishMetaItemcreates a table (ensureObjectStorage) but nothing ever dropped one —deleteMetaItemonly tombstones the metadata row, leaving the physical table behind. That made the pragmatic "publish an object just to preview it with real data, then discard if wrong" loop leave residue.Adds the inverse path, opt-in and guarded:
engine.dropObjectSchema(name)— inverse ofsyncObjectSchema; resolves the table name + driver and calls the driver's existingdropTable(DROP TABLE IF EXISTS / drop collection).deleteMetaItem({ …, dropStorage })— whentrue, drops the object's physical table after the metadata is removed. DESTRUCTIVE, so it is gated:objecttype only (others have no table),activestate only (drafts were never materialised), and never asys_-prefixed platform table. Defaultfalsekeeps delete non-destructive to data. Best-effort: a drop failure is logged, not thrown.- REST:
DELETE /meta/:type/:name?dropStorage=truethreads the flag.
This makes "publish to preview → discard" cleanly reversible. Combined with the draft-overlay read mode, it backs the team's chosen approach: lean on publish (into a dev sandbox) for data-level confirmation rather than building a full draft-data preview, and make that publish safely undoable.
-
ac1fc4c: feat(metadata): draft-overlay reads so an admin can render the console off pending drafts before publish
ADR-0033's loop is
build (draft) → review → publish, but "review" was only a JSON diff — the one thing that actually confirms an AI/hand-authored change (the rendered object page / kanban / form / nav) only existed after publish. That forces publishing unreviewed metadata just to look at it, defeating the draft gate.This adds a request-scoped draft-overlay read mode to the metadata resolution layer:
getMetaItems({ …, previewDrafts })— after the active overlay, overlaysstate='draft'rows on top (draft WINS on name collision; draft-only items surface too). Drafts are never hydrated into the process-wide SchemaRegistry.getMetaItem({ …, previewDrafts })— non-strict: prefers a draft row if one exists, else falls back to the active value (unlike the strictstate:'draft'mode, which 404sno_draft).- Every overlaid item is tagged
_draft: trueso the UI can badge it and show a "preview" banner. - The runtime HTTP dispatcher threads
?preview=draftonGET /metadata/:typeandGET /metadata/:type/:nameinto these reads.
The same overlay also unblocks the AI authoring agent referencing its own just-drafted objects (a follow-up will point
list_metadataat it). Admin gating of the?preview=draftflag is a deliberate follow-up step.Note: a brand-new draft object has no physical table until publish, so preview renders its shape (form/view/kanban/nav) but shows no data; field-additions to existing objects preview fully.
-
ac1fc4c: feat(packages): one-click discard-drafts and full delete for a package
Two distinct package-level lifecycle operations, both built on the per-item delete primitive:
-
discardPackageDrafts(packageId)— drop every pending DRAFT bound to the package, reverting it to its last published baseline. NON-destructive: active/published metadata and physical tables are untouched. Use case: "I edited this app for a while and it turned out worse than before — abandon all my changes." Routes through the sys_metadata path (no metadata-service dependency, unlike the existingPOST /packages/:id/revert, which 503s without a metadata service). REST:POST /packages/:id/discard-drafts. -
deletePackage(packageId)— remove the ENTIRE package: everysys_metadatarow (active + draft) and, by default, the physical table of each object it defined (DESTRUCTIVE).keepData: trueremoves metadata but preserves tables; thesys_-table guard still applies. Use case: "I don't want this package anymore."DELETE /packages/:idnow performs this persisted removal in addition to the in-memory registry unregister it already did (previously it left AI/runtime packages' rows and tables behind);?keepData=trueopts out of teardown.
Drafts are deleted before active rows so each object's table is torn down exactly once. Per-item failures are collected without aborting the rest.
-
- @objectstack/spec@7.9.0
- @objectstack/core@7.9.0
- @objectstack/types@7.9.0
- @objectstack/metadata-core@7.9.0
- @objectstack/formula@7.9.0
-
a75823a: feat(metadata): expose pending DRAFT metadata (ADR-0033 draft discoverability)
AI-authored metadata lands as drafts (
sys_metadatarows withstate='draft', bound to an app package), but the only list path —getMetaItems— reads the active registry, so drafts were invisible: a just-built app package looked empty and there was no "pending changes" surface.SysMetadataRepository.listDrafts({type?, packageId?})lists draft rows (mirrorslist()but scoped tostate='draft', optionally narrowed by package), returning a light header projection (no body) withpackageId.protocol.listDrafts({packageId?, type?, organizationId?})exposes it over the overlay repo.GET /api/v1/meta/_drafts?packageId=&type=surfaces it to the console. Registered in the REST server before the greedy/meta/:typeroute (and mirrored in the dispatcher) so_draftsis never captured as a metadata type name.
Read-only; no behavior change to existing list/publish paths. Powers the upcoming Studio "drafts/pending changes" view and draft-aware package contents.
-
4fbb86a: feat(packages): consolidate the package subsystem so AI-built app packages surface in Studio
The package subsystem was split across two stores that never met: the in-memory
SchemaRegistry(what the dispatcher's/api/v1/packageslist/detail andgetMetaItems({type:'package'})read — i.e. Studio's package selector) and the durablesys_packagestable (where the AI's auto app package, and anypackage-service publish, were written). Nothing reconciled the two, so an AI-createdapp.<name>package never appeared in Studio.This unifies them around one write primitive and one read source:
protocol.installPackageis now implemented (it was declared-but-missing). It is the single canonical write path: it registers the package in the in-memory registry and best-effort persists it tosys_packagesvia thepackageservice. Non-fatal when nopackageservice is wired (registry write still succeeds).- Dispatcher
POST /api/v1/packagesroutes throughprotocol.installPackage(falling back to the bare registry write when the protocol is unavailable), so HTTP installs are durable too. @objectstack/service-packagereconcilessys_packagesback into the registry on boot, without clobbering filesystem-registered packages — so persisted packages survive a restart and stay visible in the registry-backed read paths.@objectstack/service-aiapply_blueprintnow homes an app viaprotocol.installPackage(falling back to the legacypackage-service publish), so the app package lands where Studio reads it.
Still the legacy
package_idplane — sealedsys_package_versionversioning and cross-environment promotion remain ADR-0027 follow-ups. -
e631f1e: feat(metadata): publish a whole app's drafts in one shot (ADR-0033)
After an AI builds an app, its metadata is drafted (bound to an app package) and had to be published one item at a time. The package-level
POST /packages/:id/publishneeds themetadataservice (503 when absent, e.g. the showcase) and reads the in-memory registry, not the drafts.protocol.publishPackageDrafts({ packageId })promotes everysys_metadatadraft row bound to the package to active by reusing the per-itempublishMetaItemprimitive (overridable/lock guards + runtime registry refresh). Per-item failures are collected, not fatal. Nometadata-service dependency.POST /api/v1/packages/:id/publish-draftsexposes it (distinct from the registry-based/publish), returning{ success, publishedCount, failedCount, published, failed }.
Verified live: an AI-built
app.asset_management(4 drafts) published in one call — all 4 promoted to active, drafts cleared, draft objects became queryable. -
36719db: fix: AI-built apps are usable immediately — sync new object tables on publish + emit valid kanban config
Two gaps found by end-to-end testing of an AI-built app:
-
A freshly-published object couldn't accept records until a server restart. Publishing a drafted object registered it in the in-memory registry but never created its physical table (table sync only ran at boot), so inserts failed with
object_not_found("no such table"). AddedObjectQL.syncObjectSchema(name)(a targeted, idempotent single-object schema sync) and call it from the publish paths (protocol.publishMetaItemandsaveMetaItemmode:'publish', viaensureObjectStorage). Best-effort + non-fatal. New objects are now CRUD-able the moment they're published. -
AI-generated kanban views rendered as plain lists (and sometimes failed validation). The blueprint
viewBodyemittedlist.type:'kanban'with nokanbanconfig;KanbanConfigSchemarequiresgroupByFieldandcolumns. Added an optionalgroupByto the blueprint view schema (lenient + strict) and haveapply_blueprintsetlist.kanban = { groupByField, columns }— using the view's explicitgroupBywhen given, else inferring the object's firstselectfield. AI-built kanban views now validate, publish, and carry a real group-by field.
-
-
6fc2678: fix(metadata): stamp a top-level
nameonviewbodies at the write path so AI/hand-authored views surfacegetMetaItemsonly overlays asys_metadatarow when its parsed body has a top-levelname. Some view producers — notably loose{ list: <ListView> }/{ form: … }fragments that AI tools and hand-authoring emit — pass schema validation but carry no top-levelname, so the view was silently dropped from the object's view list and never appeared as a tab ("validates ≠ surfaces").saveMetaItemnow guarantees a top-levelnameon every view body at the single write chokepoint, BEFORE validation + persistence, so a nameless view is auto-corrected regardless of which authoring path produced it. It deliberately does NOT reshape the document: both thedefineViewcontainer form ({ list, listViews, … }, expanded by the loader) and the{ name, object, viewKind, config }record form are valid and the console consumes both — reshaping a container into a record risks producing an invalid record (e.g. a non-<object>.<key>name) and drops Studio-only fields (isPinned,sortOrder, …). Exported asnormalizeViewMetadataand unit-tested.(Note for follow-up: the
viewmetadata schema is itself a permissive union — it accepts an unknownviewKind, a kanban config missinggroupByField, even{}. Tightening it correctly requires first consolidating the four legitimate view shapes — record / container / flat list / flat form — and is a separate spec change.) -
Updated dependencies [06f2bbb]
-
Updated dependencies [f01f9fa]
-
Updated dependencies [36719db]
-
Updated dependencies [424ab26]
- @objectstack/spec@7.8.0
- @objectstack/formula@7.8.0
- @objectstack/core@7.8.0
- @objectstack/metadata-core@7.8.0
- @objectstack/types@7.8.0
-
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 withno such table: sys_metadataon every read.- Move the four storage-object definitions from
@objectstack/platform-objects/metadatato@objectstack/metadata-core(the lowest package shared by their real consumers);platform-objects/metadatanow re-exports them for back-compat. ObjectQLPluginregisters these objects itself (gated onenvironmentId === undefined, mirroringrestoreMetadataFromDb) so their tables always sync on platform/standalone kernels.- Gate the SQL driver's tenant-audit warning on actual multi-tenant mode —
organization_idnow exists on every table, so column presence alone no longer implies "tenant-scoped"; single-tenant boots no longer spam the warning for system writes.
- Move the four storage-object definitions from
-
Updated dependencies [b391955]
-
Updated dependencies [f06b64e]
-
Updated dependencies [825ab06]
-
Updated dependencies [023bf93]
-
Updated dependencies [764c747]
- @objectstack/spec@7.7.0
- @objectstack/formula@7.7.0
- @objectstack/metadata-core@7.7.0
- @objectstack/core@7.7.0
- @objectstack/types@7.7.0
-
7648242: Enforce every declared validation-rule type on the write path; trim the three that can't be (#1475).
The
validationsunion advertised nine rule types but only three (state_machine,cross_field,script) ran on insert/update — the other six were accepted by the schema yet silently did nothing. This closes that gap on both sides: implement the synchronous types, and trim the ones that don't belong in a write-path rule.@objectstack/objectql(additive): the rule evaluator now enforces three more types, all deterministic, synchronous, side-effect-free predicates over one record:format— a field value against aregexand/or a named format (email/url/phone/json). Runs only when the write touches the field and the value is non-empty; a malformed regex fails open.json_schema— a JSON field validated against a JSON Schema viaajv(compiled result memoised per schema). Accepts a parsed object or a JSON string; an unparseable string is itself a violation; an uncompilable schema fails open.conditional— evaluateswhen, then recurses intothen/otherwise. The nested rule supplies the message; the outer conditional'sseveritydecides blocking.needsPriorRecordnow recurses into conditional branches.
Adds
ajvas a dependency and three error codes (invalid_format,invalid_json,json_schema_violation).@objectstack/spec(breaking for unused declarations): removes theunique,async, andcustomvalidation-rule variants (and theUniquenessValidationSchema/AsyncValidationSchema/CustomValidatorSchemaexports). They were never enforced and each needs I/O or a handler model a write-path rule must not carry. Use the layer that already does each correctly: uniqueness → a unique index (ObjectSchema.indexes,partialfor scope) or field-levelunique: true; async/remote → the client form layer; custom code → abeforeInsert/beforeUpdatelifecycle hook. Field-levelunique: trueis unaffected.examples/app-showcasedemonstrates and verifies each newly-enforced type. See the ADR-0020 addendum for the rationale.
- 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/formula@7.6.0
- @objectstack/core@7.6.0
- @objectstack/types@7.6.0
- @objectstack/metadata-core@7.6.0
- @objectstack/spec@7.5.0
- @objectstack/core@7.5.0
- @objectstack/types@7.5.0
- @objectstack/metadata-core@7.5.0
- @objectstack/formula@7.5.0
- @objectstack/spec@7.4.1
- @objectstack/core@7.4.1
- @objectstack/types@7.4.1
- @objectstack/metadata-core@7.4.1
- @objectstack/formula@7.4.1
-
23c7107: ADR-0020 — converge the three "state machine" declaration shapes to one enforced
state_machinevalidation rule.Before this change a record state machine could be declared three ways (a
workflowmetadata type, anobject.stateMachinesmap, or astate_machinevalidation rule) and none of them were enforced at runtime — a declarative guardrail that was pure decoration, and a hallucination trap for AI authors.Enforcement (
@objectstack/objectql)- New
validation/rule-validator.tsevaluates the object'svalidationsunion on the write path:evaluateValidationRules,needsPriorRecord, and thelegalNextStatesintrospection helper (all exported from the package root). state_machinerules reject illegalfieldtransitions on update (with the rule'smessage);script/cross_fieldpredicate rules now also fire (they were silently broken on PATCH updates because only the patch, not the prior record, was available). The engine plumbs the prior record into rule evaluation on single-row update; multi-row (updateMany) updates log a warning and skip rule evaluation rather than enforce on incomplete data.
Convergence / retirement (
@objectstack/spec) — breaking- Retires the
workflowmetadata type (removed from the metadata-type enum, the registry, the schema map, theworkflowscollection key, and the plural→singular mapping). - Removes the
object.stateMachinesmap and thestack.workflowsarray. Thestate_machinevalidation rule is the single canonical home. - The XState-style
StateMachineSchemafile is kept (still used by the agent conversation lifecycle and the discovery protocol); only its role as theworkflowmetadata-type backing schema was removed. The optionalworkflowRPC service surface (CoreServiceName.workflow,/api/v1/workflow,IWorkflowService) is kept as a documented follow-up.
Introspection (
@objectstack/runtime)- Adds
GET /metadata/objects/:name/state/:field?from=:state, returning the legal next states for a field (next: nullwhen no FSM governs the field,[]for a declared dead-end) so UIs/agents read the transition table instead of re-deriving it.
Surfaces (
@objectstack/platform-objects,@objectstack/cli)- Studio drops the standalone "Workflow Rules" nav (state machines are edited alongside the object's other validation rules).
explainno longer listsworkflowas a related metadata type.
Migration: replace a
workflow/StateMachineConfigdeclaration with astate_machinevalidation rule on the object (field+{ from: [allowedTo] }transition table), and move any side-effecting actions (emails, task creation) into a record-triggered or scheduled Flow (ADR-0019). See the migratedexamples/app-crmflows for the pattern. - New
-
c72daad: ADR-0029 D7 — Setup app navigation contributions.
Adds the UI-layer analog of object
own/extend: a package can contribute navigation items into an app it does not own, so a shared admin app can be a thin shell while each capability plugin ships the menu for the objects it owns.@objectstack/spec— newNavigationContributionSchema({ app, group?, priority, items }) and an optionalnavigationContributionsfield on the manifest.@objectstack/objectql—SchemaRegistry.registerAppNavContribution()plus lazy merge ingetApp/getAllApps(by target group id + priority, cloning so the stored app is never mutated); the engine wiresmanifest.navigationContributionsduring app registration.@objectstack/platform-objects— the Setup app becomes a shell of empty group anchors; its entries for platform-objects-owned objects move toSETUP_NAV_CONTRIBUTIONS.@objectstack/plugin-auth— registersSETUP_NAV_CONTRIBUTIONSalongside the Setup app it already registers.@objectstack/plugin-webhooks— contributes itsWebhooks/Webhook Deliveriesentries into the Setupgroup_integrationsslot (it ownssys_webhook/sys_webhook_deliveryper K2.a), demonstrating end-to-end cross-plugin contribution.
The rendered Setup nav is identical to the former static artifact — just assembled from its owners. A disabled/absent capability contributes nothing and its slot stays empty (in addition to the existing
requiresObjectgating). This unblocks moving each remaining K2 domain's menu out of the monolith with its objects. -
eea3f1b: ADR-0029 K0 + K2.a — single-owner invariant and webhooks ownership pilot.
K0 (
@objectstack/objectql) — addSchemaRegistry.assertSingleOwnerPerObject(), the install-time backstop for the kernel-decomposition invariant: every registered object must resolve to exactly oneowncontributor. A second cross-package owner is already rejected at registration time; this additionally catches "extend with no owner" (which would otherwise resolve to nothing). Call after kernel bootstrap completes.K2.a (
@objectstack/plugin-webhooks←@objectstack/platform-objects) — move thesys_webhookobject definition out of theplatform-objectsmonolith into@objectstack/plugin-webhooks, where it joins its siblingsys_webhook_deliveryso the plugin owns both its data model and behavior as one unit.sys_webhookis no longer exported from@objectstack/platform-objects(or its/integrationsubpath, now an empty barrel); import it from@objectstack/plugin-webhooks/schemainstead. Runtime behavior is unchanged — the webhook plugin already registeredsys_webhookat runtime; only the definition's home moved. Setup-app navigation (which referencessys_webhookby name) and existing i18n bundles (object-name keyed) continue to work. Per ADR-0029 D8, migrating the object's i18n extraction into the plugin is a tracked follow-up before the next translation regeneration. -
2faf9f2: External Datasource Federation (ADR-0015) — write gate (Gate 3) + introspection plumbing.
- Write gate: ObjectQL
insert/update/deletenow block writes to a federated datasource (schemaMode !== 'managed') unless BOTHdatasource.external.allowWritesandobject.external.writableare true, throwingExternalWriteForbiddenError(codeEXTERNAL_WRITE_FORBIDDEN). Managed datasources (and objects without a datasource definition) are unaffected. NewregisterDatasourceDef()records declarative datasource ownership; manifests carryingdatasourcesare indexed duringregisterApp. engine.introspectDatasource(name)delegates to the named driver'sintrospectSchema(), wiring the external-datasource service end-to-end.
- Write gate: ObjectQL
-
a6d4cbb: Fix conditional & record-change flows silently skipping.
Two bugs together caused every flow with a start-node / edge condition to silently skip (record-change triggers fired but the flow body never ran; audit-style
previous.*gates andbudget > 100000-style gates all evaluated to false):-
service-automation — CEL engine unreachable in ESM. The condition evaluator loaded the formula engine via a CommonJS
require('@objectstack/formula'). In the package's ESM build ("type": "module") that resolves to tsup's throwing__requirestub, so every CEL evaluation threw and the swallowingcatchreturnedfalse. Replaced with a static top-level import, which binds correctly in both the ESM and CJS builds. -
objectql — prior record not exposed to update hooks.
HookContextdocuments aprevioussnapshot for update/delete, butengine.updatenever populated it (the row it fetched for validation was a local var). Record-change conditions likestatus == "done" && previous.status != "done"therefore had nopreviousto read. The engine now attaches the pre-update record tohookContext.previousfor single-id updates whenever a validation rule needs it or anafterUpdatehook is registered.
Both paths are covered by new unit tests.
-
-
58b450b: Make metadata labels follow the active UI language without a page refresh (#1319).
The client now carries the active locale on every request (
Accept-Language,setLocale/getLocale), the protocol ETag is locale-aware so cached metadata no longer collides across languages, and theclient-reactmetadata hooks refetch when the locale changes. Theapps/accountconsole wires its router locale through so a language switch relabels server-resolved object/field/view labels in place instead of leaving the UI half-translated until reload. -
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
- @objectstack/formula@7.4.0
- @objectstack/types@7.4.0
- @objectstack/metadata-core@7.4.0
- Updated dependencies [5e7c554]
- @objectstack/spec@7.3.0
- @objectstack/core@7.3.0
- @objectstack/formula@7.3.0
- @objectstack/types@7.3.0
- @objectstack/metadata-core@7.3.0
-
9096dfe:
OS_env-var prefix migration (issue #1382).All ObjectStack-owned environment variables now use the
OS_prefix. Legacy names still work for one release and emit a one-shot deprecation warning via the newreadEnvWithDeprecation()helper in@objectstack/types.Renamed (with legacy fallback):
New Legacy (deprecated) OS_AUTH_SECRETAUTH_SECRET,BETTER_AUTH_SECRETOS_AUTH_URLAUTH_BASE_URL,BETTER_AUTH_URL,OS_AUTH_BASE_URLOS_PORTPORTOS_DATABASE_URLDATABASE_URLOS_ROOT_DOMAINROOT_DOMAINOS_MULTI_ORG_ENABLEDOS_MULTI_TENANTOS_CORS_ENABLEDCORS_ENABLEDOS_CORS_ORIGINCORS_ORIGINOS_CORS_CREDENTIALSCORS_CREDENTIALSOS_CORS_MAX_AGECORS_MAX_AGEOS_AI_MODELAI_MODELOS_MCP_SERVER_ENABLEDMCP_SERVER_ENABLEDOS_MCP_SERVER_NAMEMCP_SERVER_NAMEOS_MCP_SERVER_TRANSPORTMCP_SERVER_TRANSPORTOS_NODE_IDOBJECTSTACK_NODE_IDOS_METADATA_WRITABLEOBJECTSTACK_METADATA_WRITABLEOS_DEV_CRYPTO_KEYOBJECTSTACK_DEV_CRYPTO_KEYOS_HOMEOBJECTSTACK_HOMEMigration: rename in your
.env. Legacy names continue to work this release and will be removed in a future major. Industry-standard names (NODE_ENV,HOME,OPENAI_API_KEY,TURSO_*, OAuth*_CLIENT_ID/SECRET,RESEND_API_KEY,POSTMARK_TOKEN,AI_GATEWAY_*,SMTP_*) are NOT renamed. -
Updated dependencies [9096dfe]
- @objectstack/types@7.2.1
- @objectstack/spec@7.2.1
- @objectstack/core@7.2.1
- @objectstack/metadata-core@7.2.1
- @objectstack/formula@7.2.1
- @objectstack/spec@7.2.0
- @objectstack/core@7.2.0
- @objectstack/types@7.2.0
- @objectstack/metadata-core@7.2.0
- @objectstack/formula@7.2.0
-
47a92f4: Promote
email_templateto a first-class metadata type using the canonicalEmailTemplateDefinitionSchema.Previously
email_templatehad two competing Zod schemas (Prime Directive #8 violation): the legacyEmailTemplateSchema(a sub-shape ofNotification) and the richerEmailTemplateDefinitionSchema. The runtime metadata protocol (packages/objectql/src/protocol.ts) and Studio's property panel registered the legacy one, which is why all the new fields (name,label,category,locale,bodyHtml,bodyText, …) were reported as “declared in form layout but missing from schema”.This change:
- Repoints the
email_templateentry inTYPE_TO_SCHEMA(packages/objectql/src/protocol.ts) and inBUILTIN_METADATA_TYPE_SCHEMAS(packages/spec/src/kernel/metadata-type-schemas.ts) toEmailTemplateDefinitionSchema. The legacyEmailTemplateSchemais kept only as an inline sub-shape insideNotification. - Adds an
emailTemplatescollection todefineStack()input (packages/spec/src/stack.zod.ts), registers it inMAP_SUPPORTED_FIELDS/PLURAL_TO_SINGULAR(packages/spec/src/shared/metadata-collection.zod.ts), wires it intoARTIFACT_FIELD_TO_TYPE(packages/metadata/src/plugin.ts) andAPP_CATEGORY_KEYS(packages/runtime/src/app-plugin.ts). - Rewrites
packages/spec/src/system/email-template.form.tsfor the new schema with sections for Identity, Subject, HTML body, Plain-text body, Variables, Delivery overrides, Status. - Ships three reference templates in
examples/app-crm/src/emails/:crm.deal_won(rewritten to canonical shape),crm.welcome(new),crm.lead_followup(new), and wires them into the CRM stack viaemailTemplates: Object.values(emails).
End-to-end verified in Studio: list view at
/_console/apps/studio/metadata/email_templateshows all three entries; the detail view renders the EmailTemplatePreview iframe and the property panel cleanly renders every canonical field (no missing-schema warnings).GET /api/v1/metanow returns the newpropertiesset (name, label, category, locale, subject, bodyHtml, bodyText, variables, fromOverride, replyTo, active, isSystem, description). - Repoints the
-
Updated dependencies [47a92f4]
- @objectstack/spec@7.1.0
- @objectstack/core@7.1.0
- @objectstack/formula@7.1.0
- @objectstack/types@7.1.0
- @objectstack/metadata-core@7.1.0
- Updated dependencies [74470ad]
- Updated dependencies [d29617e]
- Updated dependencies [dc72172]
- @objectstack/spec@7.0.0
- @objectstack/core@7.0.0
- @objectstack/formula@7.0.0
- @objectstack/types@7.0.0
- @objectstack/metadata-core@7.0.0
- @objectstack/spec@6.9.0
- @objectstack/core@6.9.0
- @objectstack/types@6.9.0
- @objectstack/metadata-core@6.9.0
- @objectstack/formula@6.9.0
- @objectstack/spec@6.8.1
- @objectstack/core@6.8.1
- @objectstack/types@6.8.1
- @objectstack/metadata-core@6.8.1
- @objectstack/formula@6.8.1
-
c8b9f57: Metadata Admin engine — protocol foundations.
This is the backend half of the unified Metadata Admin shipped in the Setup app. The framework now exposes everything the engine needs to render a directory tile, schema-driven form, layered diff, references graph, and destructive-change confirmation for every registered metadata type.
GET /api/v1/meta/typesis now type-rich. Each entry includes{ icon, domain, schema (JSONSchema), allowOrgOverride, allowRuntimeCreate, supportsOverlay, ui? }so the client can render without a second round-trip per type.GET /api/v1/meta/:type/:name/referencesscans every registered metadata type for pointers to the given item (object fields, view sources, flow targets, permission objects, …) and returns the inbound edges so the UI can warn before deletes.GET /api/v1/meta/:type/:name?layers=code,overlay,effectivereturns each layer separately rather than the merged effective document, powering the 3-state diff editor (code source / overlay / effective).- Destructive-change detection on
PUT /api/v1/meta/object/:nameandPUT /api/v1/meta/field/:name: rejects field type narrowing, required toggled on without a default, removed enum values, etc., unless the client opts in withforce=true. - Env-var registry patch:
OBJECTSTACK_METADATA_WRITABLE=object,field,permission,view,…flipsallowOrgOverrideon for the listed types at boot, enabling runtime overlays for production without re-deploying spec. - New guide: Adding a Metadata Type walks through registry entry + Zod schema + optional custom editor.
Setup app navigation now uses the new component-route variant (
{ type: 'component', componentRef: 'metadata:directory' }) — the temporary/dev/metaroute is removed.
- Updated dependencies [6e88f77]
- Updated dependencies [c8b9f57]
- @objectstack/spec@6.8.0
- @objectstack/core@6.8.0
- @objectstack/formula@6.8.0
- @objectstack/types@6.8.0
- @objectstack/metadata-core@6.8.0
- @objectstack/spec@6.7.1
- @objectstack/core@6.7.1
- @objectstack/types@6.7.1
- @objectstack/metadata-core@6.7.1
- @objectstack/formula@6.7.1
- Updated dependencies [430067b]
- Updated dependencies [4f9e9d4]
- @objectstack/spec@6.7.0
- @objectstack/core@6.7.0
- @objectstack/formula@6.7.0
- @objectstack/types@6.7.0
- @objectstack/metadata-core@6.7.0
- Updated dependencies [a49cfc2]
- @objectstack/spec@6.6.0
- @objectstack/core@6.6.0
- @objectstack/formula@6.6.0
- @objectstack/types@6.6.0
- @objectstack/metadata-core@6.6.0
- @objectstack/spec@6.5.1
- @objectstack/core@6.5.1
- @objectstack/types@6.5.1
- @objectstack/metadata-core@6.5.1
- @objectstack/formula@6.5.1
- @objectstack/spec@6.5.0
- @objectstack/core@6.5.0
- @objectstack/types@6.5.0
- @objectstack/metadata-core@6.5.0
- @objectstack/formula@6.5.0
- Updated dependencies [f8651cc]
- Updated dependencies [f8651cc]
- Updated dependencies [0bf6f9a]
- @objectstack/spec@6.4.0
- @objectstack/core@6.4.0
- @objectstack/formula@6.4.0
- @objectstack/types@6.4.0
- @objectstack/metadata-core@6.4.0
- @objectstack/spec@6.3.0
- @objectstack/core@6.3.0
- @objectstack/types@6.3.0
- @objectstack/metadata-core@6.3.0
- @objectstack/formula@6.3.0
- Updated dependencies [b4c74a9]
- @objectstack/spec@6.2.0
- @objectstack/core@6.2.0
- @objectstack/formula@6.2.0
- @objectstack/types@6.2.0
- @objectstack/metadata-core@6.2.0
- @objectstack/spec@6.1.1
- @objectstack/core@6.1.1
- @objectstack/types@6.1.1
- @objectstack/metadata-core@6.1.1
- @objectstack/formula@6.1.1
- Updated dependencies [93c0589]
- @objectstack/spec@6.1.0
- @objectstack/core@6.1.0
- @objectstack/formula@6.1.0
- @objectstack/types@6.1.0
- @objectstack/metadata-core@6.1.0
- Updated dependencies [629a716]
- Updated dependencies [dbc4f7d]
- Updated dependencies [944f187]
- @objectstack/spec@6.0.0
- @objectstack/core@6.0.0
- @objectstack/formula@6.0.0
- @objectstack/types@6.0.0
- @objectstack/metadata-core@6.0.0
- Updated dependencies [bab2b20]
- Updated dependencies [fa011d8]
- Updated dependencies [b806f58]
- @objectstack/spec@5.2.0
- @objectstack/metadata-core@5.2.0
- @objectstack/core@5.2.0
- @objectstack/formula@5.2.0
- @objectstack/types@5.2.0
-
75f4ee6: feat(metadata): introduce
executionPinnedcapability for runtime version pinning (ADR-0009)Adds a new capability flag on the metadata type registry so that types whose runtime transaction rows reference a specific historical version (flow, workflow, approval) get unified pinning behavior — instead of every business table re-implementing its own snapshot column.
MetadataTypeRegistryEntrySchemagainsexecutionPinned: boolean, enforced invariantexecutionPinned ⇒ supportsVersioning.flow,workflow,approvalflipped toexecutionPinned: true.approvalalso corrected tosupportsVersioning: true(it was wronglyfalse).MetadataRepository.getByHash(ref, hash)added to the interface. Production implementation inSysMetadataRepositoryresolves historical bodies throughsys_metadata_historykeyed by(organization_id, type, name, checksum). In-memory and FS repositories serve HEAD-only matches.sys_metadata_historygains an index on(organization_id, type, name, checksum)to keep hash lookups O(log n).HistoryCleanupManagerskips pinned types entirely (both age-based and count-based retention) — pinned-type history must never be GC'd.
See
docs/adr/0009-execution-pinned-metadata.mdfor full rationale and the list of rejected alternatives (no shared snapshot table, no inlined snapshot column). -
823d559: Remove
sys_metadata_history.metadata_idcolumn.The column was originally a
Field.lookupFK intosys_metadata.id, then downgraded to plaintextduring the M1 history-writes work so that DELETE tombstones could keep an orphaned ref. After M1 we concluded the column carries no business value:- Audit-time joins use
(organization_id, type, name, version), which is already a UNIQUE composite key. - The physical row id is a database-internal detail with no logical identity — it cannot follow an item through delete + recreate.
- No code reader was ever added.
This release removes the column outright:
- Dropped
metadata_idfromSysMetadataHistoryObject(@objectstack/platform-objects). - Dropped
metadataIdfromMetadataHistoryRecordSchema(@objectstack/spec). SysMetadataRepository.put/deleteno longer write the column.- Legacy
DatabaseLoader.createHistoryRecordno longer writes it;getHistoryRecord/queryHistoryfilter by(type, name)directly (no parent-row lookup needed). MetadataHistoryCleanupmaxVersionspolicy groups by(type, name)instead ofmetadata_id.
Migration: Drop the column from existing
sys_metadata_historytables in a follow-up SQL migration. Existing history rows remain queryable since(organization_id, type, name, version)is already the canonical lookup key. No consumer code should be readingmetadata_id— if you are, switch to(organization_id, type, name, version).See ADR-0008 §14 for the full rationale.
- Audit-time joins use
-
Updated dependencies [75f4ee6]
-
Updated dependencies [823d559]
- @objectstack/spec@5.1.0
- @objectstack/metadata-core@5.1.0
- @objectstack/core@5.1.0
- @objectstack/formula@5.1.0
- @objectstack/types@5.1.0
-
5e9dcb4: BREAKING — metadata: remove
projectandbranchfromMetaRefThe metadata layer no longer models project or branch. Customisation is now scoped purely to organisation. Project remains exclusively as an artifact packaging concept (the
objectstack.jsonbundle envelope); branching is left to Git.What changed:
MetaRefis now{ org, type, name, version? }(was{ org, project, branch, type, name, version? }).refKey()is the two segment string${org}/${type}/${name}(was five segments).MetadataItem.seqis monotonic per org (was per branch).BranchRef,MergeStrategy,MergeResulttypes and the optionalfork/mergemethods onMetadataRepositoryare removed.ListFilter/WatchFilter/HistoryOptionsno longer acceptprojectorbranch.FileSystemRepositorydisk layout simplified to<root>/<type>/<name>.json(was<root>/<project>/<branch>/<type>/<name>.json); change-log path is now.objectstack/.log/main.jsonlregardless of any branch concept. Constructor no longer acceptsproject/branch.SysMetadataRepository: removedprojectLabel/branchLabeloptions; thesys_metadataschema'sproject_id/branchcolumns (if present) are ignored. A future major release willDROPthem.MetadataManager.setRepository(repo, opts)no longer takes an opts object withbranch.
Migration:
-const ref = { org: 'acme', project: 'crm', branch: 'main', type: 'view', name: 'home' }; +const ref = { org: 'acme', type: 'view', name: 'home' }; -new FileSystemRepository({ root, org: 'acme', project: 'crm', branch: 'main' }); +new FileSystemRepository({ root, org: 'acme' });
Existing
sys_metadatarows continue to load; the deprecated columns are ignored at read time. -
f139a24: Subscribe
ObjectQLPlugintometadata.subscribe('object', …)so theSchemaRegistrymerge cache is invalidated and the affected object re-registered on every object metadata change (ADR-0008 M0 PR-7).Combined with the PR-6 metadata ↔ repository bridge, this closes the Studio HMR loop end-to-end: editing an object definition (file, REST write, or Studio inline edit) emits a
MetadataEvent, which flows throughMetadataManager.subscribe('object', …)into ObjectQL, which drops the cached merged definition and re-fetches the canonical body from the metadata service. Subsequent reads see the new schema with no server restart.Additions:
SchemaRegistry.invalidate(fqnOrName)andinvalidateAll()— public hooks for event-driven cache eviction; contributors are preserved soresolveObjectrecomputes against the next call.ObjectQLPlugin.start()wires the subscription when the metadata service exposessubscribe(). The handler invalidates, re-fetches viametadata.get('object', name), and re-registers with the originalpackageId/namespace. Deletes only invalidate.ObjectQLPlugin.stop()drains the subscription handles so test reloads don't leak watchers.
-
2f7e42a: ADR-0008 M0 PR-10b: introduce
SysMetadataRepository— aMetadataRepositorywrapper over the existingsys_metadatatable. M0 keeps single-row update semantics (append-only event log is M1 work). Whitelist enforcement, optimistic locking via content hash, and in-process watch fan-out are all live. Not yet wired into any production write path — PR-10c will compose it under a LayeredRepository. -
888a5c1: PR-10d.3 — feature flag for
SysMetadataRepository.putwrite path insaveMetaItem.ObjectStackProtocolImplementationnow accepts anoptions.useRepositoryWritePathflag (also honored viaOBJECTSTACK_USE_REPOSITORY_WRITE_PATH=1) that routes overlay writes throughSysMetadataRepository.put, appending to the change-log and emitting HMRseq.saveMetaItemrequest grew optionalparentVersion(If-Match) andactorfields.ConflictErroris mapped to a 409metadata_conflictAPI error.- Plural metadata type aliases (
views,dashboards, ...) are normalized to singular before the repo's overlay-allowlist gate. SysMetadataRepository.put/deletenow update/delete by rowid(the engine's strict.updatesemantics require an id ormulti:true).sys_metadata.checksumcolumn widened from 64 → 71 chars to hold the"sha256:"prefix produced byhashSpec().- Default behaviour unchanged: legacy raw-engine path remains until PR-10d.4 flips the flag and removes it.
-
09f005a: PR-10d.5 — Flip default of
useRepositoryWritePathtotrue.saveMetaItemnow routes overlay-allowed metadata types (view, dashboard, report, email_template) throughSysMetadataRepository.putby default — every write appends to the change log and emits a watch event with a monotonicseqfor HMR / replay.Non-overlay-allowed types (
object,flow,agent, ...) still take the legacy raw-engine path. This preserves control-plane bootstrap behaviour (which writesobject/flowdefinitions viasaveMetaItemand is permitted by the outer protocol gate to write any type whenprojectIdis undefined).Opt-out remains available during the deprecation window:
- Constructor:
new ObjectStackProtocolImplementation(engine, …, { useRepositoryWritePath: false }) - Env var:
OBJECTSTACK_USE_REPOSITORY_WRITE_PATH=0
The legacy raw-engine branch for overlay-allowed types is scheduled for removal in PR-10d.6 once this default has soaked for one release.
- Constructor:
-
4eb9f8c: ADR-0008 M0 PR-10a: pin overlay-whitelist + canonical-hash invariants before re-expressing the overlay path as a LayeredRepository. No runtime change — adds 28 regression tests that fail loud if a future PR weakens the shared-DB tenancy contract or breaks hash stability.
-
602cce7: test(objectql): integration coverage for
LayeredRepositorycomposed ofSysMetadataRepository(top, writable overlay) overInMemoryRepository(bottom, artifact baseline). Verifies read fallthrough, overlay-wins precedence, write routing, delete behavior, event source tagging across layers, and merged-list semantics. Part of ADR-0008 PR-10c. -
1e625b8: feat(objectql): hash-compat dry-run probe for the legacy → repository write-path migration (ADR-0008 PR-10d.1). Pure-function
runDryRun()plus a CLI (scripts/dry-run-hash-compat.ts) that audits a snapshot ofsys_metadatafor invalid JSON, non-object bodies, unstable hashes across canonical round-trip, and duplicate overlay keys. Exits non-zero when incompatibilities are found. 14 unit tests covering happy paths, error classifications (invalid_json,non_object_body,unstable_hash,missing_metadata,duplicate_overlay_key), and boundary conditions (empty snapshot, deep nesting, unicode). -
6ee42b8: fix(objectql): SysMetadataRepository reuses the existing
checksumcolumn instead of writing a non-existent_hashcolumn (ADR-0008 PR-10d.2). The productionsys_metadataschema (packages/platform-objects) already ships withchecksum: text(64)— perfect for sha256 hex — andversion: numberfor the monotonic counter. No DDL migration is required for PR-10d.3 cutover; legacy rows with NULL checksum will be lazily backfilled on first put().Also extends the PR-10d.1 dry-run probe with two new checks (
checksum_missingwarning,checksum_drifterror) and three additional tests, taking objectql to 325/325 green. -
5cfdc85: PR-10d.4 — REST plumbing for the metadata repository write path.
PUT /api/v1/meta/:type/:name(and the compound:type/:section/:namevariant) now forwards theIf-Matchheader tosaveMetaItemasparentVersion, andX-Actor(orreq.user.id) asactor. ETag-style quotes are stripped.- A failed optimistic-lock check surfaces as HTTP 409 with body
{ "error": "...", "code": "metadata_conflict" }(no protocol changes —sendErroralready honourederror.status+error.code). - Added a real-engine integration test for the repository write path
(
protocol-save-meta-repo-path-real-engine.test.ts) — addresses the PR-10d.3 rubber-duck stub-drift concern by exercisingObjectStackProtocolImplementation.saveMetaItemthroughnew ObjectQL()with an inline in-memory driver. Covers insert→update version bump, parentVersion conflict, checksum length, and plural→singular normalization.
Default behaviour unchanged: the repository write path remains opt-in via
options.useRepositoryWritePath/OBJECTSTACK_USE_REPOSITORY_WRITE_PATH=1. Flag flip and legacy path removal will follow in a separate post-soak PR. -
7825394: PR-10d.6 — remove
useRepositoryWritePathfeature flag.Overlay-allowed metadata types (
view,dashboard,report,email_template) now unconditionally route throughSysMetadataRepository.put(change-log + HMRseq). The legacy raw-engine branch is retained for non-overlay types (object,flow,agent, etc.) used during control-plane bootstrap, since the repositoryassertAllowed()whitelist would reject them.Removed:
ObjectStackProtocolImplementationconstructor option{ useRepositoryWritePath: boolean }.OBJECTSTACK_USE_REPOSITORY_WRITE_PATHenvironment variable.
There is no opt-out: behavior is now equivalent to the PR-10d.5 default.
-
96ad4df: Fix dev-mode HMR data-reload for
*.view.ts/*.flow.tssource-file edits.Three coordinated fixes close the long-standing gap where editing a declarative-metadata source file in dev (e.g.
case.view.ts) would recompiledist/objectstack.jsonbut the running server kept serving the stale boot-time value:-
@objectstack/objectql—ObjectStackProtocolImplementation.getMetaItemnow consultsMetadataService(HMR-aware) before the in-memorySchemaRegistry(boot-time cache). Previously the registry shadowed freshly-registered values:manager.register('view','case',newDef)updated MetadataManager butgetMetaItemreturned the stale registry copy because step 2 (registry) ran before step 3 (service). Reordered to "1. sys_metadata overlay → 2. MetadataService → 3. SchemaRegistry". -
@objectstack/runtime—createStandaloneStacknow enables theMetadataPluginartifact-file watcher in non-production environments (NODE_ENV !== 'production'). Previously hard-coded towatch: false, leaving nothing watchingdist/objectstack.jsonwhen the CLI dev mode recompiled it. -
@objectstack/metadata&@objectstack/metadata-fs— Both chokidar watchers now useusePolling: trueto avoidfs.watchEMFILE on macOS / busy dev hosts where the native file-descriptor pool can be exhausted by other long-running node processes.
With these three changes:
- CLI edits source → recompile artifact (~400ms)
- Server's polling chokidar detects artifact change →
_loadFromLocalFile _loadFromLocalFilecallsmanager.register(type, name, item)- MetadataService now has the fresh value
- Read path returns the fresh value via the new step-2 lookup
- Studio SSE listeners re-render
-
-
Updated dependencies [5e9dcb4]
-
Updated dependencies [4150fe4]
-
Updated dependencies [8337cdb]
-
Updated dependencies [58835a6]
-
Updated dependencies [8cc30b4]
-
Updated dependencies [32ce912]
-
Updated dependencies [2f9073a]
- @objectstack/metadata-core@5.0.0
- @objectstack/spec@5.0.0
- @objectstack/core@5.0.0
- @objectstack/formula@5.0.0
- @objectstack/types@5.0.0
-
2869891: feat: Optimistic Concurrency Control (OCC) via
If-MatchUpdate and Delete requests now accept an optional version token. When supplied, the protocol compares it against the record's current
updated_at(orversioncolumn when available) and rejects with409 CONCURRENT_UPDATEon mismatch, preventing silent overwrites when two clients edit the same record.Wire formats (opt-in, all server- and client-backward-compatible):
PATCH /data/{object}/{id}— supportsIf-Match: "<token>"header orexpectedVersion: "<token>"body field (body wins when both present).DELETE /data/{object}/{id}— supportsIf-Matchheader or?expectedVersion=...query param.- Conflict response:
409 { error, code: 'CONCURRENT_UPDATE', currentVersion, currentRecord }so the client can offer Reload / Overwrite / Cancel UX.
Behaviour
- Missing/empty version → no check (legacy callers unaffected).
- Record not found during the version probe → no check; the downstream write
produces a normal
404. - Object has no
updated_atcolumn → no check (explicit opt-out for objects without timestamps). - Quoted RFC-7232 tokens (
"…") are accepted and unquoted before comparison.
Client
client.data.update(resource, id, data, { ifMatch })andclient.data.delete(resource, id, { ifMatch })now forward the token as anIf-Matchheader.Application-level CAS (findOne + compare in protocol.ts) is used in this slice to avoid touching every storage driver. A small TOCTOU window remains; for the B2B record-editing latencies this protects against, it is more than sufficient. Drivers may later be upgraded to atomic
WHERE id=? AND updated_at=?writes for true CAS without changing the public API.Tests: 7 new cases in
protocol-data.test.tscover opt-in, match, mismatch, quote-stripping, no-timestamps, empty-token, and the delete path.
- Updated dependencies [2869891]
- @objectstack/spec@4.2.0
- @objectstack/core@4.2.0
- @objectstack/formula@4.2.0
- @objectstack/types@4.2.0
- @objectstack/spec@4.1.1
- @objectstack/core@4.1.1
- @objectstack/types@4.1.1
- @objectstack/formula@4.1.1
-
f0b3972: Driver-level tenant isolation for objects with
organization_id.SqlDrivernow auto-applies aWHERE organization_id = :tenantIdpredicate on every read/update/delete and auto-injects the column on insert when the caller passesoptions.tenantIdand the object schema declares anorganization_idfield.bulkCreate,bulkDelete,updateMany,deleteMany,countandaggregateare all scoped.ObjectQL's engine now threads
ExecutionContext.tenantIdinto the driver options for every CRUD entry point (includingexpandRelatedRecords), 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. Explicitorganization_idon an insert row always wins over the contextualtenantIdso admin tooling can still target a specific tenant.13 new tests in
sql-driver-tenant-scope.test.tsverify cross-tenant find/findOne/update/delete/count/bulkCreate/updateMany/deleteMany isolation, the unscoped admin path, and that global objects (noorganization_id) are not scoped.
-
5683206: Document the tenant-isolation bypass on raw
execute()(bothSqlDriver.execute()andengine.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 inlineWHERE organization_id = ?themselves or restrict raw execution to genuinely global statements (migrations, control-plane tables). -
0e63f2f: Declarative tenant scoping + audit warn for missing tenantId.
SqlDrivernow readsobj.tenancy.tenantFieldfirst when picking the tenant column for an object, falling back to the implicitorganization_iddetection so legacy objects keep working without a spec migration. Settenancy: { 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 withoutoptions.tenantIdnow 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 whenExecutionContext.isSystem === true(boot-time seeds, kernel mirrors). Callers can opt out per-call withoptions.bypassTenantAudit = trueor globally withOS_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.
-
Updated dependencies [2108c30]
-
Updated dependencies [23db640]
- @objectstack/spec@4.1.0
- @objectstack/core@4.1.0
- @objectstack/formula@4.1.0
- @objectstack/types@4.1.0
- 15e0df6: chore: unify all package versions to a single patch release
- Updated dependencies [15e0df6]
- @objectstack/spec@4.0.5
- @objectstack/core@4.0.5
- @objectstack/types@4.0.5
- @objectstack/formula@4.0.5
- Updated dependencies [326b66b]
- @objectstack/spec@4.0.4
- @objectstack/core@4.0.4
- @objectstack/types@4.0.4
- @objectstack/spec@4.0.3
- @objectstack/core@4.0.3
- @objectstack/types@4.0.3
- fix: ObjectQL.init() now tracks and warns about failed driver connections instead of silently swallowing errors, improving debuggability for cold-start and serverless issues.
- Updated dependencies [5f659e9]
- @objectstack/spec@4.0.2
- @objectstack/core@4.0.2
- @objectstack/types@4.0.2
-
e0b0a78: Deprecate DataEngineQueryOptions in favor of QueryAST-aligned EngineQueryOptions.
Engine, Protocol, and Client now use standard QueryAST parameter names:
filter→whereselect→fieldssort→orderByskip→offsetpopulate→expandtop→limit
The old DataEngine* schemas and types are preserved with
@deprecatedmarkers for backward compatibility.
- Updated dependencies [f08ffc3]
- Updated dependencies [e0b0a78]
- @objectstack/spec@4.0.0
- @objectstack/core@4.0.0
- @objectstack/types@4.0.0
- @objectstack/spec@3.3.1
- @objectstack/core@3.3.1
- @objectstack/types@3.3.1
- @objectstack/spec@3.3.0
- @objectstack/core@3.3.0
- @objectstack/types@3.3.0
- c3065dd: fix turso 2
- @objectstack/spec@3.2.9
- @objectstack/core@3.2.9
- @objectstack/types@3.2.9
- Auto-sync all registered object schemas to database on startup:
ObjectQLPlugin.start()now iterates every object inSchemaRegistryand callsdriver.syncSchema()after driver connections are established. This ensures tables for plugin-registered objects (e.g.sys_userfrom plugin-auth) are created or updated automatically. - Added
getDriverForObject(objectName)public method toObjectQLengine for resolving the responsible driver for a given object. - Added optional
syncSchemamethod toDriverInterfacecontract, aligning it with the fullIDataDriverprotocol. - @objectstack/spec@3.2.8
- @objectstack/core@3.2.8
- @objectstack/types@3.2.8
- @objectstack/spec@3.2.7
- @objectstack/core@3.2.7
- @objectstack/types@3.2.7
- @objectstack/spec@3.2.6
- @objectstack/core@3.2.6
- @objectstack/types@3.2.6
- @objectstack/spec@3.2.5
- @objectstack/core@3.2.5
- @objectstack/types@3.2.5
- @objectstack/spec@3.2.4
- @objectstack/core@3.2.4
- @objectstack/types@3.2.4
- @objectstack/spec@3.2.3
- @objectstack/core@3.2.3
- @objectstack/types@3.2.3
- Updated dependencies [46defbb]
- @objectstack/spec@3.2.2
- @objectstack/core@3.2.2
- @objectstack/types@3.2.2
- Updated dependencies [850b546]
- @objectstack/spec@3.2.1
- @objectstack/core@3.2.1
- @objectstack/types@3.2.1
- Updated dependencies [5901c29]
- @objectstack/spec@3.2.0
- @objectstack/core@3.2.0
- @objectstack/types@3.2.0
- Updated dependencies [953d667]
- @objectstack/spec@3.1.1
- @objectstack/core@3.1.1
- @objectstack/types@3.1.1
- Updated dependencies [0088830]
- @objectstack/spec@3.1.0
- @objectstack/core@3.1.0
- @objectstack/types@3.1.0
- Updated dependencies [92d9d99]
- @objectstack/spec@3.0.11
- @objectstack/core@3.0.11
- @objectstack/types@3.0.11
- Updated dependencies [d1e5d31]
- @objectstack/spec@3.0.10
- @objectstack/core@3.0.10
- @objectstack/types@3.0.10
- Updated dependencies [15e0df6]
- @objectstack/spec@3.0.9
- @objectstack/core@3.0.9
- @objectstack/types@3.0.9
- Updated dependencies [5a968a2]
- @objectstack/spec@3.0.8
- @objectstack/core@3.0.8
- @objectstack/types@3.0.8
- Updated dependencies [0119bd7]
- Updated dependencies [5426bdf]
- @objectstack/spec@3.0.7
- @objectstack/core@3.0.7
- @objectstack/types@3.0.7
- Updated dependencies [5df254c]
- @objectstack/spec@3.0.6
- @objectstack/core@3.0.6
- @objectstack/types@3.0.6
- Updated dependencies [23a4a68]
- @objectstack/spec@3.0.5
- @objectstack/core@3.0.5
- @objectstack/types@3.0.5
-
437b0b8: feat(objectql): add utility functions, introspection types, and kernel factory
Upstream key functionality from downstream
@objectql/coreto enable its future deprecation:- Introspection types:
IntrospectedSchema,IntrospectedTable,IntrospectedColumn,IntrospectedForeignKey - Utility functions:
toTitleCase(),convertIntrospectedSchemaToObjects() - Kernel factory:
createObjectQLKernel()withObjectQLKernelOptions
- Introspection types:
-
Updated dependencies [d738987]
- @objectstack/spec@3.0.4
- @objectstack/core@3.0.4
- @objectstack/types@3.0.4
- c7267f6: Patch release for maintenance updates and improvements.
- Updated dependencies [c7267f6]
- @objectstack/spec@3.0.3
- @objectstack/core@3.0.3
- @objectstack/types@3.0.3
- Updated dependencies [28985f5]
- @objectstack/spec@3.0.2
- @objectstack/core@3.0.2
- @objectstack/types@3.0.2
- Updated dependencies [389725a]
- @objectstack/spec@3.0.1
- @objectstack/core@3.0.1
- @objectstack/types@3.0.1
- Release v3.0.0 — unified version bump for all ObjectStack packages.
- Updated dependencies
- @objectstack/spec@3.0.0
- @objectstack/core@3.0.0
- @objectstack/types@3.0.0
- Updated dependencies
- @objectstack/spec@2.0.7
- @objectstack/core@2.0.7
- @objectstack/types@2.0.7
- Patch release for maintenance and stability improvements
- Updated dependencies
- @objectstack/spec@2.0.6
- @objectstack/core@2.0.6
- @objectstack/types@2.0.6
- Updated dependencies
- @objectstack/spec@2.0.5
- @objectstack/core@2.0.5
- @objectstack/types@2.0.5
- Patch release for maintenance and stability improvements
- Updated dependencies
- @objectstack/spec@2.0.4
- @objectstack/core@2.0.4
- @objectstack/types@2.0.4
- Patch release for maintenance and stability improvements
- Updated dependencies
- @objectstack/spec@2.0.3
- @objectstack/core@2.0.3
- @objectstack/types@2.0.3
- Updated dependencies [1db8559]
- @objectstack/spec@2.0.2
- @objectstack/core@2.0.2
- @objectstack/types@2.0.2
- Patch release for maintenance and stability improvements
- Updated dependencies
- @objectstack/spec@2.0.1
- @objectstack/core@2.0.1
- @objectstack/types@2.0.1
- Updated dependencies [38e5dd5]
- Updated dependencies [38e5dd5]
- @objectstack/spec@2.0.0
- @objectstack/core@2.0.0
- @objectstack/types@2.0.0
- Updated dependencies
- @objectstack/spec@1.0.12
- @objectstack/core@1.0.12
- @objectstack/types@1.0.12
- @objectstack/spec@1.0.11
- @objectstack/core@1.0.11
- @objectstack/types@1.0.11
- Updated dependencies [10f52e1]
- @objectstack/core@1.0.10
- @objectstack/spec@1.0.10
- @objectstack/types@1.0.10
- b9f8c68: fix: handle async metadata service detection safely to prevent startup crash
- @objectstack/spec@1.0.9
- @objectstack/core@1.0.9
- @objectstack/types@1.0.9
- @objectstack/spec@1.0.8
- @objectstack/core@1.0.8
- @objectstack/types@1.0.8
- @objectstack/spec@1.0.7
- @objectstack/core@1.0.7
- @objectstack/types@1.0.7
- Updated dependencies [a7f7b9d]
- @objectstack/spec@1.0.6
- @objectstack/core@1.0.6
- @objectstack/types@1.0.6
- b1d24bd: refactor: migrate build system from tsc to tsup for faster builds
- Replaced
tscwithtsup(using esbuild) across all packages - Added shared
tsup.config.tsin workspace root - Added
tsupas workspace dev dependency - significantly improved build performance
- Replaced
- Updated dependencies [b1d24bd]
- @objectstack/core@1.0.5
- @objectstack/types@1.0.5
- @objectstack/spec@1.0.5
- 5d13533: refactor: fix service registration compatibility and improve logging
- plugin-hono-server: register 'http.server' service alias to match core requirements
- plugin-hono-server: fix console log to show the actual bound port instead of configured port
- plugin-hono-server: reduce log verbosity (moved non-essential logs to debug level)
- objectql: automatically register 'metadata', 'data', 'and 'auth' services during initialization to satisfy kernel contracts
- cli: fix race condition in
servecommand by awaiting plugin registration calls (kernel.use) - @objectstack/spec@1.0.4
- @objectstack/core@1.0.4
- @objectstack/types@1.0.4
- 22a48f0: refactor: fix service registration compatibility and improve logging
- plugin-hono-server: register 'http.server' service alias to match core requirements
- plugin-hono-server: fix console log to show the actual bound port instead of configured port
- plugin-hono-server: reduce log verbosity (moved non-essential logs to debug level)
- objectql: automatically register 'metadata', 'data', 'and 'auth' services during initialization to satisfy kernel contracts
- Updated dependencies [fb2eabd]
- @objectstack/core@1.0.3
- @objectstack/spec@1.0.3
- @objectstack/types@1.0.3
-
a0a6c85: Infrastructure and development tooling improvements
- Add changeset configuration for automated version management
- Add comprehensive GitHub Actions workflows (CI, CodeQL, linting, releases)
- Add development configuration files (.cursorrules, .github/prompts)
- Add documentation files (ARCHITECTURE.md, CONTRIBUTING.md, workflows docs)
- Update test script configuration in package.json
- Add @objectstack/cli to devDependencies for better development experience
-
109fc5b: Unified patch release to align all package versions.
-
Updated dependencies [a0a6c85]
-
Updated dependencies [109fc5b]
- @objectstack/spec@1.0.2
- @objectstack/core@1.0.2
- @objectstack/types@1.0.2
- @objectstack/spec@1.0.1
- @objectstack/core@1.0.1
- @objectstack/types@1.0.1
- Major version release for ObjectStack Protocol v1.0.
- Stabilized Protocol Definitions
- Enhanced Runtime Plugin Support
- Fixed Type Compliance across Monorepo
- Updated dependencies
- @objectstack/spec@1.0.0
- @objectstack/core@1.0.0
- @objectstack/types@1.0.0
- Updated dependencies
- @objectstack/spec@0.9.2
- @objectstack/core@0.9.2
- @objectstack/types@0.9.2
- Patch release for maintenance and stability improvements. All packages updated with unified versioning.
- Updated dependencies
- @objectstack/spec@0.9.1
- @objectstack/core@0.9.1
- @objectstack/types@0.9.1
-
555e6a7: Refactor: Deprecated View Storage protocol in favor of Metadata Views.
- BREAKING: Removed
view-storage.zod.tsandViewStoragerelated types from@objectstack/spec. - BREAKING: Removed
createView,updateView,deleteView,listViewsfromObjectStackProtocolinterface. - BREAKING: Removed in-memory View Storage implementation from
@objectstack/objectql. - UPDATE:
@objectstack/plugin-mswnow dynamically loads@objectstack/objectqlto avoid hard dependencies.
- BREAKING: Removed
-
Updated dependencies [555e6a7]
- @objectstack/spec@0.8.2
- @objectstack/core@0.8.2
- @objectstack/types@0.8.2
- @objectstack/spec@0.8.1
- @objectstack/core@0.8.1
- @objectstack/types@0.8.1
-
This release includes a major upgrade to the core validation engine (Zod v4) and aligns all protocol definitions with stricter type safety.
- Updated dependencies
- @objectstack/spec@1.0.0
- @objectstack/core@1.0.0
- @objectstack/types@1.0.0
- fb41cc0: Patch release: Updated documentation and JSON schemas
- Updated dependencies [fb41cc0]
- @objectstack/spec@0.7.2
- @objectstack/core@0.7.2
- @objectstack/types@0.7.2
- Patch release for maintenance and stability improvements
- Updated dependencies
- @objectstack/spec@0.7.1
- @objectstack/types@0.7.1
- @objectstack/core@0.7.1
- Patch release for maintenance and stability improvements
- Updated dependencies
- @objectstack/spec@0.6.1
- @objectstack/types@0.6.1
- @objectstack/core@0.6.1
-
b2df5f7: Unified version bump to 0.5.0
- Standardized all package versions to 0.5.0 across the monorepo
- Fixed driver-memory package.json paths for proper module resolution
- Ensured all packages are in sync for the 0.5.0 release
- Updated dependencies [b2df5f7]
- @objectstack/spec@0.6.0
- @objectstack/types@0.6.0
- @objectstack/core@0.6.0
- Unify all package versions to 0.4.2
- Updated dependencies
- @objectstack/spec@0.4.2
-
Version synchronization and dependency updates
- Synchronized plugin-msw version to 0.4.1
- Updated runtime peer dependency versions to ^0.4.1
- Fixed internal dependency version mismatches
-
Updated dependencies
- @objectstack/spec@0.4.1
- Release version 0.4.0
-
Workflow and configuration improvements
- Enhanced GitHub workflows for CI, release, and PR automation
- Added comprehensive prompt templates for different protocol areas
- Improved project documentation and automation guides
- Updated changeset configuration
- Added cursor rules for better development experience
-
Updated dependencies
- @objectstack/spec@0.3.3
- Patch release for maintenance and stability improvements
- Updated dependencies
- @objectstack/spec@0.3.2
- @objectstack/spec@0.3.1
- Updated dependencies
- @objectstack/spec@1.0.0
-
Initial release of ObjectStack Protocol & Specification packages
This is the first public release of the ObjectStack ecosystem, providing:
- Core protocol definitions and TypeScript types
- ObjectQL query language and runtime
- Memory driver for in-memory data storage
- Client library for interacting with ObjectStack
- Hono server plugin for REST API endpoints
- Complete JSON schema generation for all specifications
- Updated dependencies
- @objectstack/spec@0.2.0
- Remove debug logs from registry and protocol modules
- Updated dependencies
- @objectstack/spec@0.1.2