Skip to content
Open
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
88 changes: 26 additions & 62 deletions docs/api-reference/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ createDatabaseResource<Row extends DatabaseRow, Create = Partial<Row>>(resource:
## `createDatabaseView`

```ts
createDatabaseView(id: string, name: string, propertyIds: ReadonlyArray<string>, ownership?: DatabaseViewDocument["ownership"]): DatabaseViewDocument
createDatabaseView(id: string, name: string, propertyIds: ReadonlyArray<string>, ownership?: DatabaseTableView["ownership"]): DatabaseTableView
```
## `Database`

Expand All @@ -37,12 +37,7 @@ interface DatabaseCapabilities {
## `DatabaseColumnProjection`

```ts
interface DatabaseColumnProjection {
readonly propertyId: string;
readonly visible: boolean;
readonly width?: number;
readonly pinned?: "start" | "end";
}
interface DatabaseColumnProjection extends Record<string, JSONValue> { readonly propertyId: string; readonly visible: boolean; readonly width: number | null; readonly pinned: "start" | "end" | null; }
```
## `DatabaseContextValue`

Expand All @@ -52,15 +47,15 @@ interface DatabaseContextValue<Row extends DatabaseRow = DatabaseRow> {
readonly rows: ReadonlyArray<Row>;
readonly total: number;
readonly nextCursor?: string;
readonly view: DatabaseViewDocument;
readonly views: ReadonlyArray<DatabaseViewDocument>;
readonly view: DatabaseTableView;
readonly views: ReadonlyArray<DatabaseTableView>;
readonly status: DatabaseStatus;
readonly capabilities: Required<DatabaseCapabilities>;
readonly selectedRowIds: ReadonlyArray<DatabaseRowId>;
readonly activeRow: Row | null;
readonly isCreating: boolean;
setView(view: DatabaseViewDocument): void;
saveView(view: DatabaseViewDocument): Promise<void>;
setView(view: DatabaseTableView): void;
saveView(view: DatabaseTableView): Promise<void>;
selectRows(ids: ReadonlyArray<DatabaseRowId>): void;
openRow(row: Row | null): void;
startCreate(): void;
Expand All @@ -86,45 +81,25 @@ interface DatabaseDeleteResult {
```ts
type DatabaseFailureKind = "network" | "validation" | "conflict" | "partial" | "unknown";
```
## `DatabaseFilterGroup`
## `DatabaseFilter`

```ts
interface DatabaseFilterGroup {
readonly id: string;
readonly conjunction: "and" | "or";
readonly items: ReadonlyArray<DatabaseFilterRule | DatabaseFilterGroup>;
}
interface DatabaseFilter extends Record<string, JSONValue> { readonly id: string; readonly propertyId: string; readonly operator: DatabaseFilterOperator; readonly value: JSONValue; }
```
## `DatabaseFilterOperator`
## `DatabaseFilterGroup`

```ts
type DatabaseFilterOperator =
| "equals"
| "not-equals"
| "contains"
| "greater-than"
| "greater-than-or-equal"
| "less-than"
| "less-than-or-equal"
| "is-empty";
interface DatabaseFilterGroup extends Record<string, JSONValue> { readonly id: string; readonly conjunction: "and" | "or"; readonly items: ReadonlyArray<DatabaseFilter | DatabaseFilterGroup>; }
```
## `DatabaseFilterRule`
## `DatabaseFilterOperator`

```ts
interface DatabaseFilterRule {
readonly id: string;
readonly propertyId: string;
readonly operator: DatabaseFilterOperator;
readonly value?: unknown;
}
type DatabaseFilterOperator = "equals" | "not-equals" | "contains" | "greater-than" | "greater-than-or-equal" | "less-than" | "less-than-or-equal" | "is-empty";
```
## `DatabaseGroupRule`
## `DatabaseGroup`

```ts
interface DatabaseGroupRule {
readonly propertyId: string;
readonly direction: "ascending" | "descending";
}
interface DatabaseGroup extends Record<string, JSONValue> { readonly propertyId: string; readonly direction: "ascending" | "descending"; }
```
## `DatabaseHand`

Expand All @@ -149,6 +124,7 @@ interface DatabaseHandChange<Row> {
readonly records: ReadonlyArray<Row>;
readonly origin: "cell.commit" | "record.add" | "record.delete" | "undo" | "redo";
readonly revision: number;
readonly updates?: ReadonlyArray<{ readonly recordId: string; readonly patch: Partial<Row> }>;
}
```
## `DatabaseHandContext`
Expand Down Expand Up @@ -303,13 +279,7 @@ interface DatabaseOperations<Row extends DatabaseRow, Create = Partial<Row>, Upd
## `DatabaseProjection`

```ts
interface DatabaseProjection {
readonly search: string;
readonly filter: DatabaseFilterGroup;
readonly sorts: ReadonlyArray<DatabaseSortRule>;
readonly groups: ReadonlyArray<DatabaseGroupRule>;
readonly columns: ReadonlyArray<DatabaseColumnProjection>;
}
interface DatabaseProjection extends Record<string, JSONValue> { readonly search: string; readonly filter: DatabaseFilterGroup; readonly sorts: ReadonlyArray<DatabaseSort>; readonly groups: ReadonlyArray<DatabaseGroup>; readonly columns: ReadonlyArray<DatabaseColumnProjection>; }
```
## `DatabaseProvider`

Expand All @@ -322,11 +292,11 @@ DatabaseProvider<Row extends DatabaseRow, Create = Partial<Row>, Update = Partia
interface DatabaseProviderProps<Row extends DatabaseRow, Create = Partial<Row>, Update = Partial<Row>> {
readonly resource: DatabaseResource<Row, Create>;
readonly operations: DatabaseOperations<Row, Create, Update>;
readonly defaultView: DatabaseViewDocument;
readonly view?: DatabaseViewDocument;
readonly onViewChange?: (view: DatabaseViewDocument) => void;
readonly views?: ReadonlyArray<DatabaseViewDocument>;
readonly onSaveView?: (view: DatabaseViewDocument) => Promise<void> | void;
readonly defaultView: DatabaseTableView;
readonly view?: DatabaseTableView;
readonly onViewChange?: (view: DatabaseTableView) => void;
readonly views?: ReadonlyArray<DatabaseTableView>;
readonly onSaveView?: (view: DatabaseTableView) => Promise<void> | void;
readonly capabilities?: DatabaseCapabilities;
readonly pageSize?: number;
readonly children: ReactNode;
Expand All @@ -336,7 +306,7 @@ interface DatabaseProviderProps<Row extends DatabaseRow, Create = Partial<Row>,

```ts
interface DatabaseQueryRequest {
readonly view: DatabaseViewDocument;
readonly view: DatabaseTableView;
readonly cursor?: string;
readonly pageSize: number;
readonly signal: AbortSignal;
Expand Down Expand Up @@ -371,10 +341,10 @@ type DatabaseRow = Record<string, unknown>;
```ts
type DatabaseRowId = string;
```
## `DatabaseSortRule`
## `DatabaseSort`

```ts
interface DatabaseSortRule {
interface DatabaseSort extends Record<string, JSONValue> {
readonly propertyId: string;
readonly direction: "ascending" | "descending";
}
Expand All @@ -399,16 +369,10 @@ interface DatabaseTableProps<Row extends DatabaseRow> {
readonly density?: "comfortable" | "compact";
}
```
## `DatabaseViewDocument`
## `DatabaseTableView`

```ts
interface DatabaseViewDocument {
readonly id: string;
readonly name: string;
readonly ownership: "personal" | "shared" | "locked";
readonly layout: "table";
readonly projection: DatabaseProjection;
}
interface DatabaseTableView extends Record<string, JSONValue> { readonly id: string; readonly name: string; readonly ownership: "personal" | "shared" | "locked"; readonly layout: "table"; readonly projection: DatabaseProjection; }
```
## `useDatabase`

Expand Down
48 changes: 28 additions & 20 deletions docs/api-reference/editing.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ interface DatabaseClipboard extends Record<string, JSONValue> {
readonly text: string;
}
```
## `DatabaseColumnProjection`

```ts
interface DatabaseColumnProjection extends Record<string, JSONValue> { readonly propertyId: string; readonly visible: boolean; readonly width: number | null; readonly pinned: "start" | "end" | null; }
```
## `DatabaseDocument`

```ts
Expand Down Expand Up @@ -177,11 +182,22 @@ interface DatabaseEditor {
## `DatabaseFilter`

```ts
interface DatabaseFilter extends Record<string, JSONValue> {
readonly propertyId: string;
readonly operator: "equals";
readonly value: JSONValue;
}
interface DatabaseFilter extends Record<string, JSONValue> { readonly id: string; readonly propertyId: string; readonly operator: DatabaseFilterOperator; readonly value: JSONValue; }
```
## `DatabaseFilterGroup`

```ts
interface DatabaseFilterGroup extends Record<string, JSONValue> { readonly id: string; readonly conjunction: "and" | "or"; readonly items: ReadonlyArray<DatabaseFilter | DatabaseFilterGroup>; }
```
## `DatabaseFilterOperator`

```ts
type DatabaseFilterOperator = "equals" | "not-equals" | "contains" | "greater-than" | "greater-than-or-equal" | "less-than" | "less-than-or-equal" | "is-empty";
```
## `DatabaseGroup`

```ts
interface DatabaseGroup extends Record<string, JSONValue> { readonly propertyId: string; readonly direction: "ascending" | "descending"; }
```
## `DatabaseIntent`

Expand Down Expand Up @@ -211,11 +227,7 @@ type DatabaseIntent =
| {
readonly type: "view.configure";
readonly viewId: string;
readonly propertyOrder?: ReadonlyArray<string>;
readonly propertyVisibility?: Readonly<Record<string, boolean>>;
readonly propertyWidths?: Readonly<Record<string, number>>;
readonly sort?: DatabaseSort | null;
readonly filter?: DatabaseFilter | null;
readonly projection: DatabaseProjection;
}
| {
readonly type: "clipboard.paste";
Expand All @@ -231,6 +243,11 @@ interface DatabasePoint extends Record<string, JSONValue> {
readonly propertyId: string;
}
```
## `DatabaseProjection`

```ts
interface DatabaseProjection extends Record<string, JSONValue> { readonly search: string; readonly filter: DatabaseFilterGroup; readonly sorts: ReadonlyArray<DatabaseSort>; readonly groups: ReadonlyArray<DatabaseGroup>; readonly columns: ReadonlyArray<DatabaseColumnProjection>; }
```
## `DatabaseProperty`

```ts
Expand Down Expand Up @@ -292,16 +309,7 @@ interface DatabaseSort extends Record<string, JSONValue> {
## `DatabaseTableView`

```ts
interface DatabaseTableView extends Record<string, JSONValue> {
readonly id: string;
readonly name: string;
readonly type: "table";
readonly propertyOrder: ReadonlyArray<string>;
readonly propertyVisibility: Readonly<Record<string, boolean>>;
readonly propertyWidths: Readonly<Record<string, number>>;
readonly sort: DatabaseSort | null;
readonly filter: DatabaseFilter | null;
}
interface DatabaseTableView extends Record<string, JSONValue> { readonly id: string; readonly name: string; readonly ownership: "personal" | "shared" | "locked"; readonly layout: "table"; readonly projection: DatabaseProjection; }
```
## `DatabaseTopology`

Expand Down
12 changes: 8 additions & 4 deletions docs/public/database.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Database

Database Hands는 이미 존재하는 resource schema와 CRUD API 위에 완성된 database
UX를 놓습니다. 데이터·권한 정책·업무 규칙은 host가 소유하고, Hands는 query,
projection, editing, async failure와 접근성 품질을 소유합니다.
UX를 놓습니다. 데이터·권한 정책·업무 규칙은 host가 소유하고, Editing은
saved-view projection을, Hands는 query wiring, React editing, async failure와 접근성 품질을 소유합니다.

```text
Host
Expand Down Expand Up @@ -101,8 +101,12 @@ import {
} from "@interactive-os/json-document-editing";

const editor = createDatabaseEditor(document);
const sort = nextDatabasePropertySort(view.sort, propertyId);
editor.dispatch({ type: "view.configure", viewId: view.id, sort });
const sort = nextDatabasePropertySort(view.projection.sorts[0] ?? null, propertyId);
editor.dispatch({
type: "view.configure",
viewId: view.id,
projection: { ...view.projection, sorts: sort === null ? [] : [sort] },
});
const initialValue = defaultDatabaseValue(property);
const value = databaseValueFromText(property, input.value);
if (acceptsDatabaseValue(property, value)) {
Expand Down
7 changes: 4 additions & 3 deletions fixtures/external-kit/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from "react";
import { createRoot } from "react-dom/client";
import { Database, DatabaseOperationError, createDatabaseResource, createDatabaseView, type DatabaseFilterGroup, type DatabaseOperations, type DatabaseViewDocument } from "@interactive-os/json-document-database";
import { Database, DatabaseOperationError, createDatabaseResource, createDatabaseView, type DatabaseFilter, type DatabaseFilterGroup, type DatabaseOperations, type DatabaseTableView } from "@interactive-os/json-document-database";
import "@interactive-os/json-document-database/styles.css";
import * as z from "zod/v4";
import "./styles.css";
Expand All @@ -10,7 +10,7 @@ type Task = z.infer<typeof taskSchema>;
const resource = createDatabaseResource<Task>({ id: "delivery-tasks", schema: taskSchema, getRowId: (row) => row.id, createDraft: () => ({ title: "", owner: "", points: 0, status: "backlog", shipped: false }) });
const allTasks = createDatabaseView("all", "All delivery", ["title", "owner", "points", "status", "shipped"], "shared");
const triageBase = createDatabaseView("triage", "My triage", ["status", "title", "owner", "points", "shipped"]);
const triageView: DatabaseViewDocument = { ...triageBase, projection: { ...triageBase.projection, filter: { id: "triage:root", conjunction: "and", items: [{ id: "open", propertyId: "status", operator: "not-equals", value: "done" }] }, columns: [{ propertyId: "status", visible: true, width: 150, pinned: "start" }, { propertyId: "title", visible: true, width: 300 }, { propertyId: "owner", visible: true, width: 150 }, { propertyId: "points", visible: true, width: 110 }, { propertyId: "shipped", visible: true, width: 120 }] } };
const triageView: DatabaseTableView = { ...triageBase, projection: { ...triageBase.projection, filter: { id: "triage:root", conjunction: "and", items: [{ id: "open", propertyId: "status", operator: "not-equals", value: "done" }] }, columns: [{ propertyId: "status", visible: true, width: 150, pinned: "start" }, { propertyId: "title", visible: true, width: 300, pinned: null }, { propertyId: "owner", visible: true, width: 150, pinned: null }, { propertyId: "points", visible: true, width: 110, pinned: null }, { propertyId: "shipped", visible: true, width: 120, pinned: null }] } };

let serverRows = Array.from({ length: 240 }, (_, index): Task => ({ id: `task-${index + 1}`, title: ["Triage customer feedback", "Polish billing settings", "Publish changelog", "Archive legacy exports"][index % 4]! + (index < 4 ? "" : ` ${index + 1}`), owner: ["Ada", "Lin", "Mina", "Theo"][index % 4]!, points: (index % 8) + 1, status: ["backlog", "progress", "done"][index % 3] as Task["status"], shipped: index % 3 === 2 }));
let failureMode: "none" | "network" | "conflict" = "none";
Expand Down Expand Up @@ -69,9 +69,10 @@ function DatabaseApp() {

function matchesGroup(row: Task, group: DatabaseFilterGroup): boolean {
if (group.items.length === 0) return true;
const results = group.items.map((item) => "propertyId" in item ? matchesRule(row, item.propertyId, item.operator, item.value) : matchesGroup(row, item));
const results = group.items.map((item) => isFilter(item) ? matchesRule(row, item.propertyId, item.operator, item.value) : matchesGroup(row, item));
return group.conjunction === "and" ? results.every(Boolean) : results.some(Boolean);
}
function isFilter(item: DatabaseFilter | DatabaseFilterGroup): item is DatabaseFilter { return typeof item.propertyId === "string"; }
function matchesRule(row: Task, propertyId: string, operator: string, expected: unknown): boolean {
const actual = row[propertyId as keyof Task];
if (operator === "equals") return String(actual) === String(expected);
Expand Down
5 changes: 3 additions & 2 deletions packages/json-document-database/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# @interactive-os/json-document-database

Enterprise React Database Hands for existing schemas and CRUD APIs. The host
owns data, authorization, and business rules; the package owns the interaction
quality of querying, projecting, editing, and recovering from failures.
owns data, authorization, and business rules; `@interactive-os/json-document-editing`
owns the saved-view projection contract, and this package owns the React interaction
quality of querying, editing, and recovering from failures.

```tsx
import { Database, createDatabaseResource, createDatabaseView } from "@interactive-os/json-document-database";
Expand Down
Loading