Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7c0a567
feat(tailordb,resolver): add createTable object-literal API and resol…
dqn Apr 1, 2026
cfdcf83
fix(resolver,tailordb): strengthen descriptor discrimination and vali…
dqn Apr 1, 2026
1f035b1
refactor(resolver): deduplicate KindToFieldType, optimize resolveReso…
dqn Apr 1, 2026
f378f5b
fix(resolver,tailordb): reject unknown descriptor kind values at runtime
dqn Apr 2, 2026
86dcf48
fix(resolver,tailordb): validate enum descriptor values and document …
dqn Apr 2, 2026
a2fb4bc
fix(tailordb): fix array+hooks type collapse and reject malformed pas…
dqn Apr 2, 2026
8c4b878
fix(resolver,tailordb): validate passthrough field entries have type …
dqn Apr 2, 2026
0478fdf
refactor(resolver): deduplicate resolveResolverFieldMap and remove ob…
dqn Apr 2, 2026
c22dc34
Merge remote-tracking branch 'origin/main' into worktree-staged-huggi…
dqn Apr 2, 2026
3e275c3
revert: restore processOrder.ts import order to match main
dqn Apr 2, 2026
d5be2b8
chore: add changeset for object-literal descriptor API
dqn Apr 2, 2026
6910b23
test(tailordb): add type-level option tests for createTable
dqn Apr 3, 2026
47ab17c
docs: add createTable and descriptor syntax documentation
dqn Apr 3, 2026
f987b90
feat(example): add Product type using createTable API
dqn Apr 3, 2026
4f7cc03
fix(tailordb): type array field hooks with correct output type
dqn Apr 4, 2026
20fe3d4
fix(tailordb): add createTable overload for inline hook contextual ty…
dqn Apr 4, 2026
4420200
test(tailordb): document inline enum hook TS limitation with workarou…
dqn Apr 4, 2026
216ef75
chore(example): generate migration for Product type
dqn Apr 4, 2026
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
5 changes: 5 additions & 0 deletions .changeset/object-literal-descriptor-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tailor-platform/sdk": minor
---

Add object-literal descriptor API for TailorDB types (`createTable`) and resolver fields
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ CLAUDE.local.md
llm-challenge/results/
llm-challenge/problems/*/work
.claude/tmp/
.agent/tmp/
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Refer to `example/` for working implementations of all patterns (config, models,
Key files:

- `example/tailor.config.ts` - Configuration with defineConfig, defineAuth, defineIdp, defineStaticWebSite, defineGenerators
- `example/tailordb/*.ts` - Model definitions with `db.type()`
- `example/tailordb/*.ts` - Model definitions with `db.type()` or `createTable`
- `example/resolvers/*.ts` - Resolver implementations with `createResolver`
- `example/executors/*.ts` - Executor implementations with `createExecutor`
- `example/workflows/*.ts` - Workflow implementations with `createWorkflow` / `createWorkflowJob`
Expand Down
7 changes: 7 additions & 0 deletions example/generated/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ export const InvoiceStatus = {
} as const;
export type InvoiceStatus = (typeof InvoiceStatus)[keyof typeof InvoiceStatus];

export const ProductCategory = {
"electronics": "electronics",
"clothing": "clothing",
"food": "food"
} as const;
export type ProductCategory = (typeof ProductCategory)[keyof typeof ProductCategory];

export const PurchaseOrderAttachedFilesType = {
"text": "text",
"image": "image"
Expand Down
12 changes: 12 additions & 0 deletions example/generated/tailordb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ export interface Namespace {
updatedAt: Timestamp | null;
}

Product: {
id: Generated<string>;
name: string;
sku: string;
price: number;
stock: number;
category: "electronics" | "clothing" | "food";
supplierId: string;
createdAt: Generated<Timestamp>;
updatedAt: Timestamp | null;
}

PurchaseOrder: {
id: Generated<string>;
supplierID: string;
Expand Down
212 changes: 212 additions & 0 deletions example/migrations/0001/diff.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
{
"version": 1,
"namespace": "tailordb",
"createdAt": "2026-04-04T23:52:28.003Z",
"changes": [
{
"kind": "type_added",
"typeName": "Product",
"after": {
"name": "Product",
"fields": {
"id": {
"type": "uuid",
"required": true
},
"name": {
"type": "string",
"required": true,
"description": "Product name"
},
"sku": {
"type": "string",
"required": true,
"index": true,
"unique": true,
"description": "Stock keeping unit"
},
"price": {
"type": "float",
"required": true
},
"stock": {
"type": "integer",
"required": true,
"index": true
},
"category": {
"type": "enum",
"required": true,
"allowedValues": [
{
"value": "electronics"
},
{
"value": "clothing"
},
{
"value": "food"
}
]
},
"supplierId": {
"type": "uuid",
"required": true,
"index": true,
"foreignKey": true,
"foreignKeyType": "Supplier",
"foreignKeyField": "id"
},
"createdAt": {
"type": "datetime",
"required": true,
"description": "Record creation timestamp",
"hooks": {
"create": {
"expr": "(() => /* @__PURE__ */ new Date())({ value: _value, data: _data, user: { id: user.id, type: user.type, workspaceId: user.workspace_id, attributes: user.attribute_map, attributeList: user.attributes } })"
}
}
},
"updatedAt": {
"type": "datetime",
"required": false,
"description": "Record last update timestamp",
"hooks": {
"update": {
"expr": "(() => /* @__PURE__ */ new Date())({ value: _value, data: _data, user: { id: user.id, type: user.type, workspaceId: user.workspace_id, attributes: user.attribute_map, attributeList: user.attributes } })"
}
}
}
},
"pluralForm": "Products",
"description": "Product catalog entry",
"settings": {},
"forwardRelationships": {
"supplier": {
"targetType": "Supplier",
"targetField": "supplierId",
"sourceField": "id",
"isArray": false,
"description": ""
}
},
"permissions": {
"record": {
"create": [
{
"conditions": [
[
{
"user": "role"
},
"eq",
"MANAGER"
]
],
"permit": "allow"
}
],
"read": [
{
"conditions": [
[
{
"user": "role"
},
"eq",
"MANAGER"
]
],
"permit": "allow"
},
{
"conditions": [
[
{
"user": "_loggedIn"
},
"eq",
true
]
],
"permit": "allow"
}
],
"update": [
{
"conditions": [
[
{
"user": "role"
},
"eq",
"MANAGER"
]
],
"permit": "allow"
}
],
"delete": [
{
"conditions": [
[
{
"user": "role"
},
"eq",
"MANAGER"
]
],
"permit": "allow"
}
]
},
"gql": [
{
"conditions": [
[
{
"user": "role"
},
"eq",
"MANAGER"
]
],
"actions": ["create", "read", "update", "delete", "aggregate", "bulkUpsert"],
"permit": "allow"
},
{
"conditions": [
[
{
"user": "_loggedIn"
},
"eq",
true
]
],
"actions": ["read"],
"permit": "allow"
}
]
}
}
},
{
"kind": "relationship_added",
"typeName": "Supplier",
"relationshipName": "products",
"relationshipType": "backward",
"after": {
"targetType": "Product",
"targetField": "supplierId",
"sourceField": "id",
"isArray": true,
"description": "Product catalog entry"
}
}
],
"hasBreakingChanges": false,
"breakingChanges": [],
"requiresMigrationScript": false
}
Empty file.
23 changes: 23 additions & 0 deletions example/seed/data/Product.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { t } from "@tailor-platform/sdk";
import { defineSchema } from "@tailor-platform/sdk/seed";
import { createTailorDBHook, createStandardSchema } from "@tailor-platform/sdk/test";
import { product } from "../../tailordb/product";

