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
73 changes: 65 additions & 8 deletions OBJECTSTACK_CLIENT_EVALUATION.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# @objectstack/client Evaluation for Low-Code App UI Development

> **Date:** February 10, 2026
> **Spec Version:** @objectstack/spec v2.0.1
> **Client Version:** @objectstack/client v2.0.1
> **Spec Version:** @objectstack/spec v2.0.4
> **Client Version:** @objectstack/client v2.0.4
> **Auth Plugin Version:** @objectstack/plugin-auth v2.0.3
> **ObjectUI Version:** v0.5.x
> **Scope:** Evaluate whether @objectstack/client can fully support developing a complete low-code application UI based on the @objectstack/spec protocol

Expand Down Expand Up @@ -104,6 +105,7 @@ The @objectstack/spec defines `ActionSchema` with 5 action types:

| Capability | Implementation | Client Dependency | Status |
|------------|---------------|-------------------|--------|
| **Server-Side Auth** | `@objectstack/plugin-auth` (AuthPlugin) | ObjectKernel plugin | ✅ Complete |
| **Token Authentication** | `ObjectStackAdapter({ token })` | Client constructor | ✅ Complete |
| **Dynamic Token Injection** | `ObjectStackAdapter({ fetch })` | Custom fetch wrapper | ✅ Complete |
| **Session Management** | `@object-ui/auth` (AuthProvider) | better-auth integration | ✅ Complete |
Expand All @@ -114,7 +116,7 @@ The @objectstack/spec defines `ActionSchema` with 5 action types:
| **OAuth/SSO** | better-auth plugins | External auth providers | ✅ Complete |
| **Multi-Factor Auth** | better-auth 2FA plugin | External auth providers | ✅ Available |

**Auth Assessment: 100% — Full auth stack is implemented via @object-ui/auth + better-auth.**
**Auth Assessment: 100% — Full auth stack is implemented. Server-side via @objectstack/plugin-auth (better-auth powered, ObjectQL persistence). Client-side via @object-ui/auth + better-auth.**

### 2.6 Multi-Tenancy

