rads-db 3.2.27 → 3.2.30

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/dist/config.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { g as RadsConfig, T as TypeDefinition } from './types-33ba4f76.js';
1
+ import { g as RadsConfig, T as TypeDefinition } from './types-3f6b8235.js';
2
2
  import 'mssql';
3
3
  import '_rads-db';
4
4
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { E as EntityDecoratorArgs, U as UiDecoratorArgs, a as UiFieldDecoratorArgs, V as ValidateEntityDecoratorArgs, b as ValidateFieldDecoratorArgs, F as FieldDecoratorArgs, C as ComputedDecoratorArgs, S as Schema, D as DriverConstructor, c as Driver, d as ComputedContext, R as RadsRequestContext, e as CreateRadsDbArgs, f as CreateRadsDbClientArgs } from './types-33ba4f76.js';
2
- export { O as Change, x as ComputedContextGlobal, k as CreateRadsArgsDrivers, m as CreateRadsDbArgsNormalized, ak as DeepKeys, af as DeepPartial, ad as DeepPartialWithNulls, ae as DeepPartialWithNullsItem, al as EntityMethods, w as EnumDefinition, v as FieldDefinition, J as FileSystemNode, u as FileUploadArgs, q as FileUploadDriver, p as FileUploadResult, H as GenerateClientNormalizedOptions, B as GenerateClientOptions, a9 as Get, $ as GetAggArgs, a0 as GetAggArgsAgg, a3 as GetAggArgsAny, a6 as GetAggResponse, _ as GetArgs, a2 as GetArgsAny, a5 as GetArgsInclude, Q as GetManyArgs, a1 as GetManyArgsAny, a7 as GetManyResponse, a8 as GetResponse, aa as GetResponseInclude, ab as GetResponseIncludeSelect, ac as GetResponseNoInclude, s as GetRestRoutesArgs, G as GetRestRoutesOptions, t as GetRestRoutesResponse, ah as InverseRelation, aj as JsonPutOperations, M as MinimalDriver, ai as Put, P as PutEffect, g as RadsConfig, h as RadsConfigDataSource, r as RadsDbInstance, N as RadsFeature, y as RadsHookDoc, L as RadsUiSlotDefinition, K as RadsUiSlotName, I as RadsVitePluginOptions, ag as Relation, i as RequiredFields, l as RestDriverOptions, A as RestFileUploadDriverOptions, n as SchemaLoadResult, o as SchemaValidators, z as SqlDriverOptions, T as TypeDefinition, j as ValidateStringDecoratorArgs, X as VerifyManyArgs, a4 as VerifyManyArgsAny, Y as VerifyManyResponse, Z as Where, W as WhereJsonContains } from './types-33ba4f76.js';
1
+ import { E as EntityDecoratorArgs, U as UiDecoratorArgs, a as UiFieldDecoratorArgs, V as ValidateEntityDecoratorArgs, b as ValidateFieldDecoratorArgs, F as FieldDecoratorArgs, C as ComputedDecoratorArgs, S as Schema, D as DriverConstructor, c as Driver, d as ComputedContext, R as RadsRequestContext, e as CreateRadsDbArgs, f as CreateRadsDbClientArgs } from './types-3f6b8235.js';
2
+ export { O as Change, x as ComputedContextGlobal, k as CreateRadsArgsDrivers, m as CreateRadsDbArgsNormalized, ak as DeepKeys, af as DeepPartial, ad as DeepPartialWithNulls, ae as DeepPartialWithNullsItem, al as EntityMethods, w as EnumDefinition, v as FieldDefinition, J as FileSystemNode, u as FileUploadArgs, q as FileUploadDriver, p as FileUploadResult, H as GenerateClientNormalizedOptions, B as GenerateClientOptions, a9 as Get, $ as GetAggArgs, a0 as GetAggArgsAgg, a3 as GetAggArgsAny, a6 as GetAggResponse, _ as GetArgs, a2 as GetArgsAny, a5 as GetArgsInclude, Q as GetManyArgs, a1 as GetManyArgsAny, a7 as GetManyResponse, a8 as GetResponse, aa as GetResponseInclude, ab as GetResponseIncludeSelect, ac as GetResponseNoInclude, s as GetRestRoutesArgs, G as GetRestRoutesOptions, t as GetRestRoutesResponse, ah as InverseRelation, aj as JsonPutOperations, M as MinimalDriver, ai as Put, P as PutEffect, g as RadsConfig, h as RadsConfigDataSource, r as RadsDbInstance, N as RadsFeature, y as RadsHookDoc, L as RadsUiSlotDefinition, K as RadsUiSlotName, I as RadsVitePluginOptions, ag as Relation, i as RequiredFields, l as RestDriverOptions, A as RestFileUploadDriverOptions, n as SchemaLoadResult, o as SchemaValidators, z as SqlDriverOptions, T as TypeDefinition, j as ValidateStringDecoratorArgs, X as VerifyManyArgs, a4 as VerifyManyArgsAny, Y as VerifyManyResponse, Z as Where, W as WhereJsonContains } from './types-3f6b8235.js';
3
3
  import { RadsDb } from '_rads-db';