const schemaType = t.object({
...product.pickFields(["id","createdAt"], { optional: true }),
...product.omitFields(["id","createdAt"]),
});

const hook = createTailorDBHook(product);

export const schema = defineSchema(
createStandardSchema(schemaType, hook),
{
foreignKeys: [
{"column":"supplierId","references":{"table":"Supplier","column":"id"}},
],
indexes: [
{"name":"product_sku_unique_idx","columns":["sku"],"unique":true},
],
}
);
2 changes: 2 additions & 0 deletions example/seed/exec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ const namespaceEntities = {
"Customer",
"Invoice",
"NestedProfile",
"Product",
"PurchaseOrder",
"SalesOrder",
"SalesOrderCreated",
Expand All @@ -162,6 +163,7 @@ const namespaceDeps = {
"Customer": [],
"Invoice": ["SalesOrder"],
"NestedProfile": [],
"Product": ["Supplier"],
"PurchaseOrder": ["Supplier"],
"SalesOrder": ["Customer", "User"],
"SalesOrderCreated": [],
Expand Down
28 changes: 28 additions & 0 deletions example/tailordb/product.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { createTable, timestampFields } from "@tailor-platform/sdk";
import { defaultGqlPermission, defaultPermission } from "./permissions";
import { supplier } from "./supplier";

export const product = createTable(
"Product",
{
name: { kind: "string", description: "Product name" },
sku: { kind: "string", unique: true, description: "Stock keeping unit" },
price: { kind: "float" },
stock: { kind: "int", index: true },
category: { kind: "enum", values: ["electronics", "clothing", "food"] },
supplierId: {
kind: "uuid",
relation: {
type: "n-1",
toward: { type: supplier },
},
},
...timestampFields(),
},
{
description: "Product catalog entry",
permission: defaultPermission,
gqlPermission: defaultGqlPermission,
},
);
export type product = typeof product;
49 changes: 48 additions & 1 deletion packages/sdk/docs/services/resolver.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,54 @@ export default createResolver({

## Input/Output Schemas

Define input/output schemas using methods of `t` object. Basic usage and supported field types are the same as TailorDB. TailorDB-specific options (e.g., index, relation) are not supported.
Define input/output schemas using methods of `t` object or object-literal descriptors (`{ kind: "..." }`). Both styles can be mixed in the same resolver.

### Fluent API (`t.*()`)

```typescript
createResolver({
input: {
name: t.string(),
age: t.int(),
},
output: t.object({ name: t.string(), age: t.int() }),
// ...
});
```

### Object-Literal Descriptors

Use `{ kind: "..." }` syntax as a concise alternative. Supported options: `optional`, `array`, `description`, `validate`, and `typeName` (for enum/object).

```typescript
createResolver({
name: "addNumbers",
operation: "query",
input: {
a: { kind: "int", description: "First number" },
b: { kind: "int", description: "Second number" },
},
output: { kind: "int", description: "Sum" },
body: ({ input }) => input.a + input.b,
});
```

### Mixing Styles

Fluent and descriptor fields can be freely combined:

```typescript
createResolver({
input: {
name: t.string(),
status: { kind: "enum", values: ["active", "inactive"] },
},
output: t.object({ result: t.bool() }),
// ...
});
```

### Reusing TailorDB Fields

You can reuse fields defined with `db` object, but note that unsupported options will be ignored:

Expand Down
Loading
Loading