Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .changeset/embed-objectql-example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

docs(examples): add `embed-objectql` lean-core usage example. Private package — no release impact.
32 changes: 32 additions & 0 deletions examples/embed-objectql/README.md
Original file line number Diff line number Diff line change
@@ -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.
22 changes: 22 additions & 0 deletions examples/embed-objectql/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
71 changes: 71 additions & 0 deletions examples/embed-objectql/src/index.ts
Original file line number Diff line number Diff line change
@@ -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<AccountRow[]> {
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<AccountRow[]>;
}

// 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);
});
}
19 changes: 19 additions & 0 deletions examples/embed-objectql/test/embed.test.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown> = await import('@objectstack/objectql/core');
expect(typeof core.ObjectQL).toBe('function');
expect(core.ObjectQLPlugin).toBeUndefined();
expect(core.ObjectStackProtocolImplementation).toBeUndefined();
});
});
9 changes: 9 additions & 0 deletions examples/embed-objectql/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"include": ["src/**/*", "test/**/*"],
"exclude": ["node_modules", "dist"],
"compilerOptions": {
"noEmit": true,
"types": ["node"]
}
}
22 changes: 22 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.