Expand Down Expand Up @@ -170,6 +172,19 @@ A complete low-code app built on @objectstack/spec follows this data flow:
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ ObjectStack Server │ │
│ │ │ │
│ │ ObjectKernel │ │
│ │ ├── ObjectQLPlugin (query engine) │ │
│ │ ├── DriverPlugin (data storage) │ │
│ │ ├── AuthPlugin (@objectstack/plugin-auth) │ │
│ │ │ └── better-auth (session, OAuth, 2FA, passkeys) │ │
│ │ ├── HonoServerPlugin (HTTP /api/v1/*) │ │
│ │ └── AppPlugin (stack config) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ ObjectUI Runtime │ │
│ │ │ │
│ │ AuthProvider → PermissionProvider → TenantProvider │ │
Expand Down Expand Up @@ -208,7 +223,7 @@ A complete low-code app built on @objectstack/spec follows this data flow:
| **Validation Rules** | `Data.Validation` | Server + client-side | `@object-ui/core` | ✅ Yes |
| **Permissions/RBAC** | `Security.RBAC` | Role data from server | `@object-ui/permissions` | ✅ Yes |
| **Multi-Tenancy** | `System.Tenant` | X-Tenant-ID header | `@object-ui/tenant` | ✅ Yes |
| **Authentication** | `Identity.Auth` | Token via custom fetch | `@object-ui/auth` | ✅ Yes |
| **Authentication** | `Identity.Auth` | Token via custom fetch | `@object-ui/auth` + `@objectstack/plugin-auth` | ✅ Yes |
| **i18n** | `Shared.i18n` | None (client-side) | `@object-ui/i18n` | ✅ Yes |
| **Theming** | `UI.Theme` | None (client-side) | Theme Provider | ✅ Yes |
| **AI Assistance** | `AI.Config` | AI API endpoints | `plugin-ai` | ✅ Yes |
Expand Down Expand Up @@ -291,7 +306,49 @@ export default defineStack({
});
```

### 5.2 Runtime Initialization
### 5.2 Server-Side Setup (with @objectstack/plugin-auth)

```typescript
// server.ts — Bootstrap the ObjectStack server with authentication
import { ObjectKernel } from '@objectstack/core';
import { ObjectQLPlugin } from '@objectstack/objectql';
import { AppPlugin, DriverPlugin } from '@objectstack/runtime';
import { HonoServerPlugin } from '@objectstack/plugin-hono-server';
import { InMemoryDriver } from '@objectstack/driver-memory';
import { AuthPlugin } from '@objectstack/plugin-auth';
import config from './objectstack.config';

async function startServer() {
const kernel = new ObjectKernel();

// Core engine + data driver
await kernel.use(new ObjectQLPlugin());
await kernel.use(new DriverPlugin(new InMemoryDriver()));

// Application stack
await kernel.use(new AppPlugin(config));

// Authentication via @objectstack/plugin-auth
// Provides /api/v1/auth/* endpoints (sign-in, sign-up, session, OAuth, 2FA, etc.)
await kernel.use(new AuthPlugin({
secret: process.env.AUTH_SECRET || 'dev-secret',
Comment on lines +332 to +334
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server-side setup example uses secret: process.env.AUTH_SECRET || 'dev-secret'. Similar to the CRM server code, a hard-coded fallback secret is unsafe and can be copied into production accidentally. Prefer failing fast when AUTH_SECRET is missing (or gating any fallback behind an explicit dev-only check) and mention required env vars in the example text.

Suggested change
// Provides /api/v1/auth/* endpoints (sign-in, sign-up, session, OAuth, 2FA, etc.)
await kernel.use(new AuthPlugin({
secret: process.env.AUTH_SECRET || 'dev-secret',
// Requires AUTH_SECRET to be set in the environment for signing tokens.
// Provides /api/v1/auth/* endpoints (sign-in, sign-up, session, OAuth, 2FA, etc.)
const authSecret = process.env.AUTH_SECRET;
if (!authSecret) {
throw new Error('AUTH_SECRET environment variable must be set for @objectstack/plugin-auth');
}
await kernel.use(new AuthPlugin({
secret: authSecret,

Copilot uses AI. Check for mistakes.
baseUrl: 'http://localhost:3000',
providers: [
// Optional: OAuth providers
// { id: 'google', clientId: '...', clientSecret: '...' },
],
}));

// HTTP server
await kernel.use(new HonoServerPlugin({ port: 3000 }));

await kernel.bootstrap();
}

startServer();
```

### 5.3 Runtime Initialization

```typescript
// App.tsx — Bootstrap the low-code runtime
Expand All @@ -312,7 +369,7 @@ function App() {
});

return (
<AuthProvider authUrl="/api/auth">
<AuthProvider authUrl="/api/v1/auth">
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This document switches the AuthProvider URL to /api/v1/auth, but the rest of the repo (e.g., @object-ui/auth docs/tests and apps/console) consistently uses /api/auth. Unless the server-side AuthPlugin is explicitly mounted at /api/v1/auth, this is likely to mislead readers. Either keep /api/auth here or add a short note explaining the exact route prefix used by @objectstack/plugin-auth and how to configure it to match.

Suggested change
<AuthProvider authUrl="/api/v1/auth">
<AuthProvider authUrl="/api/auth">

Copilot uses AI. Check for mistakes.
<TenantProvider>
<PermissionProvider>
<SchemaRenderer
Expand All @@ -326,7 +383,7 @@ function App() {
}
```

### 5.3 Object Definition
### 5.4 Object Definition

```typescript
// objects/task.object.ts — Spec-compliant object definition
Expand Down Expand Up @@ -382,7 +439,7 @@ export const TaskObject = ObjectSchema.create({
The combination provides:
- **Data Layer**: Complete CRUD, bulk ops, metadata, and caching via ObjectStackAdapter
- **UI Layer**: 35 packages, 91+ components, 13+ view types covering all spec requirements
- **Security**: Auth, RBAC, field/row-level permissions, multi-tenancy
- **Security**: Server-side auth via @objectstack/plugin-auth, client-side via @object-ui/auth, RBAC, field/row-level permissions, multi-tenancy
- **Developer Experience**: TypeScript-first, Shadcn design quality, expression engine
- **Extensibility**: Plugin system, custom widgets, theme customization

Expand Down
14 changes: 7 additions & 7 deletions apps/console/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,19 @@
"@object-ui/plugin-view": "workspace:*",
"@object-ui/react": "workspace:*",
"@object-ui/types": "workspace:*",
"@objectstack/client": "^2.0.1",
"@objectstack/driver-memory": "^2.0.1",
"@objectstack/objectql": "^2.0.1",
"@objectstack/plugin-msw": "^2.0.1",
"@objectstack/runtime": "^2.0.1",
"@objectstack/spec": "^2.0.1",
"@objectstack/client": "^2.0.4",
"@objectstack/driver-memory": "^2.0.4",
"@objectstack/objectql": "^2.0.4",
"@objectstack/plugin-msw": "^2.0.4",
"@objectstack/runtime": "^2.0.4",
"@objectstack/spec": "^2.0.4",
"lucide-react": "^0.563.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router-dom": "^7.13.0"
},
"devDependencies": {
"@objectstack/cli": "^2.0.1",
"@objectstack/cli": "^2.0.4",
"@tailwindcss/postcss": "^4.1.18",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
Expand Down
8 changes: 8 additions & 0 deletions apps/console/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ export default defineConfig({
transformMixedEsModules: true
},
rollupOptions: {
// @objectstack/core@2.0.4 statically imports Node.js crypto (for plugin hashing).
// The code already has a browser fallback, so we treat it as external in the browser build.
external: ['crypto'],
output: {
globals: {
crypto: '{}',
},
},
onwarn(warning, warn) {
if (
warning.code === 'UNRESOLVED_IMPORT' &&
Expand Down
9 changes: 5 additions & 4 deletions examples/crm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@
"start": "tsx server.ts"
},
"dependencies": {
"@objectstack/core": "^2.0.1",
"@objectstack/runtime": "^2.0.1",
"@objectstack/spec": "^2.0.1",
"@objectstack/core": "^2.0.4",
"@objectstack/plugin-auth": "^2.0.3",
"@objectstack/runtime": "^2.0.4",
"@objectstack/spec": "^2.0.4",
"pino": "^8.21.0"
},
"devDependencies": {
"@objectstack/cli": "^2.0.1",
"@objectstack/cli": "^2.0.4",
"typescript": "^5.0.0"
}
}
13 changes: 11 additions & 2 deletions examples/crm/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ObjectQLPlugin } from '@objectstack/objectql';
import { AppPlugin, DriverPlugin } from '@objectstack/runtime';
import { HonoServerPlugin } from '@objectstack/plugin-hono-server';
import { InMemoryDriver } from '@objectstack/driver-memory';
import { AuthPlugin } from '@objectstack/plugin-auth';
import config from './objectstack.config';
import { pino } from 'pino';
import { ConsolePlugin } from './console-plugin';
Expand Down Expand Up @@ -42,11 +43,19 @@ async function startServer() {
const appPlugin = new AppPlugin(config);
await kernel.use(appPlugin);

// 4. HTTP Server
// 4. Authentication (via @objectstack/plugin-auth)
// NOTE: In production, always set AUTH_SECRET env var. The fallback is for local development only.
const authPlugin = new AuthPlugin({
secret: process.env.AUTH_SECRET || 'objectui-dev-secret',
baseUrl: 'http://localhost:3000',
});
Comment on lines +47 to +51
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using a hard-coded default secret for authentication (even in an example). This makes it easy to accidentally run the server with a known secret. Prefer requiring AUTH_SECRET (fail fast) unless NODE_ENV === 'development', or generating a random dev secret at startup and logging a warning. Also consider sourcing baseUrl from an env var (or deriving it from the configured port) so the example works behind proxies / non-localhost environments.

Copilot uses AI. Check for mistakes.
await kernel.use(authPlugin);

// 5. HTTP Server
const serverPlugin = new HonoServerPlugin({ port: 3000 });
await kernel.use(serverPlugin);

// 5. Console Plugin
// 6. Console Plugin
const consolePlugin = new ConsolePlugin();
await kernel.use(consolePlugin);

Expand Down
4 changes: 2 additions & 2 deletions examples/kitchen-sink/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
"build": "objectstack compile objectstack.config.ts"
},
"dependencies": {
"@objectstack/spec": "^2.0.1"
"@objectstack/spec": "^2.0.4"
},
"devDependencies": {
"@objectstack/cli": "^2.0.1",
"@objectstack/cli": "^2.0.4",
"typescript": "^5.0.0"
}
}
12 changes: 6 additions & 6 deletions examples/msw-todo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
},
"dependencies": {
"@object-ui/example-todo": "workspace:*",
"@objectstack/client": "^2.0.1",
"@objectstack/driver-memory": "^2.0.1",
"@objectstack/objectql": "^2.0.1",
"@objectstack/plugin-msw": "^2.0.1",
"@objectstack/runtime": "^2.0.1",
"@objectstack/spec": "^2.0.1",
"@objectstack/client": "^2.0.4",
"@objectstack/driver-memory": "^2.0.4",
"@objectstack/objectql": "^2.0.4",
"@objectstack/plugin-msw": "^2.0.4",
"@objectstack/runtime": "^2.0.4",
"@objectstack/spec": "^2.0.4",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Expand Down
10 changes: 9 additions & 1 deletion examples/msw-todo/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,15 @@ export default defineConfig({
return;
}
warn(warning);
}
},
// @objectstack/core@2.0.4 statically imports Node.js crypto (for plugin hashing).
// The code already has a browser fallback, so we treat it as external in the browser build.
external: ['crypto'],
output: {
globals: {
crypto: '{}',
},
},
}
}
});
6 changes: 3 additions & 3 deletions examples/todo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
"build": "objectstack compile objectstack.config.ts"
},
"dependencies": {
"@objectstack/client": "^2.0.1",
"@objectstack/spec": "^2.0.1"
"@objectstack/client": "^2.0.4",
"@objectstack/spec": "^2.0.4"
},
"devDependencies": {
"@objectstack/cli": "^2.0.1",
"@objectstack/cli": "^2.0.4",
"typescript": "^5.0.0"
}
}
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@
"devDependencies": {
"@changesets/cli": "^2.29.8",
"@eslint/js": "^9.39.1",
"@objectstack/cli": "^2.0.1",
"@objectstack/core": "^2.0.1",
"@objectstack/driver-memory": "^2.0.1",
"@objectstack/objectql": "^2.0.1",
"@objectstack/plugin-msw": "^2.0.1",
"@objectstack/runtime": "^2.0.1",
"@objectstack/spec": "^2.0.1",
"@objectstack/cli": "^2.0.4",
"@objectstack/core": "^2.0.4",
"@objectstack/driver-memory": "^2.0.4",
"@objectstack/objectql": "^2.0.4",
"@objectstack/plugin-msw": "^2.0.4",
"@objectstack/runtime": "^2.0.4",
"@objectstack/spec": "^2.0.4",
"@storybook/addon-essentials": "^8.6.14",
"@storybook/addon-interactions": "^8.6.14",
"@storybook/addon-links": "^8.6.15",
Expand Down Expand Up @@ -131,7 +131,7 @@
},
"dependencies": {
"@hono/node-server": "^1.19.9",
"@objectstack/plugin-hono-server": "^2.0.1",
"@objectstack/plugin-hono-server": "^2.0.4",
"coverage-v8": "0.0.1-security",
"hono": "^4.11.9",
"pino": "^8.21.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
},
"dependencies": {
"@object-ui/types": "workspace:*",
"@objectstack/spec": "^2.0.1",
"@objectstack/spec": "^2.0.4",
"lodash": "^4.17.23",
"zod": "^4.3.6"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/data-objectstack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"dependencies": {
"@object-ui/core": "workspace:*",
"@object-ui/types": "workspace:*",
"@objectstack/client": "^2.0.1"
"@objectstack/client": "^2.0.4"
},
"devDependencies": {
"tsup": "^8.0.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-gantt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"@object-ui/fields": "workspace:*",
"@object-ui/react": "workspace:*",
"@object-ui/types": "workspace:*",
"@objectstack/spec": "^2.0.1",
"@objectstack/spec": "^2.0.4",
"lucide-react": "^0.563.0"
},
"peerDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-map/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@object-ui/core": "workspace:*",
"@object-ui/react": "workspace:*",
"@object-ui/types": "workspace:*",
"@objectstack/spec": "^2.0.1",
"@objectstack/spec": "^2.0.4",
"lucide-react": "^0.563.0",
"maplibre-gl": "^5.17.0",
"react-map-gl": "^8.1.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-timeline/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@object-ui/core": "workspace:*",
"@object-ui/react": "workspace:*",
"@object-ui/types": "workspace:*",
"@objectstack/spec": "^2.0.1",
"@objectstack/spec": "^2.0.4",
"zod": "^4.3.6"
},
"peerDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"dependencies": {
"@object-ui/core": "workspace:*",
"@object-ui/types": "workspace:*",
"@objectstack/spec": "^2.0.1",
"@objectstack/spec": "^2.0.4",
"react-hook-form": "^7.71.1"
},
"peerDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
"directory": "packages/types"
},
"dependencies": {
"@objectstack/spec": "^2.0.1",
"@objectstack/spec": "^2.0.4",
"zod": "^4.3.6"
},
"devDependencies": {
Expand Down
Loading
Loading