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
106 changes: 95 additions & 11 deletions packages/data-objectstack/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,6 +23,9 @@ npm install @object-ui/data-objectstack
```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
import { SchemaRenderer } from '@object-ui/react';
import type { ComponentSchema } from '@object-ui/types';

declare const mySchema: ComponentSchema;

// 1. Create the adapter
const dataSource = createObjectStackAdapter({
Expand All@@ -41,9 +44,21 @@ function App() {
}
```

> **Reaching the adapter-only API from TypeScript.** `createObjectStackAdapter`
> declares `DataSource` as its return type, so the members below that belong to the
> adapter rather than to every data source — `getClient`, the cache methods, the
> connection-state and batch-progress subscriptions — are not on the type the factory
> hands back, even though they are on the object it hands back. Until
> [#7323](https://github.com/objectstack-ai/objectui/issues/7323) is settled, hold the
> adapter as `ObjectStackAdapter` (the exported class, whose constructor is documented
> under **API Reference** below) wherever you use those members; the examples in this
> README do exactly that.

### Advanced Configuration

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

const dataSource = createObjectStackAdapter({
baseUrl: 'https://api.example.com',
token: 'your-api-token',
Expand DownExpand Up@@ -78,6 +93,10 @@ ObjectStack's native query format, so a schema never has to be written in the
protocol's own shape:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Query with filters (MongoDB-like operators)
const result = await dataSource.find('tasks', {
$filter: {
Expand All@@ -92,7 +111,7 @@ const result = await dataSource.find('tasks', {
// Escape hatch: reach the underlying ObjectStack client for anything
// the DataSource interface does not cover
const client = dataSource.getClient();
const metadata = await client.meta.getObject('task');
const metadata = await client.meta.getItem('object', 'task');
```

### Query Parameter Mapping
Expand DownExpand Up@@ -186,6 +205,10 @@ array that the spec's own `isFilterAST` gate rejects is refused here rather
than shipped:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// ✅ lowered — reaches the wire unchanged
await dataSource.aggregate('opportunity', {
groupBy: ['stage'],
Expand DownExpand Up@@ -215,6 +238,10 @@ declares), and an empty array (`[]` means "no filter").
### Sorting

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// OData-style
await dataSource.find('users', {
$orderby: {
Expand All@@ -231,6 +258,10 @@ await dataSource.find('users', {
The adapter includes built-in metadata caching to improve performance when fetching schemas:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Get cache statistics
const stats = dataSource.getCacheStats();
console.log(`Cache hit rate: ${stats.hitRate * 100}%`);
Expand All@@ -257,6 +288,10 @@ dataSource.clearCache();
The adapter provides real-time connection state monitoring with automatic reconnection:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Monitor connection state changes
const unsubscribe = dataSource.onConnectionStateChange((event) => {
console.log('Connection state:', event.state);
Expand DownExpand Up@@ -301,6 +336,12 @@ The adapter automatically attempts to reconnect on connection failures:
Track progress of bulk operations in real-time:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

declare const largeDataset: Array<Record<string, unknown>>;

// Monitor batch operation progress
const unsubscribe = dataSource.onBatchProgress((event) => {
console.log(`${event.operation}: ${event.percentage.toFixed(1)}%`);
Expand DownExpand Up@@ -358,24 +399,36 @@ import {
### Error Handling Example

```typescript
import {
ObjectStackError,
MetadataNotFoundError,
ConnectionError,
AuthenticationError,
type ObjectStackAdapter,
} from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

try {
const schema = await dataSource.getObjectSchema('users');
} catch (error) {
if (error instanceof MetadataNotFoundError) {
console.error(`Schema not found: ${error.details.objectName}`);
console.error(`Schema not found: ${error.details?.objectName}`);
} else if (error instanceof ConnectionError) {
console.error(`Connection failed to: ${error.url}`);
} else if (error instanceof AuthenticationError) {
console.error('Authentication required');
}

// All errors have consistent structure
console.error({
code: error.code,
message: error.message,
statusCode: error.statusCode,
details: error.details
});

// Every error this adapter throws carries the same shape
if (error instanceof ObjectStackError) {
console.error({
code: error.code,
message: error.message,
statusCode: error.statusCode,
details: error.details,
});
}
}
```

Expand All@@ -384,6 +437,12 @@ try {
Bulk operations provide detailed error reporting with partial success information:

```typescript
import { BulkOperationError, type ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

declare const records: Array<Record<string, unknown>>;

try {
await dataSource.bulk('users', 'update', records);
} catch (error) {
Expand DownExpand Up@@ -420,6 +479,10 @@ All errors include unique error codes for programmatic handling:
The adapter supports optimized batch operations with automatic fallback:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Batch create
const newUsers = await dataSource.bulk('users', 'create', [
{ name: 'Alice', email: 'alice@example.com' },
Expand DownExpand Up@@ -453,6 +516,10 @@ writes as a single all-or-nothing unit — the master-detail case, where a paren
and its children must commit or roll back together — use `batchTransaction`:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Create a parent and a child that references it, atomically.
// `{ $ref: 0 }` resolves to the id minted by operation 0 (the parent).
await dataSource.batchTransaction([
Expand DownExpand Up@@ -515,9 +582,12 @@ In addition to the main `DataSource` adapter, this package ships
persist per-user UI state (favorites, recent items) into ObjectStack.

```typescript
import { createObjectStackUserStateAdapter } from '@object-ui/data-objectstack';
import { createObjectStackUserStateAdapter, type ObjectStackAdapter } from '@object-ui/data-objectstack';
import { useAttachUserStateAdapters } from '@object-ui/app-shell';

declare const dataSource: ObjectStackAdapter;
declare const user: { id: string };

const favoritesAdapter = createObjectStackUserStateAdapter({
dataSource, // the ObjectStack DataSource
userId: user.id,
Expand All@@ -526,6 +596,8 @@ const favoritesAdapter = createObjectStackUserStateAdapter({
// onError: (op, err) => console.warn(`[user-state] ${op} failed`, err),
});

const attach = useAttachUserStateAdapters();

attach('favorites', favoritesAdapter);
```

Expand DownExpand Up@@ -560,6 +632,8 @@ for the full design.

#### Constructor

<!-- doc-snippet: fragment — the constructor SIGNATURE in prose notation, not a statement: `new ObjectStackAdapter(config: { ... })` names the parameter type where a call would carry a value. Measured TS1005x10. -->

```typescript
new ObjectStackAdapter(config: {
baseUrl: string;
Expand DownExpand Up@@ -613,6 +687,10 @@ new ObjectStackAdapter(config: {
#### Schema Not Found

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Error: MetadataNotFoundError
// Solution: Verify object name and ensure schema exists on server
const schema = await dataSource.getObjectSchema('correct_object_name');
Expand All@@ -621,6 +699,8 @@ const schema = await dataSource.getObjectSchema('correct_object_name');
#### Connection Errors

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

// Error: ConnectionError
// Solution: Check baseUrl and network connectivity
const dataSource = createObjectStackAdapter({
Expand All@@ -632,6 +712,10 @@ const dataSource = createObjectStackAdapter({
#### Cache Issues

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Clear cache if stale data is being returned
dataSource.clearCache();

Expand Down
14 changes: 14 additions & 0 deletions packages/plugin-form/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -294,6 +294,8 @@ field.
A field may declare both, and it is coherent authoring — storage-level required,
with the value guaranteed by the producer:

<!-- doc-snippet: fragment — one object-literal PROPERTY quoted out of a field map — `remind_at: Field.datetime(...)` is a key/value pair, not a statement. Measured TS1109x1. -->

```ts
remind_at: Field.datetime({ required: true, defaultValue: 'NOW()' }),
```
Expand All@@ -310,6 +312,8 @@ predicate says "required in this state", but `NOW()` / `current_user` resolve
at insert regardless of state, so the producer's guarantee covers the
conditional claim exactly as it covers the unconditional one:

<!-- doc-snippet: fragment — one object-literal PROPERTY quoted out of a field map — `remind_at: Field.datetime(...)` is a key/value pair, not a statement. Measured TS1109x1. -->

```ts
remind_at: Field.datetime({ requiredWhen: 'record.status == "scheduled"', defaultValue: 'NOW()' }),
```
Expand All@@ -331,6 +335,11 @@ The two halves of that verdict are importable, so a host with its own form
renderer applies the same rule rather than re-deriving it (objectui#6059):

```typescript
declare const field: { required?: unknown; defaultValue?: unknown };
declare const isCreateForm: boolean;
declare const values: Record<string, unknown>;
declare const objectSchema: { fields?: Record<string, { defaultValue?: unknown }> };

import { isRequiredInForm, omitServerResolvedDefaults } from '@object-ui/plugin-form';

// Should this create form enforce `required` on the field?
Expand DownExpand Up@@ -419,6 +428,8 @@ strands every section but the first outside the submit, and (in tabs) lets the
inactive panel unmount with its values — declare tabs on the single form and let
the renderer distribute the fields:

<!-- doc-snippet: fragment — a `fieldTabs` key list in prose notation, not a statement: the trailing `defaultFieldTab?:` / `fieldTabsPosition?:` entries mark OPTIONAL keys, which no object literal can spell. Measured TS1005x2 TS1109x3. -->

```typescript
{
type: 'form',
Expand DownExpand Up@@ -551,6 +562,8 @@ The same rule for side-by-side panels: the `<form>` wraps the whole panel group
and each pane holds only fields, so one react-hook-form instance spans the
divider.

<!-- doc-snippet: fragment — a `fieldPanes` key list in prose notation, not a statement: the trailing `fieldPanesOrientation?:` / `fieldPanesResizable?:` entries mark OPTIONAL keys, which no object literal can spell. Measured TS1005x2 TS1109x3. -->

```typescript
{
type: 'form',
Expand DownExpand Up@@ -853,6 +866,7 @@ and hands them to your `onSubmit`, which the renderer awaits
whatever that function does:

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
import type { FormSchema } from '@object-ui/types';

const dataSource = createObjectStackAdapter({
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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
106 changes: 95 additions & 11 deletions packages/data-objectstack/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,6 +23,9 @@ npm install @object-ui/data-objectstack
```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
import { SchemaRenderer } from '@object-ui/react';
import type { ComponentSchema } from '@object-ui/types';

declare const mySchema: ComponentSchema;

// 1. Create the adapter
const dataSource = createObjectStackAdapter({
Expand All@@ -41,9 +44,21 @@ function App() {
}
```

> **Reaching the adapter-only API from TypeScript.** `createObjectStackAdapter`
> declares `DataSource` as its return type, so the members below that belong to the
> adapter rather than to every data source — `getClient`, the cache methods, the
> connection-state and batch-progress subscriptions — are not on the type the factory
> hands back, even though they are on the object it hands back. Until
> [#7323](https://github.com/objectstack-ai/objectui/issues/7323) is settled, hold the
> adapter as `ObjectStackAdapter` (the exported class, whose constructor is documented
> under **API Reference** below) wherever you use those members; the examples in this
> README do exactly that.

### Advanced Configuration

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

const dataSource = createObjectStackAdapter({
baseUrl: 'https://api.example.com',
token: 'your-api-token',
Expand DownExpand Up@@ -78,6 +93,10 @@ ObjectStack's native query format, so a schema never has to be written in the
protocol's own shape:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Query with filters (MongoDB-like operators)
const result = await dataSource.find('tasks', {
$filter: {
Expand All@@ -92,7 +111,7 @@ const result = await dataSource.find('tasks', {
// Escape hatch: reach the underlying ObjectStack client for anything
// the DataSource interface does not cover
const client = dataSource.getClient();
const metadata = await client.meta.getObject('task');
const metadata = await client.meta.getItem('object', 'task');
```

### Query Parameter Mapping
Expand DownExpand Up@@ -186,6 +205,10 @@ array that the spec's own `isFilterAST` gate rejects is refused here rather
than shipped:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// ✅ lowered — reaches the wire unchanged
await dataSource.aggregate('opportunity', {
groupBy: ['stage'],
Expand DownExpand Up@@ -215,6 +238,10 @@ declares), and an empty array (`[]` means "no filter").
### Sorting

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// OData-style
await dataSource.find('users', {
$orderby: {
Expand All@@ -231,6 +258,10 @@ await dataSource.find('users', {
The adapter includes built-in metadata caching to improve performance when fetching schemas:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Get cache statistics
const stats = dataSource.getCacheStats();
console.log(`Cache hit rate: ${stats.hitRate * 100}%`);
Expand All@@ -257,6 +288,10 @@ dataSource.clearCache();
The adapter provides real-time connection state monitoring with automatic reconnection:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Monitor connection state changes
const unsubscribe = dataSource.onConnectionStateChange((event) => {
console.log('Connection state:', event.state);
Expand DownExpand Up@@ -301,6 +336,12 @@ The adapter automatically attempts to reconnect on connection failures:
Track progress of bulk operations in real-time:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

declare const largeDataset: Array<Record<string, unknown>>;

// Monitor batch operation progress
const unsubscribe = dataSource.onBatchProgress((event) => {
console.log(`${event.operation}: ${event.percentage.toFixed(1)}%`);
Expand DownExpand Up@@ -358,24 +399,36 @@ import {
### Error Handling Example

```typescript
import {
ObjectStackError,
MetadataNotFoundError,
ConnectionError,
AuthenticationError,
type ObjectStackAdapter,
} from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

try {
const schema = await dataSource.getObjectSchema('users');
} catch (error) {
if (error instanceof MetadataNotFoundError) {
console.error(`Schema not found: ${error.details.objectName}`);
console.error(`Schema not found: ${error.details?.objectName}`);
} else if (error instanceof ConnectionError) {
console.error(`Connection failed to: ${error.url}`);
} else if (error instanceof AuthenticationError) {
console.error('Authentication required');
}

// All errors have consistent structure
console.error({
code: error.code,
message: error.message,
statusCode: error.statusCode,
details: error.details
});

// Every error this adapter throws carries the same shape
if (error instanceof ObjectStackError) {
console.error({
code: error.code,
message: error.message,
statusCode: error.statusCode,
details: error.details,
});
}
}
```

Expand All@@ -384,6 +437,12 @@ try {
Bulk operations provide detailed error reporting with partial success information:

```typescript
import { BulkOperationError, type ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

declare const records: Array<Record<string, unknown>>;

try {
await dataSource.bulk('users', 'update', records);
} catch (error) {
Expand DownExpand Up@@ -420,6 +479,10 @@ All errors include unique error codes for programmatic handling:
The adapter supports optimized batch operations with automatic fallback:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Batch create
const newUsers = await dataSource.bulk('users', 'create', [
{ name: 'Alice', email: 'alice@example.com' },
Expand DownExpand Up@@ -453,6 +516,10 @@ writes as a single all-or-nothing unit — the master-detail case, where a paren
and its children must commit or roll back together — use `batchTransaction`:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Create a parent and a child that references it, atomically.
// `{ $ref: 0 }` resolves to the id minted by operation 0 (the parent).
await dataSource.batchTransaction([
Expand DownExpand Up@@ -515,9 +582,12 @@ In addition to the main `DataSource` adapter, this package ships
persist per-user UI state (favorites, recent items) into ObjectStack.

```typescript
import { createObjectStackUserStateAdapter } from '@object-ui/data-objectstack';
import { createObjectStackUserStateAdapter, type ObjectStackAdapter } from '@object-ui/data-objectstack';
import { useAttachUserStateAdapters } from '@object-ui/app-shell';

declare const dataSource: ObjectStackAdapter;
declare const user: { id: string };

const favoritesAdapter = createObjectStackUserStateAdapter({
dataSource, // the ObjectStack DataSource
userId: user.id,
Expand All@@ -526,6 +596,8 @@ const favoritesAdapter = createObjectStackUserStateAdapter({
// onError: (op, err) => console.warn(`[user-state] ${op} failed`, err),
});

const attach = useAttachUserStateAdapters();

attach('favorites', favoritesAdapter);
```

Expand DownExpand Up@@ -560,6 +632,8 @@ for the full design.

#### Constructor

<!-- doc-snippet: fragment — the constructor SIGNATURE in prose notation, not a statement: `new ObjectStackAdapter(config: { ... })` names the parameter type where a call would carry a value. Measured TS1005x10. -->

```typescript
new ObjectStackAdapter(config: {
baseUrl: string;
Expand DownExpand Up@@ -613,6 +687,10 @@ new ObjectStackAdapter(config: {
#### Schema Not Found

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Error: MetadataNotFoundError
// Solution: Verify object name and ensure schema exists on server
const schema = await dataSource.getObjectSchema('correct_object_name');
Expand All@@ -621,6 +699,8 @@ const schema = await dataSource.getObjectSchema('correct_object_name');
#### Connection Errors

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

// Error: ConnectionError
// Solution: Check baseUrl and network connectivity
const dataSource = createObjectStackAdapter({
Expand All@@ -632,6 +712,10 @@ const dataSource = createObjectStackAdapter({
#### Cache Issues

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Clear cache if stale data is being returned
dataSource.clearCache();

Expand Down
14 changes: 14 additions & 0 deletions packages/plugin-form/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -294,6 +294,8 @@ field.
A field may declare both, and it is coherent authoring — storage-level required,
with the value guaranteed by the producer:

<!-- doc-snippet: fragment — one object-literal PROPERTY quoted out of a field map — `remind_at: Field.datetime(...)` is a key/value pair, not a statement. Measured TS1109x1. -->

```ts
remind_at: Field.datetime({ required: true, defaultValue: 'NOW()' }),
```
Expand All@@ -310,6 +312,8 @@ predicate says "required in this state", but `NOW()` / `current_user` resolve
at insert regardless of state, so the producer's guarantee covers the
conditional claim exactly as it covers the unconditional one:

<!-- doc-snippet: fragment — one object-literal PROPERTY quoted out of a field map — `remind_at: Field.datetime(...)` is a key/value pair, not a statement. Measured TS1109x1. -->

```ts
remind_at: Field.datetime({ requiredWhen: 'record.status == "scheduled"', defaultValue: 'NOW()' }),
```
Expand All@@ -331,6 +335,11 @@ The two halves of that verdict are importable, so a host with its own form
renderer applies the same rule rather than re-deriving it (objectui#6059):

```typescript
declare const field: { required?: unknown; defaultValue?: unknown };
declare const isCreateForm: boolean;
declare const values: Record<string, unknown>;
declare const objectSchema: { fields?: Record<string, { defaultValue?: unknown }> };

import { isRequiredInForm, omitServerResolvedDefaults } from '@object-ui/plugin-form';

// Should this create form enforce `required` on the field?
Expand DownExpand Up@@ -419,6 +428,8 @@ strands every section but the first outside the submit, and (in tabs) lets the
inactive panel unmount with its values — declare tabs on the single form and let
the renderer distribute the fields:

<!-- doc-snippet: fragment — a `fieldTabs` key list in prose notation, not a statement: the trailing `defaultFieldTab?:` / `fieldTabsPosition?:` entries mark OPTIONAL keys, which no object literal can spell. Measured TS1005x2 TS1109x3. -->

```typescript
{
type: 'form',
Expand DownExpand Up@@ -551,6 +562,8 @@ The same rule for side-by-side panels: the `<form>` wraps the whole panel group
and each pane holds only fields, so one react-hook-form instance spans the
divider.

<!-- doc-snippet: fragment — a `fieldPanes` key list in prose notation, not a statement: the trailing `fieldPanesOrientation?:` / `fieldPanesResizable?:` entries mark OPTIONAL keys, which no object literal can spell. Measured TS1005x2 TS1109x3. -->

```typescript
{
type: 'form',
Expand DownExpand Up@@ -853,6 +866,7 @@ and hands them to your `onSubmit`, which the renderer awaits
whatever that function does:

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
import type { FormSchema } from '@object-ui/types';

const dataSource = createObjectStackAdapter({
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
106 changes: 95 additions & 11 deletions packages/data-objectstack/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,6 +23,9 @@ npm install @object-ui/data-objectstack
```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
import { SchemaRenderer } from '@object-ui/react';
import type { ComponentSchema } from '@object-ui/types';

declare const mySchema: ComponentSchema;

// 1. Create the adapter
const dataSource = createObjectStackAdapter({
Expand All@@ -41,9 +44,21 @@ function App() {
}
```

> **Reaching the adapter-only API from TypeScript.** `createObjectStackAdapter`
> declares `DataSource` as its return type, so the members below that belong to the
> adapter rather than to every data source — `getClient`, the cache methods, the
> connection-state and batch-progress subscriptions — are not on the type the factory
> hands back, even though they are on the object it hands back. Until
> [#7323](https://github.com/objectstack-ai/objectui/issues/7323) is settled, hold the
> adapter as `ObjectStackAdapter` (the exported class, whose constructor is documented
> under **API Reference** below) wherever you use those members; the examples in this
> README do exactly that.

### Advanced Configuration

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

const dataSource = createObjectStackAdapter({
baseUrl: 'https://api.example.com',
token: 'your-api-token',
Expand DownExpand Up@@ -78,6 +93,10 @@ ObjectStack's native query format, so a schema never has to be written in the
protocol's own shape:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Query with filters (MongoDB-like operators)
const result = await dataSource.find('tasks', {
$filter: {
Expand All@@ -92,7 +111,7 @@ const result = await dataSource.find('tasks', {
// Escape hatch: reach the underlying ObjectStack client for anything
// the DataSource interface does not cover
const client = dataSource.getClient();
const metadata = await client.meta.getObject('task');
const metadata = await client.meta.getItem('object', 'task');
```

### Query Parameter Mapping
Expand DownExpand Up@@ -186,6 +205,10 @@ array that the spec's own `isFilterAST` gate rejects is refused here rather
than shipped:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// ✅ lowered — reaches the wire unchanged
await dataSource.aggregate('opportunity', {
groupBy: ['stage'],
Expand DownExpand Up@@ -215,6 +238,10 @@ declares), and an empty array (`[]` means "no filter").
### Sorting

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// OData-style
await dataSource.find('users', {
$orderby: {
Expand All@@ -231,6 +258,10 @@ await dataSource.find('users', {
The adapter includes built-in metadata caching to improve performance when fetching schemas:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Get cache statistics
const stats = dataSource.getCacheStats();
console.log(`Cache hit rate: ${stats.hitRate * 100}%`);
Expand All@@ -257,6 +288,10 @@ dataSource.clearCache();
The adapter provides real-time connection state monitoring with automatic reconnection:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Monitor connection state changes
const unsubscribe = dataSource.onConnectionStateChange((event) => {
console.log('Connection state:', event.state);
Expand DownExpand Up@@ -301,6 +336,12 @@ The adapter automatically attempts to reconnect on connection failures:
Track progress of bulk operations in real-time:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

declare const largeDataset: Array<Record<string, unknown>>;

// Monitor batch operation progress
const unsubscribe = dataSource.onBatchProgress((event) => {
console.log(`${event.operation}: ${event.percentage.toFixed(1)}%`);
Expand DownExpand Up@@ -358,24 +399,36 @@ import {
### Error Handling Example

```typescript
import {
ObjectStackError,
MetadataNotFoundError,
ConnectionError,
AuthenticationError,
type ObjectStackAdapter,
} from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

try {
const schema = await dataSource.getObjectSchema('users');
} catch (error) {
if (error instanceof MetadataNotFoundError) {
console.error(`Schema not found: ${error.details.objectName}`);
console.error(`Schema not found: ${error.details?.objectName}`);
} else if (error instanceof ConnectionError) {
console.error(`Connection failed to: ${error.url}`);
} else if (error instanceof AuthenticationError) {
console.error('Authentication required');
}

// All errors have consistent structure
console.error({
code: error.code,
message: error.message,
statusCode: error.statusCode,
details: error.details
});

// Every error this adapter throws carries the same shape
if (error instanceof ObjectStackError) {
console.error({
code: error.code,
message: error.message,
statusCode: error.statusCode,
details: error.details,
});
}
}
```

Expand All@@ -384,6 +437,12 @@ try {
Bulk operations provide detailed error reporting with partial success information:

```typescript
import { BulkOperationError, type ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

declare const records: Array<Record<string, unknown>>;

try {
await dataSource.bulk('users', 'update', records);
} catch (error) {
Expand DownExpand Up@@ -420,6 +479,10 @@ All errors include unique error codes for programmatic handling:
The adapter supports optimized batch operations with automatic fallback:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Batch create
const newUsers = await dataSource.bulk('users', 'create', [
{ name: 'Alice', email: 'alice@example.com' },
Expand DownExpand Up@@ -453,6 +516,10 @@ writes as a single all-or-nothing unit — the master-detail case, where a paren
and its children must commit or roll back together — use `batchTransaction`:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Create a parent and a child that references it, atomically.
// `{ $ref: 0 }` resolves to the id minted by operation 0 (the parent).
await dataSource.batchTransaction([
Expand DownExpand Up@@ -515,9 +582,12 @@ In addition to the main `DataSource` adapter, this package ships
persist per-user UI state (favorites, recent items) into ObjectStack.

```typescript
import { createObjectStackUserStateAdapter } from '@object-ui/data-objectstack';
import { createObjectStackUserStateAdapter, type ObjectStackAdapter } from '@object-ui/data-objectstack';
import { useAttachUserStateAdapters } from '@object-ui/app-shell';

declare const dataSource: ObjectStackAdapter;
declare const user: { id: string };

const favoritesAdapter = createObjectStackUserStateAdapter({
dataSource, // the ObjectStack DataSource
userId: user.id,
Expand All@@ -526,6 +596,8 @@ const favoritesAdapter = createObjectStackUserStateAdapter({
// onError: (op, err) => console.warn(`[user-state] ${op} failed`, err),
});

const attach = useAttachUserStateAdapters();

attach('favorites', favoritesAdapter);
```

Expand DownExpand Up@@ -560,6 +632,8 @@ for the full design.

#### Constructor

<!-- doc-snippet: fragment — the constructor SIGNATURE in prose notation, not a statement: `new ObjectStackAdapter(config: { ... })` names the parameter type where a call would carry a value. Measured TS1005x10. -->

```typescript
new ObjectStackAdapter(config: {
baseUrl: string;
Expand DownExpand Up@@ -613,6 +687,10 @@ new ObjectStackAdapter(config: {
#### Schema Not Found

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Error: MetadataNotFoundError
// Solution: Verify object name and ensure schema exists on server
const schema = await dataSource.getObjectSchema('correct_object_name');
Expand All@@ -621,6 +699,8 @@ const schema = await dataSource.getObjectSchema('correct_object_name');
#### Connection Errors

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

// Error: ConnectionError
// Solution: Check baseUrl and network connectivity
const dataSource = createObjectStackAdapter({
Expand All@@ -632,6 +712,10 @@ const dataSource = createObjectStackAdapter({
#### Cache Issues

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Clear cache if stale data is being returned
dataSource.clearCache();

Expand Down
14 changes: 14 additions & 0 deletions packages/plugin-form/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -294,6 +294,8 @@ field.
A field may declare both, and it is coherent authoring — storage-level required,
with the value guaranteed by the producer:

<!-- doc-snippet: fragment — one object-literal PROPERTY quoted out of a field map — `remind_at: Field.datetime(...)` is a key/value pair, not a statement. Measured TS1109x1. -->

```ts
remind_at: Field.datetime({ required: true, defaultValue: 'NOW()' }),
```
Expand All@@ -310,6 +312,8 @@ predicate says "required in this state", but `NOW()` / `current_user` resolve
at insert regardless of state, so the producer's guarantee covers the
conditional claim exactly as it covers the unconditional one:

<!-- doc-snippet: fragment — one object-literal PROPERTY quoted out of a field map — `remind_at: Field.datetime(...)` is a key/value pair, not a statement. Measured TS1109x1. -->

```ts
remind_at: Field.datetime({ requiredWhen: 'record.status == "scheduled"', defaultValue: 'NOW()' }),
```
Expand All@@ -331,6 +335,11 @@ The two halves of that verdict are importable, so a host with its own form
renderer applies the same rule rather than re-deriving it (objectui#6059):

```typescript
declare const field: { required?: unknown; defaultValue?: unknown };
declare const isCreateForm: boolean;
declare const values: Record<string, unknown>;
declare const objectSchema: { fields?: Record<string, { defaultValue?: unknown }> };

import { isRequiredInForm, omitServerResolvedDefaults } from '@object-ui/plugin-form';

// Should this create form enforce `required` on the field?
Expand DownExpand Up@@ -419,6 +428,8 @@ strands every section but the first outside the submit, and (in tabs) lets the
inactive panel unmount with its values — declare tabs on the single form and let
the renderer distribute the fields:

<!-- doc-snippet: fragment — a `fieldTabs` key list in prose notation, not a statement: the trailing `defaultFieldTab?:` / `fieldTabsPosition?:` entries mark OPTIONAL keys, which no object literal can spell. Measured TS1005x2 TS1109x3. -->

```typescript
{
type: 'form',
Expand DownExpand Up@@ -551,6 +562,8 @@ The same rule for side-by-side panels: the `<form>` wraps the whole panel group
and each pane holds only fields, so one react-hook-form instance spans the
divider.

<!-- doc-snippet: fragment — a `fieldPanes` key list in prose notation, not a statement: the trailing `fieldPanesOrientation?:` / `fieldPanesResizable?:` entries mark OPTIONAL keys, which no object literal can spell. Measured TS1005x2 TS1109x3. -->

```typescript
{
type: 'form',
Expand DownExpand Up@@ -853,6 +866,7 @@ and hands them to your `onSubmit`, which the renderer awaits
whatever that function does:

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
import type { FormSchema } from '@object-ui/types';

const dataSource = createObjectStackAdapter({
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
106 changes: 95 additions & 11 deletions packages/data-objectstack/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,6 +23,9 @@ npm install @object-ui/data-objectstack
```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
import { SchemaRenderer } from '@object-ui/react';
import type { ComponentSchema } from '@object-ui/types';

declare const mySchema: ComponentSchema;

// 1. Create the adapter
const dataSource = createObjectStackAdapter({
Expand All@@ -41,9 +44,21 @@ function App() {
}
```

> **Reaching the adapter-only API from TypeScript.** `createObjectStackAdapter`
> declares `DataSource` as its return type, so the members below that belong to the
> adapter rather than to every data source — `getClient`, the cache methods, the
> connection-state and batch-progress subscriptions — are not on the type the factory
> hands back, even though they are on the object it hands back. Until
> [#7323](https://github.com/objectstack-ai/objectui/issues/7323) is settled, hold the
> adapter as `ObjectStackAdapter` (the exported class, whose constructor is documented
> under **API Reference** below) wherever you use those members; the examples in this
> README do exactly that.

### Advanced Configuration

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

const dataSource = createObjectStackAdapter({
baseUrl: 'https://api.example.com',
token: 'your-api-token',
Expand DownExpand Up@@ -78,6 +93,10 @@ ObjectStack's native query format, so a schema never has to be written in the
protocol's own shape:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Query with filters (MongoDB-like operators)
const result = await dataSource.find('tasks', {
$filter: {
Expand All@@ -92,7 +111,7 @@ const result = await dataSource.find('tasks', {
// Escape hatch: reach the underlying ObjectStack client for anything
// the DataSource interface does not cover
const client = dataSource.getClient();
const metadata = await client.meta.getObject('task');
const metadata = await client.meta.getItem('object', 'task');
```

### Query Parameter Mapping
Expand DownExpand Up@@ -186,6 +205,10 @@ array that the spec's own `isFilterAST` gate rejects is refused here rather
than shipped:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// ✅ lowered — reaches the wire unchanged
await dataSource.aggregate('opportunity', {
groupBy: ['stage'],
Expand DownExpand Up@@ -215,6 +238,10 @@ declares), and an empty array (`[]` means "no filter").
### Sorting

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// OData-style
await dataSource.find('users', {
$orderby: {
Expand All@@ -231,6 +258,10 @@ await dataSource.find('users', {
The adapter includes built-in metadata caching to improve performance when fetching schemas:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Get cache statistics
const stats = dataSource.getCacheStats();
console.log(`Cache hit rate: ${stats.hitRate * 100}%`);
Expand All@@ -257,6 +288,10 @@ dataSource.clearCache();
The adapter provides real-time connection state monitoring with automatic reconnection:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Monitor connection state changes
const unsubscribe = dataSource.onConnectionStateChange((event) => {
console.log('Connection state:', event.state);
Expand DownExpand Up@@ -301,6 +336,12 @@ The adapter automatically attempts to reconnect on connection failures:
Track progress of bulk operations in real-time:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

declare const largeDataset: Array<Record<string, unknown>>;

// Monitor batch operation progress
const unsubscribe = dataSource.onBatchProgress((event) => {
console.log(`${event.operation}: ${event.percentage.toFixed(1)}%`);
Expand DownExpand Up@@ -358,24 +399,36 @@ import {
### Error Handling Example

```typescript
import {
ObjectStackError,
MetadataNotFoundError,
ConnectionError,
AuthenticationError,
type ObjectStackAdapter,
} from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

try {
const schema = await dataSource.getObjectSchema('users');
} catch (error) {
if (error instanceof MetadataNotFoundError) {
console.error(`Schema not found: ${error.details.objectName}`);
console.error(`Schema not found: ${error.details?.objectName}`);
} else if (error instanceof ConnectionError) {
console.error(`Connection failed to: ${error.url}`);
} else if (error instanceof AuthenticationError) {
console.error('Authentication required');
}

// All errors have consistent structure
console.error({
code: error.code,
message: error.message,
statusCode: error.statusCode,
details: error.details
});

// Every error this adapter throws carries the same shape
if (error instanceof ObjectStackError) {
console.error({
code: error.code,
message: error.message,
statusCode: error.statusCode,
details: error.details,
});
}
}
```

Expand All@@ -384,6 +437,12 @@ try {
Bulk operations provide detailed error reporting with partial success information:

```typescript
import { BulkOperationError, type ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

declare const records: Array<Record<string, unknown>>;

try {
await dataSource.bulk('users', 'update', records);
} catch (error) {
Expand DownExpand Up@@ -420,6 +479,10 @@ All errors include unique error codes for programmatic handling:
The adapter supports optimized batch operations with automatic fallback:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Batch create
const newUsers = await dataSource.bulk('users', 'create', [
{ name: 'Alice', email: 'alice@example.com' },
Expand DownExpand Up@@ -453,6 +516,10 @@ writes as a single all-or-nothing unit — the master-detail case, where a paren
and its children must commit or roll back together — use `batchTransaction`:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Create a parent and a child that references it, atomically.
// `{ $ref: 0 }` resolves to the id minted by operation 0 (the parent).
await dataSource.batchTransaction([
Expand DownExpand Up@@ -515,9 +582,12 @@ In addition to the main `DataSource` adapter, this package ships
persist per-user UI state (favorites, recent items) into ObjectStack.

```typescript
import { createObjectStackUserStateAdapter } from '@object-ui/data-objectstack';
import { createObjectStackUserStateAdapter, type ObjectStackAdapter } from '@object-ui/data-objectstack';
import { useAttachUserStateAdapters } from '@object-ui/app-shell';

declare const dataSource: ObjectStackAdapter;
declare const user: { id: string };

const favoritesAdapter = createObjectStackUserStateAdapter({
dataSource, // the ObjectStack DataSource
userId: user.id,
Expand All@@ -526,6 +596,8 @@ const favoritesAdapter = createObjectStackUserStateAdapter({
// onError: (op, err) => console.warn(`[user-state] ${op} failed`, err),
});

const attach = useAttachUserStateAdapters();

attach('favorites', favoritesAdapter);
```

Expand DownExpand Up@@ -560,6 +632,8 @@ for the full design.

#### Constructor

<!-- doc-snippet: fragment — the constructor SIGNATURE in prose notation, not a statement: `new ObjectStackAdapter(config: { ... })` names the parameter type where a call would carry a value. Measured TS1005x10. -->

```typescript
new ObjectStackAdapter(config: {
baseUrl: string;
Expand DownExpand Up@@ -613,6 +687,10 @@ new ObjectStackAdapter(config: {
#### Schema Not Found

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Error: MetadataNotFoundError
// Solution: Verify object name and ensure schema exists on server
const schema = await dataSource.getObjectSchema('correct_object_name');
Expand All@@ -621,6 +699,8 @@ const schema = await dataSource.getObjectSchema('correct_object_name');
#### Connection Errors

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

// Error: ConnectionError
// Solution: Check baseUrl and network connectivity
const dataSource = createObjectStackAdapter({
Expand All@@ -632,6 +712,10 @@ const dataSource = createObjectStackAdapter({
#### Cache Issues

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Clear cache if stale data is being returned
dataSource.clearCache();

Expand Down
14 changes: 14 additions & 0 deletions packages/plugin-form/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -294,6 +294,8 @@ field.
A field may declare both, and it is coherent authoring — storage-level required,
with the value guaranteed by the producer:

<!-- doc-snippet: fragment — one object-literal PROPERTY quoted out of a field map — `remind_at: Field.datetime(...)` is a key/value pair, not a statement. Measured TS1109x1. -->

```ts
remind_at: Field.datetime({ required: true, defaultValue: 'NOW()' }),
```
Expand All@@ -310,6 +312,8 @@ predicate says "required in this state", but `NOW()` / `current_user` resolve
at insert regardless of state, so the producer's guarantee covers the
conditional claim exactly as it covers the unconditional one:

<!-- doc-snippet: fragment — one object-literal PROPERTY quoted out of a field map — `remind_at: Field.datetime(...)` is a key/value pair, not a statement. Measured TS1109x1. -->

```ts
remind_at: Field.datetime({ requiredWhen: 'record.status == "scheduled"', defaultValue: 'NOW()' }),
```
Expand All@@ -331,6 +335,11 @@ The two halves of that verdict are importable, so a host with its own form
renderer applies the same rule rather than re-deriving it (objectui#6059):

```typescript
declare const field: { required?: unknown; defaultValue?: unknown };
declare const isCreateForm: boolean;
declare const values: Record<string, unknown>;
declare const objectSchema: { fields?: Record<string, { defaultValue?: unknown }> };

import { isRequiredInForm, omitServerResolvedDefaults } from '@object-ui/plugin-form';

// Should this create form enforce `required` on the field?
Expand DownExpand Up@@ -419,6 +428,8 @@ strands every section but the first outside the submit, and (in tabs) lets the
inactive panel unmount with its values — declare tabs on the single form and let
the renderer distribute the fields:

<!-- doc-snippet: fragment — a `fieldTabs` key list in prose notation, not a statement: the trailing `defaultFieldTab?:` / `fieldTabsPosition?:` entries mark OPTIONAL keys, which no object literal can spell. Measured TS1005x2 TS1109x3. -->

```typescript
{
type: 'form',
Expand DownExpand Up@@ -551,6 +562,8 @@ The same rule for side-by-side panels: the `<form>` wraps the whole panel group
and each pane holds only fields, so one react-hook-form instance spans the
divider.

<!-- doc-snippet: fragment — a `fieldPanes` key list in prose notation, not a statement: the trailing `fieldPanesOrientation?:` / `fieldPanesResizable?:` entries mark OPTIONAL keys, which no object literal can spell. Measured TS1005x2 TS1109x3. -->

```typescript
{
type: 'form',
Expand DownExpand Up@@ -853,6 +866,7 @@ and hands them to your `onSubmit`, which the renderer awaits
whatever that function does:

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
import type { FormSchema } from '@object-ui/types';

const dataSource = createObjectStackAdapter({
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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
106 changes: 95 additions & 11 deletions packages/data-objectstack/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,6 +23,9 @@ npm install @object-ui/data-objectstack
```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
import { SchemaRenderer } from '@object-ui/react';
import type { ComponentSchema } from '@object-ui/types';

declare const mySchema: ComponentSchema;

// 1. Create the adapter
const dataSource = createObjectStackAdapter({
Expand All@@ -41,9 +44,21 @@ function App() {
}
```

> **Reaching the adapter-only API from TypeScript.** `createObjectStackAdapter`
> declares `DataSource` as its return type, so the members below that belong to the
> adapter rather than to every data source — `getClient`, the cache methods, the
> connection-state and batch-progress subscriptions — are not on the type the factory
> hands back, even though they are on the object it hands back. Until
> [#7323](https://github.com/objectstack-ai/objectui/issues/7323) is settled, hold the
> adapter as `ObjectStackAdapter` (the exported class, whose constructor is documented
> under **API Reference** below) wherever you use those members; the examples in this
> README do exactly that.

### Advanced Configuration

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

const dataSource = createObjectStackAdapter({
baseUrl: 'https://api.example.com',
token: 'your-api-token',
Expand DownExpand Up@@ -78,6 +93,10 @@ ObjectStack's native query format, so a schema never has to be written in the
protocol's own shape:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Query with filters (MongoDB-like operators)
const result = await dataSource.find('tasks', {
$filter: {
Expand All@@ -92,7 +111,7 @@ const result = await dataSource.find('tasks', {
// Escape hatch: reach the underlying ObjectStack client for anything
// the DataSource interface does not cover
const client = dataSource.getClient();
const metadata = await client.meta.getObject('task');
const metadata = await client.meta.getItem('object', 'task');
```

### Query Parameter Mapping
Expand DownExpand Up@@ -186,6 +205,10 @@ array that the spec's own `isFilterAST` gate rejects is refused here rather
than shipped:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// ✅ lowered — reaches the wire unchanged
await dataSource.aggregate('opportunity', {
groupBy: ['stage'],
Expand DownExpand Up@@ -215,6 +238,10 @@ declares), and an empty array (`[]` means "no filter").
### Sorting

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// OData-style
await dataSource.find('users', {
$orderby: {
Expand All@@ -231,6 +258,10 @@ await dataSource.find('users', {
The adapter includes built-in metadata caching to improve performance when fetching schemas:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Get cache statistics
const stats = dataSource.getCacheStats();
console.log(`Cache hit rate: ${stats.hitRate * 100}%`);
Expand All@@ -257,6 +288,10 @@ dataSource.clearCache();
The adapter provides real-time connection state monitoring with automatic reconnection:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Monitor connection state changes
const unsubscribe = dataSource.onConnectionStateChange((event) => {
console.log('Connection state:', event.state);
Expand DownExpand Up@@ -301,6 +336,12 @@ The adapter automatically attempts to reconnect on connection failures:
Track progress of bulk operations in real-time:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

declare const largeDataset: Array<Record<string, unknown>>;

// Monitor batch operation progress
const unsubscribe = dataSource.onBatchProgress((event) => {
console.log(`${event.operation}: ${event.percentage.toFixed(1)}%`);
Expand DownExpand Up@@ -358,24 +399,36 @@ import {
### Error Handling Example

```typescript
import {
ObjectStackError,
MetadataNotFoundError,
ConnectionError,
AuthenticationError,
type ObjectStackAdapter,
} from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

try {
const schema = await dataSource.getObjectSchema('users');
} catch (error) {
if (error instanceof MetadataNotFoundError) {
console.error(`Schema not found: ${error.details.objectName}`);
console.error(`Schema not found: ${error.details?.objectName}`);
} else if (error instanceof ConnectionError) {
console.error(`Connection failed to: ${error.url}`);
} else if (error instanceof AuthenticationError) {
console.error('Authentication required');
}

// All errors have consistent structure
console.error({
code: error.code,
message: error.message,
statusCode: error.statusCode,
details: error.details
});

// Every error this adapter throws carries the same shape
if (error instanceof ObjectStackError) {
console.error({
code: error.code,
message: error.message,
statusCode: error.statusCode,
details: error.details,
});
}
}
```

Expand All@@ -384,6 +437,12 @@ try {
Bulk operations provide detailed error reporting with partial success information:

```typescript
import { BulkOperationError, type ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

declare const records: Array<Record<string, unknown>>;

try {
await dataSource.bulk('users', 'update', records);
} catch (error) {
Expand DownExpand Up@@ -420,6 +479,10 @@ All errors include unique error codes for programmatic handling:
The adapter supports optimized batch operations with automatic fallback:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Batch create
const newUsers = await dataSource.bulk('users', 'create', [
{ name: 'Alice', email: 'alice@example.com' },
Expand DownExpand Up@@ -453,6 +516,10 @@ writes as a single all-or-nothing unit — the master-detail case, where a paren
and its children must commit or roll back together — use `batchTransaction`:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Create a parent and a child that references it, atomically.
// `{ $ref: 0 }` resolves to the id minted by operation 0 (the parent).
await dataSource.batchTransaction([
Expand DownExpand Up@@ -515,9 +582,12 @@ In addition to the main `DataSource` adapter, this package ships
persist per-user UI state (favorites, recent items) into ObjectStack.

```typescript
import { createObjectStackUserStateAdapter } from '@object-ui/data-objectstack';
import { createObjectStackUserStateAdapter, type ObjectStackAdapter } from '@object-ui/data-objectstack';
import { useAttachUserStateAdapters } from '@object-ui/app-shell';

declare const dataSource: ObjectStackAdapter;
declare const user: { id: string };

const favoritesAdapter = createObjectStackUserStateAdapter({
dataSource, // the ObjectStack DataSource
userId: user.id,
Expand All@@ -526,6 +596,8 @@ const favoritesAdapter = createObjectStackUserStateAdapter({
// onError: (op, err) => console.warn(`[user-state] ${op} failed`, err),
});

const attach = useAttachUserStateAdapters();

attach('favorites', favoritesAdapter);
```

Expand DownExpand Up@@ -560,6 +632,8 @@ for the full design.

#### Constructor

<!-- doc-snippet: fragment — the constructor SIGNATURE in prose notation, not a statement: `new ObjectStackAdapter(config: { ... })` names the parameter type where a call would carry a value. Measured TS1005x10. -->

```typescript
new ObjectStackAdapter(config: {
baseUrl: string;
Expand DownExpand Up@@ -613,6 +687,10 @@ new ObjectStackAdapter(config: {
#### Schema Not Found

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Error: MetadataNotFoundError
// Solution: Verify object name and ensure schema exists on server
const schema = await dataSource.getObjectSchema('correct_object_name');
Expand All@@ -621,6 +699,8 @@ const schema = await dataSource.getObjectSchema('correct_object_name');
#### Connection Errors

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

// Error: ConnectionError
// Solution: Check baseUrl and network connectivity
const dataSource = createObjectStackAdapter({
Expand All@@ -632,6 +712,10 @@ const dataSource = createObjectStackAdapter({
#### Cache Issues

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Clear cache if stale data is being returned
dataSource.clearCache();

Expand Down
14 changes: 14 additions & 0 deletions packages/plugin-form/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -294,6 +294,8 @@ field.
A field may declare both, and it is coherent authoring — storage-level required,
with the value guaranteed by the producer:

<!-- doc-snippet: fragment — one object-literal PROPERTY quoted out of a field map — `remind_at: Field.datetime(...)` is a key/value pair, not a statement. Measured TS1109x1. -->

```ts
remind_at: Field.datetime({ required: true, defaultValue: 'NOW()' }),
```
Expand All@@ -310,6 +312,8 @@ predicate says "required in this state", but `NOW()` / `current_user` resolve
at insert regardless of state, so the producer's guarantee covers the
conditional claim exactly as it covers the unconditional one:

<!-- doc-snippet: fragment — one object-literal PROPERTY quoted out of a field map — `remind_at: Field.datetime(...)` is a key/value pair, not a statement. Measured TS1109x1. -->

```ts
remind_at: Field.datetime({ requiredWhen: 'record.status == "scheduled"', defaultValue: 'NOW()' }),
```
Expand All@@ -331,6 +335,11 @@ The two halves of that verdict are importable, so a host with its own form
renderer applies the same rule rather than re-deriving it (objectui#6059):

```typescript
declare const field: { required?: unknown; defaultValue?: unknown };
declare const isCreateForm: boolean;
declare const values: Record<string, unknown>;
declare const objectSchema: { fields?: Record<string, { defaultValue?: unknown }> };

import { isRequiredInForm, omitServerResolvedDefaults } from '@object-ui/plugin-form';

// Should this create form enforce `required` on the field?
Expand DownExpand Up@@ -419,6 +428,8 @@ strands every section but the first outside the submit, and (in tabs) lets the
inactive panel unmount with its values — declare tabs on the single form and let
the renderer distribute the fields:

<!-- doc-snippet: fragment — a `fieldTabs` key list in prose notation, not a statement: the trailing `defaultFieldTab?:` / `fieldTabsPosition?:` entries mark OPTIONAL keys, which no object literal can spell. Measured TS1005x2 TS1109x3. -->

```typescript
{
type: 'form',
Expand DownExpand Up@@ -551,6 +562,8 @@ The same rule for side-by-side panels: the `<form>` wraps the whole panel group
and each pane holds only fields, so one react-hook-form instance spans the
divider.

<!-- doc-snippet: fragment — a `fieldPanes` key list in prose notation, not a statement: the trailing `fieldPanesOrientation?:` / `fieldPanesResizable?:` entries mark OPTIONAL keys, which no object literal can spell. Measured TS1005x2 TS1109x3. -->

```typescript
{
type: 'form',
Expand DownExpand Up@@ -853,6 +866,7 @@ and hands them to your `onSubmit`, which the renderer awaits
whatever that function does:

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
import type { FormSchema } from '@object-ui/types';

const dataSource = createObjectStackAdapter({
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
106 changes: 95 additions & 11 deletions packages/data-objectstack/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,6 +23,9 @@ npm install @object-ui/data-objectstack
```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
import { SchemaRenderer } from '@object-ui/react';
import type { ComponentSchema } from '@object-ui/types';

declare const mySchema: ComponentSchema;

// 1. Create the adapter
const dataSource = createObjectStackAdapter({
Expand All@@ -41,9 +44,21 @@ function App() {
}
```

> **Reaching the adapter-only API from TypeScript.** `createObjectStackAdapter`
> declares `DataSource` as its return type, so the members below that belong to the
> adapter rather than to every data source — `getClient`, the cache methods, the
> connection-state and batch-progress subscriptions — are not on the type the factory
> hands back, even though they are on the object it hands back. Until
> [#7323](https://github.com/objectstack-ai/objectui/issues/7323) is settled, hold the
> adapter as `ObjectStackAdapter` (the exported class, whose constructor is documented
> under **API Reference** below) wherever you use those members; the examples in this
> README do exactly that.

### Advanced Configuration

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

const dataSource = createObjectStackAdapter({
baseUrl: 'https://api.example.com',
token: 'your-api-token',
Expand DownExpand Up@@ -78,6 +93,10 @@ ObjectStack's native query format, so a schema never has to be written in the
protocol's own shape:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Query with filters (MongoDB-like operators)
const result = await dataSource.find('tasks', {
$filter: {
Expand All@@ -92,7 +111,7 @@ const result = await dataSource.find('tasks', {
// Escape hatch: reach the underlying ObjectStack client for anything
// the DataSource interface does not cover
const client = dataSource.getClient();
const metadata = await client.meta.getObject('task');
const metadata = await client.meta.getItem('object', 'task');
```

### Query Parameter Mapping
Expand DownExpand Up@@ -186,6 +205,10 @@ array that the spec's own `isFilterAST` gate rejects is refused here rather
than shipped:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// ✅ lowered — reaches the wire unchanged
await dataSource.aggregate('opportunity', {
groupBy: ['stage'],
Expand DownExpand Up@@ -215,6 +238,10 @@ declares), and an empty array (`[]` means "no filter").
### Sorting

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// OData-style
await dataSource.find('users', {
$orderby: {
Expand All@@ -231,6 +258,10 @@ await dataSource.find('users', {
The adapter includes built-in metadata caching to improve performance when fetching schemas:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Get cache statistics
const stats = dataSource.getCacheStats();
console.log(`Cache hit rate: ${stats.hitRate * 100}%`);
Expand All@@ -257,6 +288,10 @@ dataSource.clearCache();
The adapter provides real-time connection state monitoring with automatic reconnection:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Monitor connection state changes
const unsubscribe = dataSource.onConnectionStateChange((event) => {
console.log('Connection state:', event.state);
Expand DownExpand Up@@ -301,6 +336,12 @@ The adapter automatically attempts to reconnect on connection failures:
Track progress of bulk operations in real-time:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

declare const largeDataset: Array<Record<string, unknown>>;

// Monitor batch operation progress
const unsubscribe = dataSource.onBatchProgress((event) => {
console.log(`${event.operation}: ${event.percentage.toFixed(1)}%`);
Expand DownExpand Up@@ -358,24 +399,36 @@ import {
### Error Handling Example

```typescript
import {
ObjectStackError,
MetadataNotFoundError,
ConnectionError,
AuthenticationError,
type ObjectStackAdapter,
} from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

try {
const schema = await dataSource.getObjectSchema('users');
} catch (error) {
if (error instanceof MetadataNotFoundError) {
console.error(`Schema not found: ${error.details.objectName}`);
console.error(`Schema not found: ${error.details?.objectName}`);
} else if (error instanceof ConnectionError) {
console.error(`Connection failed to: ${error.url}`);
} else if (error instanceof AuthenticationError) {
console.error('Authentication required');
}

// All errors have consistent structure
console.error({
code: error.code,
message: error.message,
statusCode: error.statusCode,
details: error.details
});

// Every error this adapter throws carries the same shape
if (error instanceof ObjectStackError) {
console.error({
code: error.code,
message: error.message,
statusCode: error.statusCode,
details: error.details,
});
}
}
```

Expand All@@ -384,6 +437,12 @@ try {
Bulk operations provide detailed error reporting with partial success information:

```typescript
import { BulkOperationError, type ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

declare const records: Array<Record<string, unknown>>;

try {
await dataSource.bulk('users', 'update', records);
} catch (error) {
Expand DownExpand Up@@ -420,6 +479,10 @@ All errors include unique error codes for programmatic handling:
The adapter supports optimized batch operations with automatic fallback:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Batch create
const newUsers = await dataSource.bulk('users', 'create', [
{ name: 'Alice', email: 'alice@example.com' },
Expand DownExpand Up@@ -453,6 +516,10 @@ writes as a single all-or-nothing unit — the master-detail case, where a paren
and its children must commit or roll back together — use `batchTransaction`:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Create a parent and a child that references it, atomically.
// `{ $ref: 0 }` resolves to the id minted by operation 0 (the parent).
await dataSource.batchTransaction([
Expand DownExpand Up@@ -515,9 +582,12 @@ In addition to the main `DataSource` adapter, this package ships
persist per-user UI state (favorites, recent items) into ObjectStack.

```typescript
import { createObjectStackUserStateAdapter } from '@object-ui/data-objectstack';
import { createObjectStackUserStateAdapter, type ObjectStackAdapter } from '@object-ui/data-objectstack';
import { useAttachUserStateAdapters } from '@object-ui/app-shell';

declare const dataSource: ObjectStackAdapter;
declare const user: { id: string };

const favoritesAdapter = createObjectStackUserStateAdapter({
dataSource, // the ObjectStack DataSource
userId: user.id,
Expand All@@ -526,6 +596,8 @@ const favoritesAdapter = createObjectStackUserStateAdapter({
// onError: (op, err) => console.warn(`[user-state] ${op} failed`, err),
});

const attach = useAttachUserStateAdapters();

attach('favorites', favoritesAdapter);
```

Expand DownExpand Up@@ -560,6 +632,8 @@ for the full design.

#### Constructor

<!-- doc-snippet: fragment — the constructor SIGNATURE in prose notation, not a statement: `new ObjectStackAdapter(config: { ... })` names the parameter type where a call would carry a value. Measured TS1005x10. -->

```typescript
new ObjectStackAdapter(config: {
baseUrl: string;
Expand DownExpand Up@@ -613,6 +687,10 @@ new ObjectStackAdapter(config: {
#### Schema Not Found

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Error: MetadataNotFoundError
// Solution: Verify object name and ensure schema exists on server
const schema = await dataSource.getObjectSchema('correct_object_name');
Expand All@@ -621,6 +699,8 @@ const schema = await dataSource.getObjectSchema('correct_object_name');
#### Connection Errors

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

// Error: ConnectionError
// Solution: Check baseUrl and network connectivity
const dataSource = createObjectStackAdapter({
Expand All@@ -632,6 +712,10 @@ const dataSource = createObjectStackAdapter({
#### Cache Issues

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Clear cache if stale data is being returned
dataSource.clearCache();

Expand Down
14 changes: 14 additions & 0 deletions packages/plugin-form/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -294,6 +294,8 @@ field.
A field may declare both, and it is coherent authoring — storage-level required,
with the value guaranteed by the producer:

<!-- doc-snippet: fragment — one object-literal PROPERTY quoted out of a field map — `remind_at: Field.datetime(...)` is a key/value pair, not a statement. Measured TS1109x1. -->

```ts
remind_at: Field.datetime({ required: true, defaultValue: 'NOW()' }),
```
Expand All@@ -310,6 +312,8 @@ predicate says "required in this state", but `NOW()` / `current_user` resolve
at insert regardless of state, so the producer's guarantee covers the
conditional claim exactly as it covers the unconditional one:

<!-- doc-snippet: fragment — one object-literal PROPERTY quoted out of a field map — `remind_at: Field.datetime(...)` is a key/value pair, not a statement. Measured TS1109x1. -->

```ts
remind_at: Field.datetime({ requiredWhen: 'record.status == "scheduled"', defaultValue: 'NOW()' }),
```
Expand All@@ -331,6 +335,11 @@ The two halves of that verdict are importable, so a host with its own form
renderer applies the same rule rather than re-deriving it (objectui#6059):

```typescript
declare const field: { required?: unknown; defaultValue?: unknown };
declare const isCreateForm: boolean;
declare const values: Record<string, unknown>;
declare const objectSchema: { fields?: Record<string, { defaultValue?: unknown }> };

import { isRequiredInForm, omitServerResolvedDefaults } from '@object-ui/plugin-form';

// Should this create form enforce `required` on the field?
Expand DownExpand Up@@ -419,6 +428,8 @@ strands every section but the first outside the submit, and (in tabs) lets the
inactive panel unmount with its values — declare tabs on the single form and let
the renderer distribute the fields:

<!-- doc-snippet: fragment — a `fieldTabs` key list in prose notation, not a statement: the trailing `defaultFieldTab?:` / `fieldTabsPosition?:` entries mark OPTIONAL keys, which no object literal can spell. Measured TS1005x2 TS1109x3. -->

```typescript
{
type: 'form',
Expand DownExpand Up@@ -551,6 +562,8 @@ The same rule for side-by-side panels: the `<form>` wraps the whole panel group
and each pane holds only fields, so one react-hook-form instance spans the
divider.

<!-- doc-snippet: fragment — a `fieldPanes` key list in prose notation, not a statement: the trailing `fieldPanesOrientation?:` / `fieldPanesResizable?:` entries mark OPTIONAL keys, which no object literal can spell. Measured TS1005x2 TS1109x3. -->

```typescript
{
type: 'form',
Expand DownExpand Up@@ -853,6 +866,7 @@ and hands them to your `onSubmit`, which the renderer awaits
whatever that function does:

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
import type { FormSchema } from '@object-ui/types';

const dataSource = createObjectStackAdapter({
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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
106 changes: 95 additions & 11 deletions packages/data-objectstack/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,6 +23,9 @@ npm install @object-ui/data-objectstack
```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
import { SchemaRenderer } from '@object-ui/react';
import type { ComponentSchema } from '@object-ui/types';

declare const mySchema: ComponentSchema;

// 1. Create the adapter
const dataSource = createObjectStackAdapter({
Expand All@@ -41,9 +44,21 @@ function App() {
}
```

> **Reaching the adapter-only API from TypeScript.** `createObjectStackAdapter`
> declares `DataSource` as its return type, so the members below that belong to the
> adapter rather than to every data source — `getClient`, the cache methods, the
> connection-state and batch-progress subscriptions — are not on the type the factory
> hands back, even though they are on the object it hands back. Until
> [#7323](https://github.com/objectstack-ai/objectui/issues/7323) is settled, hold the
> adapter as `ObjectStackAdapter` (the exported class, whose constructor is documented
> under **API Reference** below) wherever you use those members; the examples in this
> README do exactly that.

### Advanced Configuration

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

const dataSource = createObjectStackAdapter({
baseUrl: 'https://api.example.com',
token: 'your-api-token',
Expand DownExpand Up@@ -78,6 +93,10 @@ ObjectStack's native query format, so a schema never has to be written in the
protocol's own shape:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Query with filters (MongoDB-like operators)
const result = await dataSource.find('tasks', {
$filter: {
Expand All@@ -92,7 +111,7 @@ const result = await dataSource.find('tasks', {
// Escape hatch: reach the underlying ObjectStack client for anything
// the DataSource interface does not cover
const client = dataSource.getClient();
const metadata = await client.meta.getObject('task');
const metadata = await client.meta.getItem('object', 'task');
```

### Query Parameter Mapping
Expand DownExpand Up@@ -186,6 +205,10 @@ array that the spec's own `isFilterAST` gate rejects is refused here rather
than shipped:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// ✅ lowered — reaches the wire unchanged
await dataSource.aggregate('opportunity', {
groupBy: ['stage'],
Expand DownExpand Up@@ -215,6 +238,10 @@ declares), and an empty array (`[]` means "no filter").
### Sorting

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// OData-style
await dataSource.find('users', {
$orderby: {
Expand All@@ -231,6 +258,10 @@ await dataSource.find('users', {
The adapter includes built-in metadata caching to improve performance when fetching schemas:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Get cache statistics
const stats = dataSource.getCacheStats();
console.log(`Cache hit rate: ${stats.hitRate * 100}%`);
Expand All@@ -257,6 +288,10 @@ dataSource.clearCache();
The adapter provides real-time connection state monitoring with automatic reconnection:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Monitor connection state changes
const unsubscribe = dataSource.onConnectionStateChange((event) => {
console.log('Connection state:', event.state);
Expand DownExpand Up@@ -301,6 +336,12 @@ The adapter automatically attempts to reconnect on connection failures:
Track progress of bulk operations in real-time:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

declare const largeDataset: Array<Record<string, unknown>>;

// Monitor batch operation progress
const unsubscribe = dataSource.onBatchProgress((event) => {
console.log(`${event.operation}: ${event.percentage.toFixed(1)}%`);
Expand DownExpand Up@@ -358,24 +399,36 @@ import {
### Error Handling Example

```typescript
import {
ObjectStackError,
MetadataNotFoundError,
ConnectionError,
AuthenticationError,
type ObjectStackAdapter,
} from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

try {
const schema = await dataSource.getObjectSchema('users');
} catch (error) {
if (error instanceof MetadataNotFoundError) {
console.error(`Schema not found: ${error.details.objectName}`);
console.error(`Schema not found: ${error.details?.objectName}`);
} else if (error instanceof ConnectionError) {
console.error(`Connection failed to: ${error.url}`);
} else if (error instanceof AuthenticationError) {
console.error('Authentication required');
}

// All errors have consistent structure
console.error({
code: error.code,
message: error.message,
statusCode: error.statusCode,
details: error.details
});

// Every error this adapter throws carries the same shape
if (error instanceof ObjectStackError) {
console.error({
code: error.code,
message: error.message,
statusCode: error.statusCode,
details: error.details,
});
}
}
```

Expand All@@ -384,6 +437,12 @@ try {
Bulk operations provide detailed error reporting with partial success information:

```typescript
import { BulkOperationError, type ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

declare const records: Array<Record<string, unknown>>;

try {
await dataSource.bulk('users', 'update', records);
} catch (error) {
Expand DownExpand Up@@ -420,6 +479,10 @@ All errors include unique error codes for programmatic handling:
The adapter supports optimized batch operations with automatic fallback:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Batch create
const newUsers = await dataSource.bulk('users', 'create', [
{ name: 'Alice', email: 'alice@example.com' },
Expand DownExpand Up@@ -453,6 +516,10 @@ writes as a single all-or-nothing unit — the master-detail case, where a paren
and its children must commit or roll back together — use `batchTransaction`:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Create a parent and a child that references it, atomically.
// `{ $ref: 0 }` resolves to the id minted by operation 0 (the parent).
await dataSource.batchTransaction([
Expand DownExpand Up@@ -515,9 +582,12 @@ In addition to the main `DataSource` adapter, this package ships
persist per-user UI state (favorites, recent items) into ObjectStack.

```typescript
import { createObjectStackUserStateAdapter } from '@object-ui/data-objectstack';
import { createObjectStackUserStateAdapter, type ObjectStackAdapter } from '@object-ui/data-objectstack';
import { useAttachUserStateAdapters } from '@object-ui/app-shell';

declare const dataSource: ObjectStackAdapter;
declare const user: { id: string };

const favoritesAdapter = createObjectStackUserStateAdapter({
dataSource, // the ObjectStack DataSource
userId: user.id,
Expand All@@ -526,6 +596,8 @@ const favoritesAdapter = createObjectStackUserStateAdapter({
// onError: (op, err) => console.warn(`[user-state] ${op} failed`, err),
});

const attach = useAttachUserStateAdapters();

attach('favorites', favoritesAdapter);
```

Expand DownExpand Up@@ -560,6 +632,8 @@ for the full design.

#### Constructor

<!-- doc-snippet: fragment — the constructor SIGNATURE in prose notation, not a statement: `new ObjectStackAdapter(config: { ... })` names the parameter type where a call would carry a value. Measured TS1005x10. -->

```typescript
new ObjectStackAdapter(config: {
baseUrl: string;
Expand DownExpand Up@@ -613,6 +687,10 @@ new ObjectStackAdapter(config: {
#### Schema Not Found

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Error: MetadataNotFoundError
// Solution: Verify object name and ensure schema exists on server
const schema = await dataSource.getObjectSchema('correct_object_name');
Expand All@@ -621,6 +699,8 @@ const schema = await dataSource.getObjectSchema('correct_object_name');
#### Connection Errors

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

// Error: ConnectionError
// Solution: Check baseUrl and network connectivity
const dataSource = createObjectStackAdapter({
Expand All@@ -632,6 +712,10 @@ const dataSource = createObjectStackAdapter({
#### Cache Issues

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Clear cache if stale data is being returned
dataSource.clearCache();

Expand Down
14 changes: 14 additions & 0 deletions packages/plugin-form/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -294,6 +294,8 @@ field.
A field may declare both, and it is coherent authoring — storage-level required,
with the value guaranteed by the producer:

<!-- doc-snippet: fragment — one object-literal PROPERTY quoted out of a field map — `remind_at: Field.datetime(...)` is a key/value pair, not a statement. Measured TS1109x1. -->

```ts
remind_at: Field.datetime({ required: true, defaultValue: 'NOW()' }),
```
Expand All@@ -310,6 +312,8 @@ predicate says "required in this state", but `NOW()` / `current_user` resolve
at insert regardless of state, so the producer's guarantee covers the
conditional claim exactly as it covers the unconditional one:

<!-- doc-snippet: fragment — one object-literal PROPERTY quoted out of a field map — `remind_at: Field.datetime(...)` is a key/value pair, not a statement. Measured TS1109x1. -->

```ts
remind_at: Field.datetime({ requiredWhen: 'record.status == "scheduled"', defaultValue: 'NOW()' }),
```
Expand All@@ -331,6 +335,11 @@ The two halves of that verdict are importable, so a host with its own form
renderer applies the same rule rather than re-deriving it (objectui#6059):

```typescript
declare const field: { required?: unknown; defaultValue?: unknown };
declare const isCreateForm: boolean;
declare const values: Record<string, unknown>;
declare const objectSchema: { fields?: Record<string, { defaultValue?: unknown }> };

import { isRequiredInForm, omitServerResolvedDefaults } from '@object-ui/plugin-form';

// Should this create form enforce `required` on the field?
Expand DownExpand Up@@ -419,6 +428,8 @@ strands every section but the first outside the submit, and (in tabs) lets the
inactive panel unmount with its values — declare tabs on the single form and let
the renderer distribute the fields:

<!-- doc-snippet: fragment — a `fieldTabs` key list in prose notation, not a statement: the trailing `defaultFieldTab?:` / `fieldTabsPosition?:` entries mark OPTIONAL keys, which no object literal can spell. Measured TS1005x2 TS1109x3. -->

```typescript
{
type: 'form',
Expand DownExpand Up@@ -551,6 +562,8 @@ The same rule for side-by-side panels: the `<form>` wraps the whole panel group
and each pane holds only fields, so one react-hook-form instance spans the
divider.

<!-- doc-snippet: fragment — a `fieldPanes` key list in prose notation, not a statement: the trailing `fieldPanesOrientation?:` / `fieldPanesResizable?:` entries mark OPTIONAL keys, which no object literal can spell. Measured TS1005x2 TS1109x3. -->

```typescript
{
type: 'form',
Expand DownExpand Up@@ -853,6 +866,7 @@ and hands them to your `onSubmit`, which the renderer awaits
whatever that function does:

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
import type { FormSchema } from '@object-ui/types';

const dataSource = createObjectStackAdapter({
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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
106 changes: 95 additions & 11 deletions packages/data-objectstack/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,6 +23,9 @@ npm install @object-ui/data-objectstack
```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
import { SchemaRenderer } from '@object-ui/react';
import type { ComponentSchema } from '@object-ui/types';

declare const mySchema: ComponentSchema;

// 1. Create the adapter
const dataSource = createObjectStackAdapter({
Expand All@@ -41,9 +44,21 @@ function App() {
}
```

> **Reaching the adapter-only API from TypeScript.** `createObjectStackAdapter`
> declares `DataSource` as its return type, so the members below that belong to the
> adapter rather than to every data source — `getClient`, the cache methods, the
> connection-state and batch-progress subscriptions — are not on the type the factory
> hands back, even though they are on the object it hands back. Until
> [#7323](https://github.com/objectstack-ai/objectui/issues/7323) is settled, hold the
> adapter as `ObjectStackAdapter` (the exported class, whose constructor is documented
> under **API Reference** below) wherever you use those members; the examples in this
> README do exactly that.

### Advanced Configuration

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

const dataSource = createObjectStackAdapter({
baseUrl: 'https://api.example.com',
token: 'your-api-token',
Expand DownExpand Up@@ -78,6 +93,10 @@ ObjectStack's native query format, so a schema never has to be written in the
protocol's own shape:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Query with filters (MongoDB-like operators)
const result = await dataSource.find('tasks', {
$filter: {
Expand All@@ -92,7 +111,7 @@ const result = await dataSource.find('tasks', {
// Escape hatch: reach the underlying ObjectStack client for anything
// the DataSource interface does not cover
const client = dataSource.getClient();
const metadata = await client.meta.getObject('task');
const metadata = await client.meta.getItem('object', 'task');
```

### Query Parameter Mapping
Expand DownExpand Up@@ -186,6 +205,10 @@ array that the spec's own `isFilterAST` gate rejects is refused here rather
than shipped:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// ✅ lowered — reaches the wire unchanged
await dataSource.aggregate('opportunity', {
groupBy: ['stage'],
Expand DownExpand Up@@ -215,6 +238,10 @@ declares), and an empty array (`[]` means "no filter").
### Sorting

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// OData-style
await dataSource.find('users', {
$orderby: {
Expand All@@ -231,6 +258,10 @@ await dataSource.find('users', {
The adapter includes built-in metadata caching to improve performance when fetching schemas:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Get cache statistics
const stats = dataSource.getCacheStats();
console.log(`Cache hit rate: ${stats.hitRate * 100}%`);
Expand All@@ -257,6 +288,10 @@ dataSource.clearCache();
The adapter provides real-time connection state monitoring with automatic reconnection:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Monitor connection state changes
const unsubscribe = dataSource.onConnectionStateChange((event) => {
console.log('Connection state:', event.state);
Expand DownExpand Up@@ -301,6 +336,12 @@ The adapter automatically attempts to reconnect on connection failures:
Track progress of bulk operations in real-time:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

declare const largeDataset: Array<Record<string, unknown>>;

// Monitor batch operation progress
const unsubscribe = dataSource.onBatchProgress((event) => {
console.log(`${event.operation}: ${event.percentage.toFixed(1)}%`);
Expand DownExpand Up@@ -358,24 +399,36 @@ import {
### Error Handling Example

```typescript
import {
ObjectStackError,
MetadataNotFoundError,
ConnectionError,
AuthenticationError,
type ObjectStackAdapter,
} from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

try {
const schema = await dataSource.getObjectSchema('users');
} catch (error) {
if (error instanceof MetadataNotFoundError) {
console.error(`Schema not found: ${error.details.objectName}`);
console.error(`Schema not found: ${error.details?.objectName}`);
} else if (error instanceof ConnectionError) {
console.error(`Connection failed to: ${error.url}`);
} else if (error instanceof AuthenticationError) {
console.error('Authentication required');
}

// All errors have consistent structure
console.error({
code: error.code,
message: error.message,
statusCode: error.statusCode,
details: error.details
});

// Every error this adapter throws carries the same shape
if (error instanceof ObjectStackError) {
console.error({
code: error.code,
message: error.message,
statusCode: error.statusCode,
details: error.details,
});
}
}
```

Expand All@@ -384,6 +437,12 @@ try {
Bulk operations provide detailed error reporting with partial success information:

```typescript
import { BulkOperationError, type ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

declare const records: Array<Record<string, unknown>>;

try {
await dataSource.bulk('users', 'update', records);
} catch (error) {
Expand DownExpand Up@@ -420,6 +479,10 @@ All errors include unique error codes for programmatic handling:
The adapter supports optimized batch operations with automatic fallback:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Batch create
const newUsers = await dataSource.bulk('users', 'create', [
{ name: 'Alice', email: 'alice@example.com' },
Expand DownExpand Up@@ -453,6 +516,10 @@ writes as a single all-or-nothing unit — the master-detail case, where a paren
and its children must commit or roll back together — use `batchTransaction`:

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Create a parent and a child that references it, atomically.
// `{ $ref: 0 }` resolves to the id minted by operation 0 (the parent).
await dataSource.batchTransaction([
Expand DownExpand Up@@ -515,9 +582,12 @@ In addition to the main `DataSource` adapter, this package ships
persist per-user UI state (favorites, recent items) into ObjectStack.

```typescript
import { createObjectStackUserStateAdapter } from '@object-ui/data-objectstack';
import { createObjectStackUserStateAdapter, type ObjectStackAdapter } from '@object-ui/data-objectstack';
import { useAttachUserStateAdapters } from '@object-ui/app-shell';

declare const dataSource: ObjectStackAdapter;
declare const user: { id: string };

const favoritesAdapter = createObjectStackUserStateAdapter({
dataSource, // the ObjectStack DataSource
userId: user.id,
Expand All@@ -526,6 +596,8 @@ const favoritesAdapter = createObjectStackUserStateAdapter({
// onError: (op, err) => console.warn(`[user-state] ${op} failed`, err),
});

const attach = useAttachUserStateAdapters();

attach('favorites', favoritesAdapter);
```

Expand DownExpand Up@@ -560,6 +632,8 @@ for the full design.

#### Constructor

<!-- doc-snippet: fragment — the constructor SIGNATURE in prose notation, not a statement: `new ObjectStackAdapter(config: { ... })` names the parameter type where a call would carry a value. Measured TS1005x10. -->

```typescript
new ObjectStackAdapter(config: {
baseUrl: string;
Expand DownExpand Up@@ -613,6 +687,10 @@ new ObjectStackAdapter(config: {
#### Schema Not Found

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Error: MetadataNotFoundError
// Solution: Verify object name and ensure schema exists on server
const schema = await dataSource.getObjectSchema('correct_object_name');
Expand All@@ -621,6 +699,8 @@ const schema = await dataSource.getObjectSchema('correct_object_name');
#### Connection Errors

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

// Error: ConnectionError
// Solution: Check baseUrl and network connectivity
const dataSource = createObjectStackAdapter({
Expand All@@ -632,6 +712,10 @@ const dataSource = createObjectStackAdapter({
#### Cache Issues

```typescript
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;

// Clear cache if stale data is being returned
dataSource.clearCache();

Expand Down
14 changes: 14 additions & 0 deletions packages/plugin-form/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -294,6 +294,8 @@ field.
A field may declare both, and it is coherent authoring — storage-level required,
with the value guaranteed by the producer:

<!-- doc-snippet: fragment — one object-literal PROPERTY quoted out of a field map — `remind_at: Field.datetime(...)` is a key/value pair, not a statement. Measured TS1109x1. -->

```ts
remind_at: Field.datetime({ required: true, defaultValue: 'NOW()' }),
```
Expand All@@ -310,6 +312,8 @@ predicate says "required in this state", but `NOW()` / `current_user` resolve
at insert regardless of state, so the producer's guarantee covers the
conditional claim exactly as it covers the unconditional one:

<!-- doc-snippet: fragment — one object-literal PROPERTY quoted out of a field map — `remind_at: Field.datetime(...)` is a key/value pair, not a statement. Measured TS1109x1. -->

```ts
remind_at: Field.datetime({ requiredWhen: 'record.status == "scheduled"', defaultValue: 'NOW()' }),
```
Expand All@@ -331,6 +335,11 @@ The two halves of that verdict are importable, so a host with its own form
renderer applies the same rule rather than re-deriving it (objectui#6059):

```typescript
declare const field: { required?: unknown; defaultValue?: unknown };
declare const isCreateForm: boolean;
declare const values: Record<string, unknown>;
declare const objectSchema: { fields?: Record<string, { defaultValue?: unknown }> };

import { isRequiredInForm, omitServerResolvedDefaults } from '@object-ui/plugin-form';

// Should this create form enforce `required` on the field?
Expand DownExpand Up@@ -419,6 +428,8 @@ strands every section but the first outside the submit, and (in tabs) lets the
inactive panel unmount with its values — declare tabs on the single form and let
the renderer distribute the fields:

<!-- doc-snippet: fragment — a `fieldTabs` key list in prose notation, not a statement: the trailing `defaultFieldTab?:` / `fieldTabsPosition?:` entries mark OPTIONAL keys, which no object literal can spell. Measured TS1005x2 TS1109x3. -->

```typescript
{
type: 'form',
Expand DownExpand Up@@ -551,6 +562,8 @@ The same rule for side-by-side panels: the `<form>` wraps the whole panel group
and each pane holds only fields, so one react-hook-form instance spans the
divider.

<!-- doc-snippet: fragment — a `fieldPanes` key list in prose notation, not a statement: the trailing `fieldPanesOrientation?:` / `fieldPanesResizable?:` entries mark OPTIONAL keys, which no object literal can spell. Measured TS1005x2 TS1109x3. -->

```typescript
{
type: 'form',
Expand DownExpand Up@@ -853,6 +866,7 @@ and hands them to your `onSubmit`, which the renderer awaits
whatever that function does:

```typescript
import { createObjectStackAdapter } from '@object-ui/data-objectstack';
import type { FormSchema } from '@object-ui/types';

const dataSource = createObjectStackAdapter({
Expand Down
Loading
Loading