@spooky-sync/query-builder 0.0.1-canary.16 → 0.0.1-canary.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spooky-sync/query-builder",
3
- "version": "0.0.1-canary.16",
3
+ "version": "0.0.1-canary.18",
4
4
  "description": "Type-safe query builder for SurrealDB with relationship support",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -24,7 +24,8 @@
24
24
  "surrealdb",
25
25
  "query-builder",
26
26
  "typescript",
27
- "type-safe"
27
+ "type-safe",
28
+ "tanstack-intent"
28
29
  ],
29
30
  "author": "",
30
31
  "license": "MIT",
@@ -0,0 +1,141 @@
1
+ ---
2
+ name: spooky-query-builder
3
+ description: >-
4
+ Type-safe query builder for SurrealDB used by the Spooky framework. Use when
5
+ defining schemas (SchemaStructure), building queries with QueryBuilder, handling
6
+ relationships (one/many cardinality), or working with Spooky type helpers like
7
+ TableNames, GetTable, TableModel, and QueryResult.
8
+ metadata:
9
+ author: spooky-sync
10
+ version: "0.0.1"
11
+ ---
12
+
13
+ # Spooky Query Builder
14
+
15
+ `@spooky-sync/query-builder` provides the type-safe schema definition format and query builder used throughout the Spooky framework.
16
+
17
+ ## Schema Definition
18
+
19
+ Spooky schemas are defined as `const` objects satisfying `SchemaStructure`. They are typically generated by the Spooky CLI (`spooky generate`), but can be written by hand.
20
+
21
+ ```typescript
22
+ import type { SchemaStructure } from '@spooky-sync/query-builder';
23
+
24
+ export const schema = {
25
+ tables: [
26
+ {
27
+ name: 'user',
28
+ columns: {
29
+ id: { type: 'string', optional: false },
30
+ email: { type: 'string', optional: false },
31
+ name: { type: 'string', optional: true },
32
+ },
33
+ primaryKey: ['id'],
34
+ },
35
+ {
36
+ name: 'post',
37
+ columns: {
38
+ id: { type: 'string', optional: false },
39
+ title: { type: 'string', optional: false },
40
+ body: { type: 'string', optional: false },
41
+ author: { type: 'string', optional: false, recordId: true },
42
+ },
43
+ primaryKey: ['id'],
44
+ },
45
+ ],
46
+ relationships: [
47
+ { from: 'post', field: 'author', to: 'user', cardinality: 'one' },
48
+ { from: 'user', field: 'posts', to: 'post', cardinality: 'many' },
49
+ ],
50
+ backends: {},
51
+ } as const satisfies SchemaStructure;
52
+ ```
53
+
54
+ ### Column Types
55
+
56
+ The `type` field maps to TypeScript types:
57
+
58
+ | ValueType | TypeScript Type |
59
+ |-------------|-----------------|
60
+ | `'string'` | `string` |
61
+ | `'number'` | `number` |
62
+ | `'boolean'` | `boolean` |
63
+ | `'null'` | `null` |
64
+ | `'json'` | `unknown` |
65
+
66
+ Set `optional: true` to make a field nullable (`T | null`).
67
+ Set `recordId: true` to indicate a field stores a SurrealDB RecordId.
68
+
69
+ ## QueryBuilder API
70
+
71
+ The `QueryBuilder` class provides a fluent, chainable API for constructing type-safe queries.
72
+
73
+ ```typescript
74
+ import { QueryBuilder } from '@spooky-sync/query-builder';
75
+
76
+ // Create a query builder for the "post" table
77
+ const query = new QueryBuilder(schema, 'post')
78
+ .where({ author: 'user:alice' })
79
+ .select('id', 'title', 'body')
80
+ .orderBy('title', 'desc')
81
+ .limit(10)
82
+ .offset(0)
83
+ .related('author')
84
+ .build();
85
+ ```
86
+
87
+ ### Chain Methods
88
+
89
+ | Method | Signature | Description |
90
+ |--------|-----------|-------------|
91
+ | `where` | `.where(conditions)` | Filter by field values. String IDs are auto-parsed to RecordId. |
92
+ | `select` | `.select(...fields)` | Pick specific fields. Can only be called once per query. |
93
+ | `orderBy` | `.orderBy(field, 'asc' \| 'desc')` | Sort results. Default direction is `'asc'`. |
94
+ | `limit` | `.limit(count)` | Limit the number of results. |
95
+ | `offset` | `.offset(count)` | Skip the first N results. |
96
+ | `one` | `.one()` | Return a single object instead of an array. Implicitly sets `limit(1)`. |
97
+ | `related` | `.related(field, modifier?)` | Include related data via subquery. See [Relationships](#relationships). |
98
+ | `build` | `.build()` | Finalize the query into a `FinalQuery` object. |
99
+
100
+ ### Relationships
101
+
102
+ Use `.related()` to include related tables. Relationships must be declared in the schema.
103
+
104
+ ```typescript
105
+ // One-to-one: post has one author
106
+ new QueryBuilder(schema, 'post')
107
+ .related('author')
108
+ .build();
109
+
110
+ // One-to-many: user has many posts
111
+ new QueryBuilder(schema, 'user')
112
+ .related('posts')
113
+ .build();
114
+
115
+ // Nested relationship with modifier
116
+ new QueryBuilder(schema, 'user')
117
+ .related('posts', (q) => q.orderBy('title', 'asc').limit(5))
118
+ .build();
119
+ ```
120
+
121
+ Cardinality is inferred from the schema. For `'one'` relationships, the result is an object. For `'many'`, it is an array.
122
+
123
+ ## Type Helpers
124
+
125
+ See [references/type-helpers.md](references/type-helpers.md) for a full reference of all type utilities.
126
+
127
+ Key types:
128
+
129
+ - `TableNames<S>` — Union of all table name strings
130
+ - `GetTable<S, Name>` — Extract a table definition by name
131
+ - `TableModel<T>` — Convert a table's columns to a TypeScript object type
132
+ - `QueryResult<S, TableName, RelatedFields, IsOne>` — The full result type including related fields
133
+ - `BackendNames<S>`, `BackendRoutes<S, B>`, `RoutePayload<S, B, R>` — Backend/run type helpers
134
+
135
+ ## Common Pitfalls
136
+
137
+ 1. **String IDs are auto-converted**: When you pass `'user:alice'` in a `where()`, it is automatically parsed into a SurrealDB `RecordId`. If the string does not contain `:`, and the field is named `id`, the table name is prepended.
138
+
139
+ 2. **`select()` can only be called once**: Calling it twice throws an error. Combine all fields in one call.
140
+
141
+ 3. **Schema must use `as const satisfies SchemaStructure`**: Without `as const`, TypeScript cannot infer literal types for table names and relationships, and type safety is lost.
@@ -0,0 +1,80 @@
1
+ # Type Helpers Reference
2
+
3
+ ## Schema Types
4
+
5
+ ### `SchemaStructure`
6
+
7
+ The top-level schema interface:
8
+
9
+ ```typescript
10
+ interface SchemaStructure {
11
+ readonly tables: readonly {
12
+ readonly name: string;
13
+ readonly columns: Record<string, ColumnSchema>;
14
+ readonly primaryKey: readonly string[];
15
+ }[];
16
+ readonly relationships: readonly {
17
+ readonly from: string;
18
+ readonly field: string;
19
+ readonly to: string;
20
+ readonly cardinality: 'one' | 'many';
21
+ }[];
22
+ readonly backends: Record<string, HTTPOutboxBackendDefinition>;
23
+ readonly access?: Record<string, AccessDefinition>;
24
+ readonly buckets?: readonly BucketDefinitionSchema[];
25
+ }
26
+ ```
27
+
28
+ ### `ColumnSchema`
29
+
30
+ ```typescript
31
+ interface ColumnSchema {
32
+ readonly type: 'string' | 'number' | 'boolean' | 'null' | 'json';
33
+ readonly optional: boolean;
34
+ readonly dateTime?: boolean;
35
+ readonly recordId?: boolean;
36
+ }
37
+ ```
38
+
39
+ ## Table Type Helpers
40
+
41
+ | Type | Description |
42
+ |------|-------------|
43
+ | `TableNames<S>` | Union of all table name strings from the schema |
44
+ | `GetTable<S, Name>` | Extract a specific table definition by name |
45
+ | `TableModel<T>` | Convert a table's `columns` record into a TypeScript object type |
46
+ | `TableFieldNames<T>` | Union of all column names for a table |
47
+
48
+ ### Example
49
+
50
+ ```typescript
51
+ type MySchema = typeof schema;
52
+ type Tables = TableNames<MySchema>; // 'user' | 'post'
53
+ type UserTable = GetTable<MySchema, 'user'>; // The user table definition
54
+ type UserModel = TableModel<UserTable>; // { id: string; email: string; name: string | null }
55
+ ```
56
+
57
+ ## Relationship Type Helpers
58
+
59
+ | Type | Description |
60
+ |------|-------------|
61
+ | `TableRelationships<S, TableName>` | All relationship definitions originating from a table |
62
+ | `RelationshipFields<S, TableName>` | Union of relationship field names for a table |
63
+ | `GetRelationship<S, TableName, Field>` | Get a specific relationship by table and field name |
64
+
65
+ ## Result Type Helpers
66
+
67
+ | Type | Description |
68
+ |------|-------------|
69
+ | `QueryResult<S, TableName, RelatedFields, IsOne>` | The full query result type. If `IsOne` is `true`, returns a single object; otherwise an array. Includes related fields merged into the base model. |
70
+ | `RelatedFieldsMap` | A record mapping field names to `{ to, cardinality, relatedFields }` |
71
+
72
+ ## Backend Type Helpers
73
+
74
+ | Type | Description |
75
+ |------|-------------|
76
+ | `BackendNames<S>` | Union of all backend names |
77
+ | `BackendRoutes<S, B>` | Union of all route paths for a backend |
78
+ | `RoutePayload<S, B, R>` | The typed payload object for a specific route |
79
+ | `BucketNames<S>` | Union of all bucket names |
80
+ | `BucketConfig<S, B>` | Configuration for a specific bucket |