Skip to content

Add metamodel interfaces for ObjectQL/ObjectUI contract - #1

Merged
huangyiirene merged 2 commits into
mainfrom
copilot/create-meta-model-interfaces
Jan 18, 2026
Merged

Add metamodel interfaces for ObjectQL/ObjectUI contract#1
huangyiirene merged 2 commits into
mainfrom
copilot/create-meta-model-interfaces

Conversation

CopilotAI commented Jan 18, 2026

Copy link
Copy Markdown
Contributor

Defines the type system shared between backend (ObjectQL) parser and frontend (ObjectUI) renderer.

Core Interfaces

FieldType - 16 data types covering text, numeric, date/time, lookup, selection, and special fields (file, image, json)

ObjectField - Field metadata with validation rules, constraints, lookup configuration, and display properties

ObjectEntity - Complete entity definition including fields array, keys, UI hints, audit/soft-delete flags, and search configuration

ObjectView - View configuration supporting 10 presentation types (list, form, detail, card, kanban, calendar, chart, map, timeline, custom) with columns, filters, sorting, and layouts

Usage Example

import{ObjectEntity}from'@objectstack/spec';constuserEntity: ObjectEntity={name: 'User',label: 'User',pluralLabel: 'Users',fields: [{name: 'email',label: 'Email',type: 'email',required: true,unique: true},{name: 'role',label: 'Role',type: 'select',options: [{value: 'admin',label: 'Administrator'},{value: 'user',label: 'User'}]},{name: 'manager',label: 'Manager',type: 'lookup',lookupEntity: 'User',lookupDisplayField: 'name'}],primaryKey: 'id',displayField: 'email'};

Implementation Notes

  • TSDoc comments on all properties for IntelliSense
  • Extensibility via metadata fields without core schema changes
  • Pure TypeScript, platform-agnostic (Node.js/Browser/Electron compatible)
  • Type guard isFieldType() for runtime validation
Original prompt

