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
79 changes: 79 additions & 0 deletions .changeset/7323-adapter-factory-return.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
---
'@object-ui/data-objectstack': minor
---

`createObjectStackAdapter` declares the adapter it returns, not the shared `DataSource`
interface (objectui#7323).

The factory returned `new ObjectStackAdapter(config)` while declaring `DataSource<T>`.
A wider value is assignable to a narrower annotation, so nothing ever failed to compile
— the loss was entirely on the reading side. Measured against the shipped
`dist/index.d.ts` with the doc-snippet gate's own compiler options, nine reads through
`ReturnType<typeof createObjectStackAdapter>` failed with TS2339: `getClient`,
`getCacheStats`, `invalidateCache`, `clearCache`, `getConnectionState`, `isConnected`,
`onConnectionStateChange`, `onBatchProgress` and `setSystemCapabilities`. Eight of those
nine reads are on this package's README API Reference list, and four whole README
sections are built on them; the ninth measured read is the one the factory's own JSDoc
links to (`[ADR-0066] See {@link ObjectStackAdapter.setSystemCapabilities}`). The README
list is itself **nine** adapter-only members, not eight — `connect()` is adapter-only
too and was documented all along; it simply was not one of the reads the card's
reproduction measured. So the file's own doc comment pointed the reader at a method its
declared return hid, and the two documented ways to obtain the same object — the factory
and `new ObjectStackAdapter(…)` — handed back different type surfaces.

**What the declared return now is: the whole class, not those nine reads.** The nine
above are what the reproduction measured, not the size of this change. The factory's
declared return is now `ObjectStackAdapter<T>` itself, so **every public member of the
class** is part of what the factory promises. Against `DataSource` that is **20**
members, not nine — `tsc`-computed as
`Exclude<keyof ObjectStackAdapter<unknown>, keyof DataSource<unknown>>`: `clearCache`,
`connect`, `getCacheStats`, `getCached`, `getClient`, `getConnectionState`,
`getDiscovery`, `getItems`, `invalidateCache`, `invalidateViewKeys`, `isConnected`,
`listImportMappings`, `onBatchProgress`, `onConnectionStateChange`, `onSaveAdvisory`,
`onWriteWarning`, `probeAppAccess`, `queryDataset`, `setSystemCapabilities`,
`updateDashboard`. The eleven past the documented nine were already in the shipped class
type — none is `@internal` or `@deprecated`, `stripInternal` is not set, and all were
already reachable through `new ObjectStackAdapter(…)` and through every
`ObjectStackAdapter`-typed seam in `@object-ui/react` and `app-shell` — so what widens
here is what the **factory declares**, not what the package ships. Two are escape-hatch
shaped and worth knowing before building on them: `getCached(key)` is a raw cache read,
and `getDiscovery()` reaches an internal property of the underlying `ObjectStackClient`.

**Branch taken: A (widen the factory's declared return), and why.** The card offered
three. B — moving caching, connection state and batch progress onto `DataSource` — was
rejected because those are this adapter's concerns, not every data source's; every other
`DataSource` implementation would then declare members it does not have. C — documenting
a cast — teaches a cast around a declaration that is merely narrower than the value,
which is the opposite of `declared = enforced`. A is one line and makes declared match
shipped for every documented member at once.

Two questions decided the shape and both were answered from the code before the diff.
`ObjectStackAdapter` was **already** exported from the package's only entry
(`src/index.ts`, tsup's single entry; the class is in the shipped `dist/index.d.ts`
export list, two pin tests assert the exported spelling, and `apps/console` re-exports it
by name) — so widening the return exports nothing by implication. And the narrow return
was **not** a deliberate swappability guarantee: no comment, ADR or test pinned it, and
the commit that added `autoReconnect` / `maxReconnectAttempts` / `reconnectDelay` to the
factory's own config bag left the members that observe those features off the factory's
declared return in the same change.

**One caller shape breaks: a structural stand-in for the factory's return.** A
hand-written object literal annotated `ReturnType<typeof createObjectStackAdapter>` no
longer satisfies that type, because it is now a class with private members (TS2740) —
annotate such a fake as `DataSource` instead, which is what it was standing in for.
Nothing else moves: a wider return is assignable to the narrower annotation, so
`const ds: DataSource = createObjectStackAdapter(…)` keeps compiling and keeps giving
the narrow surface to anyone who wants it.

The README's note saying the page could not yet teach the factory's shape is removed, and
the four sections built on the adapter-only members (Metadata Caching, Connection State
Monitoring, Batch Operation Progress, Troubleshooting → Cache Issues) now continue from
Basic Setup's `createObjectStackAdapter(…)` call instead of declaring the class by hand.
The docs-site page `content/docs/utilities/data-objectstack.mdx` is corrected the same
way: its prose, its factory signature fragment and its "hold the class type to reach
these" section described the old narrow return, and its Mutations and Troubleshooting
examples told the reader to construct the class by hand to reach members the factory now
declares. `src/adapterFactoryReturn.types.test.ts` pins the card's TS2339 reproduction
inverted,
with two controls: the adapter-only members stay absent from `DataSource` (fires on
option B), and the widened return stays assignable to `DataSource` (swappability kept).
99 changes: 69 additions & 30 deletions content/docs/utilities/data-objectstack.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,9 +50,11 @@ const dataSource = createObjectStackAdapter({
});
```

`createObjectStackAdapter` returns a `DataSource` — the same universal interface
every ObjectUI renderer consumes. `new ObjectStackAdapter(config)` is the class
form of the same thing.
`createObjectStackAdapter` returns an `ObjectStackAdapter` — the concrete adapter
class, which implements `DataSource`, the universal interface every ObjectUI
renderer consumes. `new ObjectStackAdapter(config)` is the class form of the same
thing and has the same type. Annotate the value as `DataSource` wherever you want
only the universal surface.

### 2. Inject it at the renderer boundary

Expand DownExpand Up@@ -106,11 +108,12 @@ full table of which blocks honour which keys.

### `createObjectStackAdapter`

Factory returning a `DataSource` backed by an ObjectStack backend.
Factory returning an `ObjectStackAdapter` — the concrete adapter class, which
implements `DataSource` — backed by an ObjectStack backend.

**Config:**

{/* doc-snippet: fragment — a SIGNATURE excerpt of the factory, quoted from its declaration so the config members can be annotated one by one: a `function` declaration with no body and a `DataSource` return type this reference block does not import (measured: TS2391 x1, TS2304 x1). Checked against the shipped `packages/data-objectstack/dist/index.d.ts`: every member listed here is declared there with the same type */}
{/* doc-snippet: fragment — a SIGNATURE excerpt of the factory, quoted from its declaration so the config members can be annotated one by one: a `function` declaration with no body and an `ObjectStackAdapter` return type this reference block does not import (measured: TS2391 x1, TS2304 x1). Because the block is DECLARED, this gate never compiles it, so the agreement with the shipped `packages/data-objectstack/dist/index.d.ts`every config member declared there with the same type, and the return type — is hand-checked at each edit, not gate-enforced; that gap is why the return type here outlived the change that widened it (objectui#7323) */}
```typescript
function createObjectStackAdapter<T = unknown>(config: {
/** ObjectStack server base URL */
Expand All@@ -132,7 +135,7 @@ function createObjectStackAdapter<T = unknown>(config: {
autoReconnect?: boolean; // default true
maxReconnectAttempts?: number; // default 3
reconnectDelay?: number; // default 1000 ms
}): DataSource<T>;
}): ObjectStackAdapter<T>;
```

**Example:**
Expand DownExpand Up@@ -161,20 +164,43 @@ const dataSource = createObjectStackAdapter<User>({ baseUrl: 'https://api.exampl

### `ObjectStackAdapter`

The class behind the factory. `new ObjectStackAdapter(config)` takes the same
config, but its declared type is the **concrete adapter** rather than the
`DataSource` interface the factory returns — which matters, because part of the
adapter's surface is not on that interface:
The class behind the factory — and the type the factory declares. `new
ObjectStackAdapter(config)` and `createObjectStackAdapter(config)` take the same
config and produce the same type, so the two forms are interchangeable:

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

type User = { id: string; name: string; email: string };

const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
// Same declared type, either way.
const fromFactory = createObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
const fromClass = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
```

**On the `DataSource` interface** (available from either form):
**What changed.** This section used to tell you to hold the class type to
reach the members listed under *Beyond `DataSource`* below, because the factory
declared `DataSource<T>` while returning `new ObjectStackAdapter(config)`: the
value always carried those members, only the declared type hid them
(objectui#7323). That distinction is gone. If you switched to
`new ObjectStackAdapter(...)` solely to reach a cache, connection-state or batch
method, you can switch back to the factory — nothing about the value changes, and
neither does its type. The class name is still worth importing when you need
something to annotate with.

Narrowing still works, and is still the right shape for a prop, a field or a test
double that must accept **any** adapter:

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

// The universal surface only, by annotation.
const dataSource: DataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });
```

**On the `DataSource` interface** (available from either form, and from any other
adapter):

- `find(resource, params?)` - Query multiple records
- `findOne(resource, id, params?)` - Get a single record by ID
Expand All@@ -184,22 +210,33 @@ const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com
- `getObjectSchema(objectName)` - Fetch schema metadata (cached)
- `bulk?(resource, operation, data)` - Batch create/update/delete on one object
- `batchTransaction?(operations)` - Cross-object atomic batch (master-detail)
- `onMutation?(listener)` - Subscribe to create/update/delete events

`bulk` and `batchTransaction` are **optional** members of `DataSource`: not every
adapter implements them, so through a `DataSource`-typed value they must be
feature-detected (`typeof dataSource.bulk === 'function'`). `ObjectStackAdapter`
implements both unconditionally, so a value held at the class type calls them
directly.
`bulk`, `batchTransaction` and `onMutation` are **optional** members of
`DataSource`: not every adapter implements them, so through a `DataSource`-typed
value they must be feature-detected (`typeof dataSource.bulk === 'function'`).
`ObjectStackAdapter` implements all three unconditionally, and the factory
declares the class, so a value from either form calls them directly. (`onMutation`
was listed under *Adapter-only* here until objectui#7323; it is optional on
`DataSource`, not absent from it.)

**Adapter-only** (hold the class type to reach these):
**Beyond `DataSource`** (declared on `ObjectStackAdapter`, so reachable from
either form):

- `connect()` - Establish the connection (called lazily by every operation)
- `getCacheStats()` / `invalidateCache(key?)` / `clearCache()` - Cache control
- `getClient()` - Access the underlying `@objectstack/client` instance
- `getConnectionState()` / `isConnected()` - Connection introspection
- `onConnectionStateChange(listener)` - Subscribe to state changes (returns unsubscribe)
- `onBatchProgress(listener)` - Subscribe to bulk progress (returns unsubscribe)
- `onMutation(listener)` - Subscribe to create/update/delete events

Those six bullets cover nine members, and they are the documented subset.
`ObjectStackAdapter` declares **20** members beyond `DataSource` in all
(`Exclude<keyof ObjectStackAdapter<unknown>, keyof DataSource<unknown>>`); the
other eleven are lower-level seams — client discovery, cache-key invalidation,
dataset and dashboard access, advisory subscriptions and capability configuration
among them — which this page does not document and which carry no compatibility
promise here.

### Per-element data binding

Expand DownExpand Up@@ -369,15 +406,16 @@ await dataSource.delete('user', user.id);

Batch writes on one object go through `bulk`, and cross-object writes that must
commit or roll back together go through `batchTransaction`. Both are optional on
the `DataSource` interface, so hold the adapter at its class type (or
feature-detect) before calling them:
the `DataSource` interface, so a value you annotated as `DataSource` still has to
feature-detect them — but the adapter implements both unconditionally and the
factory declares the adapter, so the value from Quick Start calls them directly:

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

type User = { id: string; name: string; email: string };

const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
const adapter = createObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });

await adapter.bulk('user', 'create', [
{ name: 'Alice', email: 'alice@example.com' },
Expand DownExpand Up@@ -463,17 +501,18 @@ An `AuthenticationError` (code `AUTHENTICATION_ERROR`, status 401) means the
`token` passed to `createObjectStackAdapter` was missing, expired or rejected.
Check the connection state and the values you passed in:

Connection introspection lives on the adapter class, not on the `DataSource`
interface, so hold it at the class type:
Connection introspection lives on the adapter, not on the `DataSource` interface
— and the factory declares the adapter, so the value from Quick Start reaches it
without a second construction:

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

const adapter = new ObjectStackAdapter({ baseUrl: 'https://api.example.com' });
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

console.log(adapter.getConnectionState()); // 'connected' | 'error' | ...
console.log(dataSource.getConnectionState()); // 'connected' | 'error' | ...

adapter.onConnectionStateChange((event) => {
dataSource.onConnectionStateChange((event) => {
if (event.error) console.error('Connection error:', event.error);
});
```
Expand Down
26 changes: 8 additions & 18 deletions packages/data-objectstack/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,16 +44,6 @@ 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
Expand DownExpand Up@@ -258,9 +248,9 @@ 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';
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Get cache statistics
const stats = dataSource.getCacheStats();
Expand DownExpand Up@@ -288,9 +278,9 @@ dataSource.clearCache();
The adapter provides real-time connection state monitoring with automatic reconnection:

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

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Monitor connection state changes
const unsubscribe = dataSource.onConnectionStateChange((event) => {
Expand DownExpand Up@@ -336,9 +326,9 @@ 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';
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

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

Expand DownExpand Up@@ -712,9 +702,9 @@ const dataSource = createObjectStackAdapter({
#### Cache Issues

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

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Clear cache if stale data is being returned
dataSource.clearCache();
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
79 changes: 79 additions & 0 deletions .changeset/7323-adapter-factory-return.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
---
'@object-ui/data-objectstack': minor
---

`createObjectStackAdapter` declares the adapter it returns, not the shared `DataSource`
interface (objectui#7323).

The factory returned `new ObjectStackAdapter(config)` while declaring `DataSource<T>`.
A wider value is assignable to a narrower annotation, so nothing ever failed to compile
— the loss was entirely on the reading side. Measured against the shipped
`dist/index.d.ts` with the doc-snippet gate's own compiler options, nine reads through
`ReturnType<typeof createObjectStackAdapter>` failed with TS2339: `getClient`,
`getCacheStats`, `invalidateCache`, `clearCache`, `getConnectionState`, `isConnected`,
`onConnectionStateChange`, `onBatchProgress` and `setSystemCapabilities`. Eight of those
nine reads are on this package's README API Reference list, and four whole README
sections are built on them; the ninth measured read is the one the factory's own JSDoc
links to (`[ADR-0066] See {@link ObjectStackAdapter.setSystemCapabilities}`). The README
list is itself **nine** adapter-only members, not eight — `connect()` is adapter-only
too and was documented all along; it simply was not one of the reads the card's
reproduction measured. So the file's own doc comment pointed the reader at a method its
declared return hid, and the two documented ways to obtain the same object — the factory
and `new ObjectStackAdapter(…)` — handed back different type surfaces.

**What the declared return now is: the whole class, not those nine reads.** The nine
above are what the reproduction measured, not the size of this change. The factory's
declared return is now `ObjectStackAdapter<T>` itself, so **every public member of the
class** is part of what the factory promises. Against `DataSource` that is **20**
members, not nine — `tsc`-computed as
`Exclude<keyof ObjectStackAdapter<unknown>, keyof DataSource<unknown>>`: `clearCache`,
`connect`, `getCacheStats`, `getCached`, `getClient`, `getConnectionState`,
`getDiscovery`, `getItems`, `invalidateCache`, `invalidateViewKeys`, `isConnected`,
`listImportMappings`, `onBatchProgress`, `onConnectionStateChange`, `onSaveAdvisory`,
`onWriteWarning`, `probeAppAccess`, `queryDataset`, `setSystemCapabilities`,
`updateDashboard`. The eleven past the documented nine were already in the shipped class
type — none is `@internal` or `@deprecated`, `stripInternal` is not set, and all were
already reachable through `new ObjectStackAdapter(…)` and through every
`ObjectStackAdapter`-typed seam in `@object-ui/react` and `app-shell` — so what widens
here is what the **factory declares**, not what the package ships. Two are escape-hatch
shaped and worth knowing before building on them: `getCached(key)` is a raw cache read,
and `getDiscovery()` reaches an internal property of the underlying `ObjectStackClient`.

**Branch taken: A (widen the factory's declared return), and why.** The card offered
three. B — moving caching, connection state and batch progress onto `DataSource` — was
rejected because those are this adapter's concerns, not every data source's; every other
`DataSource` implementation would then declare members it does not have. C — documenting
a cast — teaches a cast around a declaration that is merely narrower than the value,
which is the opposite of `declared = enforced`. A is one line and makes declared match
shipped for every documented member at once.

Two questions decided the shape and both were answered from the code before the diff.
`ObjectStackAdapter` was **already** exported from the package's only entry
(`src/index.ts`, tsup's single entry; the class is in the shipped `dist/index.d.ts`
export list, two pin tests assert the exported spelling, and `apps/console` re-exports it
by name) — so widening the return exports nothing by implication. And the narrow return
was **not** a deliberate swappability guarantee: no comment, ADR or test pinned it, and
the commit that added `autoReconnect` / `maxReconnectAttempts` / `reconnectDelay` to the
factory's own config bag left the members that observe those features off the factory's
declared return in the same change.

**One caller shape breaks: a structural stand-in for the factory's return.** A
hand-written object literal annotated `ReturnType<typeof createObjectStackAdapter>` no
longer satisfies that type, because it is now a class with private members (TS2740) —
annotate such a fake as `DataSource` instead, which is what it was standing in for.
Nothing else moves: a wider return is assignable to the narrower annotation, so
`const ds: DataSource = createObjectStackAdapter(…)` keeps compiling and keeps giving
the narrow surface to anyone who wants it.

The README's note saying the page could not yet teach the factory's shape is removed, and
the four sections built on the adapter-only members (Metadata Caching, Connection State
Monitoring, Batch Operation Progress, Troubleshooting → Cache Issues) now continue from
Basic Setup's `createObjectStackAdapter(…)` call instead of declaring the class by hand.
The docs-site page `content/docs/utilities/data-objectstack.mdx` is corrected the same
way: its prose, its factory signature fragment and its "hold the class type to reach
these" section described the old narrow return, and its Mutations and Troubleshooting
examples told the reader to construct the class by hand to reach members the factory now
declares. `src/adapterFactoryReturn.types.test.ts` pins the card's TS2339 reproduction
inverted,
with two controls: the adapter-only members stay absent from `DataSource` (fires on
option B), and the widened return stays assignable to `DataSource` (swappability kept).
99 changes: 69 additions & 30 deletions content/docs/utilities/data-objectstack.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,9 +50,11 @@ const dataSource = createObjectStackAdapter({
});
```

`createObjectStackAdapter` returns a `DataSource` — the same universal interface
every ObjectUI renderer consumes. `new ObjectStackAdapter(config)` is the class
form of the same thing.
`createObjectStackAdapter` returns an `ObjectStackAdapter` — the concrete adapter
class, which implements `DataSource`, the universal interface every ObjectUI
renderer consumes. `new ObjectStackAdapter(config)` is the class form of the same
thing and has the same type. Annotate the value as `DataSource` wherever you want
only the universal surface.

### 2. Inject it at the renderer boundary

Expand DownExpand Up@@ -106,11 +108,12 @@ full table of which blocks honour which keys.

### `createObjectStackAdapter`

Factory returning a `DataSource` backed by an ObjectStack backend.
Factory returning an `ObjectStackAdapter` — the concrete adapter class, which
implements `DataSource` — backed by an ObjectStack backend.

**Config:**

{/* doc-snippet: fragment — a SIGNATURE excerpt of the factory, quoted from its declaration so the config members can be annotated one by one: a `function` declaration with no body and a `DataSource` return type this reference block does not import (measured: TS2391 x1, TS2304 x1). Checked against the shipped `packages/data-objectstack/dist/index.d.ts`: every member listed here is declared there with the same type */}
{/* doc-snippet: fragment — a SIGNATURE excerpt of the factory, quoted from its declaration so the config members can be annotated one by one: a `function` declaration with no body and an `ObjectStackAdapter` return type this reference block does not import (measured: TS2391 x1, TS2304 x1). Because the block is DECLARED, this gate never compiles it, so the agreement with the shipped `packages/data-objectstack/dist/index.d.ts`every config member declared there with the same type, and the return type — is hand-checked at each edit, not gate-enforced; that gap is why the return type here outlived the change that widened it (objectui#7323) */}
```typescript
function createObjectStackAdapter<T = unknown>(config: {
/** ObjectStack server base URL */
Expand All@@ -132,7 +135,7 @@ function createObjectStackAdapter<T = unknown>(config: {
autoReconnect?: boolean; // default true
maxReconnectAttempts?: number; // default 3
reconnectDelay?: number; // default 1000 ms
}): DataSource<T>;
}): ObjectStackAdapter<T>;
```

**Example:**
Expand DownExpand Up@@ -161,20 +164,43 @@ const dataSource = createObjectStackAdapter<User>({ baseUrl: 'https://api.exampl

### `ObjectStackAdapter`

The class behind the factory. `new ObjectStackAdapter(config)` takes the same
config, but its declared type is the **concrete adapter** rather than the
`DataSource` interface the factory returns — which matters, because part of the
adapter's surface is not on that interface:
The class behind the factory — and the type the factory declares. `new
ObjectStackAdapter(config)` and `createObjectStackAdapter(config)` take the same
config and produce the same type, so the two forms are interchangeable:

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

type User = { id: string; name: string; email: string };

const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
// Same declared type, either way.
const fromFactory = createObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
const fromClass = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
```

**On the `DataSource` interface** (available from either form):
**What changed.** This section used to tell you to hold the class type to
reach the members listed under *Beyond `DataSource`* below, because the factory
declared `DataSource<T>` while returning `new ObjectStackAdapter(config)`: the
value always carried those members, only the declared type hid them
(objectui#7323). That distinction is gone. If you switched to
`new ObjectStackAdapter(...)` solely to reach a cache, connection-state or batch
method, you can switch back to the factory — nothing about the value changes, and
neither does its type. The class name is still worth importing when you need
something to annotate with.

Narrowing still works, and is still the right shape for a prop, a field or a test
double that must accept **any** adapter:

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

// The universal surface only, by annotation.
const dataSource: DataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });
```

**On the `DataSource` interface** (available from either form, and from any other
adapter):

- `find(resource, params?)` - Query multiple records
- `findOne(resource, id, params?)` - Get a single record by ID
Expand All@@ -184,22 +210,33 @@ const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com
- `getObjectSchema(objectName)` - Fetch schema metadata (cached)
- `bulk?(resource, operation, data)` - Batch create/update/delete on one object
- `batchTransaction?(operations)` - Cross-object atomic batch (master-detail)
- `onMutation?(listener)` - Subscribe to create/update/delete events

`bulk` and `batchTransaction` are **optional** members of `DataSource`: not every
adapter implements them, so through a `DataSource`-typed value they must be
feature-detected (`typeof dataSource.bulk === 'function'`). `ObjectStackAdapter`
implements both unconditionally, so a value held at the class type calls them
directly.
`bulk`, `batchTransaction` and `onMutation` are **optional** members of
`DataSource`: not every adapter implements them, so through a `DataSource`-typed
value they must be feature-detected (`typeof dataSource.bulk === 'function'`).
`ObjectStackAdapter` implements all three unconditionally, and the factory
declares the class, so a value from either form calls them directly. (`onMutation`
was listed under *Adapter-only* here until objectui#7323; it is optional on
`DataSource`, not absent from it.)

**Adapter-only** (hold the class type to reach these):
**Beyond `DataSource`** (declared on `ObjectStackAdapter`, so reachable from
either form):

- `connect()` - Establish the connection (called lazily by every operation)
- `getCacheStats()` / `invalidateCache(key?)` / `clearCache()` - Cache control
- `getClient()` - Access the underlying `@objectstack/client` instance
- `getConnectionState()` / `isConnected()` - Connection introspection
- `onConnectionStateChange(listener)` - Subscribe to state changes (returns unsubscribe)
- `onBatchProgress(listener)` - Subscribe to bulk progress (returns unsubscribe)
- `onMutation(listener)` - Subscribe to create/update/delete events

Those six bullets cover nine members, and they are the documented subset.
`ObjectStackAdapter` declares **20** members beyond `DataSource` in all
(`Exclude<keyof ObjectStackAdapter<unknown>, keyof DataSource<unknown>>`); the
other eleven are lower-level seams — client discovery, cache-key invalidation,
dataset and dashboard access, advisory subscriptions and capability configuration
among them — which this page does not document and which carry no compatibility
promise here.

### Per-element data binding

Expand DownExpand Up@@ -369,15 +406,16 @@ await dataSource.delete('user', user.id);

Batch writes on one object go through `bulk`, and cross-object writes that must
commit or roll back together go through `batchTransaction`. Both are optional on
the `DataSource` interface, so hold the adapter at its class type (or
feature-detect) before calling them:
the `DataSource` interface, so a value you annotated as `DataSource` still has to
feature-detect them — but the adapter implements both unconditionally and the
factory declares the adapter, so the value from Quick Start calls them directly:

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

type User = { id: string; name: string; email: string };

const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
const adapter = createObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });

await adapter.bulk('user', 'create', [
{ name: 'Alice', email: 'alice@example.com' },
Expand DownExpand Up@@ -463,17 +501,18 @@ An `AuthenticationError` (code `AUTHENTICATION_ERROR`, status 401) means the
`token` passed to `createObjectStackAdapter` was missing, expired or rejected.
Check the connection state and the values you passed in:

Connection introspection lives on the adapter class, not on the `DataSource`
interface, so hold it at the class type:
Connection introspection lives on the adapter, not on the `DataSource` interface
— and the factory declares the adapter, so the value from Quick Start reaches it
without a second construction:

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

const adapter = new ObjectStackAdapter({ baseUrl: 'https://api.example.com' });
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

console.log(adapter.getConnectionState()); // 'connected' | 'error' | ...
console.log(dataSource.getConnectionState()); // 'connected' | 'error' | ...

adapter.onConnectionStateChange((event) => {
dataSource.onConnectionStateChange((event) => {
if (event.error) console.error('Connection error:', event.error);
});
```
Expand Down
26 changes: 8 additions & 18 deletions packages/data-objectstack/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,16 +44,6 @@ 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
Expand DownExpand Up@@ -258,9 +248,9 @@ 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';
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Get cache statistics
const stats = dataSource.getCacheStats();
Expand DownExpand Up@@ -288,9 +278,9 @@ dataSource.clearCache();
The adapter provides real-time connection state monitoring with automatic reconnection:

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

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Monitor connection state changes
const unsubscribe = dataSource.onConnectionStateChange((event) => {
Expand DownExpand Up@@ -336,9 +326,9 @@ 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';
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

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

Expand DownExpand Up@@ -712,9 +702,9 @@ const dataSource = createObjectStackAdapter({
#### Cache Issues

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

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Clear cache if stale data is being returned
dataSource.clearCache();
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
79 changes: 79 additions & 0 deletions .changeset/7323-adapter-factory-return.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
---
'@object-ui/data-objectstack': minor
---

`createObjectStackAdapter` declares the adapter it returns, not the shared `DataSource`
interface (objectui#7323).

The factory returned `new ObjectStackAdapter(config)` while declaring `DataSource<T>`.
A wider value is assignable to a narrower annotation, so nothing ever failed to compile
— the loss was entirely on the reading side. Measured against the shipped
`dist/index.d.ts` with the doc-snippet gate's own compiler options, nine reads through
`ReturnType<typeof createObjectStackAdapter>` failed with TS2339: `getClient`,
`getCacheStats`, `invalidateCache`, `clearCache`, `getConnectionState`, `isConnected`,
`onConnectionStateChange`, `onBatchProgress` and `setSystemCapabilities`. Eight of those
nine reads are on this package's README API Reference list, and four whole README
sections are built on them; the ninth measured read is the one the factory's own JSDoc
links to (`[ADR-0066] See {@link ObjectStackAdapter.setSystemCapabilities}`). The README
list is itself **nine** adapter-only members, not eight — `connect()` is adapter-only
too and was documented all along; it simply was not one of the reads the card's
reproduction measured. So the file's own doc comment pointed the reader at a method its
declared return hid, and the two documented ways to obtain the same object — the factory
and `new ObjectStackAdapter(…)` — handed back different type surfaces.

**What the declared return now is: the whole class, not those nine reads.** The nine
above are what the reproduction measured, not the size of this change. The factory's
declared return is now `ObjectStackAdapter<T>` itself, so **every public member of the
class** is part of what the factory promises. Against `DataSource` that is **20**
members, not nine — `tsc`-computed as
`Exclude<keyof ObjectStackAdapter<unknown>, keyof DataSource<unknown>>`: `clearCache`,
`connect`, `getCacheStats`, `getCached`, `getClient`, `getConnectionState`,
`getDiscovery`, `getItems`, `invalidateCache`, `invalidateViewKeys`, `isConnected`,
`listImportMappings`, `onBatchProgress`, `onConnectionStateChange`, `onSaveAdvisory`,
`onWriteWarning`, `probeAppAccess`, `queryDataset`, `setSystemCapabilities`,
`updateDashboard`. The eleven past the documented nine were already in the shipped class
type — none is `@internal` or `@deprecated`, `stripInternal` is not set, and all were
already reachable through `new ObjectStackAdapter(…)` and through every
`ObjectStackAdapter`-typed seam in `@object-ui/react` and `app-shell` — so what widens
here is what the **factory declares**, not what the package ships. Two are escape-hatch
shaped and worth knowing before building on them: `getCached(key)` is a raw cache read,
and `getDiscovery()` reaches an internal property of the underlying `ObjectStackClient`.

**Branch taken: A (widen the factory's declared return), and why.** The card offered
three. B — moving caching, connection state and batch progress onto `DataSource` — was
rejected because those are this adapter's concerns, not every data source's; every other
`DataSource` implementation would then declare members it does not have. C — documenting
a cast — teaches a cast around a declaration that is merely narrower than the value,
which is the opposite of `declared = enforced`. A is one line and makes declared match
shipped for every documented member at once.

Two questions decided the shape and both were answered from the code before the diff.
`ObjectStackAdapter` was **already** exported from the package's only entry
(`src/index.ts`, tsup's single entry; the class is in the shipped `dist/index.d.ts`
export list, two pin tests assert the exported spelling, and `apps/console` re-exports it
by name) — so widening the return exports nothing by implication. And the narrow return
was **not** a deliberate swappability guarantee: no comment, ADR or test pinned it, and
the commit that added `autoReconnect` / `maxReconnectAttempts` / `reconnectDelay` to the
factory's own config bag left the members that observe those features off the factory's
declared return in the same change.

**One caller shape breaks: a structural stand-in for the factory's return.** A
hand-written object literal annotated `ReturnType<typeof createObjectStackAdapter>` no
longer satisfies that type, because it is now a class with private members (TS2740) —
annotate such a fake as `DataSource` instead, which is what it was standing in for.
Nothing else moves: a wider return is assignable to the narrower annotation, so
`const ds: DataSource = createObjectStackAdapter(…)` keeps compiling and keeps giving
the narrow surface to anyone who wants it.

The README's note saying the page could not yet teach the factory's shape is removed, and
the four sections built on the adapter-only members (Metadata Caching, Connection State
Monitoring, Batch Operation Progress, Troubleshooting → Cache Issues) now continue from
Basic Setup's `createObjectStackAdapter(…)` call instead of declaring the class by hand.
The docs-site page `content/docs/utilities/data-objectstack.mdx` is corrected the same
way: its prose, its factory signature fragment and its "hold the class type to reach
these" section described the old narrow return, and its Mutations and Troubleshooting
examples told the reader to construct the class by hand to reach members the factory now
declares. `src/adapterFactoryReturn.types.test.ts` pins the card's TS2339 reproduction
inverted,
with two controls: the adapter-only members stay absent from `DataSource` (fires on
option B), and the widened return stays assignable to `DataSource` (swappability kept).
99 changes: 69 additions & 30 deletions content/docs/utilities/data-objectstack.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,9 +50,11 @@ const dataSource = createObjectStackAdapter({
});
```

`createObjectStackAdapter` returns a `DataSource` — the same universal interface
every ObjectUI renderer consumes. `new ObjectStackAdapter(config)` is the class
form of the same thing.
`createObjectStackAdapter` returns an `ObjectStackAdapter` — the concrete adapter
class, which implements `DataSource`, the universal interface every ObjectUI
renderer consumes. `new ObjectStackAdapter(config)` is the class form of the same
thing and has the same type. Annotate the value as `DataSource` wherever you want
only the universal surface.

### 2. Inject it at the renderer boundary

Expand DownExpand Up@@ -106,11 +108,12 @@ full table of which blocks honour which keys.

### `createObjectStackAdapter`

Factory returning a `DataSource` backed by an ObjectStack backend.
Factory returning an `ObjectStackAdapter` — the concrete adapter class, which
implements `DataSource` — backed by an ObjectStack backend.

**Config:**

{/* doc-snippet: fragment — a SIGNATURE excerpt of the factory, quoted from its declaration so the config members can be annotated one by one: a `function` declaration with no body and a `DataSource` return type this reference block does not import (measured: TS2391 x1, TS2304 x1). Checked against the shipped `packages/data-objectstack/dist/index.d.ts`: every member listed here is declared there with the same type */}
{/* doc-snippet: fragment — a SIGNATURE excerpt of the factory, quoted from its declaration so the config members can be annotated one by one: a `function` declaration with no body and an `ObjectStackAdapter` return type this reference block does not import (measured: TS2391 x1, TS2304 x1). Because the block is DECLARED, this gate never compiles it, so the agreement with the shipped `packages/data-objectstack/dist/index.d.ts`every config member declared there with the same type, and the return type — is hand-checked at each edit, not gate-enforced; that gap is why the return type here outlived the change that widened it (objectui#7323) */}
```typescript
function createObjectStackAdapter<T = unknown>(config: {
/** ObjectStack server base URL */
Expand All@@ -132,7 +135,7 @@ function createObjectStackAdapter<T = unknown>(config: {
autoReconnect?: boolean; // default true
maxReconnectAttempts?: number; // default 3
reconnectDelay?: number; // default 1000 ms
}): DataSource<T>;
}): ObjectStackAdapter<T>;
```

**Example:**
Expand DownExpand Up@@ -161,20 +164,43 @@ const dataSource = createObjectStackAdapter<User>({ baseUrl: 'https://api.exampl

### `ObjectStackAdapter`

The class behind the factory. `new ObjectStackAdapter(config)` takes the same
config, but its declared type is the **concrete adapter** rather than the
`DataSource` interface the factory returns — which matters, because part of the
adapter's surface is not on that interface:
The class behind the factory — and the type the factory declares. `new
ObjectStackAdapter(config)` and `createObjectStackAdapter(config)` take the same
config and produce the same type, so the two forms are interchangeable:

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

type User = { id: string; name: string; email: string };

const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
// Same declared type, either way.
const fromFactory = createObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
const fromClass = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
```

**On the `DataSource` interface** (available from either form):
**What changed.** This section used to tell you to hold the class type to
reach the members listed under *Beyond `DataSource`* below, because the factory
declared `DataSource<T>` while returning `new ObjectStackAdapter(config)`: the
value always carried those members, only the declared type hid them
(objectui#7323). That distinction is gone. If you switched to
`new ObjectStackAdapter(...)` solely to reach a cache, connection-state or batch
method, you can switch back to the factory — nothing about the value changes, and
neither does its type. The class name is still worth importing when you need
something to annotate with.

Narrowing still works, and is still the right shape for a prop, a field or a test
double that must accept **any** adapter:

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

// The universal surface only, by annotation.
const dataSource: DataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });
```

**On the `DataSource` interface** (available from either form, and from any other
adapter):

- `find(resource, params?)` - Query multiple records
- `findOne(resource, id, params?)` - Get a single record by ID
Expand All@@ -184,22 +210,33 @@ const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com
- `getObjectSchema(objectName)` - Fetch schema metadata (cached)
- `bulk?(resource, operation, data)` - Batch create/update/delete on one object
- `batchTransaction?(operations)` - Cross-object atomic batch (master-detail)
- `onMutation?(listener)` - Subscribe to create/update/delete events

`bulk` and `batchTransaction` are **optional** members of `DataSource`: not every
adapter implements them, so through a `DataSource`-typed value they must be
feature-detected (`typeof dataSource.bulk === 'function'`). `ObjectStackAdapter`
implements both unconditionally, so a value held at the class type calls them
directly.
`bulk`, `batchTransaction` and `onMutation` are **optional** members of
`DataSource`: not every adapter implements them, so through a `DataSource`-typed
value they must be feature-detected (`typeof dataSource.bulk === 'function'`).
`ObjectStackAdapter` implements all three unconditionally, and the factory
declares the class, so a value from either form calls them directly. (`onMutation`
was listed under *Adapter-only* here until objectui#7323; it is optional on
`DataSource`, not absent from it.)

**Adapter-only** (hold the class type to reach these):
**Beyond `DataSource`** (declared on `ObjectStackAdapter`, so reachable from
either form):

- `connect()` - Establish the connection (called lazily by every operation)
- `getCacheStats()` / `invalidateCache(key?)` / `clearCache()` - Cache control
- `getClient()` - Access the underlying `@objectstack/client` instance
- `getConnectionState()` / `isConnected()` - Connection introspection
- `onConnectionStateChange(listener)` - Subscribe to state changes (returns unsubscribe)
- `onBatchProgress(listener)` - Subscribe to bulk progress (returns unsubscribe)
- `onMutation(listener)` - Subscribe to create/update/delete events

Those six bullets cover nine members, and they are the documented subset.
`ObjectStackAdapter` declares **20** members beyond `DataSource` in all
(`Exclude<keyof ObjectStackAdapter<unknown>, keyof DataSource<unknown>>`); the
other eleven are lower-level seams — client discovery, cache-key invalidation,
dataset and dashboard access, advisory subscriptions and capability configuration
among them — which this page does not document and which carry no compatibility
promise here.

### Per-element data binding

Expand DownExpand Up@@ -369,15 +406,16 @@ await dataSource.delete('user', user.id);

Batch writes on one object go through `bulk`, and cross-object writes that must
commit or roll back together go through `batchTransaction`. Both are optional on
the `DataSource` interface, so hold the adapter at its class type (or
feature-detect) before calling them:
the `DataSource` interface, so a value you annotated as `DataSource` still has to
feature-detect them — but the adapter implements both unconditionally and the
factory declares the adapter, so the value from Quick Start calls them directly:

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

type User = { id: string; name: string; email: string };

const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
const adapter = createObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });

await adapter.bulk('user', 'create', [
{ name: 'Alice', email: 'alice@example.com' },
Expand DownExpand Up@@ -463,17 +501,18 @@ An `AuthenticationError` (code `AUTHENTICATION_ERROR`, status 401) means the
`token` passed to `createObjectStackAdapter` was missing, expired or rejected.
Check the connection state and the values you passed in:

Connection introspection lives on the adapter class, not on the `DataSource`
interface, so hold it at the class type:
Connection introspection lives on the adapter, not on the `DataSource` interface
— and the factory declares the adapter, so the value from Quick Start reaches it
without a second construction:

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

const adapter = new ObjectStackAdapter({ baseUrl: 'https://api.example.com' });
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

console.log(adapter.getConnectionState()); // 'connected' | 'error' | ...
console.log(dataSource.getConnectionState()); // 'connected' | 'error' | ...

adapter.onConnectionStateChange((event) => {
dataSource.onConnectionStateChange((event) => {
if (event.error) console.error('Connection error:', event.error);
});
```
Expand Down
26 changes: 8 additions & 18 deletions packages/data-objectstack/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,16 +44,6 @@ 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
Expand DownExpand Up@@ -258,9 +248,9 @@ 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';
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Get cache statistics
const stats = dataSource.getCacheStats();
Expand DownExpand Up@@ -288,9 +278,9 @@ dataSource.clearCache();
The adapter provides real-time connection state monitoring with automatic reconnection:

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

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Monitor connection state changes
const unsubscribe = dataSource.onConnectionStateChange((event) => {
Expand DownExpand Up@@ -336,9 +326,9 @@ 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';
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

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

Expand DownExpand Up@@ -712,9 +702,9 @@ const dataSource = createObjectStackAdapter({
#### Cache Issues

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

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Clear cache if stale data is being returned
dataSource.clearCache();
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
79 changes: 79 additions & 0 deletions .changeset/7323-adapter-factory-return.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
---
'@object-ui/data-objectstack': minor
---

`createObjectStackAdapter` declares the adapter it returns, not the shared `DataSource`
interface (objectui#7323).

The factory returned `new ObjectStackAdapter(config)` while declaring `DataSource<T>`.
A wider value is assignable to a narrower annotation, so nothing ever failed to compile
— the loss was entirely on the reading side. Measured against the shipped
`dist/index.d.ts` with the doc-snippet gate's own compiler options, nine reads through
`ReturnType<typeof createObjectStackAdapter>` failed with TS2339: `getClient`,
`getCacheStats`, `invalidateCache`, `clearCache`, `getConnectionState`, `isConnected`,
`onConnectionStateChange`, `onBatchProgress` and `setSystemCapabilities`. Eight of those
nine reads are on this package's README API Reference list, and four whole README
sections are built on them; the ninth measured read is the one the factory's own JSDoc
links to (`[ADR-0066] See {@link ObjectStackAdapter.setSystemCapabilities}`). The README
list is itself **nine** adapter-only members, not eight — `connect()` is adapter-only
too and was documented all along; it simply was not one of the reads the card's
reproduction measured. So the file's own doc comment pointed the reader at a method its
declared return hid, and the two documented ways to obtain the same object — the factory
and `new ObjectStackAdapter(…)` — handed back different type surfaces.

**What the declared return now is: the whole class, not those nine reads.** The nine
above are what the reproduction measured, not the size of this change. The factory's
declared return is now `ObjectStackAdapter<T>` itself, so **every public member of the
class** is part of what the factory promises. Against `DataSource` that is **20**
members, not nine — `tsc`-computed as
`Exclude<keyof ObjectStackAdapter<unknown>, keyof DataSource<unknown>>`: `clearCache`,
`connect`, `getCacheStats`, `getCached`, `getClient`, `getConnectionState`,
`getDiscovery`, `getItems`, `invalidateCache`, `invalidateViewKeys`, `isConnected`,
`listImportMappings`, `onBatchProgress`, `onConnectionStateChange`, `onSaveAdvisory`,
`onWriteWarning`, `probeAppAccess`, `queryDataset`, `setSystemCapabilities`,
`updateDashboard`. The eleven past the documented nine were already in the shipped class
type — none is `@internal` or `@deprecated`, `stripInternal` is not set, and all were
already reachable through `new ObjectStackAdapter(…)` and through every
`ObjectStackAdapter`-typed seam in `@object-ui/react` and `app-shell` — so what widens
here is what the **factory declares**, not what the package ships. Two are escape-hatch
shaped and worth knowing before building on them: `getCached(key)` is a raw cache read,
and `getDiscovery()` reaches an internal property of the underlying `ObjectStackClient`.

**Branch taken: A (widen the factory's declared return), and why.** The card offered
three. B — moving caching, connection state and batch progress onto `DataSource` — was
rejected because those are this adapter's concerns, not every data source's; every other
`DataSource` implementation would then declare members it does not have. C — documenting
a cast — teaches a cast around a declaration that is merely narrower than the value,
which is the opposite of `declared = enforced`. A is one line and makes declared match
shipped for every documented member at once.

Two questions decided the shape and both were answered from the code before the diff.
`ObjectStackAdapter` was **already** exported from the package's only entry
(`src/index.ts`, tsup's single entry; the class is in the shipped `dist/index.d.ts`
export list, two pin tests assert the exported spelling, and `apps/console` re-exports it
by name) — so widening the return exports nothing by implication. And the narrow return
was **not** a deliberate swappability guarantee: no comment, ADR or test pinned it, and
the commit that added `autoReconnect` / `maxReconnectAttempts` / `reconnectDelay` to the
factory's own config bag left the members that observe those features off the factory's
declared return in the same change.

**One caller shape breaks: a structural stand-in for the factory's return.** A
hand-written object literal annotated `ReturnType<typeof createObjectStackAdapter>` no
longer satisfies that type, because it is now a class with private members (TS2740) —
annotate such a fake as `DataSource` instead, which is what it was standing in for.
Nothing else moves: a wider return is assignable to the narrower annotation, so
`const ds: DataSource = createObjectStackAdapter(…)` keeps compiling and keeps giving
the narrow surface to anyone who wants it.

The README's note saying the page could not yet teach the factory's shape is removed, and
the four sections built on the adapter-only members (Metadata Caching, Connection State
Monitoring, Batch Operation Progress, Troubleshooting → Cache Issues) now continue from
Basic Setup's `createObjectStackAdapter(…)` call instead of declaring the class by hand.
The docs-site page `content/docs/utilities/data-objectstack.mdx` is corrected the same
way: its prose, its factory signature fragment and its "hold the class type to reach
these" section described the old narrow return, and its Mutations and Troubleshooting
examples told the reader to construct the class by hand to reach members the factory now
declares. `src/adapterFactoryReturn.types.test.ts` pins the card's TS2339 reproduction
inverted,
with two controls: the adapter-only members stay absent from `DataSource` (fires on
option B), and the widened return stays assignable to `DataSource` (swappability kept).
99 changes: 69 additions & 30 deletions content/docs/utilities/data-objectstack.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,9 +50,11 @@ const dataSource = createObjectStackAdapter({
});
```

`createObjectStackAdapter` returns a `DataSource` — the same universal interface
every ObjectUI renderer consumes. `new ObjectStackAdapter(config)` is the class
form of the same thing.
`createObjectStackAdapter` returns an `ObjectStackAdapter` — the concrete adapter
class, which implements `DataSource`, the universal interface every ObjectUI
renderer consumes. `new ObjectStackAdapter(config)` is the class form of the same
thing and has the same type. Annotate the value as `DataSource` wherever you want
only the universal surface.

### 2. Inject it at the renderer boundary

Expand DownExpand Up@@ -106,11 +108,12 @@ full table of which blocks honour which keys.

### `createObjectStackAdapter`

Factory returning a `DataSource` backed by an ObjectStack backend.
Factory returning an `ObjectStackAdapter` — the concrete adapter class, which
implements `DataSource` — backed by an ObjectStack backend.

**Config:**

{/* doc-snippet: fragment — a SIGNATURE excerpt of the factory, quoted from its declaration so the config members can be annotated one by one: a `function` declaration with no body and a `DataSource` return type this reference block does not import (measured: TS2391 x1, TS2304 x1). Checked against the shipped `packages/data-objectstack/dist/index.d.ts`: every member listed here is declared there with the same type */}
{/* doc-snippet: fragment — a SIGNATURE excerpt of the factory, quoted from its declaration so the config members can be annotated one by one: a `function` declaration with no body and an `ObjectStackAdapter` return type this reference block does not import (measured: TS2391 x1, TS2304 x1). Because the block is DECLARED, this gate never compiles it, so the agreement with the shipped `packages/data-objectstack/dist/index.d.ts`every config member declared there with the same type, and the return type — is hand-checked at each edit, not gate-enforced; that gap is why the return type here outlived the change that widened it (objectui#7323) */}
```typescript
function createObjectStackAdapter<T = unknown>(config: {
/** ObjectStack server base URL */
Expand All@@ -132,7 +135,7 @@ function createObjectStackAdapter<T = unknown>(config: {
autoReconnect?: boolean; // default true
maxReconnectAttempts?: number; // default 3
reconnectDelay?: number; // default 1000 ms
}): DataSource<T>;
}): ObjectStackAdapter<T>;
```

**Example:**
Expand DownExpand Up@@ -161,20 +164,43 @@ const dataSource = createObjectStackAdapter<User>({ baseUrl: 'https://api.exampl

### `ObjectStackAdapter`

The class behind the factory. `new ObjectStackAdapter(config)` takes the same
config, but its declared type is the **concrete adapter** rather than the
`DataSource` interface the factory returns — which matters, because part of the
adapter's surface is not on that interface:
The class behind the factory — and the type the factory declares. `new
ObjectStackAdapter(config)` and `createObjectStackAdapter(config)` take the same
config and produce the same type, so the two forms are interchangeable:

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

type User = { id: string; name: string; email: string };

const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
// Same declared type, either way.
const fromFactory = createObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
const fromClass = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
```

**On the `DataSource` interface** (available from either form):
**What changed.** This section used to tell you to hold the class type to
reach the members listed under *Beyond `DataSource`* below, because the factory
declared `DataSource<T>` while returning `new ObjectStackAdapter(config)`: the
value always carried those members, only the declared type hid them
(objectui#7323). That distinction is gone. If you switched to
`new ObjectStackAdapter(...)` solely to reach a cache, connection-state or batch
method, you can switch back to the factory — nothing about the value changes, and
neither does its type. The class name is still worth importing when you need
something to annotate with.

Narrowing still works, and is still the right shape for a prop, a field or a test
double that must accept **any** adapter:

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

// The universal surface only, by annotation.
const dataSource: DataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });
```

**On the `DataSource` interface** (available from either form, and from any other
adapter):

- `find(resource, params?)` - Query multiple records
- `findOne(resource, id, params?)` - Get a single record by ID
Expand All@@ -184,22 +210,33 @@ const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com
- `getObjectSchema(objectName)` - Fetch schema metadata (cached)
- `bulk?(resource, operation, data)` - Batch create/update/delete on one object
- `batchTransaction?(operations)` - Cross-object atomic batch (master-detail)
- `onMutation?(listener)` - Subscribe to create/update/delete events

`bulk` and `batchTransaction` are **optional** members of `DataSource`: not every
adapter implements them, so through a `DataSource`-typed value they must be
feature-detected (`typeof dataSource.bulk === 'function'`). `ObjectStackAdapter`
implements both unconditionally, so a value held at the class type calls them
directly.
`bulk`, `batchTransaction` and `onMutation` are **optional** members of
`DataSource`: not every adapter implements them, so through a `DataSource`-typed
value they must be feature-detected (`typeof dataSource.bulk === 'function'`).
`ObjectStackAdapter` implements all three unconditionally, and the factory
declares the class, so a value from either form calls them directly. (`onMutation`
was listed under *Adapter-only* here until objectui#7323; it is optional on
`DataSource`, not absent from it.)

**Adapter-only** (hold the class type to reach these):
**Beyond `DataSource`** (declared on `ObjectStackAdapter`, so reachable from
either form):

- `connect()` - Establish the connection (called lazily by every operation)
- `getCacheStats()` / `invalidateCache(key?)` / `clearCache()` - Cache control
- `getClient()` - Access the underlying `@objectstack/client` instance
- `getConnectionState()` / `isConnected()` - Connection introspection
- `onConnectionStateChange(listener)` - Subscribe to state changes (returns unsubscribe)
- `onBatchProgress(listener)` - Subscribe to bulk progress (returns unsubscribe)
- `onMutation(listener)` - Subscribe to create/update/delete events

Those six bullets cover nine members, and they are the documented subset.
`ObjectStackAdapter` declares **20** members beyond `DataSource` in all
(`Exclude<keyof ObjectStackAdapter<unknown>, keyof DataSource<unknown>>`); the
other eleven are lower-level seams — client discovery, cache-key invalidation,
dataset and dashboard access, advisory subscriptions and capability configuration
among them — which this page does not document and which carry no compatibility
promise here.

### Per-element data binding

Expand DownExpand Up@@ -369,15 +406,16 @@ await dataSource.delete('user', user.id);

Batch writes on one object go through `bulk`, and cross-object writes that must
commit or roll back together go through `batchTransaction`. Both are optional on
the `DataSource` interface, so hold the adapter at its class type (or
feature-detect) before calling them:
the `DataSource` interface, so a value you annotated as `DataSource` still has to
feature-detect them — but the adapter implements both unconditionally and the
factory declares the adapter, so the value from Quick Start calls them directly:

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

type User = { id: string; name: string; email: string };

const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
const adapter = createObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });

await adapter.bulk('user', 'create', [
{ name: 'Alice', email: 'alice@example.com' },
Expand DownExpand Up@@ -463,17 +501,18 @@ An `AuthenticationError` (code `AUTHENTICATION_ERROR`, status 401) means the
`token` passed to `createObjectStackAdapter` was missing, expired or rejected.
Check the connection state and the values you passed in:

Connection introspection lives on the adapter class, not on the `DataSource`
interface, so hold it at the class type:
Connection introspection lives on the adapter, not on the `DataSource` interface
— and the factory declares the adapter, so the value from Quick Start reaches it
without a second construction:

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

const adapter = new ObjectStackAdapter({ baseUrl: 'https://api.example.com' });
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

console.log(adapter.getConnectionState()); // 'connected' | 'error' | ...
console.log(dataSource.getConnectionState()); // 'connected' | 'error' | ...

adapter.onConnectionStateChange((event) => {
dataSource.onConnectionStateChange((event) => {
if (event.error) console.error('Connection error:', event.error);
});
```
Expand Down
26 changes: 8 additions & 18 deletions packages/data-objectstack/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,16 +44,6 @@ 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
Expand DownExpand Up@@ -258,9 +248,9 @@ 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';
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Get cache statistics
const stats = dataSource.getCacheStats();
Expand DownExpand Up@@ -288,9 +278,9 @@ dataSource.clearCache();
The adapter provides real-time connection state monitoring with automatic reconnection:

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

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Monitor connection state changes
const unsubscribe = dataSource.onConnectionStateChange((event) => {
Expand DownExpand Up@@ -336,9 +326,9 @@ 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';
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

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

Expand DownExpand Up@@ -712,9 +702,9 @@ const dataSource = createObjectStackAdapter({
#### Cache Issues

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

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Clear cache if stale data is being returned
dataSource.clearCache();
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
79 changes: 79 additions & 0 deletions .changeset/7323-adapter-factory-return.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
---
'@object-ui/data-objectstack': minor
---

`createObjectStackAdapter` declares the adapter it returns, not the shared `DataSource`
interface (objectui#7323).

The factory returned `new ObjectStackAdapter(config)` while declaring `DataSource<T>`.
A wider value is assignable to a narrower annotation, so nothing ever failed to compile
— the loss was entirely on the reading side. Measured against the shipped
`dist/index.d.ts` with the doc-snippet gate's own compiler options, nine reads through
`ReturnType<typeof createObjectStackAdapter>` failed with TS2339: `getClient`,
`getCacheStats`, `invalidateCache`, `clearCache`, `getConnectionState`, `isConnected`,
`onConnectionStateChange`, `onBatchProgress` and `setSystemCapabilities`. Eight of those
nine reads are on this package's README API Reference list, and four whole README
sections are built on them; the ninth measured read is the one the factory's own JSDoc
links to (`[ADR-0066] See {@link ObjectStackAdapter.setSystemCapabilities}`). The README
list is itself **nine** adapter-only members, not eight — `connect()` is adapter-only
too and was documented all along; it simply was not one of the reads the card's
reproduction measured. So the file's own doc comment pointed the reader at a method its
declared return hid, and the two documented ways to obtain the same object — the factory
and `new ObjectStackAdapter(…)` — handed back different type surfaces.

**What the declared return now is: the whole class, not those nine reads.** The nine
above are what the reproduction measured, not the size of this change. The factory's
declared return is now `ObjectStackAdapter<T>` itself, so **every public member of the
class** is part of what the factory promises. Against `DataSource` that is **20**
members, not nine — `tsc`-computed as
`Exclude<keyof ObjectStackAdapter<unknown>, keyof DataSource<unknown>>`: `clearCache`,
`connect`, `getCacheStats`, `getCached`, `getClient`, `getConnectionState`,
`getDiscovery`, `getItems`, `invalidateCache`, `invalidateViewKeys`, `isConnected`,
`listImportMappings`, `onBatchProgress`, `onConnectionStateChange`, `onSaveAdvisory`,
`onWriteWarning`, `probeAppAccess`, `queryDataset`, `setSystemCapabilities`,
`updateDashboard`. The eleven past the documented nine were already in the shipped class
type — none is `@internal` or `@deprecated`, `stripInternal` is not set, and all were
already reachable through `new ObjectStackAdapter(…)` and through every
`ObjectStackAdapter`-typed seam in `@object-ui/react` and `app-shell` — so what widens
here is what the **factory declares**, not what the package ships. Two are escape-hatch
shaped and worth knowing before building on them: `getCached(key)` is a raw cache read,
and `getDiscovery()` reaches an internal property of the underlying `ObjectStackClient`.

**Branch taken: A (widen the factory's declared return), and why.** The card offered
three. B — moving caching, connection state and batch progress onto `DataSource` — was
rejected because those are this adapter's concerns, not every data source's; every other
`DataSource` implementation would then declare members it does not have. C — documenting
a cast — teaches a cast around a declaration that is merely narrower than the value,
which is the opposite of `declared = enforced`. A is one line and makes declared match
shipped for every documented member at once.

Two questions decided the shape and both were answered from the code before the diff.
`ObjectStackAdapter` was **already** exported from the package's only entry
(`src/index.ts`, tsup's single entry; the class is in the shipped `dist/index.d.ts`
export list, two pin tests assert the exported spelling, and `apps/console` re-exports it
by name) — so widening the return exports nothing by implication. And the narrow return
was **not** a deliberate swappability guarantee: no comment, ADR or test pinned it, and
the commit that added `autoReconnect` / `maxReconnectAttempts` / `reconnectDelay` to the
factory's own config bag left the members that observe those features off the factory's
declared return in the same change.

**One caller shape breaks: a structural stand-in for the factory's return.** A
hand-written object literal annotated `ReturnType<typeof createObjectStackAdapter>` no
longer satisfies that type, because it is now a class with private members (TS2740) —
annotate such a fake as `DataSource` instead, which is what it was standing in for.
Nothing else moves: a wider return is assignable to the narrower annotation, so
`const ds: DataSource = createObjectStackAdapter(…)` keeps compiling and keeps giving
the narrow surface to anyone who wants it.

The README's note saying the page could not yet teach the factory's shape is removed, and
the four sections built on the adapter-only members (Metadata Caching, Connection State
Monitoring, Batch Operation Progress, Troubleshooting → Cache Issues) now continue from
Basic Setup's `createObjectStackAdapter(…)` call instead of declaring the class by hand.
The docs-site page `content/docs/utilities/data-objectstack.mdx` is corrected the same
way: its prose, its factory signature fragment and its "hold the class type to reach
these" section described the old narrow return, and its Mutations and Troubleshooting
examples told the reader to construct the class by hand to reach members the factory now
declares. `src/adapterFactoryReturn.types.test.ts` pins the card's TS2339 reproduction
inverted,
with two controls: the adapter-only members stay absent from `DataSource` (fires on
option B), and the widened return stays assignable to `DataSource` (swappability kept).
99 changes: 69 additions & 30 deletions content/docs/utilities/data-objectstack.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,9 +50,11 @@ const dataSource = createObjectStackAdapter({
});
```

`createObjectStackAdapter` returns a `DataSource` — the same universal interface
every ObjectUI renderer consumes. `new ObjectStackAdapter(config)` is the class
form of the same thing.
`createObjectStackAdapter` returns an `ObjectStackAdapter` — the concrete adapter
class, which implements `DataSource`, the universal interface every ObjectUI
renderer consumes. `new ObjectStackAdapter(config)` is the class form of the same
thing and has the same type. Annotate the value as `DataSource` wherever you want
only the universal surface.

### 2. Inject it at the renderer boundary

Expand DownExpand Up@@ -106,11 +108,12 @@ full table of which blocks honour which keys.

### `createObjectStackAdapter`

Factory returning a `DataSource` backed by an ObjectStack backend.
Factory returning an `ObjectStackAdapter` — the concrete adapter class, which
implements `DataSource` — backed by an ObjectStack backend.

**Config:**

{/* doc-snippet: fragment — a SIGNATURE excerpt of the factory, quoted from its declaration so the config members can be annotated one by one: a `function` declaration with no body and a `DataSource` return type this reference block does not import (measured: TS2391 x1, TS2304 x1). Checked against the shipped `packages/data-objectstack/dist/index.d.ts`: every member listed here is declared there with the same type */}
{/* doc-snippet: fragment — a SIGNATURE excerpt of the factory, quoted from its declaration so the config members can be annotated one by one: a `function` declaration with no body and an `ObjectStackAdapter` return type this reference block does not import (measured: TS2391 x1, TS2304 x1). Because the block is DECLARED, this gate never compiles it, so the agreement with the shipped `packages/data-objectstack/dist/index.d.ts`every config member declared there with the same type, and the return type — is hand-checked at each edit, not gate-enforced; that gap is why the return type here outlived the change that widened it (objectui#7323) */}
```typescript
function createObjectStackAdapter<T = unknown>(config: {
/** ObjectStack server base URL */
Expand All@@ -132,7 +135,7 @@ function createObjectStackAdapter<T = unknown>(config: {
autoReconnect?: boolean; // default true
maxReconnectAttempts?: number; // default 3
reconnectDelay?: number; // default 1000 ms
}): DataSource<T>;
}): ObjectStackAdapter<T>;
```

**Example:**
Expand DownExpand Up@@ -161,20 +164,43 @@ const dataSource = createObjectStackAdapter<User>({ baseUrl: 'https://api.exampl

### `ObjectStackAdapter`

The class behind the factory. `new ObjectStackAdapter(config)` takes the same
config, but its declared type is the **concrete adapter** rather than the
`DataSource` interface the factory returns — which matters, because part of the
adapter's surface is not on that interface:
The class behind the factory — and the type the factory declares. `new
ObjectStackAdapter(config)` and `createObjectStackAdapter(config)` take the same
config and produce the same type, so the two forms are interchangeable:

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

type User = { id: string; name: string; email: string };

const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
// Same declared type, either way.
const fromFactory = createObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
const fromClass = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
```

**On the `DataSource` interface** (available from either form):
**What changed.** This section used to tell you to hold the class type to
reach the members listed under *Beyond `DataSource`* below, because the factory
declared `DataSource<T>` while returning `new ObjectStackAdapter(config)`: the
value always carried those members, only the declared type hid them
(objectui#7323). That distinction is gone. If you switched to
`new ObjectStackAdapter(...)` solely to reach a cache, connection-state or batch
method, you can switch back to the factory — nothing about the value changes, and
neither does its type. The class name is still worth importing when you need
something to annotate with.

Narrowing still works, and is still the right shape for a prop, a field or a test
double that must accept **any** adapter:

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

// The universal surface only, by annotation.
const dataSource: DataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });
```

**On the `DataSource` interface** (available from either form, and from any other
adapter):

- `find(resource, params?)` - Query multiple records
- `findOne(resource, id, params?)` - Get a single record by ID
Expand All@@ -184,22 +210,33 @@ const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com
- `getObjectSchema(objectName)` - Fetch schema metadata (cached)
- `bulk?(resource, operation, data)` - Batch create/update/delete on one object
- `batchTransaction?(operations)` - Cross-object atomic batch (master-detail)
- `onMutation?(listener)` - Subscribe to create/update/delete events

`bulk` and `batchTransaction` are **optional** members of `DataSource`: not every
adapter implements them, so through a `DataSource`-typed value they must be
feature-detected (`typeof dataSource.bulk === 'function'`). `ObjectStackAdapter`
implements both unconditionally, so a value held at the class type calls them
directly.
`bulk`, `batchTransaction` and `onMutation` are **optional** members of
`DataSource`: not every adapter implements them, so through a `DataSource`-typed
value they must be feature-detected (`typeof dataSource.bulk === 'function'`).
`ObjectStackAdapter` implements all three unconditionally, and the factory
declares the class, so a value from either form calls them directly. (`onMutation`
was listed under *Adapter-only* here until objectui#7323; it is optional on
`DataSource`, not absent from it.)

**Adapter-only** (hold the class type to reach these):
**Beyond `DataSource`** (declared on `ObjectStackAdapter`, so reachable from
either form):

- `connect()` - Establish the connection (called lazily by every operation)
- `getCacheStats()` / `invalidateCache(key?)` / `clearCache()` - Cache control
- `getClient()` - Access the underlying `@objectstack/client` instance
- `getConnectionState()` / `isConnected()` - Connection introspection
- `onConnectionStateChange(listener)` - Subscribe to state changes (returns unsubscribe)
- `onBatchProgress(listener)` - Subscribe to bulk progress (returns unsubscribe)
- `onMutation(listener)` - Subscribe to create/update/delete events

Those six bullets cover nine members, and they are the documented subset.
`ObjectStackAdapter` declares **20** members beyond `DataSource` in all
(`Exclude<keyof ObjectStackAdapter<unknown>, keyof DataSource<unknown>>`); the
other eleven are lower-level seams — client discovery, cache-key invalidation,
dataset and dashboard access, advisory subscriptions and capability configuration
among them — which this page does not document and which carry no compatibility
promise here.

### Per-element data binding

Expand DownExpand Up@@ -369,15 +406,16 @@ await dataSource.delete('user', user.id);

Batch writes on one object go through `bulk`, and cross-object writes that must
commit or roll back together go through `batchTransaction`. Both are optional on
the `DataSource` interface, so hold the adapter at its class type (or
feature-detect) before calling them:
the `DataSource` interface, so a value you annotated as `DataSource` still has to
feature-detect them — but the adapter implements both unconditionally and the
factory declares the adapter, so the value from Quick Start calls them directly:

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

type User = { id: string; name: string; email: string };

const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
const adapter = createObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });

await adapter.bulk('user', 'create', [
{ name: 'Alice', email: 'alice@example.com' },
Expand DownExpand Up@@ -463,17 +501,18 @@ An `AuthenticationError` (code `AUTHENTICATION_ERROR`, status 401) means the
`token` passed to `createObjectStackAdapter` was missing, expired or rejected.
Check the connection state and the values you passed in:

Connection introspection lives on the adapter class, not on the `DataSource`
interface, so hold it at the class type:
Connection introspection lives on the adapter, not on the `DataSource` interface
— and the factory declares the adapter, so the value from Quick Start reaches it
without a second construction:

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

const adapter = new ObjectStackAdapter({ baseUrl: 'https://api.example.com' });
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

console.log(adapter.getConnectionState()); // 'connected' | 'error' | ...
console.log(dataSource.getConnectionState()); // 'connected' | 'error' | ...

adapter.onConnectionStateChange((event) => {
dataSource.onConnectionStateChange((event) => {
if (event.error) console.error('Connection error:', event.error);
});
```
Expand Down
26 changes: 8 additions & 18 deletions packages/data-objectstack/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,16 +44,6 @@ 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
Expand DownExpand Up@@ -258,9 +248,9 @@ 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';
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Get cache statistics
const stats = dataSource.getCacheStats();
Expand DownExpand Up@@ -288,9 +278,9 @@ dataSource.clearCache();
The adapter provides real-time connection state monitoring with automatic reconnection:

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

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Monitor connection state changes
const unsubscribe = dataSource.onConnectionStateChange((event) => {
Expand DownExpand Up@@ -336,9 +326,9 @@ 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';
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

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

Expand DownExpand Up@@ -712,9 +702,9 @@ const dataSource = createObjectStackAdapter({
#### Cache Issues

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

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Clear cache if stale data is being returned
dataSource.clearCache();
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
79 changes: 79 additions & 0 deletions .changeset/7323-adapter-factory-return.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
---
'@object-ui/data-objectstack': minor
---

`createObjectStackAdapter` declares the adapter it returns, not the shared `DataSource`
interface (objectui#7323).

The factory returned `new ObjectStackAdapter(config)` while declaring `DataSource<T>`.
A wider value is assignable to a narrower annotation, so nothing ever failed to compile
— the loss was entirely on the reading side. Measured against the shipped
`dist/index.d.ts` with the doc-snippet gate's own compiler options, nine reads through
`ReturnType<typeof createObjectStackAdapter>` failed with TS2339: `getClient`,
`getCacheStats`, `invalidateCache`, `clearCache`, `getConnectionState`, `isConnected`,
`onConnectionStateChange`, `onBatchProgress` and `setSystemCapabilities`. Eight of those
nine reads are on this package's README API Reference list, and four whole README
sections are built on them; the ninth measured read is the one the factory's own JSDoc
links to (`[ADR-0066] See {@link ObjectStackAdapter.setSystemCapabilities}`). The README
list is itself **nine** adapter-only members, not eight — `connect()` is adapter-only
too and was documented all along; it simply was not one of the reads the card's
reproduction measured. So the file's own doc comment pointed the reader at a method its
declared return hid, and the two documented ways to obtain the same object — the factory
and `new ObjectStackAdapter(…)` — handed back different type surfaces.

**What the declared return now is: the whole class, not those nine reads.** The nine
above are what the reproduction measured, not the size of this change. The factory's
declared return is now `ObjectStackAdapter<T>` itself, so **every public member of the
class** is part of what the factory promises. Against `DataSource` that is **20**
members, not nine — `tsc`-computed as
`Exclude<keyof ObjectStackAdapter<unknown>, keyof DataSource<unknown>>`: `clearCache`,
`connect`, `getCacheStats`, `getCached`, `getClient`, `getConnectionState`,
`getDiscovery`, `getItems`, `invalidateCache`, `invalidateViewKeys`, `isConnected`,
`listImportMappings`, `onBatchProgress`, `onConnectionStateChange`, `onSaveAdvisory`,
`onWriteWarning`, `probeAppAccess`, `queryDataset`, `setSystemCapabilities`,
`updateDashboard`. The eleven past the documented nine were already in the shipped class
type — none is `@internal` or `@deprecated`, `stripInternal` is not set, and all were
already reachable through `new ObjectStackAdapter(…)` and through every
`ObjectStackAdapter`-typed seam in `@object-ui/react` and `app-shell` — so what widens
here is what the **factory declares**, not what the package ships. Two are escape-hatch
shaped and worth knowing before building on them: `getCached(key)` is a raw cache read,
and `getDiscovery()` reaches an internal property of the underlying `ObjectStackClient`.

**Branch taken: A (widen the factory's declared return), and why.** The card offered
three. B — moving caching, connection state and batch progress onto `DataSource` — was
rejected because those are this adapter's concerns, not every data source's; every other
`DataSource` implementation would then declare members it does not have. C — documenting
a cast — teaches a cast around a declaration that is merely narrower than the value,
which is the opposite of `declared = enforced`. A is one line and makes declared match
shipped for every documented member at once.

Two questions decided the shape and both were answered from the code before the diff.
`ObjectStackAdapter` was **already** exported from the package's only entry
(`src/index.ts`, tsup's single entry; the class is in the shipped `dist/index.d.ts`
export list, two pin tests assert the exported spelling, and `apps/console` re-exports it
by name) — so widening the return exports nothing by implication. And the narrow return
was **not** a deliberate swappability guarantee: no comment, ADR or test pinned it, and
the commit that added `autoReconnect` / `maxReconnectAttempts` / `reconnectDelay` to the
factory's own config bag left the members that observe those features off the factory's
declared return in the same change.

**One caller shape breaks: a structural stand-in for the factory's return.** A
hand-written object literal annotated `ReturnType<typeof createObjectStackAdapter>` no
longer satisfies that type, because it is now a class with private members (TS2740) —
annotate such a fake as `DataSource` instead, which is what it was standing in for.
Nothing else moves: a wider return is assignable to the narrower annotation, so
`const ds: DataSource = createObjectStackAdapter(…)` keeps compiling and keeps giving
the narrow surface to anyone who wants it.

The README's note saying the page could not yet teach the factory's shape is removed, and
the four sections built on the adapter-only members (Metadata Caching, Connection State
Monitoring, Batch Operation Progress, Troubleshooting → Cache Issues) now continue from
Basic Setup's `createObjectStackAdapter(…)` call instead of declaring the class by hand.
The docs-site page `content/docs/utilities/data-objectstack.mdx` is corrected the same
way: its prose, its factory signature fragment and its "hold the class type to reach
these" section described the old narrow return, and its Mutations and Troubleshooting
examples told the reader to construct the class by hand to reach members the factory now
declares. `src/adapterFactoryReturn.types.test.ts` pins the card's TS2339 reproduction
inverted,
with two controls: the adapter-only members stay absent from `DataSource` (fires on
option B), and the widened return stays assignable to `DataSource` (swappability kept).
99 changes: 69 additions & 30 deletions content/docs/utilities/data-objectstack.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,9 +50,11 @@ const dataSource = createObjectStackAdapter({
});
```

`createObjectStackAdapter` returns a `DataSource` — the same universal interface
every ObjectUI renderer consumes. `new ObjectStackAdapter(config)` is the class
form of the same thing.
`createObjectStackAdapter` returns an `ObjectStackAdapter` — the concrete adapter
class, which implements `DataSource`, the universal interface every ObjectUI
renderer consumes. `new ObjectStackAdapter(config)` is the class form of the same
thing and has the same type. Annotate the value as `DataSource` wherever you want
only the universal surface.

### 2. Inject it at the renderer boundary

Expand DownExpand Up@@ -106,11 +108,12 @@ full table of which blocks honour which keys.

### `createObjectStackAdapter`

Factory returning a `DataSource` backed by an ObjectStack backend.
Factory returning an `ObjectStackAdapter` — the concrete adapter class, which
implements `DataSource` — backed by an ObjectStack backend.

**Config:**

{/* doc-snippet: fragment — a SIGNATURE excerpt of the factory, quoted from its declaration so the config members can be annotated one by one: a `function` declaration with no body and a `DataSource` return type this reference block does not import (measured: TS2391 x1, TS2304 x1). Checked against the shipped `packages/data-objectstack/dist/index.d.ts`: every member listed here is declared there with the same type */}
{/* doc-snippet: fragment — a SIGNATURE excerpt of the factory, quoted from its declaration so the config members can be annotated one by one: a `function` declaration with no body and an `ObjectStackAdapter` return type this reference block does not import (measured: TS2391 x1, TS2304 x1). Because the block is DECLARED, this gate never compiles it, so the agreement with the shipped `packages/data-objectstack/dist/index.d.ts`every config member declared there with the same type, and the return type — is hand-checked at each edit, not gate-enforced; that gap is why the return type here outlived the change that widened it (objectui#7323) */}
```typescript
function createObjectStackAdapter<T = unknown>(config: {
/** ObjectStack server base URL */
Expand All@@ -132,7 +135,7 @@ function createObjectStackAdapter<T = unknown>(config: {
autoReconnect?: boolean; // default true
maxReconnectAttempts?: number; // default 3
reconnectDelay?: number; // default 1000 ms
}): DataSource<T>;
}): ObjectStackAdapter<T>;
```

**Example:**
Expand DownExpand Up@@ -161,20 +164,43 @@ const dataSource = createObjectStackAdapter<User>({ baseUrl: 'https://api.exampl

### `ObjectStackAdapter`

The class behind the factory. `new ObjectStackAdapter(config)` takes the same
config, but its declared type is the **concrete adapter** rather than the
`DataSource` interface the factory returns — which matters, because part of the
adapter's surface is not on that interface:
The class behind the factory — and the type the factory declares. `new
ObjectStackAdapter(config)` and `createObjectStackAdapter(config)` take the same
config and produce the same type, so the two forms are interchangeable:

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

type User = { id: string; name: string; email: string };

const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
// Same declared type, either way.
const fromFactory = createObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
const fromClass = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
```

**On the `DataSource` interface** (available from either form):
**What changed.** This section used to tell you to hold the class type to
reach the members listed under *Beyond `DataSource`* below, because the factory
declared `DataSource<T>` while returning `new ObjectStackAdapter(config)`: the
value always carried those members, only the declared type hid them
(objectui#7323). That distinction is gone. If you switched to
`new ObjectStackAdapter(...)` solely to reach a cache, connection-state or batch
method, you can switch back to the factory — nothing about the value changes, and
neither does its type. The class name is still worth importing when you need
something to annotate with.

Narrowing still works, and is still the right shape for a prop, a field or a test
double that must accept **any** adapter:

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

// The universal surface only, by annotation.
const dataSource: DataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });
```

**On the `DataSource` interface** (available from either form, and from any other
adapter):

- `find(resource, params?)` - Query multiple records
- `findOne(resource, id, params?)` - Get a single record by ID
Expand All@@ -184,22 +210,33 @@ const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com
- `getObjectSchema(objectName)` - Fetch schema metadata (cached)
- `bulk?(resource, operation, data)` - Batch create/update/delete on one object
- `batchTransaction?(operations)` - Cross-object atomic batch (master-detail)
- `onMutation?(listener)` - Subscribe to create/update/delete events

`bulk` and `batchTransaction` are **optional** members of `DataSource`: not every
adapter implements them, so through a `DataSource`-typed value they must be
feature-detected (`typeof dataSource.bulk === 'function'`). `ObjectStackAdapter`
implements both unconditionally, so a value held at the class type calls them
directly.
`bulk`, `batchTransaction` and `onMutation` are **optional** members of
`DataSource`: not every adapter implements them, so through a `DataSource`-typed
value they must be feature-detected (`typeof dataSource.bulk === 'function'`).
`ObjectStackAdapter` implements all three unconditionally, and the factory
declares the class, so a value from either form calls them directly. (`onMutation`
was listed under *Adapter-only* here until objectui#7323; it is optional on
`DataSource`, not absent from it.)

**Adapter-only** (hold the class type to reach these):
**Beyond `DataSource`** (declared on `ObjectStackAdapter`, so reachable from
either form):

- `connect()` - Establish the connection (called lazily by every operation)
- `getCacheStats()` / `invalidateCache(key?)` / `clearCache()` - Cache control
- `getClient()` - Access the underlying `@objectstack/client` instance
- `getConnectionState()` / `isConnected()` - Connection introspection
- `onConnectionStateChange(listener)` - Subscribe to state changes (returns unsubscribe)
- `onBatchProgress(listener)` - Subscribe to bulk progress (returns unsubscribe)
- `onMutation(listener)` - Subscribe to create/update/delete events

Those six bullets cover nine members, and they are the documented subset.
`ObjectStackAdapter` declares **20** members beyond `DataSource` in all
(`Exclude<keyof ObjectStackAdapter<unknown>, keyof DataSource<unknown>>`); the
other eleven are lower-level seams — client discovery, cache-key invalidation,
dataset and dashboard access, advisory subscriptions and capability configuration
among them — which this page does not document and which carry no compatibility
promise here.

### Per-element data binding

Expand DownExpand Up@@ -369,15 +406,16 @@ await dataSource.delete('user', user.id);

Batch writes on one object go through `bulk`, and cross-object writes that must
commit or roll back together go through `batchTransaction`. Both are optional on
the `DataSource` interface, so hold the adapter at its class type (or
feature-detect) before calling them:
the `DataSource` interface, so a value you annotated as `DataSource` still has to
feature-detect them — but the adapter implements both unconditionally and the
factory declares the adapter, so the value from Quick Start calls them directly:

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

type User = { id: string; name: string; email: string };

const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
const adapter = createObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });

await adapter.bulk('user', 'create', [
{ name: 'Alice', email: 'alice@example.com' },
Expand DownExpand Up@@ -463,17 +501,18 @@ An `AuthenticationError` (code `AUTHENTICATION_ERROR`, status 401) means the
`token` passed to `createObjectStackAdapter` was missing, expired or rejected.
Check the connection state and the values you passed in:

Connection introspection lives on the adapter class, not on the `DataSource`
interface, so hold it at the class type:
Connection introspection lives on the adapter, not on the `DataSource` interface
— and the factory declares the adapter, so the value from Quick Start reaches it
without a second construction:

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

const adapter = new ObjectStackAdapter({ baseUrl: 'https://api.example.com' });
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

console.log(adapter.getConnectionState()); // 'connected' | 'error' | ...
console.log(dataSource.getConnectionState()); // 'connected' | 'error' | ...

adapter.onConnectionStateChange((event) => {
dataSource.onConnectionStateChange((event) => {
if (event.error) console.error('Connection error:', event.error);
});
```
Expand Down
26 changes: 8 additions & 18 deletions packages/data-objectstack/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,16 +44,6 @@ 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
Expand DownExpand Up@@ -258,9 +248,9 @@ 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';
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Get cache statistics
const stats = dataSource.getCacheStats();
Expand DownExpand Up@@ -288,9 +278,9 @@ dataSource.clearCache();
The adapter provides real-time connection state monitoring with automatic reconnection:

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

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Monitor connection state changes
const unsubscribe = dataSource.onConnectionStateChange((event) => {
Expand DownExpand Up@@ -336,9 +326,9 @@ 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';
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

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

Expand DownExpand Up@@ -712,9 +702,9 @@ const dataSource = createObjectStackAdapter({
#### Cache Issues

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

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Clear cache if stale data is being returned
dataSource.clearCache();
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
79 changes: 79 additions & 0 deletions .changeset/7323-adapter-factory-return.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
---
'@object-ui/data-objectstack': minor
---

`createObjectStackAdapter` declares the adapter it returns, not the shared `DataSource`
interface (objectui#7323).

The factory returned `new ObjectStackAdapter(config)` while declaring `DataSource<T>`.
A wider value is assignable to a narrower annotation, so nothing ever failed to compile
— the loss was entirely on the reading side. Measured against the shipped
`dist/index.d.ts` with the doc-snippet gate's own compiler options, nine reads through
`ReturnType<typeof createObjectStackAdapter>` failed with TS2339: `getClient`,
`getCacheStats`, `invalidateCache`, `clearCache`, `getConnectionState`, `isConnected`,
`onConnectionStateChange`, `onBatchProgress` and `setSystemCapabilities`. Eight of those
nine reads are on this package's README API Reference list, and four whole README
sections are built on them; the ninth measured read is the one the factory's own JSDoc
links to (`[ADR-0066] See {@link ObjectStackAdapter.setSystemCapabilities}`). The README
list is itself **nine** adapter-only members, not eight — `connect()` is adapter-only
too and was documented all along; it simply was not one of the reads the card's
reproduction measured. So the file's own doc comment pointed the reader at a method its
declared return hid, and the two documented ways to obtain the same object — the factory
and `new ObjectStackAdapter(…)` — handed back different type surfaces.

**What the declared return now is: the whole class, not those nine reads.** The nine
above are what the reproduction measured, not the size of this change. The factory's
declared return is now `ObjectStackAdapter<T>` itself, so **every public member of the
class** is part of what the factory promises. Against `DataSource` that is **20**
members, not nine — `tsc`-computed as
`Exclude<keyof ObjectStackAdapter<unknown>, keyof DataSource<unknown>>`: `clearCache`,
`connect`, `getCacheStats`, `getCached`, `getClient`, `getConnectionState`,
`getDiscovery`, `getItems`, `invalidateCache`, `invalidateViewKeys`, `isConnected`,
`listImportMappings`, `onBatchProgress`, `onConnectionStateChange`, `onSaveAdvisory`,
`onWriteWarning`, `probeAppAccess`, `queryDataset`, `setSystemCapabilities`,
`updateDashboard`. The eleven past the documented nine were already in the shipped class
type — none is `@internal` or `@deprecated`, `stripInternal` is not set, and all were
already reachable through `new ObjectStackAdapter(…)` and through every
`ObjectStackAdapter`-typed seam in `@object-ui/react` and `app-shell` — so what widens
here is what the **factory declares**, not what the package ships. Two are escape-hatch
shaped and worth knowing before building on them: `getCached(key)` is a raw cache read,
and `getDiscovery()` reaches an internal property of the underlying `ObjectStackClient`.

**Branch taken: A (widen the factory's declared return), and why.** The card offered
three. B — moving caching, connection state and batch progress onto `DataSource` — was
rejected because those are this adapter's concerns, not every data source's; every other
`DataSource` implementation would then declare members it does not have. C — documenting
a cast — teaches a cast around a declaration that is merely narrower than the value,
which is the opposite of `declared = enforced`. A is one line and makes declared match
shipped for every documented member at once.

Two questions decided the shape and both were answered from the code before the diff.
`ObjectStackAdapter` was **already** exported from the package's only entry
(`src/index.ts`, tsup's single entry; the class is in the shipped `dist/index.d.ts`
export list, two pin tests assert the exported spelling, and `apps/console` re-exports it
by name) — so widening the return exports nothing by implication. And the narrow return
was **not** a deliberate swappability guarantee: no comment, ADR or test pinned it, and
the commit that added `autoReconnect` / `maxReconnectAttempts` / `reconnectDelay` to the
factory's own config bag left the members that observe those features off the factory's
declared return in the same change.

**One caller shape breaks: a structural stand-in for the factory's return.** A
hand-written object literal annotated `ReturnType<typeof createObjectStackAdapter>` no
longer satisfies that type, because it is now a class with private members (TS2740) —
annotate such a fake as `DataSource` instead, which is what it was standing in for.
Nothing else moves: a wider return is assignable to the narrower annotation, so
`const ds: DataSource = createObjectStackAdapter(…)` keeps compiling and keeps giving
the narrow surface to anyone who wants it.

The README's note saying the page could not yet teach the factory's shape is removed, and
the four sections built on the adapter-only members (Metadata Caching, Connection State
Monitoring, Batch Operation Progress, Troubleshooting → Cache Issues) now continue from
Basic Setup's `createObjectStackAdapter(…)` call instead of declaring the class by hand.
The docs-site page `content/docs/utilities/data-objectstack.mdx` is corrected the same
way: its prose, its factory signature fragment and its "hold the class type to reach
these" section described the old narrow return, and its Mutations and Troubleshooting
examples told the reader to construct the class by hand to reach members the factory now
declares. `src/adapterFactoryReturn.types.test.ts` pins the card's TS2339 reproduction
inverted,
with two controls: the adapter-only members stay absent from `DataSource` (fires on
option B), and the widened return stays assignable to `DataSource` (swappability kept).
99 changes: 69 additions & 30 deletions content/docs/utilities/data-objectstack.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,9 +50,11 @@ const dataSource = createObjectStackAdapter({
});
```

`createObjectStackAdapter` returns a `DataSource` — the same universal interface
every ObjectUI renderer consumes. `new ObjectStackAdapter(config)` is the class
form of the same thing.
`createObjectStackAdapter` returns an `ObjectStackAdapter` — the concrete adapter
class, which implements `DataSource`, the universal interface every ObjectUI
renderer consumes. `new ObjectStackAdapter(config)` is the class form of the same
thing and has the same type. Annotate the value as `DataSource` wherever you want
only the universal surface.

### 2. Inject it at the renderer boundary

Expand DownExpand Up@@ -106,11 +108,12 @@ full table of which blocks honour which keys.

### `createObjectStackAdapter`

Factory returning a `DataSource` backed by an ObjectStack backend.
Factory returning an `ObjectStackAdapter` — the concrete adapter class, which
implements `DataSource` — backed by an ObjectStack backend.

**Config:**

{/* doc-snippet: fragment — a SIGNATURE excerpt of the factory, quoted from its declaration so the config members can be annotated one by one: a `function` declaration with no body and a `DataSource` return type this reference block does not import (measured: TS2391 x1, TS2304 x1). Checked against the shipped `packages/data-objectstack/dist/index.d.ts`: every member listed here is declared there with the same type */}
{/* doc-snippet: fragment — a SIGNATURE excerpt of the factory, quoted from its declaration so the config members can be annotated one by one: a `function` declaration with no body and an `ObjectStackAdapter` return type this reference block does not import (measured: TS2391 x1, TS2304 x1). Because the block is DECLARED, this gate never compiles it, so the agreement with the shipped `packages/data-objectstack/dist/index.d.ts`every config member declared there with the same type, and the return type — is hand-checked at each edit, not gate-enforced; that gap is why the return type here outlived the change that widened it (objectui#7323) */}
```typescript
function createObjectStackAdapter<T = unknown>(config: {
/** ObjectStack server base URL */
Expand All@@ -132,7 +135,7 @@ function createObjectStackAdapter<T = unknown>(config: {
autoReconnect?: boolean; // default true
maxReconnectAttempts?: number; // default 3
reconnectDelay?: number; // default 1000 ms
}): DataSource<T>;
}): ObjectStackAdapter<T>;
```

**Example:**
Expand DownExpand Up@@ -161,20 +164,43 @@ const dataSource = createObjectStackAdapter<User>({ baseUrl: 'https://api.exampl

### `ObjectStackAdapter`

The class behind the factory. `new ObjectStackAdapter(config)` takes the same
config, but its declared type is the **concrete adapter** rather than the
`DataSource` interface the factory returns — which matters, because part of the
adapter's surface is not on that interface:
The class behind the factory — and the type the factory declares. `new
ObjectStackAdapter(config)` and `createObjectStackAdapter(config)` take the same
config and produce the same type, so the two forms are interchangeable:

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

type User = { id: string; name: string; email: string };

const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
// Same declared type, either way.
const fromFactory = createObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
const fromClass = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
```

**On the `DataSource` interface** (available from either form):
**What changed.** This section used to tell you to hold the class type to
reach the members listed under *Beyond `DataSource`* below, because the factory
declared `DataSource<T>` while returning `new ObjectStackAdapter(config)`: the
value always carried those members, only the declared type hid them
(objectui#7323). That distinction is gone. If you switched to
`new ObjectStackAdapter(...)` solely to reach a cache, connection-state or batch
method, you can switch back to the factory — nothing about the value changes, and
neither does its type. The class name is still worth importing when you need
something to annotate with.

Narrowing still works, and is still the right shape for a prop, a field or a test
double that must accept **any** adapter:

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

// The universal surface only, by annotation.
const dataSource: DataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });
```

**On the `DataSource` interface** (available from either form, and from any other
adapter):

- `find(resource, params?)` - Query multiple records
- `findOne(resource, id, params?)` - Get a single record by ID
Expand All@@ -184,22 +210,33 @@ const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com
- `getObjectSchema(objectName)` - Fetch schema metadata (cached)
- `bulk?(resource, operation, data)` - Batch create/update/delete on one object
- `batchTransaction?(operations)` - Cross-object atomic batch (master-detail)
- `onMutation?(listener)` - Subscribe to create/update/delete events

`bulk` and `batchTransaction` are **optional** members of `DataSource`: not every
adapter implements them, so through a `DataSource`-typed value they must be
feature-detected (`typeof dataSource.bulk === 'function'`). `ObjectStackAdapter`
implements both unconditionally, so a value held at the class type calls them
directly.
`bulk`, `batchTransaction` and `onMutation` are **optional** members of
`DataSource`: not every adapter implements them, so through a `DataSource`-typed
value they must be feature-detected (`typeof dataSource.bulk === 'function'`).
`ObjectStackAdapter` implements all three unconditionally, and the factory
declares the class, so a value from either form calls them directly. (`onMutation`
was listed under *Adapter-only* here until objectui#7323; it is optional on
`DataSource`, not absent from it.)

**Adapter-only** (hold the class type to reach these):
**Beyond `DataSource`** (declared on `ObjectStackAdapter`, so reachable from
either form):

- `connect()` - Establish the connection (called lazily by every operation)
- `getCacheStats()` / `invalidateCache(key?)` / `clearCache()` - Cache control
- `getClient()` - Access the underlying `@objectstack/client` instance
- `getConnectionState()` / `isConnected()` - Connection introspection
- `onConnectionStateChange(listener)` - Subscribe to state changes (returns unsubscribe)
- `onBatchProgress(listener)` - Subscribe to bulk progress (returns unsubscribe)
- `onMutation(listener)` - Subscribe to create/update/delete events

Those six bullets cover nine members, and they are the documented subset.
`ObjectStackAdapter` declares **20** members beyond `DataSource` in all
(`Exclude<keyof ObjectStackAdapter<unknown>, keyof DataSource<unknown>>`); the
other eleven are lower-level seams — client discovery, cache-key invalidation,
dataset and dashboard access, advisory subscriptions and capability configuration
among them — which this page does not document and which carry no compatibility
promise here.

### Per-element data binding

Expand DownExpand Up@@ -369,15 +406,16 @@ await dataSource.delete('user', user.id);

Batch writes on one object go through `bulk`, and cross-object writes that must
commit or roll back together go through `batchTransaction`. Both are optional on
the `DataSource` interface, so hold the adapter at its class type (or
feature-detect) before calling them:
the `DataSource` interface, so a value you annotated as `DataSource` still has to
feature-detect them — but the adapter implements both unconditionally and the
factory declares the adapter, so the value from Quick Start calls them directly:

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

type User = { id: string; name: string; email: string };

const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
const adapter = createObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });

await adapter.bulk('user', 'create', [
{ name: 'Alice', email: 'alice@example.com' },
Expand DownExpand Up@@ -463,17 +501,18 @@ An `AuthenticationError` (code `AUTHENTICATION_ERROR`, status 401) means the
`token` passed to `createObjectStackAdapter` was missing, expired or rejected.
Check the connection state and the values you passed in:

Connection introspection lives on the adapter class, not on the `DataSource`
interface, so hold it at the class type:
Connection introspection lives on the adapter, not on the `DataSource` interface
— and the factory declares the adapter, so the value from Quick Start reaches it
without a second construction:

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

const adapter = new ObjectStackAdapter({ baseUrl: 'https://api.example.com' });
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

console.log(adapter.getConnectionState()); // 'connected' | 'error' | ...
console.log(dataSource.getConnectionState()); // 'connected' | 'error' | ...

adapter.onConnectionStateChange((event) => {
dataSource.onConnectionStateChange((event) => {
if (event.error) console.error('Connection error:', event.error);
});
```
Expand Down
26 changes: 8 additions & 18 deletions packages/data-objectstack/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,16 +44,6 @@ 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
Expand DownExpand Up@@ -258,9 +248,9 @@ 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';
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Get cache statistics
const stats = dataSource.getCacheStats();
Expand DownExpand Up@@ -288,9 +278,9 @@ dataSource.clearCache();
The adapter provides real-time connection state monitoring with automatic reconnection:

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

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Monitor connection state changes
const unsubscribe = dataSource.onConnectionStateChange((event) => {
Expand DownExpand Up@@ -336,9 +326,9 @@ 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';
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

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

Expand DownExpand Up@@ -712,9 +702,9 @@ const dataSource = createObjectStackAdapter({
#### Cache Issues

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

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Clear cache if stale data is being returned
dataSource.clearCache();
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
79 changes: 79 additions & 0 deletions .changeset/7323-adapter-factory-return.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
---
'@object-ui/data-objectstack': minor
---

`createObjectStackAdapter` declares the adapter it returns, not the shared `DataSource`
interface (objectui#7323).

The factory returned `new ObjectStackAdapter(config)` while declaring `DataSource<T>`.
A wider value is assignable to a narrower annotation, so nothing ever failed to compile
— the loss was entirely on the reading side. Measured against the shipped
`dist/index.d.ts` with the doc-snippet gate's own compiler options, nine reads through
`ReturnType<typeof createObjectStackAdapter>` failed with TS2339: `getClient`,
`getCacheStats`, `invalidateCache`, `clearCache`, `getConnectionState`, `isConnected`,
`onConnectionStateChange`, `onBatchProgress` and `setSystemCapabilities`. Eight of those
nine reads are on this package's README API Reference list, and four whole README
sections are built on them; the ninth measured read is the one the factory's own JSDoc
links to (`[ADR-0066] See {@link ObjectStackAdapter.setSystemCapabilities}`). The README
list is itself **nine** adapter-only members, not eight — `connect()` is adapter-only
too and was documented all along; it simply was not one of the reads the card's
reproduction measured. So the file's own doc comment pointed the reader at a method its
declared return hid, and the two documented ways to obtain the same object — the factory
and `new ObjectStackAdapter(…)` — handed back different type surfaces.

**What the declared return now is: the whole class, not those nine reads.** The nine
above are what the reproduction measured, not the size of this change. The factory's
declared return is now `ObjectStackAdapter<T>` itself, so **every public member of the
class** is part of what the factory promises. Against `DataSource` that is **20**
members, not nine — `tsc`-computed as
`Exclude<keyof ObjectStackAdapter<unknown>, keyof DataSource<unknown>>`: `clearCache`,
`connect`, `getCacheStats`, `getCached`, `getClient`, `getConnectionState`,
`getDiscovery`, `getItems`, `invalidateCache`, `invalidateViewKeys`, `isConnected`,
`listImportMappings`, `onBatchProgress`, `onConnectionStateChange`, `onSaveAdvisory`,
`onWriteWarning`, `probeAppAccess`, `queryDataset`, `setSystemCapabilities`,
`updateDashboard`. The eleven past the documented nine were already in the shipped class
type — none is `@internal` or `@deprecated`, `stripInternal` is not set, and all were
already reachable through `new ObjectStackAdapter(…)` and through every
`ObjectStackAdapter`-typed seam in `@object-ui/react` and `app-shell` — so what widens
here is what the **factory declares**, not what the package ships. Two are escape-hatch
shaped and worth knowing before building on them: `getCached(key)` is a raw cache read,
and `getDiscovery()` reaches an internal property of the underlying `ObjectStackClient`.

**Branch taken: A (widen the factory's declared return), and why.** The card offered
three. B — moving caching, connection state and batch progress onto `DataSource` — was
rejected because those are this adapter's concerns, not every data source's; every other
`DataSource` implementation would then declare members it does not have. C — documenting
a cast — teaches a cast around a declaration that is merely narrower than the value,
which is the opposite of `declared = enforced`. A is one line and makes declared match
shipped for every documented member at once.

Two questions decided the shape and both were answered from the code before the diff.
`ObjectStackAdapter` was **already** exported from the package's only entry
(`src/index.ts`, tsup's single entry; the class is in the shipped `dist/index.d.ts`
export list, two pin tests assert the exported spelling, and `apps/console` re-exports it
by name) — so widening the return exports nothing by implication. And the narrow return
was **not** a deliberate swappability guarantee: no comment, ADR or test pinned it, and
the commit that added `autoReconnect` / `maxReconnectAttempts` / `reconnectDelay` to the
factory's own config bag left the members that observe those features off the factory's
declared return in the same change.

**One caller shape breaks: a structural stand-in for the factory's return.** A
hand-written object literal annotated `ReturnType<typeof createObjectStackAdapter>` no
longer satisfies that type, because it is now a class with private members (TS2740) —
annotate such a fake as `DataSource` instead, which is what it was standing in for.
Nothing else moves: a wider return is assignable to the narrower annotation, so
`const ds: DataSource = createObjectStackAdapter(…)` keeps compiling and keeps giving
the narrow surface to anyone who wants it.

The README's note saying the page could not yet teach the factory's shape is removed, and
the four sections built on the adapter-only members (Metadata Caching, Connection State
Monitoring, Batch Operation Progress, Troubleshooting → Cache Issues) now continue from
Basic Setup's `createObjectStackAdapter(…)` call instead of declaring the class by hand.
The docs-site page `content/docs/utilities/data-objectstack.mdx` is corrected the same
way: its prose, its factory signature fragment and its "hold the class type to reach
these" section described the old narrow return, and its Mutations and Troubleshooting
examples told the reader to construct the class by hand to reach members the factory now
declares. `src/adapterFactoryReturn.types.test.ts` pins the card's TS2339 reproduction
inverted,
with two controls: the adapter-only members stay absent from `DataSource` (fires on
option B), and the widened return stays assignable to `DataSource` (swappability kept).
99 changes: 69 additions & 30 deletions content/docs/utilities/data-objectstack.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,9 +50,11 @@ const dataSource = createObjectStackAdapter({
});
```

`createObjectStackAdapter` returns a `DataSource` — the same universal interface
every ObjectUI renderer consumes. `new ObjectStackAdapter(config)` is the class
form of the same thing.
`createObjectStackAdapter` returns an `ObjectStackAdapter` — the concrete adapter
class, which implements `DataSource`, the universal interface every ObjectUI
renderer consumes. `new ObjectStackAdapter(config)` is the class form of the same
thing and has the same type. Annotate the value as `DataSource` wherever you want
only the universal surface.

### 2. Inject it at the renderer boundary

Expand DownExpand Up@@ -106,11 +108,12 @@ full table of which blocks honour which keys.

### `createObjectStackAdapter`

Factory returning a `DataSource` backed by an ObjectStack backend.
Factory returning an `ObjectStackAdapter` — the concrete adapter class, which
implements `DataSource` — backed by an ObjectStack backend.

**Config:**

{/* doc-snippet: fragment — a SIGNATURE excerpt of the factory, quoted from its declaration so the config members can be annotated one by one: a `function` declaration with no body and a `DataSource` return type this reference block does not import (measured: TS2391 x1, TS2304 x1). Checked against the shipped `packages/data-objectstack/dist/index.d.ts`: every member listed here is declared there with the same type */}
{/* doc-snippet: fragment — a SIGNATURE excerpt of the factory, quoted from its declaration so the config members can be annotated one by one: a `function` declaration with no body and an `ObjectStackAdapter` return type this reference block does not import (measured: TS2391 x1, TS2304 x1). Because the block is DECLARED, this gate never compiles it, so the agreement with the shipped `packages/data-objectstack/dist/index.d.ts`every config member declared there with the same type, and the return type — is hand-checked at each edit, not gate-enforced; that gap is why the return type here outlived the change that widened it (objectui#7323) */}
```typescript
function createObjectStackAdapter<T = unknown>(config: {
/** ObjectStack server base URL */
Expand All@@ -132,7 +135,7 @@ function createObjectStackAdapter<T = unknown>(config: {
autoReconnect?: boolean; // default true
maxReconnectAttempts?: number; // default 3
reconnectDelay?: number; // default 1000 ms
}): DataSource<T>;
}): ObjectStackAdapter<T>;
```

**Example:**
Expand DownExpand Up@@ -161,20 +164,43 @@ const dataSource = createObjectStackAdapter<User>({ baseUrl: 'https://api.exampl

### `ObjectStackAdapter`

The class behind the factory. `new ObjectStackAdapter(config)` takes the same
config, but its declared type is the **concrete adapter** rather than the
`DataSource` interface the factory returns — which matters, because part of the
adapter's surface is not on that interface:
The class behind the factory — and the type the factory declares. `new
ObjectStackAdapter(config)` and `createObjectStackAdapter(config)` take the same
config and produce the same type, so the two forms are interchangeable:

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

type User = { id: string; name: string; email: string };

const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
// Same declared type, either way.
const fromFactory = createObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
const fromClass = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
```

**On the `DataSource` interface** (available from either form):
**What changed.** This section used to tell you to hold the class type to
reach the members listed under *Beyond `DataSource`* below, because the factory
declared `DataSource<T>` while returning `new ObjectStackAdapter(config)`: the
value always carried those members, only the declared type hid them
(objectui#7323). That distinction is gone. If you switched to
`new ObjectStackAdapter(...)` solely to reach a cache, connection-state or batch
method, you can switch back to the factory — nothing about the value changes, and
neither does its type. The class name is still worth importing when you need
something to annotate with.

Narrowing still works, and is still the right shape for a prop, a field or a test
double that must accept **any** adapter:

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

// The universal surface only, by annotation.
const dataSource: DataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });
```

**On the `DataSource` interface** (available from either form, and from any other
adapter):

- `find(resource, params?)` - Query multiple records
- `findOne(resource, id, params?)` - Get a single record by ID
Expand All@@ -184,22 +210,33 @@ const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com
- `getObjectSchema(objectName)` - Fetch schema metadata (cached)
- `bulk?(resource, operation, data)` - Batch create/update/delete on one object
- `batchTransaction?(operations)` - Cross-object atomic batch (master-detail)
- `onMutation?(listener)` - Subscribe to create/update/delete events

`bulk` and `batchTransaction` are **optional** members of `DataSource`: not every
adapter implements them, so through a `DataSource`-typed value they must be
feature-detected (`typeof dataSource.bulk === 'function'`). `ObjectStackAdapter`
implements both unconditionally, so a value held at the class type calls them
directly.
`bulk`, `batchTransaction` and `onMutation` are **optional** members of
`DataSource`: not every adapter implements them, so through a `DataSource`-typed
value they must be feature-detected (`typeof dataSource.bulk === 'function'`).
`ObjectStackAdapter` implements all three unconditionally, and the factory
declares the class, so a value from either form calls them directly. (`onMutation`
was listed under *Adapter-only* here until objectui#7323; it is optional on
`DataSource`, not absent from it.)

**Adapter-only** (hold the class type to reach these):
**Beyond `DataSource`** (declared on `ObjectStackAdapter`, so reachable from
either form):

- `connect()` - Establish the connection (called lazily by every operation)
- `getCacheStats()` / `invalidateCache(key?)` / `clearCache()` - Cache control
- `getClient()` - Access the underlying `@objectstack/client` instance
- `getConnectionState()` / `isConnected()` - Connection introspection
- `onConnectionStateChange(listener)` - Subscribe to state changes (returns unsubscribe)
- `onBatchProgress(listener)` - Subscribe to bulk progress (returns unsubscribe)
- `onMutation(listener)` - Subscribe to create/update/delete events

Those six bullets cover nine members, and they are the documented subset.
`ObjectStackAdapter` declares **20** members beyond `DataSource` in all
(`Exclude<keyof ObjectStackAdapter<unknown>, keyof DataSource<unknown>>`); the
other eleven are lower-level seams — client discovery, cache-key invalidation,
dataset and dashboard access, advisory subscriptions and capability configuration
among them — which this page does not document and which carry no compatibility
promise here.

### Per-element data binding

Expand DownExpand Up@@ -369,15 +406,16 @@ await dataSource.delete('user', user.id);

Batch writes on one object go through `bulk`, and cross-object writes that must
commit or roll back together go through `batchTransaction`. Both are optional on
the `DataSource` interface, so hold the adapter at its class type (or
feature-detect) before calling them:
the `DataSource` interface, so a value you annotated as `DataSource` still has to
feature-detect them — but the adapter implements both unconditionally and the
factory declares the adapter, so the value from Quick Start calls them directly:

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

type User = { id: string; name: string; email: string };

const adapter = new ObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });
const adapter = createObjectStackAdapter<User>({ baseUrl: 'https://api.example.com' });

await adapter.bulk('user', 'create', [
{ name: 'Alice', email: 'alice@example.com' },
Expand DownExpand Up@@ -463,17 +501,18 @@ An `AuthenticationError` (code `AUTHENTICATION_ERROR`, status 401) means the
`token` passed to `createObjectStackAdapter` was missing, expired or rejected.
Check the connection state and the values you passed in:

Connection introspection lives on the adapter class, not on the `DataSource`
interface, so hold it at the class type:
Connection introspection lives on the adapter, not on the `DataSource` interface
— and the factory declares the adapter, so the value from Quick Start reaches it
without a second construction:

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

const adapter = new ObjectStackAdapter({ baseUrl: 'https://api.example.com' });
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

console.log(adapter.getConnectionState()); // 'connected' | 'error' | ...
console.log(dataSource.getConnectionState()); // 'connected' | 'error' | ...

adapter.onConnectionStateChange((event) => {
dataSource.onConnectionStateChange((event) => {
if (event.error) console.error('Connection error:', event.error);
});
```
Expand Down
26 changes: 8 additions & 18 deletions packages/data-objectstack/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,16 +44,6 @@ 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
Expand DownExpand Up@@ -258,9 +248,9 @@ 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';
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Get cache statistics
const stats = dataSource.getCacheStats();
Expand DownExpand Up@@ -288,9 +278,9 @@ dataSource.clearCache();
The adapter provides real-time connection state monitoring with automatic reconnection:

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

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

// Monitor connection state changes
const unsubscribe = dataSource.onConnectionStateChange((event) => {
Expand DownExpand Up@@ -336,9 +326,9 @@ 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';
import { createObjectStackAdapter } from '@object-ui/data-objectstack';

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

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

Expand DownExpand Up@@ -712,9 +702,9 @@ const dataSource = createObjectStackAdapter({
#### Cache Issues

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

declare const dataSource: ObjectStackAdapter;
const dataSource = createObjectStackAdapter({ baseUrl: 'https://api.example.com' });

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