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
35 changes: 35 additions & 0 deletions .changeset/18915-published-readme-examples-compile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
"@objectstack/cli": patch
"@objectstack/client": patch
"@objectstack/client-react": patch
"@objectstack/driver-memory": patch
"@objectstack/driver-mongodb": patch
"@objectstack/driver-turso": patch
"@objectstack/mcp": patch
"@objectstack/observability": patch
"@objectstack/plugin-auth": patch
"@objectstack/rest": patch
"@objectstack/runtime": patch
"@objectstack/service-cache": patch
"@objectstack/service-i18n": patch
"@objectstack/service-job": patch
"@objectstack/service-package": patch
"@objectstack/service-queue": patch
"@objectstack/service-realtime": patch
"@objectstack/service-storage": patch
"@objectstack/spec": patch
"@objectstack/types": patch
---

The TypeScript examples in these packages' **published** `README.md` now compile against the package they document — 43 of the 44 blocks the `measure-markdown-ts-blocks` census reported as syntactically valid and wrong, in documents that ship inside the npm tarball.

`README.md` is listed in every one of these packages' `files[]`, so these bytes are the artefact a consumer — or a consumer's AI — reads and copies. What the census counted was not style: the examples named options the packages no longer accept, chained a method that returns a promise, and implemented interfaces they never imported.

The corrections, by class:

- **Legacy option vocabulary.** `@objectstack/client-react`'s hooks take `fields` / `orderBy` / `limit` / `where`, not `select` / `sort` / `top` / `filters`, and `PaginatedResult` carries `records`, not `value`. `@objectstack/service-job` takes `timeoutMs`, `@objectstack/service-queue` takes `maxAttempts`, and `IDataEngine.find` takes `where`.
- **Async registration used synchronously.** `ObjectKernel.use()` returns `Promise<this>`, so `kernel.use(a).use(b)` does not chain; the examples now `await` each registration. `ObjectKernelConfig` has no `plugins` member.
- **Interfaces implemented but never imported.** Several plugin examples wrote `implements Plugin` with no import, which bound to the DOM's `Plugin`; they now import `Plugin` / `PluginContext` and declare the required `init`. `PluginContext.getService<T>()` has no default type argument, so the examples that read a service now name its contract.
- **Removed or never-existing API.** `@objectstack/driver-memory`'s default export is a legacy `onEnable` object that `kernel.use()` refuses — the quick start now registers through `DriverPlugin`; its persistence adapters take an options bag under `persistence.adapter`. `defineStack` has no `driver` key. `@objectstack/rest`'s `RestServer` takes the host `IHttpServer` first and `registerRoutes()` takes no arguments; `RouteManager` is constructed on a server. `@objectstack/spec`'s `ObjectSchema.parse()` returns the value — the `{ success, data }` envelope is `safeParse`'s. `useMutation` has no `onMutate` / mutation context.