Please create a src/types/meta directory.
Define the Metamodel interfaces: ObjectEntity, ObjectField, and ObjectView.
These interfaces will be the contract shared between the backend (ObjectQL) parser and the frontend (ObjectUI) renderer.
Include specific types for FieldType (e.g., text, number, lookup)."


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
CopilotAI changed the title [WIP] Add Metamodel interfaces for backend and frontendAdd metamodel interfaces for ObjectQL/ObjectUI contractJan 18, 2026
@huangyiirene
huangyiirene marked this pull request as ready for review January 18, 2026 09:09
@huangyiirene
huangyiirene merged commit b45c7f2 into mainJan 18, 2026
hotlong added a commit that referenced this pull request May 20, 2026
Closes gap #1 from the production-readiness review: the client-side
ObjectForm / inline-grid masker (shipped earlier) was only a UX layer
— a hand-crafted POST or direct ObjectQL call could still target any
field. This commit closes the loop by enforcing field-level write
permissions in the SecurityPlugin middleware.
Behavior: on every insert/update, after the existing CRUD check and
before the tenant/owner auto-injection, the middleware now scans the
caller's payload against the merged field permissions for the target
object. If the payload references any field the caller is not
permitted to edit, the engine throws PermissionDeniedError (HTTP 403)
with the offending field names exposed via details.forbiddenFields.
Design choices:
- **Fail-closed via throw, not silent strip.** Silent strip hides the
boundary from honest clients (partial-save confusion: 'why didn't
my change save?') AND gives probing clients no signal that the
field exists. Throwing makes the boundary observable in both
directions — legitimate UIs get an actionable error; probing
clients learn nothing they could not already infer.
- **Allow-list semantics.** Only fields explicitly enumerated in a
permission set's 'fields' map are constrained. Fields without a
rule pass through untouched.
- **Bulk inserts checked row-by-row.** Arrays are scanned in full; a
single offender in any row rejects the entire batch atomically.
- **Runs BEFORE auto-injection.** The tenant/owner auto-fill (org_id,
owner_id) is system-supplied from ExecutionContext, not from the
caller's payload, so it is not subject to the user's edit
permissions even when the user has no rule for those fields.
- **System operations bypass entirely.** ExecutionContext.isSystem
short-circuits the whole security middleware including this check.
API additions:
- FieldMasker.detectForbiddenWrites(data, fieldPermissions): string[]
— exported helper for adapters that want to perform the check
out-of-band (e.g., strip-then-warn instead of fail-closed).
Documentation:
- content/docs/guides/security.mdx — new 'Server-side enforcement
(fail-closed)' subsection under Field-Level Security with the 403
response shape, the why-throw-vs-strip rationale, allow-list
semantics, and the bulk/system bypass rules.
- .changeset/security-fls-write-enforcement.md — minor bump.
Tests: 7 unit tests for FieldMasker.detectForbiddenWrites + 8
integration tests via the existing security middleware harness
covering insert/update/bulk/system-bypass/no-rule passthrough.
53 plugin-security tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
xuyushun441-sys pushed a commit that referenced this pull request May 22, 2026
Vendor-neutral observability primitives, extracted as a standalone
package so deployment-target code (cloud, self-hosted, ...) can depend
on the contracts without pulling in the whole runtime.
Owns:
- Contracts: MetricsRegistry, ErrorReporter, MetricSample, CapturedError
(Logger is re-exported from @objectstack/spec/contracts).
- Semantic conventions (SEMCONV): canonical Prometheus-style metric
names emitted by the framework, plus the back-compat RUNTIME_METRICS
alias.
- Metric exporters: Noop, InMemory (with totalCounter/histogramValues/
lastGauge helpers), Console, and OtlpHttp (buffered JSON exporter,
flush()-on-demand so it works on Workers as well as Node).
- Error reporters: Noop, InMemory, Console (structured JSON to stderr).
- Loggers: Noop, Console, Json (production-ready structured logging
that satisfies the existing @objectstack/spec Logger contract).
Backwards compatibility:
- @objectstack/runtime now depends on @objectstack/observability and
its src/observability/{metrics,error-reporter}.ts files are thin
re-export shims, so existing internal imports (and the public
runtime/index.ts surface) are unchanged.
Tests: 34 new tests covering all exporters; @objectstack/runtime test
suite still passes (the 2 pre-existing app-plugin.test.ts failures
around i18n service warnings are not affected by this change — they
were already failing on main).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
xuyushun441-sys pushed a commit that referenced this pull request May 22, 2026
Introduces an opt-in path in ObjectStackProtocolImplementation.saveMetaItem
that writes overlay metadata through SysMetadataRepository.put instead of
the raw engine, so writes append to the change-log and emit HMR seq events.
Behavioural changes (all behind options.useRepositoryWritePath /
OBJECTSTACK_USE_REPOSITORY_WRITE_PATH=1):
- saveMetaItem request gained optional parentVersion (If-Match) and
actor fields. ConflictError -> 409 metadata_conflict.
- Plural type aliases (views, dashboards, ...) normalized to singular
before the repo's overlay-allowlist gate (rubber-duck #5).
- Object-registry mutation moved AFTER successful put() so a conflict
does not leave the in-memory registry stale (rubber-duck #3 invariant
test added).
Repo/test-fake fixes uncovered by rubber-duck review:
- SysMetadataRepository.put/delete now update/delete by row id because
the engine's strict .update requires id or multi:true (rubber-duck #1).
- sys_metadata.checksum column widened from 64 -> 71 chars to hold the
sha256: prefix produced by hashSpec() (rubber-duck #2).
- Three test fake engines extended to support both overlay-tuple and
id-based where lookups.
333/333 objectql tests pass.
Deferred to PR-10d.4: REST plumbing for parentVersion/actor
(rubber-duck #6), race-window retry for omitted parentVersion
(rubber-duck #4), default flag flip + legacy path removal.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
xuyushun441-sys pushed a commit that referenced this pull request May 23, 2026
Walking through Studio as a low-code developer surfaced a fundamental
gap: it is a beautiful metadata BROWSER but offers no authoring
affordances. The #1 reflex of every Airtable / Power Apps user — add a
field — has no entry point in our UI.
This change adds two authoring touchpoints to the Object Hub > Fields
panel that respect Prime Directive #6 (no temporary workarounds) and
stay true to metadata-as-code:
1. + Add field button
A primary CTA in the toolbar opens a guided dialog (AddFieldDialog)
with a type picker (18 supported field types, each with icon +
one-line semantics), a derived snake_case machine-name preview, and a
live snippet preview. Two actions:
• Copy snippet — pastes a defineField-style literal into the
clipboard, ready to drop into the fields: { … } block.
• Open .object.ts in VS Code — vscode:// deep-link via the
existing vscode-objectstack extension.
Filesystem writes from the browser are intentionally avoided. When
the runtime overlay write-path matures (ADR-0005), the dialog can
swap the snippet flow for a real persist call without changing its
contract.
2. Click any field row to open a detail drawer
Rows are now cursor-pointer and trigger a side Sheet
(FieldDetailDrawer) showing the full normalised field spec — all
properties, options enumerated, references, formula, validation —
plus the same VS Code deep-link and a per-field Copy snippet that
emits just this field's literal. The drawer is read-only; users who
want to edit follow the VS Code link.
The previous behaviour (clicking a row did nothing) was the single
biggest dead-end during the persona walkthrough. The drawer is the
minimum viable acknowledgement that a field is an interactive object,
not a static row of text.
Plumbing changes
- ObjectSchemaInspector preserves every property of the field spec
(spread over the cherry-picked subset) so the drawer has access to
schema properties beyond the table columns.
- Added a ChevronRight column on the right edge of every row,
group-hover translate-x for the same drill-in affordance used on
MetadataListPage compact rows.
- CopyButton stops propagation so the row click does not fire when
copying the field name.
Build / tests
pnpm --filter @objectstack/studio build — clean.
pnpm --filter @objectstack/studio test — 69/69 tests pass; same 2
pre-existing @object-ui/core/dist/evaluator/ExpressionEvaluator module
resolution failures in playground-plugins / plugin-system suites,
unrelated to this work.
Files
- apps/studio/src/components/FieldDetailDrawer.tsx (new, ~160 lines)
- apps/studio/src/components/AddFieldDialog.tsx (new, ~280 lines)
- apps/studio/src/components/ObjectSchemaInspector.tsx
· Imports FieldDetailDrawer, AddFieldDialog, Plus, ChevronRight
· State for selectedField + addOpen
· Preserves full field spec via spread in fieldEntries
· Toolbar: + Add field primary CTA
· TableRow: cursor-pointer, onClick → setSelectedField
· New chevron column on right; colSpan bumped to 7
· Drawer + dialog mounted at end of component
· CopyButton stops click propagation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
xuyushun441-sys added a commit that referenced this pull request May 31, 2026
…nector-rest (ADR-0018) (#1416)
Promote `connector_action` to a built-in baseline node — the generic-dispatch
counterpart to `http_request`: where http_request calls any raw URL,
connector_action invokes any registered connector's declared action.
- engine: connector registry (registerConnector / unregisterConnector /
resolveConnectorAction / getRegisteredConnectors) + ConnectorActionHandler /
ConnectorActionContext / RegisteredConnector types. registerConnector validates
via ConnectorSchema and asserts every declared action has a handler.
- builtin/connector-nodes.ts: connector_action executor (source:'builtin',
category:'io', all three paradigms), wired into installBuiltinNodes() — the core
plugin now seeds 11 baseline node types. Missing connector fails the step (not
flow registration) with a clear error.
- packages/connectors/connector-rest (@objectstack/connector-rest): the reference
concrete connector. createRestConnector + ConnectorRestPlugin, `request` action,
static auth (none/api-key/basic/bearer), no OAuth2 refresh (enterprise tier).
- New packages/connectors/ workspace category (alongside plugins/services/adapters).
- ADR-0018 §Addendum: records the decision, resolves Open-question #1, supersedes
M2's "connector_action dropped from baseline".
Tests: service-automation 87/87, connector-rest 10/10 (incl. end-to-end kernel boot:
both plugins -> connector_action flow -> REST handler).
Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
os-zhuang added a commit that referenced this pull request Jul 30, 2026
…-dist guard (#4156)
Three CI changes, all lessons #4065 taught the hard way. CI configuration only.
1. Nightly rerun-safety gate. Every job in this repo runs on a fresh clone,
which makes CI structurally incapable of seeing a suite that pollutes its own
working tree and therefore passes exactly once — CI always runs pass #1, so
it is always green. #4065 sat in the repo through every CI run it ever had
and surfaced only because somebody ran the full suite twice in one checkout
while doing unrelated work, where it looked like THEIR change had broken
something. The new job runs the full suite twice in one tree with `--force`
(turbo would otherwise replay the cache and report green without executing
anything) and fails if pass 2 disagrees with pass 1.
2. `timeout-minutes` on all eight ci.yml jobs. There were none, so every job
inherited GitHub's 6-hour default — and a job stuck that way reads as "still
running" rather than broken, the worst failure mode a gate can have.
3. A build-output guard against compiled test files. A package built with plain
`tsc` that does not exclude tests emits `dist/**/*.test.js`: `files: ["dist"]`
publishes them, and a package with no vitest config COLLECTS them alongside
its sources, so every `src/**/*.test.ts` also runs as a stale duplicate frozen
at the last build. That silently defeats edits. @objectstack/cli shipped
exactly that (81 test files / 849 tests where its sources hold 58 / 581) until
#4065. Everything else builds with tsup, so this gate exists to stop the NEXT
tsc-built package repeating it.
Follow-up to #4100. Related: #4154.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@huangyiirene