4
4
  export { RadsDb } from '_rads-db';
5
5
  import 'mssql';
@@ -43,15 +43,20 @@ type GetArgsAny = GetArgs<any>;
43
43
  type GetAggArgsAny = GetAggArgs<any>;
44
44
  type VerifyManyArgsAny = VerifyManyArgs<any>;
45
45
  type RelationsAndNestedObjects<EN extends keyof EntityMeta> = EntityMeta[EN]['relations'] & EntityMeta[EN]['nestedObjects'];
46
+ type JsonFieldsInclude<EN extends keyof EntityMeta> = [EntityMeta[EN]['jsonFields']] extends [never] ? unknown : {
47
+ [K in EntityMeta[EN]['jsonFields'] & string]?: {
48
+ _pick?: readonly string[];
49
+ };
50
+ };
46
51
  type GetArgsInclude<EN extends keyof EntityMeta, R extends keyof RelationsAndNestedObjects<EN> = keyof RelationsAndNestedObjects<EN>> = [R] extends [never] ? {
47
- _pick?: EntityMeta[EN]['primitives'][];
48
- } : {
49
- _pick?: (EntityMeta[EN]['primitives'] | R)[];
52
+ _pick?: readonly (EntityMeta[EN]['primitives'] | EntityMeta[EN]['jsonFields'])[];
53
+ } & JsonFieldsInclude<EN> : {
54
+ _pick?: readonly (EntityMeta[EN]['primitives'] | EntityMeta[EN]['jsonFields'] | R)[];
50
55
  } & {
51
56
  [K in R]?: GetArgsInclude<RelationsAndNestedObjects<EN>[K]['entityName']>;
52
- };
57
+ } & JsonFieldsInclude<EN>;
53
58
  type GetAggResponse<EN extends keyof EntityMeta, A extends GetAggArgs<EN>> = {
54
- [K in A['agg'][0]]: K extends '_count' ? number : number | undefined;
59
+ [K in A['agg'][number]]: K extends '_count' ? number : number | undefined;
55
60
  };
