diff --git a/content/docs/api/index.mdx b/content/docs/api/index.mdx index ac94174720..44742af731 100644 --- a/content/docs/api/index.mdx +++ b/content/docs/api/index.mdx @@ -15,7 +15,7 @@ ObjectStack exposes a fully typed REST API. All endpoints use JSON request/respo | :--- | :--- | | **REST** | ✅ Auto-generated from the protocol (`@objectstack/rest`) — CRUD, query, batch, metadata, packages | | **Realtime** | ⚠️ In-process pub/sub service (`@objectstack/service-realtime`, single-instance); the `/realtime/*` REST routes and WebSocket/SSE transport are plugin-provided — none ships in the open framework | -| **MCP** | ✅ Non-system objects and their actions exposed as Model Context Protocol tools, gated by the caller's permissions/RLS rather than a per-action opt-in flag ([AI module](/docs/ai)) | +| **MCP** | ✅ Non-system objects exposed automatically as Model Context Protocol tools; actions additionally require the author's `ai.exposed` opt-in — every call is gated by the caller's permissions/RLS ([AI module](/docs/ai)) | | **GraphQL** | ⚠️ Route is wired but **bring-your-own service**: `/graphql` returns 501 unless an implementation of the `IGraphQLService` contract is registered — none ships in the open framework | | **OData** | ⚠️ Vocabulary only: REST list endpoints accept OData-style operators (e.g. `$top`), but there is no standalone OData endpoint | @@ -58,6 +58,23 @@ sequenceDiagram See [Actions as Tools](/docs/ai/actions-as-tools) for the `run_action` bridge and the [MCP reference](/docs/references/ai/mcp) for binding external MCP servers into your agents. +## Authentication + +Every REST call runs as a principal — anonymous requests only see what your +permission model grants anonymous users. Two ways to authenticate: + +- **Session cookie** (browsers, quick local tests): `POST /api/v1/auth/sign-in/email` + with `{ "email": "…", "password": "…" }` sets the session cookie — reuse it with + `curl -c cookies.txt` / `-b cookies.txt`. On a fresh dev database the seeded + admin is `admin@objectos.ai` / `admin123`. +- **API key** (scripts, CI, headless agents): mint one with `POST /api/v1/keys` + (the key is shown once), or from **Setup → Connect an Agent** in the Console. + Send it as `x-api-key: osk_…` or `Authorization: Bearer osk_…`. + +See [Authentication](/docs/permissions/authentication) for the full identity +surface (OAuth flows, sessions, providers) and +[Plugin Endpoints](/docs/api/plugin-endpoints) for the auth route catalog. + ## What's in this module - [Data API](/docs/api/data-api) — CRUD, batch operations, record cloning, and analytics queries diff --git a/content/docs/data-modeling/field-type-decision-tree.mdx b/content/docs/data-modeling/field-type-decision-tree.mdx index 159bc0d314..1f96b30c8e 100644 --- a/content/docs/data-modeling/field-type-decision-tree.mdx +++ b/content/docs/data-modeling/field-type-decision-tree.mdx @@ -146,7 +146,7 @@ flowchart TD |:---|:---|:---| | `number` | Any numeric value (set `precision`/`scale` for decimals) | `price`, `weight`, `quantity` | | `currency` | Money amounts with currency code | `amount`, `total_price` | -| `percent` | Percentage values (0–100 or 0–1) | `completion`, `discount` | +| `percent` | Percentage values, stored as fractions 0–1 (`0.85` = 85%) | `completion`, `discount` | | `rating` | Star ratings (typically 1–5) | `satisfaction`, `difficulty` | | `slider` | Numeric value via slider UI | `volume`, `priority_level` | diff --git a/content/docs/data-modeling/field-types.mdx b/content/docs/data-modeling/field-types.mdx index 8c906eb9d1..41cbc47dfd 100644 --- a/content/docs/data-modeling/field-types.mdx +++ b/content/docs/data-modeling/field-types.mdx @@ -301,7 +301,7 @@ Reference to a record in another object (foreign key). | Property | Type | Default | Description | |:---|:---|:---|:---| | `reference` | `string` | **required** | Target object name (snake_case) | -| `referenceFilters` | `string[]` | — | Filters applied to lookup dialogs (e.g. `"active = true"`) | +| `referenceFilters` | `string[]` | — | **Legacy** — accepted by the schema but not read by the record-picker (filters nothing). Use structured `lookupFilters` + `dependsOn` instead; see [Relationships](/docs/data-modeling/relationships) | | `deleteBehavior` | `'restrict' \| 'cascade' \| 'set_null'` | `'set_null'` | Behavior when referenced record is deleted (a *required* lookup left at the default `set_null` is escalated to `restrict`, since a NOT NULL foreign key cannot be cleared) | ```typescript @@ -323,7 +323,7 @@ Parent-child relationship (cascading delete by default). | Property | Type | Default | Description | |:---|:---|:---|:---| | `reference` | `string` | **required** | Target (master) object name | -| `referenceFilters` | `string[]` | — | Filters applied to lookup dialogs (e.g. `"active = true"`) | +| `referenceFilters` | `string[]` | — | **Legacy** — accepted by the schema but not read by the record-picker (filters nothing). Use structured `lookupFilters` + `dependsOn` instead; see [Relationships](/docs/data-modeling/relationships) | | `deleteBehavior` | `'restrict' \| 'cascade' \| 'set_null'` | `'cascade'` | Behavior when parent is deleted (master-detail cascades unless set to `restrict`) | | `inlineEdit` | `boolean \| 'grid' \| 'form'` | — | Edit child records inline on the parent create/edit form (`true` = auto-pick, `'grid'`, or `'form'`) | | `inlineColumns` | `array` | — | Optional explicit inline grid columns | diff --git a/content/docs/data-modeling/fields.mdx b/content/docs/data-modeling/fields.mdx index 44770458f8..1ae9f3237c 100644 --- a/content/docs/data-modeling/fields.mdx +++ b/content/docs/data-modeling/fields.mdx @@ -63,7 +63,8 @@ notes: Field.markdown({ label: 'Notes' }), ```typescript quantity: Field.number({ label: 'Quantity', min: 0, max: 10000, step: 1 }), price: Field.currency({ label: 'Price', scale: 2, min: 0 }), -discount: Field.percent({ label: 'Discount', scale: 2, min: 0, max: 100 }), +// percent stores a fraction: 0.15 = 15% (the UI renders it as a percentage) +discount: Field.percent({ label: 'Discount', scale: 2, min: 0, max: 1 }), ``` **Currency Configuration:** @@ -145,10 +146,13 @@ account: Field.lookup('account', { required: true, }), -// Filtered lookup +// Filtered lookup — structured filters, cascading from another field primary_contact: Field.lookup('contact', { label: 'Primary Contact', - referenceFilters: ['account = {account}', 'is_active = true'], + dependsOn: ['account'], + lookupFilters: [ + { field: 'is_active', operator: 'eq', value: true }, + ], }), // Master-detail (cascade delete) diff --git a/content/docs/data-modeling/validation-rules.mdx b/content/docs/data-modeling/validation-rules.mdx index c437e1c1ec..c3c47336ae 100644 --- a/content/docs/data-modeling/validation-rules.mdx +++ b/content/docs/data-modeling/validation-rules.mdx @@ -244,7 +244,7 @@ These properties apply to **all** field types and are validated by the base `Fie | Property | Type | Default | Validation Behavior | |:---|:---|:---|:---| | `reference` | `string` | — | **Required.** Target object name | -| `referenceFilters` | `string[]` | — | Filter expressions for lookup dialogs | +| `referenceFilters` | `string[]` | — | **Legacy** — schema-accepted but not read by the record-picker; use `lookupFilters` + `dependsOn` | | `deleteBehavior` | `enum` | `set_null` | `set_null`, `cascade`, or `restrict` | | `multiple` | `boolean` | `false` | Allow multiple references | diff --git a/content/docs/deployment/backup-restore.mdx b/content/docs/deployment/backup-restore.mdx index 8f0097bc62..0797015d46 100644 --- a/content/docs/deployment/backup-restore.mdx +++ b/content/docs/deployment/backup-restore.mdx @@ -57,10 +57,11 @@ sqlite3 /srv/data/app.db ".backup '/backups/app-$(date +%F).db'" The default location when `OS_DATABASE_URL` is unset is `/data/objectstack.db` under the ObjectStack home directory. -### Turso / libSQL and managed databases +### Managed databases (hosted Postgres, MongoDB Atlas, …) Use the provider's snapshot, branching, or point-in-time-restore facilities. -Nothing ObjectStack-specific applies. +Nothing ObjectStack-specific applies. (Turso/libSQL applies only to ObjectStack +Cloud-managed environments — the open framework does not ship that driver.) ## Backing up uploaded files diff --git a/content/docs/deployment/self-hosting.mdx b/content/docs/deployment/self-hosting.mdx index c9e0d49010..6a8ba37459 100644 --- a/content/docs/deployment/self-hosting.mdx +++ b/content/docs/deployment/self-hosting.mdx @@ -32,7 +32,7 @@ workable default: | Variable | Why it must be set | |:---|:---| -| `OS_DATABASE_URL` | Without it, data lands in a SQLite file under the ObjectStack home directory (`~/.objectstack`, or `/.objectstack` next to a project config) — fine for one box, wrong for containers. Use `postgres://…`, `libsql://…`, or a mounted `file:…` path. | +| `OS_DATABASE_URL` | Without it, data lands in a SQLite file under the ObjectStack home directory (`~/.objectstack`, or `/.objectstack` next to a project config) — fine for one box, wrong for containers. Use `postgres://…`, `mongodb://…`, or a mounted `file:…` path (`libsql://` / Turso is **not** supported by the open framework — that driver ships in ObjectStack Cloud). | | `OS_AUTH_SECRET` | Session secret for the auth plugin (`AUTH_SECRET` is the legacy alias). Without it, `/api/v1/auth/*` is **silently skipped** — the server runs unauthenticated. | | `OS_SECRET_KEY` | 32-byte master key encrypting every stored secret (`openssl rand -hex 32`). On a container's ephemeral filesystem the auto-minted key is **lost on restart**, making previously-encrypted secrets undecryptable. | | `OS_PORT` | `os start` **fails loudly** if the port is busy (it never auto-shifts like `os dev`). Pin it and keep your reverse-proxy upstream in sync. | @@ -117,7 +117,7 @@ straight from release storage instead of a mount.) For a self-contained deployable image, extend it. The Dockerfile below (plus the compose stack in the next section and a `.dockerignore`) ships ready-to-copy in -[`examples/docker`](https://github.com/objectstack-ai/framework/tree/main/examples/docker) +[`docker/`](https://github.com/objectstack-ai/framework/tree/main/docker) — drop the files into your scaffolded project. ```dockerfile title="Dockerfile" @@ -314,6 +314,20 @@ decrypt each other's secrets. All replicas must share the same `OS_SECRET_KEY`, `OS_AUTH_SECRET`, and database. See [Cluster](/docs/kernel/cluster). +## First boot: create the admin + +On a fresh production database there are no users yet. Open the deployment's +root URL and **sign up — the very first account to register becomes the +bootstrap admin** (this works even with `OS_DISABLE_SIGNUP=true`, which only +blocks sign-ups after that first account exists). Do this immediately after +the first deploy, before sharing the URL; then create your real user accounts +and lock sign-up down via `OS_AUTH_SIGNUP_ENABLED` / +[SSO](/docs/permissions/sso) as policy dictates. + +Note the production server seeds **no** dev credentials — the +`admin@objectos.ai` / `admin123` account you may know from `os dev` exists only +on empty development databases. + ## Go-live Before pointing real users at the deployment, walk the diff --git a/content/docs/deployment/vercel.mdx b/content/docs/deployment/vercel.mdx index 0fa55e5e0b..af3e1009d9 100644 --- a/content/docs/deployment/vercel.mdx +++ b/content/docs/deployment/vercel.mdx @@ -78,6 +78,13 @@ export async function ensureApp(): Promise { } ``` + +`InMemoryDriver` keeps this snippet self-contained, but on serverless **all data +is lost on every cold start** — fine for a demo, wrong for anything real. Before +going past a proof of concept, swap the driver for a real database (see +[Database Drivers](/docs/data-modeling/drivers)) and point `OS_DATABASE_URL` at it. + + **2. Create the API entrypoint** (`api/index.ts`): ```typescript diff --git a/content/docs/getting-started/build-with-claude-code.mdx b/content/docs/getting-started/build-with-claude-code.mdx index 5c2bf5368f..0b7510cd0c 100644 --- a/content/docs/getting-started/build-with-claude-code.mdx +++ b/content/docs/getting-started/build-with-claude-code.mdx @@ -257,11 +257,12 @@ Validation proves the metadata is *well-formed*. It can't prove the app does wha *you* meant. That's the human half of the loop: **run it and look.** ```bash -os dev --ui +npx os dev --ui ``` -Open [http://localhost:3000/_console/](http://localhost:3000/_console/) and drive -the app like a user: +Open [http://localhost:3000/_console/](http://localhost:3000/_console/) and sign in +as the dev admin the server seeds on an empty database +(`admin@objectos.ai` / `admin123`). Then drive the app like a user: - Create a ticket. Confirm the fields, labels, and picklist colors match intent. - Check the **Resolve** action shows on an open ticket — and **disappears** once @@ -316,7 +317,12 @@ its own OAuth 2.1 authorization server, so interactive clients just open a browser login (you connect as yourself, no admin-minted credentials): ```bash +# the dev server you just booted in step 5 +claude mcp add --transport http support-desk http://localhost:3000/api/v1/mcp + +# or a deployed instance claude mcp add --transport http support-desk https://your-deployment.example.com/api/v1/mcp + # first tool use opens a browser login — you're connected as yourself ``` diff --git a/content/docs/getting-started/common-patterns.mdx b/content/docs/getting-started/common-patterns.mdx index 005ca08bac..16cb44b65c 100644 --- a/content/docs/getting-started/common-patterns.mdx +++ b/content/docs/getting-started/common-patterns.mdx @@ -11,6 +11,15 @@ This guide covers the most common patterns you will use when building applicatio **Import:** `import { defineStack, defineView, defineApp, defineFlow, defineAgent } from '@objectstack/spec'` + +**Before you copy:** the examples use short object names (`project`, `order`) for +readability. In a real project, `os validate` additionally enforces your project's +**namespace prefix** on every object name (`my_app_project`) and an explicit +**`sharingModel`** on every custom object (ADR-0090) — the patterns below include +`sharingModel`; remember to add your prefix. See +[Validating Metadata](/docs/getting-started/validating-metadata). + + --- ## 1. CRUD Object Definition @@ -27,6 +36,7 @@ export default defineStack({ { name: 'project', label: 'Project', + sharingModel: 'public_read_write', fields: { title: { label: 'Title', type: 'text', required: true, maxLength: 200 }, description: { label: 'Description', type: 'textarea' }, @@ -53,6 +63,7 @@ export default defineStack({ objects: { project: { label: 'Project', + sharingModel: 'public_read_write', fields: { title: { label: 'Title', type: 'text', required: true, maxLength: 200 }, description: { label: 'Description', type: 'textarea' }, @@ -85,6 +96,7 @@ Create a parent-child relationship between a master and its detail records. Note { name: 'order', label: 'Order', + sharingModel: 'public_read', fields: { order_number: { label: 'Order #', type: 'autonumber', autonumberFormat: 'ORD-{0000}' }, customer: { label: 'Customer', type: 'lookup', reference: 'contact' }, @@ -106,8 +118,10 @@ Create a parent-child relationship between a master and its detail records. Note } }, { + // Master-detail child: access is derived from the parent order record name: 'order_line', label: 'Order Line', + sharingModel: 'controlled_by_parent', fields: { order: { label: 'Order', @@ -265,6 +279,7 @@ Define a multi-step approval flow for records. objects: [{ name: 'expense_report', label: 'Expense Report', + sharingModel: 'private', fields: { title: { label: 'Title', type: 'text', required: true }, amount: { label: 'Amount', type: 'currency' }, @@ -402,6 +417,7 @@ Restrict field visibility and editability based on user profiles. objects: [{ name: 'employee', label: 'Employee', + sharingModel: 'private', fields: { name: { label: 'Name', type: 'text', required: true }, email: { label: 'Email', type: 'email', required: true }, diff --git a/content/docs/getting-started/index.mdx b/content/docs/getting-started/index.mdx index ea4608cba7..1e89a346b7 100644 --- a/content/docs/getting-started/index.mdx +++ b/content/docs/getting-started/index.mdx @@ -157,16 +157,21 @@ Before you start, make sure you have the following installed: | Tool | Minimum Version | Check Command | |:---|:---|:---| | **Node.js** | 18.0.0+ | `node --version` | -| **pnpm** | 8.0.0+ (repo pins 10.x) | `pnpm --version` | -| **TypeScript** | 5.3.0+ (project targets 6.x) | `npx tsc --version` | + +That's everything the standard path needs — `npx create-objectstack my-app` +scaffolds a project with its own TypeScript toolchain and npm scripts. -**Why pnpm?** ObjectStack uses pnpm workspaces for monorepo management. Install it with `npm install -g pnpm` or `corepack enable` (corepack will pull the pinned pnpm 10.x from the repo's `packageManager` field). +**pnpm and TypeScript versions only matter for the framework monorepo.** If you +clone [`objectstack-ai/framework`](https://github.com/objectstack-ai/framework) +itself (to contribute, or to run the bundled examples), you'll additionally need +pnpm 8+ (`corepack enable` pulls the pinned 10.x) — and the repo builds against +TypeScript 6.x. ## Common Setup Issues -### pnpm not found +### pnpm not found (framework monorepo only) ```bash # Install pnpm globally npm install -g pnpm diff --git a/content/docs/getting-started/quick-reference.mdx b/content/docs/getting-started/quick-reference.mdx index c6649c0864..6f41dffae8 100644 --- a/content/docs/getting-started/quick-reference.mdx +++ b/content/docs/getting-started/quick-reference.mdx @@ -89,12 +89,12 @@ Runtime environment, logging, jobs, caching, and observability. | **[Cache](/docs/references/system/cache)** | `cache.zod.ts` | CacheConfig | Caching layer | | **[Change Management](/docs/references/system/change-management)** | `change-management.zod.ts` | ChangeManagement | Change tracking | | **[Collaboration](/docs/references/system/collaboration)** | `collaboration.zod.ts` | Collaboration | Real-time collab | -| **[Compliance](/docs/references/system/compliance)** | `compliance.zod.ts` | Compliance | Regulatory controls | +| **Compliance** | `compliance.zod.ts` | Compliance | Regulatory controls | | **[Encryption](/docs/references/system/encryption)** | `encryption.zod.ts` | Encryption | Encryption & keys | | **[HTTP Server](/docs/references/system/http-server)** | `http-server.zod.ts` | HTTPServer | HTTP server config | | **[Job](/docs/references/system/job)** | `job.zod.ts` | Job, JobSchedule | Background job queue | | **[Logging](/docs/references/system/logging)** | `logging.zod.ts` | LoggingConfig | Structured logging | -| **[Masking](/docs/references/system/masking)** | `masking.zod.ts` | Masking | Data masking | +| **Masking** | `masking.zod.ts` | Masking | Data masking | | **[Message Queue](/docs/references/system/message-queue)** | `message-queue.zod.ts` | MessageQueue | Message queuing | | **[Metadata Persistence](/docs/references/system/metadata-persistence)** | `metadata-persistence.zod.ts` | MetadataPersistence | Metadata storage | | **[Metrics](/docs/references/system/metrics)** | `metrics.zod.ts` | Metrics | Application metrics | diff --git a/content/docs/getting-started/quick-start.mdx b/content/docs/getting-started/quick-start.mdx index 84324a545f..c08041442f 100644 --- a/content/docs/getting-started/quick-start.mdx +++ b/content/docs/getting-started/quick-start.mdx @@ -146,8 +146,8 @@ Reading the metadata is half of verification; running the app is the other half. Two commands, both of which the agent runs for you but you can run yourself: ```bash -os validate # the gate — schema + CEL predicates + widget bindings (no artifact) -os dev --ui # boot the app, then open http://localhost:3000/_console/ +npx os validate # the gate — schema + CEL predicates + widget bindings (no artifact) +npx os dev --ui # boot the app, then open http://localhost:3000/_console/ ``` `os validate` proves the metadata is *well-formed*; the **Console** at `/_console/` diff --git a/content/docs/kernel/services-checklist.mdx b/content/docs/kernel/services-checklist.mdx index d735e13e9d..bc01fc6b2e 100644 --- a/content/docs/kernel/services-checklist.mdx +++ b/content/docs/kernel/services-checklist.mdx @@ -5,6 +5,14 @@ description: Complete inventory of ObjectStack kernel services with protocol met # Kernel Services Checklist + +**This checklist is out of date.** It predates several packages that have since +shipped — SQL and MongoDB drivers, automation, realtime, storage, and more (see +[Plugins & Packages](/docs/plugins/packages) for the current catalog). Keep it +for the protocol-method inventory; treat the per-service status columns as +historical until this page is regenerated. + + The ObjectStack protocol defines **17 kernel services** registered via the `CoreServiceName` enum. Each service maps to a set of protocol methods (57 total), governed by the `ObjectStackProtocol` interface. **Key architecture principle**: The kernel itself only provides **data** and **metadata** (framework). Everything else — including **auth** and **automation** — is delivered by **plugins**. The current `@objectstack/objectql` package is an example kernel implementation to get the basic API running; production kernels will be rebuilt as new plugins. diff --git a/content/docs/permissions/permission-metadata.mdx b/content/docs/permissions/permission-metadata.mdx index 2d306623fd..438e1a9192 100644 --- a/content/docs/permissions/permission-metadata.mdx +++ b/content/docs/permissions/permission-metadata.mdx @@ -196,7 +196,8 @@ contextVariables: { The built-in context variables available in RLS `using`/`check` clauses include `current_user.id`, `current_user.organization_id`, and `current_user.positions` -(ADR-0068). `roles` is a **string array** — the only canonical role field — so test +(ADR-0068). `positions` is a **string array** — the canonical assignment field +(D3 reserves the word "role"; there is no `current_user.roles`) — so test membership with CEL: `'org_admin' in current_user.positions` or `current_user.positions.exists(p, p == 'sales_manager')`. The framework-seeded built-in position names are `platform_admin`, `org_owner`, `org_admin`, and diff --git a/content/docs/permissions/permissions-matrix.mdx b/content/docs/permissions/permissions-matrix.mdx index 2440a3101f..e2f1a39c9e 100644 --- a/content/docs/permissions/permissions-matrix.mdx +++ b/content/docs/permissions/permissions-matrix.mdx @@ -189,13 +189,19 @@ OWD sets the baseline access level for each object across the entire organizatio | Default | `sharingModel` | Read Access | Write Access | Use When | |:---|:---|:---|:---|:---| -| **Public Read/Write** | `read_write` | All users | All users | Low-sensitivity data (e.g., tasks, wiki pages) | -| **Public Read Only** | `read` | All users | Owner + shared | Moderate sensitivity (e.g., accounts, contacts) | +| **Public Read/Write** | `public_read_write` | All users | All users | Low-sensitivity data (e.g., tasks, wiki pages) | +| **Public Read Only** | `public_read` | All users | Owner + shared | Moderate sensitivity (e.g., accounts, contacts) | | **Private** | `private` | Owner + shared | Owner + shared | High sensitivity (e.g., opportunities, HR records) | -| **Full Access** | `full` | All users | All users | Legacy alias — enforced identically to `public_read_write` | +| **Controlled by Parent** | `controlled_by_parent` | Derived from the master record | Derived from the master record | Master-detail children (e.g., order lines) | - -Since ADR-0056 (D1), `object.sharingModel` accepts the **canonical OWD vocabulary** — `private`, `public_read`, `public_read_write`, and `controlled_by_parent` — *in addition to* the legacy `read` / `read_write` / `full` spellings shown above (both are valid; the canonical values are preferred for new objects). `controlled_by_parent` is for child objects in a master-detail relationship, where access is **derived from the parent record** (a line is visible/editable only if its master is). These models are enforced by `plugin-sharing` + `plugin-security` and dogfood-proven over the real HTTP stack. + +These are the **only four canonical values** (ADR-0090 D4). The legacy spellings +`read` / `read_write` / `full` were **removed** — the conversion pipeline rewrites +them to `public_read` / `public_read_write` on upgrade, and an unknown value +resolves to `private` (fail-closed). A custom object that omits `sharingModel` +resolves to `private` at runtime (D1), and `os validate`'s security-posture check +requires every custom object to declare it explicitly. These models are enforced +by `plugin-sharing` + `plugin-security` and dogfood-proven over the real HTTP stack. ### Configuration Example @@ -204,10 +210,10 @@ Since ADR-0056 (D1), `object.sharingModel` accepts the **canonical OWD vocabular // OWD is declared on each object, not as a central map. defineStack({ objects: { - account: { sharingModel: 'read' /* ...fields */ }, - contact: { sharingModel: 'read' }, + account: { sharingModel: 'public_read' /* ...fields */ }, + contact: { sharingModel: 'public_read' }, opportunity: { sharingModel: 'private' }, - task: { sharingModel: 'read_write' }, + task: { sharingModel: 'public_read_write' }, hr_record: { sharingModel: 'private' }, }, }); diff --git a/content/docs/plugins/development.mdx b/content/docs/plugins/development.mdx index 86fa279773..6cfaffe41f 100644 --- a/content/docs/plugins/development.mdx +++ b/content/docs/plugins/development.mdx @@ -105,15 +105,15 @@ export function createHelloPlugin(): Plugin { ctx.registerService('greeting', greetingService); // Hook into the record lifecycle via the data engine. - // Data hooks receive a single HookContext; mutate the flat hookCtx.input - // in before* hooks to change the operation. + // Data hooks receive a single HookContext. The input shape is + // `{ doc }` for inserts (`{ id, doc }` for updates) — mutate + // `hookCtx.input.doc` in before* hooks to change the operation. const engine = ctx.getService('data'); engine.registerHook('beforeInsert', async (hookCtx: any) => { - if (hookCtx.input.first_name) { + const doc = hookCtx.input.doc; + if (doc?.first_name) { // Auto-generate a greeting field - hookCtx.input.welcome_message = greetingService.greet( - hookCtx.input.first_name as string - ); + doc.welcome_message = greetingService.greet(doc.first_name as string); } }, { object: 'contact' }); // the engine only runs this hook for 'contact' } @@ -198,7 +198,7 @@ describe('HelloPlugin', () => { createHelloPlugin().init(ctx); const hookCtx = { object: 'contact', input: { doc: { first_name: 'Bob' } as Record } }; await hooks['beforeInsert'](hookCtx); - expect(hookCtx.input.welcome_message).toBe('Hello, Bob! Welcome to ObjectStack.'); + expect(hookCtx.input.doc.welcome_message).toBe('Hello, Bob! Welcome to ObjectStack.'); }); it('should scope the hook to the contact object', () => { diff --git a/docker/README.md b/docker/README.md index 6d92f8d846..b29039a609 100644 --- a/docker/README.md +++ b/docker/README.md @@ -25,7 +25,8 @@ Multi-arch: `linux/amd64` + `linux/arm64`. ## Usage -**Extend it** (the usual path — see [`examples/docker`](../examples/docker)): +**Extend it** (the usual path — the full walkthrough lives in +[Self-Hosted Deployment](https://objectstack.ai/docs/deployment/self-hosting)): ```dockerfile FROM ghcr.io/objectstack-ai/objectstack:14.8.0