Skip to content

Commit fea3ef2

Browse files
os-zhuangclaude
andauthored
docs(examples): add embed-objectql — lean @objectstack/objectql/core usage (ADR-0076) (#2417)
* docs(examples): add embed-objectql — using the lean @objectstack/objectql/core entry (ADR-0076) A minimal, runnable example (the deferred ADR-0076 P4/P5) showing how a thin host embeds the ObjectQL engine as a plain library via `@objectstack/objectql/core` — no kernel, no ObjectQLPlugin, no @objectstack/metadata-protocol. The object is an ordinary `ObjectSchema.create({...})` (the same shape shipped to a full backend): one object model, two hosts, only the capability set differs. Doubles as a CI smoke (examples/* are in `turbo run test`): asserts CRUD round-trips via the lean entry and that the entry exposes ObjectQL but not ObjectQLPlugin / ObjectStackProtocolImplementation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(changeset): empty changeset for private embed-objectql example (no release) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent d710092 commit fea3ef2

7 files changed

Lines changed: 179 additions & 0 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
---
3+
4+
docs(examples): add `embed-objectql` lean-core usage example. Private package — no release impact.

examples/embed-objectql/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Embedding ObjectQL as a library (`@objectstack/objectql/core`)
2+
3+
Minimal example of using the **ObjectQL data engine on its own** — no kernel, no
4+
plugins, no metadata-management protocol. This is the path for a thin host (e.g.
5+
a gateway, an edge worker, a CLI tool) that wants the query/CRUD engine and the
6+
*same* object definitions as a full ObjectStack backend, without the platform.
7+
8+
## The point (ADR-0076)
9+
10+
Import from the **lean entry**:
11+
12+
```ts
13+
import { ObjectQL } from '@objectstack/objectql/core';
14+
```
15+
16+
`@objectstack/objectql/core` exposes the engine, registry, hooks, and validation
17+
only. It does **not** pull in `ObjectQLPlugin`, the kernel factory, or
18+
`@objectstack/metadata-protocol` (the 268KB metadata-management layer), so none
19+
of that lands in your bundle. (The batteries-included `@objectstack/objectql`
20+
entry still re-exports everything for full hosts.)
21+
22+
The object here is an ordinary `ObjectSchema.create({...})` — identical to what
23+
you would ship in a `*.object.ts` to a full backend. **One object model, two
24+
hosts; only the installed capability set differs.**
25+
26+
## Run
27+
28+
```bash
29+
pnpm --filter @objectstack/example-embed-objectql test # smoke
30+
```
31+
32+
See [`src/index.ts`](./src/index.ts) for the ~40-line embed.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "@objectstack/example-embed-objectql",
3+
"version": "0.0.0",
4+
"private": true,
5+
"description": "Embed the ObjectQL engine as a plain library via @objectstack/objectql/core — no kernel, no plugins, no metadata protocol (ADR-0076).",
6+
"type": "module",
7+
"scripts": {
8+
"demo": "vitest run",
9+
"test": "vitest run",
10+
"typecheck": "tsc --noEmit"
11+
},
12+
"dependencies": {
13+
"@objectstack/objectql": "workspace:*",
14+
"@objectstack/driver-memory": "workspace:*",
15+
"@objectstack/spec": "workspace:*"
16+
},
17+
"devDependencies": {
18+
"@types/node": "^26.0.0",
19+
"typescript": "^6.0.3",
20+
"vitest": "^4.1.9"
21+
}
22+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
//
3+
// Embedding the ObjectQL engine as a plain library (ADR-0076).
4+
//
5+
// This imports from `@objectstack/objectql/core` — the LEAN entry. It pulls the
6+
// data engine (query/CRUD/hooks/validation) only: NO kernel, NO ObjectQLPlugin,
7+
// and NOT `@objectstack/metadata-protocol` (the 268KB metadata-management layer).
8+
// Ideal for a thin, latency-sensitive host (e.g. a gateway) that wants the
9+
// engine and the *same* object definitions as the full platform, without the
10+
// platform itself.
11+
//
12+
// The object below is an ordinary `ObjectSchema.create({...})` — the exact same
13+
// shape you would ship in a `*.object.ts` to a full ObjectStack backend. One
14+
// object model, two hosts; only the installed capability set differs.
15+
16+
import { ObjectQL } from '@objectstack/objectql/core';
17+
import { InMemoryDriver } from '@objectstack/driver-memory';
18+
import { ObjectSchema, Field, type ServiceObject } from '@objectstack/spec/data';
19+
20+
export const Account = ObjectSchema.create({
21+
name: 'account',
22+
label: 'Account',
23+
pluralLabel: 'Accounts',
24+
fields: {
25+
name: Field.text({ label: 'Name', required: true }),
26+
industry: Field.text({ label: 'Industry' }),
27+
active: Field.boolean({ label: 'Active' }),
28+
},
29+
});
30+
31+
export interface AccountRow {
32+
id: string;
33+
name: string;
34+
industry?: string;
35+
active?: boolean;
36+
}
37+
38+
/** Boot a standalone engine, register one object, do CRUD, return active rows. */
39+
export async function runEmbeddedEngine(): Promise<AccountRow[]> {
40+
const engine = new ObjectQL();
41+
engine.registerDriver(new InMemoryDriver({ persistence: false }), true);
42+
await engine.init();
43+
44+
// Register the object directly — the registry lives in the core engine, so no
45+
// kernel/plugin/metadata-protocol is involved. (`ObjectSchema.create` is the
46+
// authoring shape; `registerObject` takes the canonical `ServiceObject`.)
47+
engine.registry.registerObject(Account as ServiceObject, 'example-embed');
48+
49+
await engine.insert('account', { name: 'Acme', industry: 'Manufacturing', active: true });
50+
await engine.insert('account', { name: 'Globex', industry: 'Energy', active: false });
51+
await engine.insert('account', { name: 'Initech', industry: 'Software', active: true });
52+
53+
return engine.find('account', {
54+
where: { active: true },
55+
orderBy: [{ field: 'name', order: 'asc' }],
56+
}) as Promise<AccountRow[]>;
57+
}
58+
59+
// Allow `node`/`tsx`-style direct execution to print the result.
60+
if (import.meta.url === `file://${process.argv[1]}`) {
61+
runEmbeddedEngine()
62+
.then((rows) => {
63+
// eslint-disable-next-line no-console
64+
console.log(`Active accounts (${rows.length}):`, rows.map((r) => r.name).join(', '));
65+
})
66+
.catch((err) => {
67+
// eslint-disable-next-line no-console
68+
console.error(err);
69+
process.exit(1);
70+
});
71+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import { describe, it, expect } from 'vitest';
4+
import { runEmbeddedEngine } from '../src/index.js';
5+
6+
describe('embed @objectstack/objectql/core (ADR-0076)', () => {
7+
it('runs the engine standalone and round-trips CRUD via the lean entry', async () => {
8+
const active = await runEmbeddedEngine();
9+
expect(active.map((r) => r.name)).toEqual(['Acme', 'Initech']);
10+
expect(active.every((r) => r.active === true)).toBe(true);
11+
});
12+
13+
it('the lean entry exposes the engine but not the kernel plugin / protocol', async () => {
14+
const core: Record<string, unknown> = await import('@objectstack/objectql/core');
15+
expect(typeof core.ObjectQL).toBe('function');
16+
expect(core.ObjectQLPlugin).toBeUndefined();
17+
expect(core.ObjectStackProtocolImplementation).toBeUndefined();
18+
});
19+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"include": ["src/**/*", "test/**/*"],
4+
"exclude": ["node_modules", "dist"],
5+
"compilerOptions": {
6+
"noEmit": true,
7+
"types": ["node"]
8+
}
9+
}

pnpm-lock.yaml

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)