schemock 0.0.1
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/LICENSE +21 -0
- package/README.md +82 -0
- package/dist/adapters/index.d.mts +1364 -0
- package/dist/adapters/index.d.ts +1364 -0
- package/dist/adapters/index.js +36988 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/index.mjs +36972 -0
- package/dist/adapters/index.mjs.map +1 -0
- package/dist/cli/index.d.mts +831 -0
- package/dist/cli/index.d.ts +831 -0
- package/dist/cli/index.js +4425 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/index.mjs +4401 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/cli.js +6776 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +39439 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +39367 -0
- package/dist/index.mjs.map +1 -0
- package/dist/middleware/index.d.mts +688 -0
- package/dist/middleware/index.d.ts +688 -0
- package/dist/middleware/index.js +921 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/index.mjs +899 -0
- package/dist/middleware/index.mjs.map +1 -0
- package/dist/react/index.d.mts +316 -0
- package/dist/react/index.d.ts +316 -0
- package/dist/react/index.js +466 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +456 -0
- package/dist/react/index.mjs.map +1 -0
- package/dist/runtime/index.d.mts +814 -0
- package/dist/runtime/index.d.ts +814 -0
- package/dist/runtime/index.js +1270 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/index.mjs +1246 -0
- package/dist/runtime/index.mjs.map +1 -0
- package/dist/schema/index.d.mts +838 -0
- package/dist/schema/index.d.ts +838 -0
- package/dist/schema/index.js +696 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/index.mjs +681 -0
- package/dist/schema/index.mjs.map +1 -0
- package/dist/types-C1MiZh1d.d.ts +96 -0
- package/dist/types-C2bd2vgy.d.mts +773 -0
- package/dist/types-C2bd2vgy.d.ts +773 -0
- package/dist/types-C9VMgu3E.d.mts +289 -0
- package/dist/types-DV2DS7wj.d.mts +96 -0
- package/dist/types-c2AN3vky.d.ts +289 -0
- package/package.json +116 -0
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for the Schemock Schema DSL
|
|
3
|
+
*
|
|
4
|
+
* @module schema/types
|
|
5
|
+
* @category Schema
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Constraint configuration for fields
|
|
9
|
+
*/
|
|
10
|
+
interface FieldConstraints {
|
|
11
|
+
/** Minimum value (for numbers) or minimum length (for strings/arrays) */
|
|
12
|
+
min?: number;
|
|
13
|
+
/** Maximum value (for numbers) or maximum length (for strings/arrays) */
|
|
14
|
+
max?: number;
|
|
15
|
+
/** Regular expression pattern for string validation */
|
|
16
|
+
pattern?: RegExp;
|
|
17
|
+
/** Custom validation message */
|
|
18
|
+
message?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Base interface for all field definitions in the schema DSL.
|
|
22
|
+
* Fields define the structure and behavior of entity properties.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const emailField: FieldDefinition<string> = {
|
|
27
|
+
* type: 'email',
|
|
28
|
+
* hint: 'internet.email',
|
|
29
|
+
* nullable: false,
|
|
30
|
+
* unique: true,
|
|
31
|
+
* };
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
interface FieldDefinition<T = unknown> {
|
|
35
|
+
/** The primitive type of the field (string, number, boolean, date, array, object, ref) */
|
|
36
|
+
type: string;
|
|
37
|
+
/** Faker.js hint for generating mock data (e.g., 'person.fullName', 'internet.email') */
|
|
38
|
+
hint?: string;
|
|
39
|
+
/** Whether the field can be null */
|
|
40
|
+
nullable?: boolean;
|
|
41
|
+
/** Whether the field must be unique across all entities */
|
|
42
|
+
unique?: boolean;
|
|
43
|
+
/** Whether the field is read-only (excluded from create/update operations) */
|
|
44
|
+
readOnly?: boolean;
|
|
45
|
+
/** Default value when not provided */
|
|
46
|
+
default?: T;
|
|
47
|
+
/** Validation constraints */
|
|
48
|
+
constraints?: FieldConstraints;
|
|
49
|
+
/** For array fields: the type of items */
|
|
50
|
+
items?: FieldDefinition;
|
|
51
|
+
/** For object fields: the shape of nested properties */
|
|
52
|
+
shape?: Record<string, FieldDefinition>;
|
|
53
|
+
/** For ref fields: the target entity name */
|
|
54
|
+
target?: string;
|
|
55
|
+
/** For enum fields: the allowed values */
|
|
56
|
+
values?: readonly T[];
|
|
57
|
+
/** Internal type marker for TypeScript inference */
|
|
58
|
+
readonly _type?: T;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Builder interface for creating field definitions with chainable methods.
|
|
62
|
+
* All field builder methods return a new builder instance to support method chaining.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* const field = createFieldBuilder('string')
|
|
67
|
+
* .min(1)
|
|
68
|
+
* .max(100)
|
|
69
|
+
* .nullable()
|
|
70
|
+
* .default('');
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
interface FieldBuilder<T> {
|
|
74
|
+
/** The primitive type of the field */
|
|
75
|
+
type: string;
|
|
76
|
+
/** Faker.js hint for generating mock data */
|
|
77
|
+
hint?: string;
|
|
78
|
+
/** Whether the field can be null - use nullable() method to set */
|
|
79
|
+
isNullable?: boolean;
|
|
80
|
+
/** Whether the field must be unique */
|
|
81
|
+
isUnique?: boolean;
|
|
82
|
+
/** Whether the field is read-only */
|
|
83
|
+
isReadOnly?: boolean;
|
|
84
|
+
/** Default value */
|
|
85
|
+
defaultValue?: T;
|
|
86
|
+
/** Validation constraints */
|
|
87
|
+
constraints?: FieldConstraints;
|
|
88
|
+
/** For array fields */
|
|
89
|
+
items?: FieldDefinition;
|
|
90
|
+
/** For object fields */
|
|
91
|
+
shape?: Record<string, FieldDefinition>;
|
|
92
|
+
/** For ref fields */
|
|
93
|
+
target?: string;
|
|
94
|
+
/** For enum fields */
|
|
95
|
+
values?: readonly T[];
|
|
96
|
+
/** Internal type marker */
|
|
97
|
+
readonly _type?: T;
|
|
98
|
+
/** Mark the field as nullable */
|
|
99
|
+
nullable(): FieldBuilder<T | null>;
|
|
100
|
+
/** Mark the field as unique */
|
|
101
|
+
unique(message?: string): FieldBuilder<T>;
|
|
102
|
+
/** Mark the field as read-only */
|
|
103
|
+
readOnly(): FieldBuilder<T>;
|
|
104
|
+
/** Set a default value */
|
|
105
|
+
default(value: T): FieldBuilder<T>;
|
|
106
|
+
/** Set minimum constraint */
|
|
107
|
+
min(value: number, message?: string): FieldBuilder<T>;
|
|
108
|
+
/** Set maximum constraint */
|
|
109
|
+
max(value: number, message?: string): FieldBuilder<T>;
|
|
110
|
+
/** Set regex pattern constraint */
|
|
111
|
+
pattern(regex: RegExp, message?: string): FieldBuilder<T>;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Extended builder for string fields with additional string-specific options
|
|
115
|
+
*/
|
|
116
|
+
interface StringFieldBuilder extends FieldBuilder<string> {
|
|
117
|
+
/** Set minimum length */
|
|
118
|
+
min(length: number, message?: string): StringFieldBuilder;
|
|
119
|
+
/** Set maximum length */
|
|
120
|
+
max(length: number, message?: string): StringFieldBuilder;
|
|
121
|
+
/** Set regex pattern */
|
|
122
|
+
pattern(regex: RegExp, message?: string): StringFieldBuilder;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Extended builder for number fields with numeric-specific options
|
|
126
|
+
*/
|
|
127
|
+
interface NumberFieldBuilder extends FieldBuilder<number> {
|
|
128
|
+
/** Set minimum value */
|
|
129
|
+
min(value: number, message?: string): NumberFieldBuilder;
|
|
130
|
+
/** Set maximum value */
|
|
131
|
+
max(value: number, message?: string): NumberFieldBuilder;
|
|
132
|
+
/** Constrain to integer values */
|
|
133
|
+
int(): NumberFieldBuilder;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Extended builder for date fields with date-specific options
|
|
137
|
+
*/
|
|
138
|
+
interface DateFieldBuilder extends FieldBuilder<Date> {
|
|
139
|
+
/** Constrain to past dates */
|
|
140
|
+
past(): DateFieldBuilder;
|
|
141
|
+
/** Constrain to future dates */
|
|
142
|
+
future(): DateFieldBuilder;
|
|
143
|
+
/** Constrain to recent dates */
|
|
144
|
+
recent(): DateFieldBuilder;
|
|
145
|
+
/** Constrain between specific dates */
|
|
146
|
+
between(options: {
|
|
147
|
+
from: string | Date;
|
|
148
|
+
to: string | Date;
|
|
149
|
+
}): DateFieldBuilder;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Extended builder for enum fields with type-safe values
|
|
153
|
+
*/
|
|
154
|
+
interface EnumFieldBuilder<T extends string> extends FieldBuilder<T> {
|
|
155
|
+
/** Set default enum value */
|
|
156
|
+
default(value: T): EnumFieldBuilder<T>;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Extended builder for reference fields pointing to other entities
|
|
160
|
+
*/
|
|
161
|
+
interface RefFieldBuilder extends FieldBuilder<string> {
|
|
162
|
+
/** The target entity name */
|
|
163
|
+
readonly target: string;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Extended builder for array fields
|
|
167
|
+
*/
|
|
168
|
+
interface ArrayFieldBuilder<T> extends FieldBuilder<T[]> {
|
|
169
|
+
/** Set minimum array length */
|
|
170
|
+
min(length: number): ArrayFieldBuilder<T>;
|
|
171
|
+
/** Set maximum array length */
|
|
172
|
+
max(length: number): ArrayFieldBuilder<T>;
|
|
173
|
+
/** Set exact array length */
|
|
174
|
+
length(count: number): ArrayFieldBuilder<T>;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Extended builder for object fields with nested shape
|
|
178
|
+
*/
|
|
179
|
+
interface ObjectFieldBuilder<T> extends FieldBuilder<T> {
|
|
180
|
+
/** The shape definition of nested properties */
|
|
181
|
+
readonly shape: Record<string, FieldDefinition>;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Defines a relationship between entities.
|
|
185
|
+
* Supports one-to-one, one-to-many, and many-to-many relationships.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```typescript
|
|
189
|
+
* const userPostsRelation: RelationDefinition = {
|
|
190
|
+
* type: 'hasMany',
|
|
191
|
+
* target: 'post',
|
|
192
|
+
* foreignKey: 'authorId',
|
|
193
|
+
* orderBy: { createdAt: 'desc' },
|
|
194
|
+
* };
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
interface RelationDefinition {
|
|
198
|
+
/** The type of relationship */
|
|
199
|
+
type: 'hasMany' | 'belongsTo' | 'hasOne';
|
|
200
|
+
/** The target entity name */
|
|
201
|
+
target: string;
|
|
202
|
+
/** The foreign key field name (on the related entity for hasMany/hasOne, on this entity for belongsTo) */
|
|
203
|
+
foreignKey?: string;
|
|
204
|
+
/** Whether to eagerly load the relation by default */
|
|
205
|
+
eager?: boolean;
|
|
206
|
+
/** Default ordering for hasMany relations */
|
|
207
|
+
orderBy?: Record<string, 'asc' | 'desc'>;
|
|
208
|
+
/** Default limit for hasMany relations */
|
|
209
|
+
limit?: number;
|
|
210
|
+
/** For many-to-many: the junction table name */
|
|
211
|
+
through?: string;
|
|
212
|
+
/** For many-to-many: the other foreign key on the junction table */
|
|
213
|
+
otherKey?: string;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Configuration for computed fields that derive their value from other data.
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```typescript
|
|
220
|
+
* const fullNameComputed: ComputedFieldDefinition<string> = {
|
|
221
|
+
* resolve: (entity) => `${entity.firstName} ${entity.lastName}`,
|
|
222
|
+
* dependsOn: ['firstName', 'lastName'],
|
|
223
|
+
* };
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
interface ComputedFieldDefinition<T = unknown> {
|
|
227
|
+
/** Function to generate mock data for this computed field */
|
|
228
|
+
mock?: () => T;
|
|
229
|
+
/** Function to resolve the actual value from entity data */
|
|
230
|
+
resolve: (entity: Record<string, unknown>, db: unknown, ctx: unknown) => T | Promise<T>;
|
|
231
|
+
/** Fields this computed field depends on (for ordering resolution) */
|
|
232
|
+
dependsOn?: string[];
|
|
233
|
+
/** Internal type marker */
|
|
234
|
+
readonly _computed: true;
|
|
235
|
+
readonly _type?: T;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Type guard to check if a field definition is a computed field
|
|
239
|
+
*/
|
|
240
|
+
declare function isComputedField(field: unknown): field is ComputedFieldDefinition;
|
|
241
|
+
/**
|
|
242
|
+
* Type guard to check if a field definition is a relation
|
|
243
|
+
*/
|
|
244
|
+
declare function isRelation(field: unknown): field is RelationDefinition;
|
|
245
|
+
/**
|
|
246
|
+
* API configuration for an entity
|
|
247
|
+
*/
|
|
248
|
+
interface EntityApiConfig {
|
|
249
|
+
/** Base path for REST endpoints (e.g., '/api/users') */
|
|
250
|
+
basePath: string;
|
|
251
|
+
/** Enable/disable specific CRUD operations */
|
|
252
|
+
operations?: {
|
|
253
|
+
list?: boolean;
|
|
254
|
+
get?: boolean;
|
|
255
|
+
create?: boolean;
|
|
256
|
+
update?: boolean;
|
|
257
|
+
delete?: boolean;
|
|
258
|
+
[key: string]: boolean | {
|
|
259
|
+
method: string;
|
|
260
|
+
path: string;
|
|
261
|
+
params?: string[];
|
|
262
|
+
} | undefined;
|
|
263
|
+
};
|
|
264
|
+
/** Pagination configuration */
|
|
265
|
+
pagination?: {
|
|
266
|
+
style: 'offset' | 'cursor';
|
|
267
|
+
defaultLimit?: number;
|
|
268
|
+
maxLimit?: number;
|
|
269
|
+
};
|
|
270
|
+
/** Relationship endpoint configuration */
|
|
271
|
+
relationships?: Record<string, {
|
|
272
|
+
endpoint?: boolean;
|
|
273
|
+
operations?: Array<'list' | 'create' | 'update' | 'delete'>;
|
|
274
|
+
}>;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Index configuration for database optimization.
|
|
278
|
+
*
|
|
279
|
+
* @example Simple index
|
|
280
|
+
* ```typescript
|
|
281
|
+
* { fields: ['authorId'] }
|
|
282
|
+
* ```
|
|
283
|
+
*
|
|
284
|
+
* @example Composite unique index
|
|
285
|
+
* ```typescript
|
|
286
|
+
* { fields: ['email', 'tenantId'], unique: true }
|
|
287
|
+
* ```
|
|
288
|
+
*
|
|
289
|
+
* @example Full-text search index
|
|
290
|
+
* ```typescript
|
|
291
|
+
* {
|
|
292
|
+
* fields: ['title'],
|
|
293
|
+
* type: 'gin',
|
|
294
|
+
* using: "to_tsvector('english', title)",
|
|
295
|
+
* }
|
|
296
|
+
* ```
|
|
297
|
+
*
|
|
298
|
+
* @example Partial index
|
|
299
|
+
* ```typescript
|
|
300
|
+
* {
|
|
301
|
+
* fields: ['createdAt'],
|
|
302
|
+
* where: "status = 'active'",
|
|
303
|
+
* }
|
|
304
|
+
* ```
|
|
305
|
+
*/
|
|
306
|
+
interface IndexConfig {
|
|
307
|
+
/** Column names to index */
|
|
308
|
+
fields: string[];
|
|
309
|
+
/** Custom index name (auto-generated if not provided) */
|
|
310
|
+
name?: string;
|
|
311
|
+
/** Index type (default: btree) */
|
|
312
|
+
type?: 'btree' | 'hash' | 'gin' | 'gist' | 'brin';
|
|
313
|
+
/** Custom expression for functional indexes (e.g., "to_tsvector('english', title)") */
|
|
314
|
+
using?: string;
|
|
315
|
+
/** Create unique index */
|
|
316
|
+
unique?: boolean;
|
|
317
|
+
/** Partial index condition (WHERE clause) */
|
|
318
|
+
where?: string;
|
|
319
|
+
/** Use CONCURRENTLY for production migrations (avoids locking) */
|
|
320
|
+
concurrently?: boolean;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* RPC/Stored procedure argument definition.
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```typescript
|
|
327
|
+
* { name: 'user_id', type: 'uuid' }
|
|
328
|
+
* { name: 'limit', type: 'integer', default: '20' }
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
331
|
+
interface RPCArgument {
|
|
332
|
+
/** Argument name */
|
|
333
|
+
name: string;
|
|
334
|
+
/** PostgreSQL type (uuid, text, integer, boolean, jsonb, etc.) */
|
|
335
|
+
type: string;
|
|
336
|
+
/** Default value as SQL expression (optional) */
|
|
337
|
+
default?: string;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* RPC/Stored procedure configuration.
|
|
341
|
+
*
|
|
342
|
+
* @example Simple query function
|
|
343
|
+
* ```typescript
|
|
344
|
+
* {
|
|
345
|
+
* args: [{ name: 'user_id', type: 'uuid' }],
|
|
346
|
+
* returns: 'post[]',
|
|
347
|
+
* sql: "SELECT * FROM posts WHERE author_id = $1",
|
|
348
|
+
* volatility: 'stable',
|
|
349
|
+
* }
|
|
350
|
+
* ```
|
|
351
|
+
*
|
|
352
|
+
* @example Mutation function
|
|
353
|
+
* ```typescript
|
|
354
|
+
* {
|
|
355
|
+
* args: [{ name: 'post_id', type: 'uuid' }],
|
|
356
|
+
* returns: 'post',
|
|
357
|
+
* sql: "UPDATE posts SET status = 'published' WHERE id = $1 RETURNING *",
|
|
358
|
+
* volatility: 'volatile',
|
|
359
|
+
* }
|
|
360
|
+
* ```
|
|
361
|
+
*
|
|
362
|
+
* @example PL/pgSQL function
|
|
363
|
+
* ```typescript
|
|
364
|
+
* {
|
|
365
|
+
* args: [{ name: 'user_id', type: 'uuid' }],
|
|
366
|
+
* returns: 'integer',
|
|
367
|
+
* language: 'plpgsql',
|
|
368
|
+
* sql: `
|
|
369
|
+
* DECLARE
|
|
370
|
+
* v_count integer;
|
|
371
|
+
* BEGIN
|
|
372
|
+
* SELECT COUNT(*) INTO v_count FROM posts WHERE author_id = user_id;
|
|
373
|
+
* RETURN v_count;
|
|
374
|
+
* END;
|
|
375
|
+
* `,
|
|
376
|
+
* }
|
|
377
|
+
* ```
|
|
378
|
+
*/
|
|
379
|
+
interface RPCConfig {
|
|
380
|
+
/** Function arguments */
|
|
381
|
+
args?: RPCArgument[];
|
|
382
|
+
/** Return type: 'void', entity name (e.g., 'post'), array (e.g., 'post[]'), or PostgreSQL type */
|
|
383
|
+
returns: string;
|
|
384
|
+
/** SQL function body */
|
|
385
|
+
sql: string;
|
|
386
|
+
/** Function language (default: sql) */
|
|
387
|
+
language?: 'sql' | 'plpgsql';
|
|
388
|
+
/** Volatility marker for query optimization (default: volatile) */
|
|
389
|
+
volatility?: 'volatile' | 'stable' | 'immutable';
|
|
390
|
+
/** Security context (default: invoker) */
|
|
391
|
+
security?: 'invoker' | 'definer';
|
|
392
|
+
/** Function description for documentation */
|
|
393
|
+
description?: string;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Options for entity schema configuration
|
|
397
|
+
*/
|
|
398
|
+
interface EntityOptions<T = unknown> {
|
|
399
|
+
/** API endpoint configuration */
|
|
400
|
+
api?: EntityApiConfig;
|
|
401
|
+
/** Whether to automatically add timestamp fields (createdAt, updatedAt) */
|
|
402
|
+
timestamps?: boolean;
|
|
403
|
+
/** Row-level security configuration */
|
|
404
|
+
rls?: RLSConfig<T>;
|
|
405
|
+
/** Database indexes for query optimization */
|
|
406
|
+
indexes?: IndexConfig[];
|
|
407
|
+
/** RPC/Stored procedures related to this entity */
|
|
408
|
+
rpc?: Record<string, RPCConfig>;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Complete schema definition for an entity.
|
|
412
|
+
* This is the primary output of the defineData function.
|
|
413
|
+
*
|
|
414
|
+
* @example
|
|
415
|
+
* ```typescript
|
|
416
|
+
* const userSchema: EntitySchema = {
|
|
417
|
+
* name: 'user',
|
|
418
|
+
* fields: { id: field.uuid(), name: field.string() },
|
|
419
|
+
* relations: { posts: hasMany('post') },
|
|
420
|
+
* computed: { postCount: field.computed({...}) },
|
|
421
|
+
* };
|
|
422
|
+
* ```
|
|
423
|
+
*/
|
|
424
|
+
/**
|
|
425
|
+
* Generic context for RLS evaluation.
|
|
426
|
+
* Can represent users, API keys, services, tenants, or any other context.
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```typescript
|
|
430
|
+
* // User-based context
|
|
431
|
+
* const userCtx: RLSContext = { userId: 'user-123', role: 'admin', orgId: 'org-456' };
|
|
432
|
+
*
|
|
433
|
+
* // API key context (no user)
|
|
434
|
+
* const apiKeyCtx: RLSContext = { tenantId: 'tenant-789', scope: 'read' };
|
|
435
|
+
*
|
|
436
|
+
* // Service-to-service context
|
|
437
|
+
* const serviceCtx: RLSContext = { serviceId: 'payment-service', environment: 'prod' };
|
|
438
|
+
* ```
|
|
439
|
+
*/
|
|
440
|
+
interface RLSContext {
|
|
441
|
+
[key: string]: unknown;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Row-level security policy filter function.
|
|
445
|
+
* Receives the row data and current context (can be null if no context set).
|
|
446
|
+
*/
|
|
447
|
+
type RLSFilter<T = unknown> = (row: T, context: RLSContext | null) => boolean;
|
|
448
|
+
/**
|
|
449
|
+
* Scope mapping: maps a row field to a context key.
|
|
450
|
+
* Used for automatic policy generation.
|
|
451
|
+
*
|
|
452
|
+
* @example
|
|
453
|
+
* ```typescript
|
|
454
|
+
* // Row field 'authorId' must match context key 'userId'
|
|
455
|
+
* { field: 'authorId', contextKey: 'userId' }
|
|
456
|
+
*
|
|
457
|
+
* // Row field 'tenantId' must match context key 'tenantId'
|
|
458
|
+
* { field: 'tenantId', contextKey: 'tenantId' }
|
|
459
|
+
* ```
|
|
460
|
+
*/
|
|
461
|
+
interface RLSScopeMapping {
|
|
462
|
+
/** The field name on the row/entity */
|
|
463
|
+
field: string;
|
|
464
|
+
/** The key in the RLS context to compare against */
|
|
465
|
+
contextKey: string;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Bypass condition for RLS.
|
|
469
|
+
* When the context matches this condition, RLS is bypassed.
|
|
470
|
+
*/
|
|
471
|
+
interface RLSBypass {
|
|
472
|
+
/** Context key to check */
|
|
473
|
+
contextKey: string;
|
|
474
|
+
/** Values that trigger bypass (e.g., ['admin', 'superuser']) */
|
|
475
|
+
values: string[];
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Row-level security configuration for an entity.
|
|
479
|
+
* Works with any context - users, API keys, services, tenants, etc.
|
|
480
|
+
*
|
|
481
|
+
* @example Simple scope-based (works without users)
|
|
482
|
+
* ```typescript
|
|
483
|
+
* const Post = defineData('post', { ... }, {
|
|
484
|
+
* rls: {
|
|
485
|
+
* // Rows filtered by tenantId from context
|
|
486
|
+
* scope: [{ field: 'tenantId', contextKey: 'tenantId' }],
|
|
487
|
+
* },
|
|
488
|
+
* });
|
|
489
|
+
* ```
|
|
490
|
+
*
|
|
491
|
+
* @example User-based with owner field
|
|
492
|
+
* ```typescript
|
|
493
|
+
* const Post = defineData('post', { ... }, {
|
|
494
|
+
* rls: {
|
|
495
|
+
* scope: [{ field: 'authorId', contextKey: 'userId' }],
|
|
496
|
+
* bypass: [{ contextKey: 'role', values: ['admin'] }],
|
|
497
|
+
* },
|
|
498
|
+
* });
|
|
499
|
+
* ```
|
|
500
|
+
*
|
|
501
|
+
* @example Custom filter functions
|
|
502
|
+
* ```typescript
|
|
503
|
+
* const Post = defineData('post', { ... }, {
|
|
504
|
+
* rls: {
|
|
505
|
+
* select: (row, ctx) => row.isPublic || row.authorId === ctx?.userId,
|
|
506
|
+
* insert: (row, ctx) => row.authorId === ctx?.userId,
|
|
507
|
+
* },
|
|
508
|
+
* });
|
|
509
|
+
* ```
|
|
510
|
+
*/
|
|
511
|
+
interface RLSConfig<T = unknown> {
|
|
512
|
+
/**
|
|
513
|
+
* Scope mappings for automatic policy generation.
|
|
514
|
+
* Each mapping requires row[field] === context[contextKey].
|
|
515
|
+
*/
|
|
516
|
+
scope?: RLSScopeMapping[];
|
|
517
|
+
/**
|
|
518
|
+
* Conditions that bypass RLS entirely.
|
|
519
|
+
* If any bypass condition matches, all operations are allowed.
|
|
520
|
+
*/
|
|
521
|
+
bypass?: RLSBypass[];
|
|
522
|
+
/** Custom filter for SELECT/read operations */
|
|
523
|
+
select?: RLSFilter<T>;
|
|
524
|
+
/** Custom filter for INSERT operations */
|
|
525
|
+
insert?: RLSFilter<T>;
|
|
526
|
+
/** Custom filter for UPDATE operations */
|
|
527
|
+
update?: RLSFilter<T>;
|
|
528
|
+
/** Custom filter for DELETE operations */
|
|
529
|
+
delete?: RLSFilter<T>;
|
|
530
|
+
/**
|
|
531
|
+
* PostgreSQL-compatible policy SQL for PGlite/Supabase.
|
|
532
|
+
* Use current_setting('app.{contextKey}') to reference context values.
|
|
533
|
+
*/
|
|
534
|
+
sql?: {
|
|
535
|
+
select?: string;
|
|
536
|
+
insert?: string;
|
|
537
|
+
update?: string;
|
|
538
|
+
delete?: string;
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
interface EntitySchema<T = unknown> {
|
|
542
|
+
/** The unique name of this entity */
|
|
543
|
+
name: string;
|
|
544
|
+
/** Field definitions */
|
|
545
|
+
fields: Record<string, FieldDefinition>;
|
|
546
|
+
/** Relation definitions */
|
|
547
|
+
relations?: Record<string, RelationDefinition>;
|
|
548
|
+
/** Computed field definitions */
|
|
549
|
+
computed?: Record<string, ComputedFieldDefinition>;
|
|
550
|
+
/** Whether to include timestamp fields */
|
|
551
|
+
timestamps?: boolean;
|
|
552
|
+
/** API configuration */
|
|
553
|
+
api?: EntityApiConfig;
|
|
554
|
+
/** Row-level security configuration */
|
|
555
|
+
rls?: RLSConfig<T>;
|
|
556
|
+
/** Database indexes for query optimization */
|
|
557
|
+
indexes?: IndexConfig[];
|
|
558
|
+
/** RPC/Stored procedures related to this entity */
|
|
559
|
+
rpc?: Record<string, RPCConfig>;
|
|
560
|
+
/** Internal type marker */
|
|
561
|
+
readonly _entity?: T;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Embed configuration for including related data in views
|
|
565
|
+
*/
|
|
566
|
+
interface EmbedConfig {
|
|
567
|
+
/** Maximum number of items to include */
|
|
568
|
+
limit?: number;
|
|
569
|
+
/** Ordering for embedded items */
|
|
570
|
+
orderBy?: Record<string, 'asc' | 'desc'>;
|
|
571
|
+
/** Fields to include (defaults to all) */
|
|
572
|
+
select?: string[];
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* View field value type - supports fields, computed, embeds, and nested objects
|
|
576
|
+
*/
|
|
577
|
+
type ViewFieldValue = FieldDefinition | ComputedFieldDefinition | {
|
|
578
|
+
_embed: true;
|
|
579
|
+
entity: EntitySchema;
|
|
580
|
+
config?: EmbedConfig;
|
|
581
|
+
} | Record<string, FieldDefinition | ComputedFieldDefinition>;
|
|
582
|
+
/**
|
|
583
|
+
* View field definitions (subset of entity fields + embedded relations)
|
|
584
|
+
*/
|
|
585
|
+
type ViewFields = Record<string, ViewFieldValue>;
|
|
586
|
+
/**
|
|
587
|
+
* Options for view schema configuration
|
|
588
|
+
*/
|
|
589
|
+
interface ViewOptions {
|
|
590
|
+
/** The API endpoint for this view */
|
|
591
|
+
endpoint: string;
|
|
592
|
+
/** URL parameters required for this view */
|
|
593
|
+
params: string[];
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Schema definition for a computed view over entities.
|
|
597
|
+
*
|
|
598
|
+
* @example
|
|
599
|
+
* ```typescript
|
|
600
|
+
* const userFullView: ViewSchema = {
|
|
601
|
+
* name: 'user-full',
|
|
602
|
+
* fields: { id: field.uuid(), name: field.string() },
|
|
603
|
+
* endpoint: '/api/users/:id/full',
|
|
604
|
+
* params: ['id'],
|
|
605
|
+
* };
|
|
606
|
+
* ```
|
|
607
|
+
*/
|
|
608
|
+
interface ViewSchema {
|
|
609
|
+
/** The unique name of this view */
|
|
610
|
+
name: string;
|
|
611
|
+
/** Field definitions for the view output */
|
|
612
|
+
fields: ViewFields;
|
|
613
|
+
/** The API endpoint */
|
|
614
|
+
endpoint: string;
|
|
615
|
+
/** Required URL parameters */
|
|
616
|
+
params: string[];
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* HTTP methods supported for custom endpoints
|
|
620
|
+
*/
|
|
621
|
+
type EndpointMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
622
|
+
/**
|
|
623
|
+
* Context passed to mock resolver functions.
|
|
624
|
+
* Provides access to request data and the mock database.
|
|
625
|
+
*
|
|
626
|
+
* @example
|
|
627
|
+
* ```typescript
|
|
628
|
+
* mockResolver: async ({ params, body, db }) => {
|
|
629
|
+
* const users = db.user.findMany({ where: { name: { contains: params.q } } });
|
|
630
|
+
* return { results: users, total: users.length };
|
|
631
|
+
* }
|
|
632
|
+
* ```
|
|
633
|
+
*/
|
|
634
|
+
interface MockResolverContext<TParams = Record<string, unknown>, TBody = Record<string, unknown>> {
|
|
635
|
+
/** Parsed query/path parameters */
|
|
636
|
+
params: TParams;
|
|
637
|
+
/** Parsed request body (for POST/PUT/PATCH) */
|
|
638
|
+
body: TBody;
|
|
639
|
+
/** Access to mock database */
|
|
640
|
+
db: unknown;
|
|
641
|
+
/** Request headers */
|
|
642
|
+
headers: Record<string, string>;
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Configuration for defining a custom endpoint.
|
|
646
|
+
*
|
|
647
|
+
* @example
|
|
648
|
+
* ```typescript
|
|
649
|
+
* const config: EndpointConfig = {
|
|
650
|
+
* method: 'GET',
|
|
651
|
+
* params: { q: field.string(), limit: field.number.int().default(20) },
|
|
652
|
+
* response: { results: field.array(field.object({...})), total: field.number.int() },
|
|
653
|
+
* mockResolver: async ({ params, db }) => ({ results: [], total: 0 }),
|
|
654
|
+
* };
|
|
655
|
+
* ```
|
|
656
|
+
*/
|
|
657
|
+
interface EndpointConfig<TParams = Record<string, unknown>, TBody = Record<string, unknown>, TResponse = unknown> {
|
|
658
|
+
/** HTTP method */
|
|
659
|
+
method: EndpointMethod;
|
|
660
|
+
/** Query/path parameter definitions */
|
|
661
|
+
params?: Record<string, FieldBuilder<unknown> | FieldDefinition>;
|
|
662
|
+
/** Request body definition (for POST/PUT/PATCH) */
|
|
663
|
+
body?: Record<string, FieldBuilder<unknown> | FieldDefinition>;
|
|
664
|
+
/** Response schema definition */
|
|
665
|
+
response: Record<string, FieldBuilder<unknown> | FieldDefinition>;
|
|
666
|
+
/** Mock resolver function that generates fake responses */
|
|
667
|
+
mockResolver: (ctx: MockResolverContext<TParams, TBody>) => TResponse | Promise<TResponse>;
|
|
668
|
+
/** Optional description for documentation */
|
|
669
|
+
description?: string;
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Complete endpoint schema definition.
|
|
673
|
+
* This is the output of the defineEndpoint function.
|
|
674
|
+
*
|
|
675
|
+
* @example
|
|
676
|
+
* ```typescript
|
|
677
|
+
* const SearchEndpoint = defineEndpoint('/api/search', {
|
|
678
|
+
* method: 'GET',
|
|
679
|
+
* params: { q: field.string() },
|
|
680
|
+
* response: { results: field.array(field.string()) },
|
|
681
|
+
* mockResolver: async ({ params, db }) => ({ results: [] }),
|
|
682
|
+
* });
|
|
683
|
+
* ```
|
|
684
|
+
*/
|
|
685
|
+
interface EndpointSchema<TParams = Record<string, unknown>, TBody = Record<string, unknown>, TResponse = unknown> {
|
|
686
|
+
/** Endpoint path (e.g., '/api/search', '/api/orders/:id') */
|
|
687
|
+
path: string;
|
|
688
|
+
/** HTTP method */
|
|
689
|
+
method: EndpointMethod;
|
|
690
|
+
/** Parameter definitions (normalized to FieldDefinition) */
|
|
691
|
+
params: Record<string, FieldDefinition>;
|
|
692
|
+
/** Body definitions (normalized to FieldDefinition) */
|
|
693
|
+
body: Record<string, FieldDefinition>;
|
|
694
|
+
/** Response definitions (normalized to FieldDefinition) */
|
|
695
|
+
response: Record<string, FieldDefinition>;
|
|
696
|
+
/** Mock resolver function */
|
|
697
|
+
mockResolver: (ctx: MockResolverContext<TParams, TBody>) => TResponse | Promise<TResponse>;
|
|
698
|
+
/** Description for documentation */
|
|
699
|
+
description?: string;
|
|
700
|
+
/** Internal marker for type identification */
|
|
701
|
+
readonly _endpoint: true;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Type guard to check if a value is an EndpointSchema
|
|
705
|
+
*/
|
|
706
|
+
declare function isEndpointSchema(value: unknown): value is EndpointSchema;
|
|
707
|
+
/**
|
|
708
|
+
* Maps field types to their TypeScript equivalents
|
|
709
|
+
*/
|
|
710
|
+
type FieldTypeMap = {
|
|
711
|
+
string: string;
|
|
712
|
+
uuid: string;
|
|
713
|
+
email: string;
|
|
714
|
+
url: string;
|
|
715
|
+
number: number;
|
|
716
|
+
int: number;
|
|
717
|
+
float: number;
|
|
718
|
+
boolean: boolean;
|
|
719
|
+
date: Date;
|
|
720
|
+
array: unknown[];
|
|
721
|
+
object: Record<string, unknown>;
|
|
722
|
+
ref: string;
|
|
723
|
+
enum: string;
|
|
724
|
+
};
|
|
725
|
+
/**
|
|
726
|
+
* Infer the TypeScript type from a FieldDefinition
|
|
727
|
+
*/
|
|
728
|
+
type InferFieldType<F extends FieldDefinition> = F['nullable'] extends true ? (F extends {
|
|
729
|
+
_type: infer T;
|
|
730
|
+
} ? T : FieldTypeMap[F['type'] & keyof FieldTypeMap]) | null : F extends {
|
|
731
|
+
_type: infer T;
|
|
732
|
+
} ? T : FieldTypeMap[F['type'] & keyof FieldTypeMap];
|
|
733
|
+
/**
|
|
734
|
+
* Infer the complete entity type from an EntitySchema
|
|
735
|
+
*
|
|
736
|
+
* @example
|
|
737
|
+
* ```typescript
|
|
738
|
+
* const User = defineData('user', { id: field.uuid(), name: field.string() });
|
|
739
|
+
* type UserType = InferEntity<typeof User>;
|
|
740
|
+
* // { id: string; name: string; }
|
|
741
|
+
* ```
|
|
742
|
+
*/
|
|
743
|
+
type InferEntity<S extends EntitySchema> = {
|
|
744
|
+
[K in keyof S['fields']]: InferFieldType<S['fields'][K]>;
|
|
745
|
+
} & (S['relations'] extends Record<string, RelationDefinition> ? {
|
|
746
|
+
[K in keyof S['relations']]?: S['relations'][K]['type'] extends 'hasMany' ? unknown[] : unknown;
|
|
747
|
+
} : object) & (S['computed'] extends Record<string, ComputedFieldDefinition> ? {
|
|
748
|
+
[K in keyof S['computed']]: S['computed'][K]['_type'];
|
|
749
|
+
} : object);
|
|
750
|
+
/**
|
|
751
|
+
* Infer the type for creating a new entity (excludes id, readOnly, computed fields)
|
|
752
|
+
*
|
|
753
|
+
* @example
|
|
754
|
+
* ```typescript
|
|
755
|
+
* type UserCreate = InferCreate<typeof User>;
|
|
756
|
+
* // { name: string; email: string; } (without id, createdAt, etc.)
|
|
757
|
+
* ```
|
|
758
|
+
*/
|
|
759
|
+
type InferCreate<S extends EntitySchema> = {
|
|
760
|
+
[K in keyof S['fields'] as S['fields'][K]['readOnly'] extends true ? never : K extends 'id' ? never : K]: S['fields'][K]['default'] extends undefined ? InferFieldType<S['fields'][K]> : InferFieldType<S['fields'][K]> | undefined;
|
|
761
|
+
};
|
|
762
|
+
/**
|
|
763
|
+
* Infer the type for updating an entity (all fields optional, excludes readOnly)
|
|
764
|
+
*
|
|
765
|
+
* @example
|
|
766
|
+
* ```typescript
|
|
767
|
+
* type UserUpdate = InferUpdate<typeof User>;
|
|
768
|
+
* // { name?: string; email?: string; }
|
|
769
|
+
* ```
|
|
770
|
+
*/
|
|
771
|
+
type InferUpdate<S extends EntitySchema> = Partial<InferCreate<S>>;
|
|
772
|
+
|
|
773
|
+
export { type ArrayFieldBuilder as A, type InferUpdate as B, type ComputedFieldDefinition as C, type DateFieldBuilder as D, type EnumFieldBuilder as E, type FieldBuilder as F, type IndexConfig as I, type MockResolverContext as M, type NumberFieldBuilder as N, type ObjectFieldBuilder as O, type RefFieldBuilder as R, type StringFieldBuilder as S, type ViewFieldValue as V, type FieldConstraints as a, type FieldDefinition as b, type RelationDefinition as c, isRelation as d, type EntityApiConfig as e, type RPCArgument as f, type RPCConfig as g, type EntityOptions as h, isComputedField as i, type RLSContext as j, type RLSFilter as k, type RLSScopeMapping as l, type RLSBypass as m, type RLSConfig as n, type EntitySchema as o, type EmbedConfig as p, type ViewFields as q, type ViewOptions as r, type ViewSchema as s, type EndpointMethod as t, type EndpointConfig as u, type EndpointSchema as v, isEndpointSchema as w, type InferFieldType as x, type InferEntity as y, type InferCreate as z };
|