No runtime code changed and no gate was added (#18715 ruling F). One block is deliberately left: `@objectstack/knowledge-ragflow`'s README writes `source.options.datasetId`, which is what the shipped adapter reads and what `KnowledgeSourceSchema` does not declare — correcting the document either way would contradict one of the two, so the conflict is reported rather than papered over.
4 changes: 2 additions & 2 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ The CLI looks for `objectstack.config.ts` (or `.js`, `.mjs`) in the current dire

```typescript
import { defineStack } from '@objectstack/spec';
import * as objects from './src/objects';
import { project, task } from './src/objects';

export default defineStack({
manifest: {
Expand All @@ -141,7 +141,7 @@ export default defineStack({
type: 'app',
name: 'My App',
},
objects: Object.values(objects),
objects: [project, task],
});
```

Expand Down
104 changes: 58 additions & 46 deletions packages/client-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,17 @@ import { useQuery } from '@objectstack/client-react';

function TaskList() {
const { data, isLoading, error, refetch } = useQuery('todo_task', {
select: ['id', 'subject', 'priority'],
sort: ['-created_at'],
top: 20
fields: ['id', 'subject', 'priority'],
orderBy: ['-created_at'],
limit: 20
});

if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;

return (
<div>
{data?.value.map(task => (
{data?.records.map(task => (
<div key={task.id}>{task.subject}</div>
))}
<button onClick={refetch}>Refresh</button>
Expand All @@ -73,6 +73,7 @@ function TaskList() {
#### Mutate Data

```tsx
import type { FormEvent } from 'react';
import { useMutation } from '@objectstack/client-react';

function CreateTaskForm() {
Expand All @@ -82,7 +83,7 @@ function CreateTaskForm() {
}
});

const handleSubmit = (e) => {
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
mutate({
subject: 'New Task',
Expand Down Expand Up @@ -119,12 +120,12 @@ function PaginatedTaskList() {
hasPreviousPage
} = usePagination('todo_task', {
pageSize: 10,
sort: ['-created_at']
orderBy: ['-created_at']
});

return (
<div>
{data?.value.map(task => (
{data?.records.map(task => (
<div key={task.id}>{task.subject}</div>
))}
<div className="pagination">
Expand Down Expand Up @@ -155,7 +156,7 @@ function InfiniteTaskList() {
isFetchingNextPage
} = useInfiniteQuery('todo_task', {
pageSize: 20,
sort: ['-created_at']
orderBy: ['-created_at']
});

return (
Expand All @@ -180,7 +181,7 @@ function InfiniteTaskList() {
```tsx
import { useObject } from '@objectstack/client-react';

function ObjectSchemaViewer({ objectName }) {
function ObjectSchemaViewer({ objectName }: { objectName: string }) {
const { data: schema, isLoading } = useObject(objectName);

if (isLoading) return <div>Loading schema...</div>;
Expand All @@ -199,7 +200,7 @@ function ObjectSchemaViewer({ objectName }) {
```tsx
import { useView } from '@objectstack/client-react';

function ViewConfiguration({ objectName }) {
function ViewConfiguration({ objectName }: { objectName: string }) {
const { data: view, isLoading } = useView(objectName, 'list');

if (isLoading) return <div>Loading view...</div>;
Expand All @@ -218,7 +219,7 @@ function ViewConfiguration({ objectName }) {
```tsx
import { useFields } from '@objectstack/client-react';

function FieldList({ objectName }) {
function FieldList({ objectName }: { objectName: string }) {
const { data: fields, isLoading } = useFields(objectName);

if (isLoading) return <div>Loading fields...</div>;
Expand Down Expand Up @@ -269,7 +270,7 @@ interface Task {
}

const { data } = useQuery<Task>('todo_task');
// data.value is typed as Task[]
// data.records is typed as Task[]

const { mutate } = useMutation<Task, Partial<Task>>('todo_task', 'create');
// mutate expects Partial<Task>
Expand All @@ -280,23 +281,26 @@ const { mutate } = useMutation<Task, Partial<Task>>('todo_task', 'create');
### Master-Detail View

```tsx
import { useState } from 'react';
import { useQuery } from '@objectstack/client-react';

function TaskList() {
const [selectedId, setSelectedId] = useState<string | null>(null);

const { data: tasks } = useQuery('todo_task', {
select: ['id', 'subject'],
sort: ['-created_at']
fields: ['id', 'subject'],
orderBy: ['-created_at']
});

const { data: selectedTask } = useQuery('todo_task', {
filters: ['id', '=', selectedId],
where: { id: selectedId },
enabled: !!selectedId // Only fetch when ID is selected
});

return (
<div className="flex">
<TaskListPanel tasks={tasks?.value} onSelect={setSelectedId} />
<TaskDetail task={selectedTask?.value?.[0]} />
<TaskListPanel tasks={tasks?.records} onSelect={setSelectedId} />
<TaskDetail task={selectedTask?.records?.[0]} />
</div>
);
}
Expand All @@ -305,26 +309,31 @@ function TaskList() {
### Optimistic Updates

```tsx
function TaskToggle({ taskId, completed }) {
import { useState } from 'react';
import { useMutation } from '@objectstack/client-react';

function TaskToggle({ taskId, completed }: { taskId: string; completed: boolean }) {
// `useMutation` has no mutation-context hook, so the optimistic value is held
// locally and rolled back from `onError`.
const [checked, setChecked] = useState(completed);

const { mutate } = useMutation('todo_task', 'update', {
onMutate: async (variables) => {
// Optimistically update UI
return { previousValue: completed };
},
onError: (error, variables, context) => {
// Revert on error
console.error('Update failed, reverting', context.previousValue);
},
onSuccess: () => {
// Refetch to ensure data consistency
queryClient.invalidateQueries(['todo_task']);
onError: (error: Error) => {
console.error('Update failed, reverting', error);
setChecked(completed);
}
});

return (
<Checkbox
checked={completed}
onChange={(e) => mutate({ id: taskId, is_completed: e.target.checked })}
<input
type="checkbox"
checked={checked}
onChange={(e) => {
const next = e.target.checked;
setChecked(next);
// The `update` operation takes `{ id, data }`.
mutate({ id: taskId, data: { is_completed: next } });
}}
/>
);
}
Expand All @@ -333,22 +342,24 @@ function TaskToggle({ taskId, completed }) {
### Dependent Queries

```tsx
function ProjectTasks({ projectId }) {
import { useQuery } from '@objectstack/client-react';

function ProjectTasks({ projectId }: { projectId: string }) {
// First, get project details
const { data: project } = useQuery('project', {
filters: ['id', '=', projectId]
where: { id: projectId }
});

// Then, get tasks for this project
const { data: tasks } = useQuery('todo_task', {
filters: ['project_id', '=', projectId],
where: { project_id: projectId },
enabled: !!project // Only fetch when project is loaded
});

return (
<div>
<h2>{project?.value?.[0]?.name}</h2>
<TaskList tasks={tasks?.value} />
<h2>{project?.records?.[0]?.name}</h2>
<TaskList tasks={tasks?.records} />
</div>
);
}
Expand All @@ -357,14 +368,15 @@ function ProjectTasks({ projectId }) {
### Search with Debounce

```tsx
import { useDeferredValue } from 'react';
import { useDeferredValue, useState } from 'react';
import { useQuery } from '@objectstack/client-react';

function TaskSearch() {
const [searchTerm, setSearchTerm] = useState('');
const deferredSearch = useDeferredValue(searchTerm);

const { data, isLoading } = useQuery('todo_task', {
filters: ['subject', 'contains', deferredSearch],
where: { subject: { $contains: deferredSearch } },
enabled: deferredSearch.length >= 3 // Only search with 3+ chars
});

Expand All @@ -377,7 +389,7 @@ function TaskSearch() {
placeholder="Search tasks..."
/>
{isLoading && <Spinner />}
<TaskList tasks={data?.value} />
<TaskList tasks={data?.records} />
</div>
);
}
Expand Down
12 changes: 8 additions & 4 deletions packages/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ async function main() {
// 2. Connect (Fetches system capabilities)
await client.connect();

// 3. Metadata Access
const todoSchema = await client.meta.getItem('object', 'todo_task');
// 3. Metadata Access — the document is carried under `item`
const { item } = await client.meta.getItem('object', 'todo_task');
const todoSchema = item as { fields: Record<string, unknown> };
console.log('Fields:', todoSchema.fields);

// Save Metadata (New Feature)
Expand Down Expand Up @@ -143,9 +144,12 @@ Batch operations support the following options:
The client provides standardized error handling with machine-readable error codes:

```typescript
import type { StandardError } from '@objectstack/client';

try {
await client.data.create('todo_task', { subject: '' });
} catch (error) {
} catch (caught) {
const error = caught as Error & Partial<StandardError>;
console.error('Error code:', error.code); // e.g., 'validation_error'
console.error('Category:', error.category); // e.g., 'validation'
console.error('HTTP status:', error.httpStatus); // e.g., 400
Expand Down Expand Up @@ -288,7 +292,7 @@ const cubes = await client.analytics.meta('sales');
console.log(cubes[0].name);

// Automation
const run = await client.automation.trigger('send_welcome_email', { userId });
const run = await client.automation.trigger('send_welcome_email', { userId: 'usr_123' });
console.log(run.status);

// File Storage
Expand Down
11 changes: 6 additions & 5 deletions packages/drivers/driver-memory/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ pnpm add @objectstack/driver-memory

```typescript
import { ObjectKernel } from '@objectstack/core';
import memoryPlugin from '@objectstack/driver-memory';
import { DriverPlugin } from '@objectstack/runtime';
import { InMemoryDriver } from '@objectstack/driver-memory';

const kernel = new ObjectKernel();
kernel.use(memoryPlugin); // default plugin
await kernel.use(new DriverPlugin(new InMemoryDriver(), 'memory'));
await kernel.bootstrap();
```

Expand All @@ -41,7 +42,7 @@ await driver.connect();
import { InMemoryDriver, FileSystemPersistenceAdapter } from '@objectstack/driver-memory';

const driver = new InMemoryDriver({
persistence: new FileSystemPersistenceAdapter('./data/snapshot.json'),
persistence: { adapter: new FileSystemPersistenceAdapter({ path: './data/snapshot.json' }) },
});
await driver.connect();
```
Expand All @@ -52,15 +53,15 @@ await driver.connect();
import { InMemoryDriver, LocalStoragePersistenceAdapter } from '@objectstack/driver-memory';

const driver = new InMemoryDriver({
persistence: new LocalStoragePersistenceAdapter('objectstack:dev'),
persistence: { adapter: new LocalStoragePersistenceAdapter({ key: 'objectstack:dev' }) },
});
```

## Key Exports

| Export | Kind | Description |
|:---|:---|:---|
| `default` | kernel plugin | Drop-in plugin. |
| `default` | legacy plugin object | Legacy `onEnable` shape — not a kernel `Plugin`; register through `DriverPlugin`. |
| `InMemoryDriver` | class | Driver instance for direct use. |
| `InMemoryStrategy` | class | Query execution strategy used by ObjectQL. |
| `FileSystemPersistenceAdapter` | class | Node-only persistence. |
Expand Down
17 changes: 10 additions & 7 deletions packages/drivers/driver-mongodb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,19 @@ explicit override (recognised values: `mongodb`, `mongo`).

```typescript
import { defineStack } from '@objectstack/spec';
import { DriverPlugin } from '@objectstack/runtime';
import { MongoDBDriver } from '@objectstack/driver-mongodb';

export default defineStack({
driver: new MongoDBDriver({
url: 'mongodb://localhost:27017/myapp',
database: 'myapp', // Optional: overrides URI database
maxPoolSize: 10, // Optional: connection pool size (default: 10)
minPoolSize: 1, // Optional: minimum pool (default: 1)
connectTimeoutMS: 10000, // Optional: connection timeout
}),
plugins: [
new DriverPlugin(new MongoDBDriver({
url: 'mongodb://localhost:27017/myapp',
database: 'myapp', // Optional: overrides URI database
maxPoolSize: 10, // Optional: connection pool size (default: 10)
minPoolSize: 1, // Optional: minimum pool (default: 1)
connectTimeoutMS: 10000, // Optional: connection timeout
}), 'mongodb'),
],
});
```

Expand Down
Loading
Loading