diff --git a/.changeset/embed-objectql-example.md b/.changeset/embed-objectql-example.md new file mode 100644 index 0000000000..f631da433e --- /dev/null +++ b/.changeset/embed-objectql-example.md @@ -0,0 +1,4 @@ +--- +--- + +docs(examples): add `embed-objectql` lean-core usage example. Private package — no release impact. diff --git a/examples/embed-objectql/README.md b/examples/embed-objectql/README.md new file mode 100644 index 0000000000..fe9602b1a1 --- /dev/null +++ b/examples/embed-objectql/README.md @@ -0,0 +1,32 @@ +# Embedding ObjectQL as a library (`@objectstack/objectql/core`) + +Minimal example of using the **ObjectQL data engine on its own** — no kernel, no +plugins, no metadata-management protocol. This is the path for a thin host (e.g. +a gateway, an edge worker, a CLI tool) that wants the query/CRUD engine and the +*same* object definitions as a full ObjectStack backend, without the platform. + +## The point (ADR-0076) + +Import from the **lean entry**: + +```ts +import { ObjectQL } from '@objectstack/objectql/core'; +``` + +`@objectstack/objectql/core` exposes the engine, registry, hooks, and validation +only. It does **not** pull in `ObjectQLPlugin`, the kernel factory, or +`@objectstack/metadata-protocol` (the 268KB metadata-management layer), so none +of that lands in your bundle. (The batteries-included `@objectstack/objectql` +entry still re-exports everything for full hosts.) + +The object here is an ordinary `ObjectSchema.create({...})` — identical to what +you would ship in a `*.object.ts` to a full backend. **One object model, two +hosts; only the installed capability set differs.** + +## Run + +```bash +pnpm --filter @objectstack/example-embed-objectql test # smoke +``` + +See [`src/index.ts`](./src/index.ts) for the ~40-line embed. diff --git a/examples/embed-objectql/package.json b/examples/embed-objectql/package.json new file mode 100644 index 0000000000..f0c3bdf48c --- /dev/null +++ b/examples/embed-objectql/package.json @@ -0,0 +1,22 @@ +{ + "name": "@objectstack/example-embed-objectql", + "version": "0.0.0", + "private": true, + "description": "Embed the ObjectQL engine as a plain library via @objectstack/objectql/core — no kernel, no plugins, no metadata protocol (ADR-0076).", + "type": "module", + "scripts": { + "demo": "vitest run", + "test": "vitest run", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@objectstack/objectql": "workspace:*", + "@objectstack/driver-memory": "workspace:*", + "@objectstack/spec": "workspace:*" + }, + "devDependencies": { + "@types/node": "^26.0.0", + "typescript": "^6.0.3", + "vitest": "^4.1.9" + } +} diff --git a/examples/embed-objectql/src/index.ts b/examples/embed-objectql/src/index.ts new file mode 100644 index 0000000000..9cb8530f3e --- /dev/null +++ b/examples/embed-objectql/src/index.ts @@ -0,0 +1,71 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. +// +// Embedding the ObjectQL engine as a plain library (ADR-0076). +// +// This imports from `@objectstack/objectql/core` — the LEAN entry. It pulls the +// data engine (query/CRUD/hooks/validation) only: NO kernel, NO ObjectQLPlugin, +// and NOT `@objectstack/metadata-protocol` (the 268KB metadata-management layer). +// Ideal for a thin, latency-sensitive host (e.g. a gateway) that wants the +// engine and the *same* object definitions as the full platform, without the +// platform itself. +// +// The object below is an ordinary `ObjectSchema.create({...})` — the exact same +// shape you would ship in a `*.object.ts` to a full ObjectStack backend. One +// object model, two hosts; only the installed capability set differs. + +import { ObjectQL } from '@objectstack/objectql/core'; +import { InMemoryDriver } from '@objectstack/driver-memory'; +import { ObjectSchema, Field, type ServiceObject } from '@objectstack/spec/data'; + +export const Account = ObjectSchema.create({ + name: 'account', + label: 'Account', + pluralLabel: 'Accounts', + fields: { + name: Field.text({ label: 'Name', required: true }), + industry: Field.text({ label: 'Industry' }), + active: Field.boolean({ label: 'Active' }), + }, +}); + +export interface AccountRow { + id: string; + name: string; + industry?: string; + active?: boolean; +} + +/** Boot a standalone engine, register one object, do CRUD, return active rows. */ +export async function runEmbeddedEngine(): Promise { + const engine = new ObjectQL(); + engine.registerDriver(new InMemoryDriver({ persistence: false }), true); + await engine.init(); + + // Register the object directly — the registry lives in the core engine, so no + // kernel/plugin/metadata-protocol is involved. (`ObjectSchema.create` is the + // authoring shape; `registerObject` takes the canonical `ServiceObject`.) + engine.registry.registerObject(Account as ServiceObject, 'example-embed'); + + await engine.insert('account', { name: 'Acme', industry: 'Manufacturing', active: true }); + await engine.insert('account', { name: 'Globex', industry: 'Energy', active: false }); + await engine.insert('account', { name: 'Initech', industry: 'Software', active: true }); + + return engine.find('account', { + where: { active: true }, + orderBy: [{ field: 'name', order: 'asc' }], + }) as Promise; +} + +// Allow `node`/`tsx`-style direct execution to print the result. +if (import.meta.url === `file://${process.argv[1]}`) { + runEmbeddedEngine() + .then((rows) => { + // eslint-disable-next-line no-console + console.log(`Active accounts (${rows.length}):`, rows.map((r) => r.name).join(', ')); + }) + .catch((err) => { + // eslint-disable-next-line no-console + console.error(err); + process.exit(1); + }); +} diff --git a/examples/embed-objectql/test/embed.test.ts b/examples/embed-objectql/test/embed.test.ts new file mode 100644 index 0000000000..2b9e117c70 --- /dev/null +++ b/examples/embed-objectql/test/embed.test.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { describe, it, expect } from 'vitest'; +import { runEmbeddedEngine } from '../src/index.js'; + +describe('embed @objectstack/objectql/core (ADR-0076)', () => { + it('runs the engine standalone and round-trips CRUD via the lean entry', async () => { + const active = await runEmbeddedEngine(); + expect(active.map((r) => r.name)).toEqual(['Acme', 'Initech']); + expect(active.every((r) => r.active === true)).toBe(true); + }); + + it('the lean entry exposes the engine but not the kernel plugin / protocol', async () => { + const core: Record = await import('@objectstack/objectql/core'); + expect(typeof core.ObjectQL).toBe('function'); + expect(core.ObjectQLPlugin).toBeUndefined(); + expect(core.ObjectStackProtocolImplementation).toBeUndefined(); + }); +}); diff --git a/examples/embed-objectql/tsconfig.json b/examples/embed-objectql/tsconfig.json new file mode 100644 index 0000000000..f9a5e26624 --- /dev/null +++ b/examples/embed-objectql/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*", "test/**/*"], + "exclude": ["node_modules", "dist"], + "compilerOptions": { + "noEmit": true, + "types": ["node"] + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14be2822bd..c8dc729e28 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -209,6 +209,28 @@ importers: specifier: ^4.1.9 version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/coverage-v8@4.1.9)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.0.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) + examples/embed-objectql: + dependencies: + '@objectstack/driver-memory': + specifier: workspace:* + version: link:../../packages/plugins/driver-memory + '@objectstack/objectql': + specifier: workspace:* + version: link:../../packages/objectql + '@objectstack/spec': + specifier: workspace:* + version: link:../../packages/spec + devDependencies: + '@types/node': + specifier: ^26.0.0 + version: 26.0.0 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + vitest: + specifier: ^4.1.9 + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/coverage-v8@4.1.9)(happy-dom@20.10.2)(msw@2.14.6(@types/node@26.0.0)(typescript@6.0.3))(vite@8.0.16(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) + packages/adapters/hono: dependencies: '@objectstack/plugin-hono-server':