56
61
  interface GetManyResponse<EN extends keyof EntityMeta, A extends GetArgs<EN>> {
57
62
  nodes: GetResponse<EN, A>[];
@@ -70,14 +75,16 @@ type Get<EntityName extends keyof EntityMeta, Include extends keyof EntityMeta[E
70
75
  type RelationData<EN extends keyof EntityMeta, K extends keyof EntityMeta[EN]['relations']> = Pick<EntityMeta[EN]['relations'][K]['entity'], EntityMeta[EN]['relations'][K]['denormFields']>;
71
76
  type KeepArray<TMaybeArray, TType> = NonNullable<TMaybeArray> extends any[] ? TType[] : TType;
72
77
  type GetResponseInclude<EN extends keyof EntityMeta, I extends GetArgsInclude<EN>> = I extends {
73
- _pick: string[];
78
+ _pick: readonly string[];
74
79
  } ? GetResponseIncludeSelect<EN, I> : {
75
80
  [K in keyof EntityMeta[EN]['type']]: K extends keyof EntityMeta[EN]['relations'] ? K extends keyof I ? KeepArray<EntityMeta[EN]['type'][K], GetResponseInclude<EntityMeta[EN]['relations'][K]['entityName'], I[K]>> : KeepArray<EntityMeta[EN]['type'][K], RelationData<EN, K>> : EntityMeta[EN]['type'][K];
76
81
  };
77
82
  type GetResponseIncludeSelect<EN extends keyof EntityMeta, I extends GetArgsInclude<EN>> = I extends {
78
- _pick: (infer P)[];
83
+ _pick: readonly (infer P)[];
79
84
  } ? Pick<{
80
- [K in keyof EntityMeta[EN]['type']]: K extends keyof EntityMeta[EN]['relations'] ? K extends keyof Omit<I, '_pick'> ? KeepArray<EntityMeta[EN]['type'][K], GetResponseInclude<EntityMeta[EN]['relations'][K]['entityName'], Omit<I, '_pick'>[K]>> : KeepArray<EntityMeta[EN]['type'][K], RelationData<EN, K>> : EntityMeta[EN]['type'][K];
85
+ [K in keyof EntityMeta[EN]['type']]: K extends keyof EntityMeta[EN]['relations'] ? K extends keyof Omit<I, '_pick'> ? KeepArray<EntityMeta[EN]['type'][K], GetResponseInclude<EntityMeta[EN]['relations'][K]['entityName'], Omit<I, '_pick'>[K]>> : KeepArray<EntityMeta[EN]['type'][K], RelationData<EN, K>> : K extends EntityMeta[EN]['jsonFields'] ? K extends keyof Omit<I, '_pick'> ? Omit<I, '_pick'>[K] extends {
86
+ _pick: (infer JP)[];
87
+ } ? Pick<NonNullable<EntityMeta[EN]['type'][K]>, JP & string> : EntityMeta[EN]['type'][K] : EntityMeta[EN]['type'][K] : EntityMeta[EN]['type'][K];
81
88
  }, ((P & string) | (Exclude<keyof I, '_pick'> & string)) & keyof EntityMeta[EN]['type']> : never;
82
89
  type GetResponseNoInclude<EN extends keyof EntityMeta> = {
83
90
  [K in keyof EntityMeta[EN]['type']]: K extends keyof EntityMeta[EN]['relations'] ? KeepArray<EntityMeta[EN]['type'][K], RelationData<EN, K>> : EntityMeta[EN]['type'][K];
@@ -119,14 +126,14 @@ interface EntityMethods<E, EN extends keyof EntityMeta> {
119
126
  /** Used to access underlying mechanism of storage directly.
120
127
  * Warning: bypasses all rads features - schema won't be validated, default values won't be filled, etc. */
121
128
  driver: Driver;
122
- get<A extends GetArgs<EN>>(args: A, ctx?: RadsRequestContext): MaybePromise$1<GetResponse<EN, A>>;
123
- getMany<A extends GetManyArgs<EN>>(args?: A, ctx?: RadsRequestContext): MaybePromise$1<GetManyResponse<EN, A>>;
124
- getAgg<A extends GetAggArgs<EN>>(args: A, ctx?: RadsRequestContext): MaybePromise$1<GetAggResponse<EN, A>>;
125
- getAll<A extends GetManyArgs<EN>>(args?: A, ctx?: RadsRequestContext): MaybePromise$1<GetManyResponse<EN, A>['nodes']>;
129
+ get<const A extends GetArgs<EN>>(args: A, ctx?: RadsRequestContext): MaybePromise$1<GetResponse<EN, A>>;
130
+ getMany<const A extends GetManyArgs<EN>>(args?: A, ctx?: RadsRequestContext): MaybePromise$1<GetManyResponse<EN, A>>;
131
+ getAgg<const A extends GetAggArgs<EN>>(args: A, ctx?: RadsRequestContext): MaybePromise$1<GetAggResponse<EN, A>>;
132
+ getAll<const A extends GetManyArgs<EN>>(args?: A, ctx?: RadsRequestContext): MaybePromise$1<GetManyResponse<EN, A>['nodes']>;
126
133
  put(data: Put<EN>, ctx?: RadsRequestContext): MaybePromise$1<GetResponseNoInclude<EN>>;
127
134
  putMany(data: Put<EN>[], ctx?: RadsRequestContext): MaybePromise$1<GetResponseNoInclude<EN>[]>;
128
- verifyMany<A extends VerifyManyArgs<EN>>(args?: A, ctx?: RadsRequestContext): MaybePromise$1<VerifyManyResponse>;
129
- verifyAll<A extends VerifyManyArgs<EN>>(args?: A, ctx?: RadsRequestContext): MaybePromise$1<Pick<VerifyManyResponse, 'correctCount' | 'incorrectCount'>>;
135
+ verifyMany<const A extends VerifyManyArgs<EN>>(args?: A, ctx?: RadsRequestContext): MaybePromise$1<VerifyManyResponse>;
136
+ verifyAll<const A extends VerifyManyArgs<EN>>(args?: A, ctx?: RadsRequestContext): MaybePromise$1<Pick<VerifyManyResponse, 'correctCount' | 'incorrectCount'>>;
130
137
  }
131
138
  type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...0[]];
132
139
  type Primitive = string | number | boolean | symbol | null | undefined;
@@ -1,5 +1,5 @@
1
- import type { Schema } from '@/types';
2
- import type { GetAggArgsAny, GetManyArgsAny } from '@/typesClientEngine';
1
+ import type { MinimalDriver, Schema } from '../types';
2
+ import type { GetAggArgsAny, GetManyArgsAny } from '../typesClientEngine';
3
3
  interface MemoryDriverOptions {
4
4
  }
5
5
  declare const _default: (options?: MemoryDriverOptions) => (schema: Schema, entity: string) => MinimalDriver;
@@ -7,6 +7,6 @@ export default _default;
7
7
  export declare function getAggFromArray(array: any[], args: GetAggArgsAny): Record<string, any>;
8
8
  export declare function queryArray(array: any[], args: GetManyArgsAny): {
9
9
  nodes: any;
10
- cursor: any;
10
+ cursor: string | null;
11
11
  };
12
12
  export declare function getFilter(where: Record<string, any>, namePrefix?: string): ((x: any) => boolean | undefined) | null;
@@ -1,3 +1,3 @@
1
- import type { RestDriverOptions, Schema } from '@/types';
1
+ import type { MinimalDriver, RestDriverOptions, Schema } from '../types';
2
2
  declare const _default: (options?: RestDriverOptions) => (schema: Schema, entity: string) => MinimalDriver;
3
3
  export default _default;
package/drivers/sql.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { SqlDriverOptions } from '@/types';
1
+ import type { SqlDriverOptions } from '../types';
2
2
  import type { Schema } from 'rads-db';
3
3
  declare const _default: (options: SqlDriverOptions) => (schema: Schema, entity: string) => MinimalDriver;
4
4
  export default _default;
@@ -1,5 +1,14 @@
1
- import type { DriverConstructor } from '@/types';
2
- declare const _default: (options: CacheOptions) => RadsFeature;
1
+ import type { DriverConstructor } from '../types';
2
+ declare const _default: (options: CacheOptions) => {
3
+ name: string;
4
+ init(db: Record<string, any>, context: import("../types").ComputedContextGlobal): void;
5
+ beforeGet(args: import("../typesClientEngine").GetArgsAny, ctx: import("../types").RadsRequestContext, context: import("../types").ComputedContext): Promise<Record<string, any> | Record<string, any>[] | {
6
+ nodes: Record<string, any>[];
7
+ cursor: string | undefined;
8
+ } | undefined>;
9
+ afterGet(items: any[], args: import("../typesClientEngine").GetArgsAny, ctx: import("../types").RadsRequestContext, context: import("../types").ComputedContext): Promise<void>;
10
+ afterPut(items: import("../types").RadsHookDoc[], ctx: import("../types").RadsRequestContext, computedContext: import("../types").ComputedContext): Promise<void>;
11
+ };
3
12
  export default _default;
4
13
  export interface EntityCacheOptions {
5
14
  driver?: DriverConstructor;
@@ -1,4 +1,4 @@
1
- import type { ComputedContext, RadsFeature, RadsRequestContext } from '@/types';
1
+ import type { ComputedContext, RadsFeature, RadsRequestContext } from '../types';
2
2
  export interface EventSourcingFeatureOptions {
3
3
  applyEventIf?: (event: any, context: ComputedContext, ctx: RadsRequestContext) => boolean;
4
4
  freezeEvent?: (event: any, context: ComputedContext, ctx: RadsRequestContext) => boolean;
@@ -1,2 +1,5 @@
1
- declare const _default: () => RadsFeature;
1
+ declare const _default: () => {
2
+ name: string;
3
+ beforeGet(args: import("../typesClientEngine").GetArgsAny, ctx: import("../types").RadsRequestContext, context: import("../types").ComputedContext): void;
4
+ };
2
5
  export default _default;
@@ -1,4 +1,5 @@
1
1
  import type { StoragePipelineOptions } from '@azure/storage-blob';
2
+ import type { FileUploadDriver } from '../types';
2
3
  interface AzureStorageBlobUploadDriverOptions {
3
4
  connectionString: string;
4
5
  defaultContainer?: string;
@@ -1,3 +1,4 @@
1
+ import type { FileUploadDriver } from '../types';
1
2
  interface MemoryFileUploadDriverOptions {
2
3
  }
3
4
  declare const _default: (options?: MemoryFileUploadDriverOptions) => FileUploadDriver;
@@ -1,3 +1,3 @@
1
- import type { RestFileUploadDriverOptions } from '@/types';
1
+ import type { FileUploadDriver, RestFileUploadDriverOptions } from '../types';
2
2
  declare const _default: (options: RestFileUploadDriverOptions) => FileUploadDriver;
3
3
  export default _default;
@@ -1,4 +1,5 @@
1
1
  import type { SupabaseClient } from '@supabase/supabase-js';
2
+ import type { FileUploadDriver } from '../types';
2
3
  declare const _default: (options: SupabaseFileUploadDriverOptions) => FileUploadDriver;
3
4
  export default _default;
4
5
  interface SupabaseFileUploadDriverOptions {
package/llms.txt ADDED
@@ -0,0 +1,409 @@
1
+ # rads-db
2
+
3
+ > TypeScript-first universal data access library. Define entities as decorated classes, get strongly-typed CRUD methods, filtering, pagination, JOINs across any storage backend. Works on both server and browser.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ npm install rads-db
9
+ ```
10
+
11
+ Create `rads.config.ts` at the project root:
12
+
13
+ ```typescript
14
+ import { defineRadsConfig, schemaFromFiles } from 'rads-db/config'
15
+
16
+ export default defineRadsConfig({
17
+ dataSources: {
18
+ db: {
19
+ schema: schemaFromFiles('./entities'), // folder with your entity classes
20
+ },
21
+ },
22
+ })
23
+ ```
24
+
25
+ Run codegen after every schema change (generates TypeScript types and metadata):
26
+
27
+ ```bash
28
+ npx rads
29
+ ```
30
+
31
+ Automate in `package.json`:
32
+
33
+ ```json
34
+ {
35
+ "scripts": {
36
+ "dev": "npx rads && <your-dev-command>",
37
+ "build": "npx rads && <your-build-command>"
38
+ }
39
+ }
40
+ ```
41
+
42
+ ## Defining Entities
43
+
44
+ Each entity is a TypeScript class in the entities folder. Use decorators from `rads-db`:
45
+
46
+ ```typescript
47
+ import { entity, field, ui, validate, computed, precomputed, keepHistory } from 'rads-db'
48
+ import type { Relation, InverseRelation } from 'rads-db'
49
+ import type { TcRole } from './TcRole'
50
+ import type { TcPost } from './TcPost'
51
+
52
+ @entity()
53
+ @ui<typeof TcUser>({ name: 'User', namePlural: 'Users', nameField: 'name', captionFields: ['id', 'name'], group: 'People' })
54
+ export class TcUser {
55
+ id!: string
56
+
57
+ @validate<TcUser, any, string>({ preset: 'email' })
58
+ @ui({ name: 'Email Address', hint: 'Used for login' })
59
+ email!: string
60
+
61
+ name!: string
62
+ age?: number
63
+ isActive?: boolean
64
+ tags: string[] = []
65
+
66
+ // Relation: only { id } is stored; use include: { tcRole: {} } to load full object
67
+ tcRole!: Relation<TcRole>
68
+
69
+ // Relation with denormalized fields stored alongside the id
70
+ tcRoleWithName!: Relation<TcRole, 'name'>
71
+
72
+ // Computed inverse relation — not stored, derived at query time
73
+ tcPosts?: InverseRelation<'TcPost'>
74
+
75
+ @computed()
76
+ fullLabel?: string // computed server-side, not stored
77
+
78
+ @precomputed({ preset: 'eventSourcing' })
79
+ status?: string // precomputed and stored when source events change
80
+
81
+ @keepHistory()
82
+ statusHistory?: string // keeps full history of changes
83
+ }
84
+ ```
85
+
86
+ ### Decorator reference
87
+
88
+ | Decorator | Target | Purpose |
89
+ |-----------|--------|---------|
90
+ | `@entity(args?)` | class | Marks class as a database entity; `args.driver` overrides default driver |
91
+ | `@ui(args)` | class or field | Display hints for rads-ui (name, icon, captionFields, nameField, etc.) |
92
+ | `@validate(args)` | class or field | Validation rules (presets, regex, min/maxLength, etc.) |
93
+ | `@field(args?)` | field | Marks a relation field; `args.relation` points to the related class |
94
+ | `@computed()` | field | Computed at query time, never stored |
95
+ | `@precomputed()` | field | Computed and stored; recalculated when source data changes |
96
+ | `@keepHistory()` | field | Stores full change history |
97
+
98
+ ### Relation types
99
+
100
+ ```typescript
101
+ Relation<T> // stores { id } only
102
+ Relation<T, 'field1'|'field2'> // stores { id, field1, field2 } (denormalized)
103
+ InverseRelation<'EntityName'> // reverse lookup — computed, not stored
104
+ ```
105
+
106
+ ### Field validation presets (string)
107
+
108
+ `text` | `html` | `markdown` | `alpha` | `alphanum` | `number` | `decimalNumber` | `email` | `icon` | `imageUrl` | `fileUrl` | `absoluteUrl` | `relativeUrl` | `phoneNumber` | `datetime` | `date` | `time` | `timeInterval` | `duration` | `hexColor`
109
+
110
+ ## Creating the DB instance
111
+
112
+ ```typescript
113
+ import { createRadsDb } from 'rads-db'
114
+
115
+ const db = createRadsDb('db')
116
+ // db.tcUser, db.tcPost, db.tcRole, … — one property per entity (lowerFirst of class name)
117
+ ```
118
+
119
+ ## CRUD Methods
120
+
121
+ Every entity exposes the same set of methods on `db.<entityHandle>`:
122
+
123
+ ### get — fetch one record
124
+
125
+ ```typescript
126
+ const user = await db.tcUser.get({ where: { id: 'user1' } })
127
+ // returns undefined if not found
128
+ ```
129
+
130
+ ### getAll — fetch all matching records
131
+
132
+ ```typescript
133
+ const users = await db.tcUser.getAll({ where: { isActive: true } })
134
+ // returns T[]
135
+ ```
136
+
137
+ ### getMany — paginated fetch
138
+
139
+ ```typescript
140
+ const page = await db.tcUser.getMany({
141
+ where: { isActive: true },
142
+ maxItemCount: 20,
143
+ cursor: previousPage.cursor, // pass cursor from previous response for next page
144
+ orderByArray: ['name_asc', 'age_desc'],
145
+ })
146
+ // returns { nodes: T[], cursor: string | undefined }
147
+ // cursor === undefined means no more pages
148
+ ```
149
+
150
+ ### getAgg — aggregate queries
151
+
152
+ ```typescript
153
+ const agg = await db.tcUser.getAgg({
154
+ where: { isActive: true },
155
+ agg: ['_count', 'age_min', 'age_max', 'age_sum'],
156
+ })
157
+ // agg._count: number; agg.age_min: number | undefined; etc.
158
+ ```
159
+
160
+ ### put — create or update one record (upsert by id)
161
+
162
+ ```typescript
163
+ const saved = await db.tcUser.put({ id: 'user1', name: 'Alice', tcRole: { id: 'role1' } })
164
+ // returns the full saved record
165
+ // missing optional fields stay unchanged; null erases the value
166
+ ```
167
+
168
+ ### putMany — batch upsert
169
+
170
+ ```typescript
171
+ const saved = await db.tcUser.putMany([
172
+ { id: 'user1', name: 'Alice', tcRole: { id: 'role1' } },
173
+ { id: 'user2', name: 'Bob', tcRole: { id: 'role1' } },
174
+ ])
175
+ ```
176
+
177
+ ### construct — create in-memory object with defaults + a new UUID
178
+
179
+ ```typescript
180
+ const newUser = db.tcUser.construct({ name: 'Draft' })
181
+ // newUser.id is a fresh UUID; defaults from field declarations are applied
182
+ ```
183
+
184
+ ## Where Filters
185
+
186
+ Where clauses are fully typed. Every primitive field supports operators via suffix:
187
+
188
+ ```typescript
189
+ await db.tcUser.getAll({
190
+ where: {
191
+ name: 'Alice', // exact match
192
+ name_startsWith: 'Al',
193
+ name_istartsWith: 'al', // case-insensitive
194
+ name_contains: 'lic',
195
+ name_icontains: 'LIC',
196
+ name_endsWith: 'ice',
197
+ name_in: ['Alice', 'Bob'],
198
+ name_notIn: ['Charlie'],
199
+ age_gt: 18,
200
+ age_gte: 18,
201
+ age_lt: 65,
202
+ age_lte: 65,
203
+ isActive: true,
204
+
205
+ // Logical combinators
206
+ _and: [{ isActive: true }, { age_gte: 18 }],
207
+ _or: [{ name: 'Alice' }, { name: 'Bob' }],
208
+ _not: { isActive: false },
209
+
210
+ // Nested object filter
211
+ tcRole: { id: 'role1' },
212
+ },
213
+ })
214
+ ```
215
+
216
+ ## Include (JOINs)
217
+
218
+ Load related entities inline. Works across different databases/drivers:
219
+
220
+ ```typescript
221
+ const post = await db.tcPost.get({
222
+ where: { id: 'post1' },
223
+ include: {
224
+ tcUser: {}, // load full TcUser object
225
+ tcUser: { tcRole: {} }, // nested: load tcUser and its tcRole
226
+ tcComments: { _pick: ['id', 'text'] }, // load only specific fields of relation
227
+ },
228
+ })
229
+ ```
230
+
231
+ ## Field selection with `_pick`
232
+
233
+ ```typescript
234
+ const users = await db.tcUser.getAll({
235
+ include: { _pick: ['id', 'name'] }, // return only id and name
236
+ })
237
+ ```
238
+
239
+ ## TypeScript utility types
240
+
241
+ ```typescript
242
+ import type { Get, Put } from 'rads-db'
243
+
244
+ // Get — shape returned by read operations (all fields non-nullable)
245
+ type UserView = Get<'TcUser'>
246
+ // With relations and field selection:
247
+ type UserWithRole = Get<'TcUser', { tcRole: {}; _pick: ['id', 'name'] }>
248
+
249
+ // Put — shape accepted by write operations (all fields except id are optional; null erases a value)
250
+ type UserWrite = Put<'TcUser'>
251
+ ```
252
+
253
+ **Form state pattern** — use `Put` for editable state; `Get` is assignable to `Put`:
254
+
255
+ ```typescript
256
+ const data = await db.tcUser.get({ where: { id } })
257
+ const form: Put<'TcUser'> = { ...data } // Get is assignable to Put ✓
258
+ form.age = null // null = erase the field
259
+ await db.tcUser.put(form)
260
+ ```
261
+
262
+ ## Context (second argument to every method)
263
+
264
+ Pass `RadsRequestContext` as the second argument to control behaviour per-request:
265
+
266
+ ```typescript
267
+ import type { RadsRequestContext } from 'rads-db'
268
+
269
+ const ctx: RadsRequestContext = {
270
+ getUser: () => ({ id: 'user1', role: 'admin' }),
271
+ dryRun: true, // no changes written; effects returned as logs
272
+ noCache: true, // bypass caching layer
273
+ excludeFeatures: ['softDelete'], // skip specific features
274
+ }
275
+
276
+ const result = await db.tcUser.put(data, ctx)
277
+ ```
278
+
279
+ ## Features
280
+
281
+ Features hook into the get/put pipeline. Pass them to `createRadsDb`:
282
+
283
+ ```typescript
284
+ import { createRadsDb } from 'rads-db'
285
+ import softDelete from 'rads-db/features/softDelete'
286
+ import eventSourcing from 'rads-db/features/eventSourcing'
287
+ import cache from 'rads-db/features/cache'
288
+
289
+ const db = createRadsDb('db', {
290
+ features: [
291
+ softDelete(),
292
+ eventSourcing(),
293
+ cache(),
294
+ ],
295
+ })
296
+ ```
297
+
298
+ ### Built-in features
299
+
300
+ | Feature | Import | What it does |
301
+ |---------|--------|-------------|
302
+ | `softDelete` | `rads-db/features/softDelete` | Filters out records where `isDeleted: true` automatically |
303
+ | `eventSourcing` | `rads-db/features/eventSourcing` | Rebuilds aggregate from event log; entity needs `@precomputed({ preset: 'eventSourcing' })` |
304
+ | `cache` | `rads-db/features/cache` | Adds a caching layer; bypass with `ctx.noCache = true` |
305
+
306
+ ### Custom feature
307
+
308
+ ```typescript
309
+ import type { RadsFeature } from 'rads-db'
310
+
311
+ const auditLog = (): RadsFeature => ({
312
+ name: 'auditLog',
313
+ afterPut(items, ctx, computedContext) {
314
+ for (const { doc, oldDoc } of items) {
315
+ console.log('changed', computedContext.typeName, doc.id)
316
+ }
317
+ },
318
+ })
319
+ ```
320
+
321
+ ## Drivers
322
+
323
+ Configure per data-source in `rads.config.ts` or pass to `createRadsDb`:
324
+
325
+ | Import | Storage |
326
+ |--------|---------|
327
+ | `rads-db/drivers/memory` | In-memory (default; great for tests) |
328
+ | `rads-db/drivers/sql` | MSSQL / MySQL / PostgreSQL (via mssql / mysql2 / pg) |
329
+ | `rads-db/drivers/azureCosmos` | Azure Cosmos DB |
330
+ | `rads-db/drivers/azureStorageBlob` | Azure Blob Storage |
331
+ | `rads-db/drivers/indexedDb` | Browser IndexedDB |
332
+ | `rads-db/drivers/restApi` | REST API (used by `createRadsDbClient`) |
333
+
334
+ ```typescript
335
+ import sql from 'rads-db/drivers/sql'
336
+
337
+ const db = createRadsDb('db', {
338
+ driver: sql({
339
+ server: 'localhost',
340
+ database: 'mydb',
341
+ authentication: { type: 'default', options: { userName: 'sa', password: 'pass' } },
342
+ }),
343
+ })
344
+ ```
345
+
346
+ ## Client-side usage (browser)
347
+
348
+ ```typescript
349
+ import { createRadsDbClient } from 'rads-db'
350
+
351
+ // Uses fetch to talk to the server REST API; same methods as server-side db
352
+ const db = createRadsDbClient('db', { driver: { baseUrl: '/api' } })
353
+
354
+ const users = await db.tcUser.getAll({ where: { isActive: true } })
355
+ ```
356
+
357
+ ## REST API / Server endpoints
358
+
359
+ Expose all entities over HTTP using `getRestRoutes`:
360
+
361
+ ```typescript
362
+ // h3 / Nitro (Nuxt)
363
+ import { defineEventHandler, getMethod, readBody } from 'h3'
364
+ import { createRadsDb } from 'rads-db'
365
+ import { getRestRoutes } from 'rads-db/integrations/restEndpoints'
366
+
367
+ const db = createRadsDb('db')
368
+ const routes = getRestRoutes({ db, prefix: '/api/' })
369
+
370
+ export default defineEventHandler(async event => {
371
+ const path = event.path.split('?')[0]
372
+ const method = getMethod(event)
373
+ const handler = routes[path]?.[method]
374
+ if (!handler) return
375
+ const body = method !== 'GET' ? await readBody(event) : undefined
376
+ return handler({ body, context: event, headers: {} })
377
+ })
378
+ ```
379
+
380
+ Generated routes per entity (e.g. `TcUser` → handle `tcUser`):
381
+
382
+ | Path | Method | Description |
383
+ |------|--------|-------------|
384
+ | `/api/tcUser` | `POST` | `db.tcUser.get(body)` |
385
+ | `/api/tcUser` | `PUT` | `db.tcUser.put(body.data)` |
386
+ | `/api/tcUser/list` | `POST` | `db.tcUser.getMany(body)` |
387
+ | `/api/tcUser/list` | `PUT` | `db.tcUser.putMany(body.data)` |
388
+ | `/api/tcUser/agg` | `POST` | `db.tcUser.getAgg(body)` |
389
+
390
+ ## OpenAPI / Swagger
391
+
392
+ ```typescript
393
+ import { getOpenApiSpec } from 'rads-db/integrations/getOpenApiSpec'
394
+
395
+ const spec = getOpenApiSpec(
396
+ { db, prefix: '/api/' },
397
+ { title: 'My App API', version: '1.0.0' },
398
+ )
399
+ ```
400
+
401
+ ## Utilities
402
+
403
+ ```typescript
404
+ import { merge, diff, cleanEntity } from 'rads-db'
405
+
406
+ merge(target, source) // deep merge source into target
407
+ diff(oldObj, newObj) // returns changed fields only
408
+ cleanEntity(entity) // strips undefined/null fields
409
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rads-db",
3
- "version": "3.2.27",
3
+ "version": "3.2.30",
4
4
  "description": "Say goodbye to boilerplate code and hello to efficient and elegant syntax.",
5
5
  "author": "",
6
6
  "license": "ISC",
@@ -48,7 +48,8 @@
48
48
  "drivers",
49
49
  "fileUploadDrivers",
50
50
  "integrations",
51
- "features"
51
+ "features",
52
+ "llms.txt"
52
53
  ],
53
54
  "peerDependencies": {
54
55
  "@azure/cosmos": ">=3",