@proofkit/fmodata 0.1.0-alpha.15 → 0.1.0-alpha.16
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/README.md +4 -165
- package/dist/esm/client/builders/select-mixin.d.ts +2 -2
- package/dist/esm/client/builders/select-mixin.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +2 -12
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/error-parser.js.map +1 -1
- package/dist/esm/client/query/query-builder.d.ts +9 -16
- package/dist/esm/client/query/query-builder.js +7 -76
- package/dist/esm/client/query/query-builder.js.map +1 -1
- package/dist/esm/client/query/types.d.ts +5 -5
- package/dist/esm/client/record-builder.d.ts +5 -5
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/orm/column.d.ts +21 -4
- package/dist/esm/orm/column.js +5 -2
- package/dist/esm/orm/column.js.map +1 -1
- package/dist/esm/orm/field-builders.d.ts +1 -1
- package/dist/esm/orm/field-builders.js +1 -1
- package/dist/esm/orm/field-builders.js.map +1 -1
- package/dist/esm/orm/operators.d.ts +19 -19
- package/dist/esm/orm/operators.js +31 -12
- package/dist/esm/orm/operators.js.map +1 -1
- package/dist/esm/orm/table.d.ts +10 -9
- package/dist/esm/orm/table.js +5 -3
- package/dist/esm/orm/table.js.map +1 -1
- package/dist/esm/transform.js +1 -5
- package/dist/esm/transform.js.map +1 -1
- package/dist/esm/types.d.ts +9 -43
- package/dist/esm/types.js.map +1 -1
- package/package.json +1 -1
- package/src/client/builders/select-mixin.ts +2 -3
- package/src/client/entity-set.ts +25 -7
- package/src/client/error-parser.ts +3 -0
- package/src/client/query/query-builder.ts +14 -123
- package/src/client/query/types.ts +6 -5
- package/src/client/record-builder.ts +9 -6
- package/src/index.ts +3 -15
- package/src/orm/column.ts +33 -5
- package/src/orm/field-builders.ts +1 -1
- package/src/orm/operators.ts +105 -51
- package/src/orm/table.ts +21 -13
- package/src/types.ts +10 -51
- package/dist/esm/filter-types.d.ts +0 -76
- package/src/filter-types.ts +0 -97
package/README.md
CHANGED
|
@@ -323,156 +323,6 @@ Available operators:
|
|
|
323
323
|
- **Null**: `isNull()`, `isNotNull()`
|
|
324
324
|
- **Logical**: `and()`, `or()`, `not()`
|
|
325
325
|
|
|
326
|
-
#### Legacy Filter API (DO NOT USE, will be removed shortly)
|
|
327
|
-
|
|
328
|
-
The filter system supports three syntaxes: shorthand, single operator objects, and arrays for multiple operators.
|
|
329
|
-
|
|
330
|
-
You can use the legacy `filter()` method in three ways:
|
|
331
|
-
|
|
332
|
-
**1. Shorthand (direct value):**
|
|
333
|
-
|
|
334
|
-
```typescript
|
|
335
|
-
.filter({ name: "John" })
|
|
336
|
-
// Equivalent to: { name: [{ eq: "John" }] }
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
**2. Single operator object:**
|
|
340
|
-
|
|
341
|
-
```typescript
|
|
342
|
-
.filter({ age: { gt: 18 } })
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
**3. Array of operators (for multiple operators on same field):**
|
|
346
|
-
|
|
347
|
-
```typescript
|
|
348
|
-
.filter({ age: [{ gt: 18 }, { lt: 65 }] })
|
|
349
|
-
// Result: age gt 18 and age lt 65
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
The array pattern prevents duplicate operators on the same field and allows multiple conditions with implicit AND.
|
|
353
|
-
|
|
354
|
-
#### Available Operators
|
|
355
|
-
|
|
356
|
-
**String fields:**
|
|
357
|
-
|
|
358
|
-
- `eq`, `ne` - equality/inequality
|
|
359
|
-
- `contains`, `startswith`, `endswith` - string functions
|
|
360
|
-
- `gt`, `ge`, `lt`, `le` - comparison
|
|
361
|
-
- `in` - match any value in array
|
|
362
|
-
|
|
363
|
-
**Number fields:**
|
|
364
|
-
|
|
365
|
-
- `eq`, `ne`, `gt`, `ge`, `lt`, `le` - comparisons
|
|
366
|
-
- `in` - match any value in array
|
|
367
|
-
|
|
368
|
-
**Boolean fields:**
|
|
369
|
-
|
|
370
|
-
- `eq`, `ne` - equality only
|
|
371
|
-
|
|
372
|
-
**Date fields:**
|
|
373
|
-
|
|
374
|
-
- `eq`, `ne`, `gt`, `ge`, `lt`, `le` - date comparisons
|
|
375
|
-
- `in` - match any date in array
|
|
376
|
-
|
|
377
|
-
#### Shorthand Syntax
|
|
378
|
-
|
|
379
|
-
For simple equality checks, use the shorthand:
|
|
380
|
-
|
|
381
|
-
```typescript
|
|
382
|
-
const result = await db.from("users").list().filter({ name: "John" }).execute();
|
|
383
|
-
// Equivalent to: { name: [{ eq: "John" }] }
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
#### Examples
|
|
387
|
-
|
|
388
|
-
```typescript
|
|
389
|
-
// Equality filter (single operator)
|
|
390
|
-
const activeUsers = await db
|
|
391
|
-
.from("users")
|
|
392
|
-
.list()
|
|
393
|
-
.filter({ active: { eq: true } })
|
|
394
|
-
.execute();
|
|
395
|
-
|
|
396
|
-
// Comparison operators (single operator)
|
|
397
|
-
const adultUsers = await db
|
|
398
|
-
.from("users")
|
|
399
|
-
.list()
|
|
400
|
-
.filter({ age: { gt: 18 } })
|
|
401
|
-
.execute();
|
|
402
|
-
|
|
403
|
-
// String operators (single operator)
|
|
404
|
-
const johns = await db
|
|
405
|
-
.from("users")
|
|
406
|
-
.list()
|
|
407
|
-
.filter({ name: { contains: "John" } })
|
|
408
|
-
.execute();
|
|
409
|
-
|
|
410
|
-
// Multiple operators on same field (array syntax, implicit AND)
|
|
411
|
-
const rangeQuery = await db
|
|
412
|
-
.from("users")
|
|
413
|
-
.list()
|
|
414
|
-
.filter({ age: [{ gt: 18 }, { lt: 65 }] })
|
|
415
|
-
.execute();
|
|
416
|
-
|
|
417
|
-
// Combine filters with AND
|
|
418
|
-
const result = await db
|
|
419
|
-
.from("users")
|
|
420
|
-
.list()
|
|
421
|
-
.filter({
|
|
422
|
-
and: [{ active: [{ eq: true }] }, { age: [{ gt: 18 }] }],
|
|
423
|
-
})
|
|
424
|
-
.execute();
|
|
425
|
-
|
|
426
|
-
// Combine filters with OR
|
|
427
|
-
const result = await db
|
|
428
|
-
.from("users")
|
|
429
|
-
.list()
|
|
430
|
-
.filter({
|
|
431
|
-
or: [{ name: [{ eq: "John" }] }, { name: [{ eq: "Jane" }] }],
|
|
432
|
-
})
|
|
433
|
-
.execute();
|
|
434
|
-
|
|
435
|
-
// IN operator
|
|
436
|
-
const result = await db
|
|
437
|
-
.from("users")
|
|
438
|
-
.list()
|
|
439
|
-
.filter({ age: [{ in: [18, 21, 25] }] })
|
|
440
|
-
.execute();
|
|
441
|
-
|
|
442
|
-
// Null checks
|
|
443
|
-
const result = await db
|
|
444
|
-
.from("users")
|
|
445
|
-
.list()
|
|
446
|
-
.filter({ deletedAt: [{ eq: null }] })
|
|
447
|
-
.execute();
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
#### Logical Operators
|
|
451
|
-
|
|
452
|
-
Combine multiple conditions with `and`, `or`, `not`:
|
|
453
|
-
|
|
454
|
-
```typescript
|
|
455
|
-
const result = await db
|
|
456
|
-
.from("users")
|
|
457
|
-
.list()
|
|
458
|
-
.filter({
|
|
459
|
-
and: [{ name: [{ contains: "John" }] }, { age: [{ gt: 18 }] }],
|
|
460
|
-
})
|
|
461
|
-
.execute();
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
#### Escape Hatch
|
|
465
|
-
|
|
466
|
-
For unsupported edge cases, pass a raw OData filter string:
|
|
467
|
-
|
|
468
|
-
```typescript
|
|
469
|
-
const result = await db
|
|
470
|
-
.from("users")
|
|
471
|
-
.list()
|
|
472
|
-
.filter("substringof('John', name)")
|
|
473
|
-
.execute();
|
|
474
|
-
```
|
|
475
|
-
|
|
476
326
|
### Sorting
|
|
477
327
|
|
|
478
328
|
Sort results using `orderBy()`. The method supports both column references (new ORM API) and string field names (legacy API).
|
|
@@ -565,7 +415,7 @@ Use `single()` to ensure exactly one record is returned (returns an error if zer
|
|
|
565
415
|
const result = await db
|
|
566
416
|
.from(users)
|
|
567
417
|
.list()
|
|
568
|
-
.
|
|
418
|
+
.where(eq(users.email, "user@example.com"))
|
|
569
419
|
.single()
|
|
570
420
|
.execute();
|
|
571
421
|
|
|
@@ -581,7 +431,7 @@ Use `maybeSingle()` when you want at most one record (returns `null` if no recor
|
|
|
581
431
|
const result = await db
|
|
582
432
|
.from(users)
|
|
583
433
|
.list()
|
|
584
|
-
.
|
|
434
|
+
.where(eq(users.email, "user@example.com"))
|
|
585
435
|
.maybeSingle()
|
|
586
436
|
.execute();
|
|
587
437
|
|
|
@@ -618,17 +468,6 @@ const result = await db
|
|
|
618
468
|
.top(10)
|
|
619
469
|
.skip(0)
|
|
620
470
|
.execute();
|
|
621
|
-
|
|
622
|
-
// Using legacy API
|
|
623
|
-
const result = await db
|
|
624
|
-
.from("users")
|
|
625
|
-
.list()
|
|
626
|
-
.select("username", "email", "age")
|
|
627
|
-
.filter({ age: { gt: 18 } })
|
|
628
|
-
.orderBy("username")
|
|
629
|
-
.top(10)
|
|
630
|
-
.skip(0)
|
|
631
|
-
.execute();
|
|
632
471
|
```
|
|
633
472
|
|
|
634
473
|
## CRUD Operations
|
|
@@ -712,7 +551,7 @@ const result = await db
|
|
|
712
551
|
const result = await db
|
|
713
552
|
.from("users")
|
|
714
553
|
.update({ active: false })
|
|
715
|
-
.where((q) => q.
|
|
554
|
+
.where((q) => q.where(eq(users.active, true)).top(10))
|
|
716
555
|
.execute();
|
|
717
556
|
```
|
|
718
557
|
|
|
@@ -1798,7 +1637,7 @@ const queryString = db
|
|
|
1798
1637
|
.from("users")
|
|
1799
1638
|
.list()
|
|
1800
1639
|
.select("username", "email")
|
|
1801
|
-
.
|
|
1640
|
+
.where(eq(users.active, true))
|
|
1802
1641
|
.orderBy("username")
|
|
1803
1642
|
.top(10)
|
|
1804
1643
|
.getQueryString();
|
|
@@ -6,7 +6,7 @@ import { Column } from '../../orm/column.js';
|
|
|
6
6
|
* @param fields - Field names or Column references
|
|
7
7
|
* @returns Object with selectedFields array
|
|
8
8
|
*/
|
|
9
|
-
export declare function processSelectFields(...fields: (string | Column<any, string>)[]): {
|
|
9
|
+
export declare function processSelectFields(...fields: (string | Column<any, any, string>)[]): {
|
|
10
10
|
selectedFields: string[];
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
@@ -18,7 +18,7 @@ export declare function processSelectFields(...fields: (string | Column<any, str
|
|
|
18
18
|
* @param tableName - Expected table name for validation
|
|
19
19
|
* @returns Object with selectedFields array and fieldMapping for renamed fields
|
|
20
20
|
*/
|
|
21
|
-
export declare function processSelectWithRenames<TTableName extends string>(fields: Record<string, Column<any, TTableName>>, tableName: string): {
|
|
21
|
+
export declare function processSelectWithRenames<TTableName extends string>(fields: Record<string, Column<any, any, TTableName>>, tableName: string): {
|
|
22
22
|
selectedFields: string[];
|
|
23
23
|
fieldMapping: Record<string, string>;
|
|
24
24
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select-mixin.js","sources":["../../../../src/client/builders/select-mixin.ts"],"sourcesContent":["import { isColumn, type Column } from \"../../orm/column\";\n\n/**\n * Utility function for processing select() calls.\n * Used by both QueryBuilder and RecordBuilder to eliminate duplication.\n *\n * @param fields - Field names or Column references\n * @returns Object with selectedFields array\n */\nexport function processSelectFields(\n ...fields: (string | Column<any, string>)[]\n): { selectedFields: string[] } {\n const fieldNames = fields.map((field) => {\n if (isColumn(field)) {\n return field.fieldName as string;\n }\n return String(field);\n });\n return { selectedFields: [...new Set(fieldNames)] };\n}\n\n/**\n * Processes select() calls with field renaming support.\n * Validates columns belong to the correct table and builds field mapping for renamed fields.\n * Used by both QueryBuilder and RecordBuilder to eliminate duplication.\n *\n * @param fields - Object mapping output keys to column references\n * @param tableName - Expected table name for validation\n * @returns Object with selectedFields array and fieldMapping for renamed fields\n */\nexport function processSelectWithRenames<TTableName extends string>(\n fields: Record<string, Column<any, TTableName>>,\n tableName: string,\n): { selectedFields: string[]; fieldMapping: Record<string, string> } {\n const selectedFields: string[] = [];\n const fieldMapping: Record<string, string> = {};\n\n for (const [outputKey, column] of Object.entries(fields)) {\n if (!isColumn(column)) {\n throw new Error(\n `select() expects column references, but got: ${typeof column}`,\n );\n }\n\n // Warn (not throw) on table mismatch for consistency\n if (column.tableName !== tableName) {\n console.warn(\n `Column ${column.toString()} is from table \"${column.tableName}\", but query is for table \"${tableName}\"`,\n );\n }\n\n const fieldName = column.fieldName;\n selectedFields.push(fieldName);\n\n // Build mapping from field name to output key (only if renamed)\n if (fieldName !== outputKey) {\n fieldMapping[fieldName] = outputKey;\n }\n }\n\n return {\n selectedFields,\n fieldMapping: Object.keys(fieldMapping).length > 0 ? fieldMapping : {},\n };\n}\n\n/**\n * Legacy class name for backward compatibility.\n * @deprecated Use processSelectFields function instead\n */\nexport class SelectMixin {\n static processSelect = processSelectFields;\n}\n
|
|
1
|
+
{"version":3,"file":"select-mixin.js","sources":["../../../../src/client/builders/select-mixin.ts"],"sourcesContent":["import { isColumn, type Column } from \"../../orm/column\";\n\n/**\n * Utility function for processing select() calls.\n * Used by both QueryBuilder and RecordBuilder to eliminate duplication.\n *\n * @param fields - Field names or Column references\n * @returns Object with selectedFields array\n */\nexport function processSelectFields(\n ...fields: (string | Column<any, any, string>)[]\n): { selectedFields: string[] } {\n const fieldNames = fields.map((field) => {\n if (isColumn(field)) {\n return field.fieldName as string;\n }\n return String(field);\n });\n return { selectedFields: [...new Set(fieldNames)] };\n}\n\n/**\n * Processes select() calls with field renaming support.\n * Validates columns belong to the correct table and builds field mapping for renamed fields.\n * Used by both QueryBuilder and RecordBuilder to eliminate duplication.\n *\n * @param fields - Object mapping output keys to column references\n * @param tableName - Expected table name for validation\n * @returns Object with selectedFields array and fieldMapping for renamed fields\n */\nexport function processSelectWithRenames<TTableName extends string>(\n fields: Record<string, Column<any, any, TTableName>>,\n tableName: string,\n): { selectedFields: string[]; fieldMapping: Record<string, string> } {\n const selectedFields: string[] = [];\n const fieldMapping: Record<string, string> = {};\n\n for (const [outputKey, column] of Object.entries(fields)) {\n if (!isColumn(column)) {\n throw new Error(\n `select() expects column references, but got: ${typeof column}`,\n );\n }\n\n // Warn (not throw) on table mismatch for consistency\n if (column.tableName !== tableName) {\n console.warn(\n `Column ${column.toString()} is from table \"${column.tableName}\", but query is for table \"${tableName}\"`,\n );\n }\n\n const fieldName = column.fieldName;\n selectedFields.push(fieldName);\n\n // Build mapping from field name to output key (only if renamed)\n if (fieldName !== outputKey) {\n fieldMapping[fieldName] = outputKey;\n }\n }\n\n return {\n selectedFields,\n fieldMapping: Object.keys(fieldMapping).length > 0 ? fieldMapping : {},\n };\n}\n\n/**\n * Legacy class name for backward compatibility.\n * @deprecated Use processSelectFields function instead\n */\nexport class SelectMixin {\n static processSelect = processSelectFields;\n}\n"],"names":[],"mappings":";AA8BgB,SAAA,yBACd,QACA,WACoE;AACpE,QAAM,iBAA2B,CAAC;AAClC,QAAM,eAAuC,CAAC;AAE9C,aAAW,CAAC,WAAW,MAAM,KAAK,OAAO,QAAQ,MAAM,GAAG;AACpD,QAAA,CAAC,SAAS,MAAM,GAAG;AACrB,YAAM,IAAI;AAAA,QACR,gDAAgD,OAAO,MAAM;AAAA,MAC/D;AAAA,IAAA;AAIE,QAAA,OAAO,cAAc,WAAW;AAC1B,cAAA;AAAA,QACN,UAAU,OAAO,UAAU,mBAAmB,OAAO,SAAS,8BAA8B,SAAS;AAAA,MACvG;AAAA,IAAA;AAGF,UAAM,YAAY,OAAO;AACzB,mBAAe,KAAK,SAAS;AAG7B,QAAI,cAAc,WAAW;AAC3B,mBAAa,SAAS,IAAI;AAAA,IAAA;AAAA,EAC5B;AAGK,SAAA;AAAA,IACL;AAAA,IACA,cAAc,OAAO,KAAK,YAAY,EAAE,SAAS,IAAI,eAAe,CAAA;AAAA,EACtE;AACF;"}
|
|
@@ -5,16 +5,7 @@ import { InsertBuilder } from './insert-builder.js';
|
|
|
5
5
|
import { DeleteBuilder } from './delete-builder.js';
|
|
6
6
|
import { UpdateBuilder } from './update-builder.js';
|
|
7
7
|
import { Database } from './database.js';
|
|
8
|
-
import { FMTable, InferSchemaOutputFromFMTable, InsertDataFromFMTable, UpdateDataFromFMTable, ValidExpandTarget
|
|
9
|
-
import { Column } from '../orm/column.js';
|
|
10
|
-
import { FieldBuilder } from '../orm/field-builders.js';
|
|
11
|
-
/**
|
|
12
|
-
* Helper type to extract properly-typed columns from an FMTable.
|
|
13
|
-
* This preserves the specific column types instead of widening to `any`.
|
|
14
|
-
*/
|
|
15
|
-
type ExtractColumnsFromOcc<T> = T extends FMTable<infer TFields, infer TName, any> ? TFields extends Record<string, FieldBuilder<any, any, any, any>> ? {
|
|
16
|
-
[K in keyof TFields]: Column<InferFieldOutput<TFields[K]>, TName>;
|
|
17
|
-
} : never : never;
|
|
8
|
+
import { FMTable, InferSchemaOutputFromFMTable, InsertDataFromFMTable, UpdateDataFromFMTable, ValidExpandTarget } from '../orm/table.js';
|
|
18
9
|
export declare class EntitySet<Occ extends FMTable<any, any>> {
|
|
19
10
|
private occurrence;
|
|
20
11
|
private databaseName;
|
|
@@ -37,7 +28,7 @@ export declare class EntitySet<Occ extends FMTable<any, any>> {
|
|
|
37
28
|
context: ExecutionContext;
|
|
38
29
|
database: Database;
|
|
39
30
|
}): EntitySet<Occ>;
|
|
40
|
-
list(): QueryBuilder<Occ, keyof InferSchemaOutputFromFMTable<Occ>, false, false, {}
|
|
31
|
+
list(): QueryBuilder<Occ, keyof InferSchemaOutputFromFMTable<Occ>, false, false, {}>;
|
|
41
32
|
get(id: string | number): RecordBuilder<Occ, false, keyof InferSchemaOutputFromFMTable<Occ>, keyof InferSchemaOutputFromFMTable<Occ>, {}>;
|
|
42
33
|
insert(data: InsertDataFromFMTable<Occ>, options: {
|
|
43
34
|
returnFullRecord: false;
|
|
@@ -54,4 +45,3 @@ export declare class EntitySet<Occ extends FMTable<any, any>> {
|
|
|
54
45
|
delete(): DeleteBuilder<Occ>;
|
|
55
46
|
navigate<TargetTable extends FMTable<any, any>>(targetTable: ValidExpandTarget<Occ, TargetTable>): EntitySet<TargetTable extends FMTable<any, any> ? TargetTable : never>;
|
|
56
47
|
}
|
|
57
|
-
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-set.js","sources":["../../../src/client/entity-set.ts"],"sourcesContent":["import type { ExecutionContext, InferSchemaType } from \"../types\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { QueryBuilder } from \"./query/index\";\nimport { RecordBuilder } from \"./record-builder\";\nimport { InsertBuilder } from \"./insert-builder\";\nimport { DeleteBuilder } from \"./delete-builder\";\nimport { UpdateBuilder } from \"./update-builder\";\nimport { Database } from \"./database\";\nimport type {\n FMTable,\n InferSchemaOutputFromFMTable,\n InferInputSchemaFromFMTable,\n InsertDataFromFMTable,\n UpdateDataFromFMTable,\n ValidExpandTarget,\n ExtractTableName,\n FMTableWithColumns,\n InferFieldOutput,\n} from \"../orm/table\";\nimport {\n FMTable as FMTableClass,\n getDefaultSelect,\n getNavigationPaths,\n getTableName,\n getTableColumns,\n getTableFields,\n} from \"../orm/table\";\nimport type { Column } from \"../orm/column\";\nimport type { FieldBuilder } from \"../orm/field-builders\";\n\n// Helper type to extract defaultSelect from an FMTable\n// Since TypeScript can't extract Symbol-indexed properties at the type level,\n// we simplify to return keyof InferSchemaFromFMTable<O> when O is an FMTable.\n// The actual defaultSelect logic is handled at runtime.\ntype ExtractDefaultSelect<O> =\n O extends FMTable<any, any> ? keyof InferSchemaOutputFromFMTable<O> : never;\n\n/**\n * Helper type to extract properly-typed columns from an FMTable.\n * This preserves the specific column types instead of widening to `any`.\n */\ntype ExtractColumnsFromOcc<T> =\n T extends FMTable<infer TFields, infer TName, any>\n ? TFields extends Record<string, FieldBuilder<any, any, any, any>>\n ? { [K in keyof TFields]: Column<InferFieldOutput<TFields[K]>, TName> }\n : never\n : never;\n\nexport class EntitySet<Occ extends FMTable<any, any>> {\n private occurrence: Occ;\n private databaseName: string;\n private context: ExecutionContext;\n private database: Database; // Database instance for accessing occurrences\n private isNavigateFromEntitySet?: boolean;\n private navigateRelation?: string;\n private navigateSourceTableName?: string;\n private navigateBasePath?: string; // Full base path for chained navigations\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n database?: any;\n }) {\n this.occurrence = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.database = config.database;\n // Get useEntityIds from database if available, otherwise default to false\n this.databaseUseEntityIds =\n (config.database as any)?._useEntityIds ?? false;\n }\n\n // Type-only method to help TypeScript infer the schema from table\n static create<Occ extends FMTable<any, any>>(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n database: Database;\n }): EntitySet<Occ> {\n return new EntitySet<Occ>({\n occurrence: config.occurrence,\n databaseName: config.databaseName,\n context: config.context,\n database: config.database,\n });\n }\n\n list() {\n const builder = new QueryBuilder<Occ>({\n occurrence: this.occurrence as Occ,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Apply defaultSelect if occurrence exists and select hasn't been called\n if (this.occurrence) {\n // FMTable - access via helper functions\n const defaultSelectValue = getDefaultSelect(this.occurrence);\n const tableSchema = (this.occurrence as any)[FMTableClass.Symbol.Schema];\n let schema: Record<string, StandardSchemaV1> | undefined;\n\n if (tableSchema) {\n // Extract schema from StandardSchemaV1\n const zodSchema = tableSchema[\"~standard\"]?.schema;\n if (\n zodSchema &&\n typeof zodSchema === \"object\" &&\n \"shape\" in zodSchema\n ) {\n schema = zodSchema.shape as Record<string, StandardSchemaV1>;\n }\n }\n\n if (defaultSelectValue === \"schema\") {\n // Use getTableColumns to get all columns and select them\n // This is equivalent to select(getTableColumns(occurrence))\n // Use ExtractColumnsFromOcc to preserve the properly-typed column types\n const allColumns = getTableColumns(\n this.occurrence as any,\n ) as ExtractColumnsFromOcc<Occ>;\n return builder.select(allColumns).top(1000);\n } else if (typeof defaultSelectValue === \"object\") {\n // defaultSelectValue is a select object (Record<string, Column>)\n // Use it directly with select()\n // Use ExtractColumnsFromOcc to preserve the properly-typed column types\n return builder\n .select(defaultSelectValue as ExtractColumnsFromOcc<Occ>)\n .top(1000);\n }\n // If defaultSelect is \"all\", no changes needed (current behavior)\n }\n\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (builder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n // recordId is intentionally not set (undefined) to indicate navigation from EntitySet\n };\n }\n\n // Apply default pagination limit of 1000 records to prevent stack overflow\n // with large datasets. Users can override with .top() if needed.\n return builder.top(1000);\n }\n\n get(\n id: string | number,\n ): RecordBuilder<\n Occ,\n false,\n keyof InferSchemaOutputFromFMTable<Occ>,\n keyof InferSchemaOutputFromFMTable<Occ>,\n {}\n > {\n const builder = new RecordBuilder<Occ>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n recordId: id,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Apply defaultSelect if occurrence exists\n if (this.occurrence) {\n // FMTable - access via helper functions\n const defaultSelectValue = getDefaultSelect(this.occurrence);\n const tableSchema = (this.occurrence as any)[FMTableClass.Symbol.Schema];\n let schema: Record<string, StandardSchemaV1> | undefined;\n\n if (tableSchema) {\n // Extract schema from StandardSchemaV1\n const zodSchema = tableSchema[\"~standard\"]?.schema;\n if (\n zodSchema &&\n typeof zodSchema === \"object\" &&\n \"shape\" in zodSchema\n ) {\n schema = zodSchema.shape as Record<string, StandardSchemaV1>;\n }\n }\n\n if (defaultSelectValue === \"schema\") {\n // Use getTableColumns to get all columns and select them\n // This is equivalent to select(getTableColumns(occurrence))\n // Use ExtractColumnsFromOcc to preserve the properly-typed column types\n const allColumns = getTableColumns(\n this.occurrence as any,\n ) as ExtractColumnsFromOcc<Occ>;\n const selectedBuilder = builder.select(allColumns);\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (selectedBuilder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n };\n }\n return selectedBuilder as any;\n } else if (\n typeof defaultSelectValue === \"object\" &&\n defaultSelectValue !== null &&\n !Array.isArray(defaultSelectValue)\n ) {\n // defaultSelectValue is a select object (Record<string, Column>)\n // Use it directly with select()\n // Use ExtractColumnsFromOcc to preserve the properly-typed column types\n const selectedBuilder = builder.select(\n defaultSelectValue as ExtractColumnsFromOcc<Occ>,\n );\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (selectedBuilder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n };\n }\n return selectedBuilder as any;\n }\n // If defaultSelect is \"all\", no changes needed (current behavior)\n }\n\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (builder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n };\n }\n return builder as any;\n }\n\n // Overload: when returnFullRecord is false\n insert(\n data: InsertDataFromFMTable<Occ>,\n options: { returnFullRecord: false },\n ): InsertBuilder<Occ, \"minimal\">;\n\n // Overload: when returnFullRecord is true or omitted (default)\n insert(\n data: InsertDataFromFMTable<Occ>,\n options?: { returnFullRecord?: true },\n ): InsertBuilder<Occ, \"representation\">;\n\n // Implementation\n insert(\n data: InsertDataFromFMTable<Occ>,\n options?: { returnFullRecord?: boolean },\n ): InsertBuilder<Occ, \"minimal\" | \"representation\"> {\n const returnPreference =\n options?.returnFullRecord === false ? \"minimal\" : \"representation\";\n\n return new InsertBuilder<Occ, typeof returnPreference>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n data: data as any, // Input type is validated/transformed at runtime\n returnPreference: returnPreference as any,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n // Overload: when returnFullRecord is explicitly true\n update(\n data: UpdateDataFromFMTable<Occ>,\n options: { returnFullRecord: true },\n ): UpdateBuilder<Occ, \"representation\">;\n\n // Overload: when returnFullRecord is false or omitted (default)\n update(\n data: UpdateDataFromFMTable<Occ>,\n options?: { returnFullRecord?: false },\n ): UpdateBuilder<Occ, \"minimal\">;\n\n // Implementation\n update(\n data: UpdateDataFromFMTable<Occ>,\n options?: { returnFullRecord?: boolean },\n ): UpdateBuilder<Occ, \"minimal\" | \"representation\"> {\n const returnPreference =\n options?.returnFullRecord === true ? \"representation\" : \"minimal\";\n\n return new UpdateBuilder<Occ, typeof returnPreference>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n data: data as any, // Input type is validated/transformed at runtime\n returnPreference: returnPreference as any,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n delete(): DeleteBuilder<Occ> {\n return new DeleteBuilder<Occ>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n }) as any;\n }\n\n // Implementation\n navigate<TargetTable extends FMTable<any, any>>(\n targetTable: ValidExpandTarget<Occ, TargetTable>,\n ): EntitySet<TargetTable extends FMTable<any, any> ? TargetTable : never> {\n // Check if it's an FMTable object or a string\n let relationName: string;\n\n // FMTable object - extract name and validate\n relationName = getTableName(targetTable);\n\n // Runtime validation: Check if relation name is in navigationPaths\n if (\n this.occurrence &&\n FMTableClass.Symbol.NavigationPaths in this.occurrence\n ) {\n const navigationPaths = (this.occurrence as any)[\n FMTableClass.Symbol.NavigationPaths\n ] as readonly string[];\n if (navigationPaths && !navigationPaths.includes(relationName)) {\n console.warn(\n `Cannot navigate to \"${relationName}\". Valid navigation paths: ${navigationPaths.length > 0 ? navigationPaths.join(\", \") : \"none\"}`,\n );\n }\n }\n\n // Create EntitySet with target table\n const entitySet = new EntitySet<any>({\n occurrence: targetTable,\n databaseName: this.databaseName,\n context: this.context,\n database: this.database,\n });\n // Store the navigation info in the EntitySet\n (entitySet as any).isNavigateFromEntitySet = true;\n (entitySet as any).navigateRelation = relationName;\n\n // Build the full base path for chained navigations\n if (this.isNavigateFromEntitySet && this.navigateBasePath) {\n // Already have a base path from previous navigation - extend it with current relation\n (entitySet as any).navigateBasePath =\n `${this.navigateBasePath}/${this.navigateRelation}`;\n (entitySet as any).navigateSourceTableName = this.navigateSourceTableName;\n } else if (this.isNavigateFromEntitySet && this.navigateRelation) {\n // First chained navigation - create base path from source/relation\n (entitySet as any).navigateBasePath =\n `${this.navigateSourceTableName}/${this.navigateRelation}`;\n (entitySet as any).navigateSourceTableName = this.navigateSourceTableName;\n } else {\n // Initial navigation - source is just the table name\n (entitySet as any).navigateSourceTableName = getTableName(\n this.occurrence,\n );\n }\n return entitySet;\n }\n}\n"],"names":["FMTableClass"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"entity-set.js","sources":["../../../src/client/entity-set.ts"],"sourcesContent":["import type { ExecutionContext, InferSchemaType } from \"../types\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { QueryBuilder } from \"./query/index\";\nimport { RecordBuilder } from \"./record-builder\";\nimport { InsertBuilder } from \"./insert-builder\";\nimport { DeleteBuilder } from \"./delete-builder\";\nimport { UpdateBuilder } from \"./update-builder\";\nimport { Database } from \"./database\";\nimport type {\n FMTable,\n InferSchemaOutputFromFMTable,\n InferInputSchemaFromFMTable,\n InsertDataFromFMTable,\n UpdateDataFromFMTable,\n ValidExpandTarget,\n ExtractTableName,\n FMTableWithColumns,\n InferFieldOutput,\n ColumnMap,\n} from \"../orm/table\";\nimport {\n FMTable as FMTableClass,\n getDefaultSelect,\n getNavigationPaths,\n getTableName,\n getTableColumns,\n getTableFields,\n} from \"../orm/table\";\nimport type { Column } from \"../orm/column\";\nimport type { FieldBuilder } from \"../orm/field-builders\";\n\n// Helper type to extract defaultSelect from an FMTable\n// Since TypeScript can't extract Symbol-indexed properties at the type level,\n// we simplify to return keyof InferSchemaFromFMTable<O> when O is an FMTable.\n// The actual defaultSelect logic is handled at runtime.\ntype ExtractDefaultSelect<O> =\n O extends FMTable<any, any> ? keyof InferSchemaOutputFromFMTable<O> : never;\n\n/**\n * Helper type to extract properly-typed columns from an FMTable.\n * This preserves the specific column types instead of widening to `any`.\n */\ntype ExtractColumnsFromOcc<T> =\n T extends FMTable<infer TFields, infer TName, any>\n ? TFields extends Record<string, FieldBuilder<any, any, any, any>>\n ? ColumnMap<TFields, TName>\n : never\n : never;\n\nexport class EntitySet<Occ extends FMTable<any, any>> {\n private occurrence: Occ;\n private databaseName: string;\n private context: ExecutionContext;\n private database: Database; // Database instance for accessing occurrences\n private isNavigateFromEntitySet?: boolean;\n private navigateRelation?: string;\n private navigateSourceTableName?: string;\n private navigateBasePath?: string; // Full base path for chained navigations\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n database?: any;\n }) {\n this.occurrence = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.database = config.database;\n // Get useEntityIds from database if available, otherwise default to false\n this.databaseUseEntityIds =\n (config.database as any)?._useEntityIds ?? false;\n }\n\n // Type-only method to help TypeScript infer the schema from table\n static create<Occ extends FMTable<any, any>>(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n database: Database;\n }): EntitySet<Occ> {\n return new EntitySet<Occ>({\n occurrence: config.occurrence,\n databaseName: config.databaseName,\n context: config.context,\n database: config.database,\n });\n }\n\n list(): QueryBuilder<\n Occ,\n keyof InferSchemaOutputFromFMTable<Occ>,\n false,\n false,\n {}\n > {\n const builder = new QueryBuilder<Occ>({\n occurrence: this.occurrence as Occ,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Apply defaultSelect if occurrence exists and select hasn't been called\n if (this.occurrence) {\n // FMTable - access via helper functions\n const defaultSelectValue = getDefaultSelect(this.occurrence);\n const tableSchema = (this.occurrence as any)[FMTableClass.Symbol.Schema];\n let schema: Record<string, StandardSchemaV1> | undefined;\n\n if (tableSchema) {\n // Extract schema from StandardSchemaV1\n const zodSchema = tableSchema[\"~standard\"]?.schema;\n if (\n zodSchema &&\n typeof zodSchema === \"object\" &&\n \"shape\" in zodSchema\n ) {\n schema = zodSchema.shape as Record<string, StandardSchemaV1>;\n }\n }\n\n if (defaultSelectValue === \"schema\") {\n // Use getTableColumns to get all columns and select them\n // This is equivalent to select(getTableColumns(occurrence))\n // Cast to the declared return type - runtime behavior handles the actual selection\n const allColumns = getTableColumns(\n this.occurrence as any,\n ) as ExtractColumnsFromOcc<Occ>;\n return builder.select(allColumns).top(1000) as QueryBuilder<\n Occ,\n keyof InferSchemaOutputFromFMTable<Occ>,\n false,\n false,\n {}\n >;\n } else if (typeof defaultSelectValue === \"object\") {\n // defaultSelectValue is a select object (Record<string, Column>)\n // Cast to the declared return type - runtime behavior handles the actual selection\n return builder\n .select(defaultSelectValue as ExtractColumnsFromOcc<Occ>)\n .top(1000) as QueryBuilder<\n Occ,\n keyof InferSchemaOutputFromFMTable<Occ>,\n false,\n false,\n {}\n >;\n }\n // If defaultSelect is \"all\", no changes needed (current behavior)\n }\n\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (builder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n // recordId is intentionally not set (undefined) to indicate navigation from EntitySet\n };\n }\n\n // Apply default pagination limit of 1000 records to prevent stack overflow\n // with large datasets. Users can override with .top() if needed.\n return builder.top(1000);\n }\n\n get(\n id: string | number,\n ): RecordBuilder<\n Occ,\n false,\n keyof InferSchemaOutputFromFMTable<Occ>,\n keyof InferSchemaOutputFromFMTable<Occ>,\n {}\n > {\n const builder = new RecordBuilder<Occ>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n recordId: id,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Apply defaultSelect if occurrence exists\n if (this.occurrence) {\n // FMTable - access via helper functions\n const defaultSelectValue = getDefaultSelect(this.occurrence);\n const tableSchema = (this.occurrence as any)[FMTableClass.Symbol.Schema];\n let schema: Record<string, StandardSchemaV1> | undefined;\n\n if (tableSchema) {\n // Extract schema from StandardSchemaV1\n const zodSchema = tableSchema[\"~standard\"]?.schema;\n if (\n zodSchema &&\n typeof zodSchema === \"object\" &&\n \"shape\" in zodSchema\n ) {\n schema = zodSchema.shape as Record<string, StandardSchemaV1>;\n }\n }\n\n if (defaultSelectValue === \"schema\") {\n // Use getTableColumns to get all columns and select them\n // This is equivalent to select(getTableColumns(occurrence))\n // Use ExtractColumnsFromOcc to preserve the properly-typed column types\n const allColumns = getTableColumns(\n this.occurrence as any,\n ) as ExtractColumnsFromOcc<Occ>;\n const selectedBuilder = builder.select(allColumns);\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (selectedBuilder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n };\n }\n return selectedBuilder as any;\n } else if (\n typeof defaultSelectValue === \"object\" &&\n defaultSelectValue !== null &&\n !Array.isArray(defaultSelectValue)\n ) {\n // defaultSelectValue is a select object (Record<string, Column>)\n // Use it directly with select()\n // Use ExtractColumnsFromOcc to preserve the properly-typed column types\n const selectedBuilder = builder.select(\n defaultSelectValue as ExtractColumnsFromOcc<Occ>,\n );\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (selectedBuilder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n };\n }\n return selectedBuilder as any;\n }\n // If defaultSelect is \"all\", no changes needed (current behavior)\n }\n\n // Propagate navigation context if present\n if (\n this.isNavigateFromEntitySet &&\n this.navigateRelation &&\n this.navigateSourceTableName\n ) {\n (builder as any).navigation = {\n relation: this.navigateRelation,\n sourceTableName: this.navigateSourceTableName,\n basePath: this.navigateBasePath,\n };\n }\n return builder as any;\n }\n\n // Overload: when returnFullRecord is false\n insert(\n data: InsertDataFromFMTable<Occ>,\n options: { returnFullRecord: false },\n ): InsertBuilder<Occ, \"minimal\">;\n\n // Overload: when returnFullRecord is true or omitted (default)\n insert(\n data: InsertDataFromFMTable<Occ>,\n options?: { returnFullRecord?: true },\n ): InsertBuilder<Occ, \"representation\">;\n\n // Implementation\n insert(\n data: InsertDataFromFMTable<Occ>,\n options?: { returnFullRecord?: boolean },\n ): InsertBuilder<Occ, \"minimal\" | \"representation\"> {\n const returnPreference =\n options?.returnFullRecord === false ? \"minimal\" : \"representation\";\n\n return new InsertBuilder<Occ, typeof returnPreference>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n data: data as any, // Input type is validated/transformed at runtime\n returnPreference: returnPreference as any,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n // Overload: when returnFullRecord is explicitly true\n update(\n data: UpdateDataFromFMTable<Occ>,\n options: { returnFullRecord: true },\n ): UpdateBuilder<Occ, \"representation\">;\n\n // Overload: when returnFullRecord is false or omitted (default)\n update(\n data: UpdateDataFromFMTable<Occ>,\n options?: { returnFullRecord?: false },\n ): UpdateBuilder<Occ, \"minimal\">;\n\n // Implementation\n update(\n data: UpdateDataFromFMTable<Occ>,\n options?: { returnFullRecord?: boolean },\n ): UpdateBuilder<Occ, \"minimal\" | \"representation\"> {\n const returnPreference =\n options?.returnFullRecord === true ? \"representation\" : \"minimal\";\n\n return new UpdateBuilder<Occ, typeof returnPreference>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n data: data as any, // Input type is validated/transformed at runtime\n returnPreference: returnPreference as any,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n delete(): DeleteBuilder<Occ> {\n return new DeleteBuilder<Occ>({\n occurrence: this.occurrence,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n }) as any;\n }\n\n // Implementation\n navigate<TargetTable extends FMTable<any, any>>(\n targetTable: ValidExpandTarget<Occ, TargetTable>,\n ): EntitySet<TargetTable extends FMTable<any, any> ? TargetTable : never> {\n // Check if it's an FMTable object or a string\n let relationName: string;\n\n // FMTable object - extract name and validate\n relationName = getTableName(targetTable);\n\n // Runtime validation: Check if relation name is in navigationPaths\n if (\n this.occurrence &&\n FMTableClass.Symbol.NavigationPaths in this.occurrence\n ) {\n const navigationPaths = (this.occurrence as any)[\n FMTableClass.Symbol.NavigationPaths\n ] as readonly string[];\n if (navigationPaths && !navigationPaths.includes(relationName)) {\n console.warn(\n `Cannot navigate to \"${relationName}\". Valid navigation paths: ${navigationPaths.length > 0 ? navigationPaths.join(\", \") : \"none\"}`,\n );\n }\n }\n\n // Create EntitySet with target table\n const entitySet = new EntitySet<any>({\n occurrence: targetTable,\n databaseName: this.databaseName,\n context: this.context,\n database: this.database,\n });\n // Store the navigation info in the EntitySet\n (entitySet as any).isNavigateFromEntitySet = true;\n (entitySet as any).navigateRelation = relationName;\n\n // Build the full base path for chained navigations\n if (this.isNavigateFromEntitySet && this.navigateBasePath) {\n // Already have a base path from previous navigation - extend it with current relation\n (entitySet as any).navigateBasePath =\n `${this.navigateBasePath}/${this.navigateRelation}`;\n (entitySet as any).navigateSourceTableName = this.navigateSourceTableName;\n } else if (this.isNavigateFromEntitySet && this.navigateRelation) {\n // First chained navigation - create base path from source/relation\n (entitySet as any).navigateBasePath =\n `${this.navigateSourceTableName}/${this.navigateRelation}`;\n (entitySet as any).navigateSourceTableName = this.navigateSourceTableName;\n } else {\n // Initial navigation - source is just the table name\n (entitySet as any).navigateSourceTableName = getTableName(\n this.occurrence,\n );\n }\n return entitySet;\n }\n}\n"],"names":["FMTableClass"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-parser.js","sources":["../../../src/client/error-parser.ts"],"sourcesContent":["import {\n HTTPError,\n ODataError,\n SchemaLockedError,\n FMODataErrorType,\n} from \"../errors\";\nimport { safeJsonParse } from \"./sanitize-json\";\n\n/**\n * Parses an error response and returns an appropriate error object.\n * This helper is used by builder processResponse methods to handle error responses\n * consistently, particularly important for batch operations where errors need to be\n * properly parsed from the response body.\n *\n * @param response - The Response object (may be from batch or direct request)\n * @param url - The URL that was requested (for error context)\n * @returns An appropriate error object (ODataError, SchemaLockedError, or HTTPError)\n */\nexport async function parseErrorResponse(\n response: Response,\n url: string,\n): Promise<FMODataErrorType> {\n // Try to parse error body if it's JSON\n let errorBody: { error?: { code?: string | number; message?: string } } | undefined;\n \n try {\n if (response.headers.get(\"content-type\")?.includes(\"application/json\")) {\n errorBody = await safeJsonParse<typeof errorBody>(response);\n }\n } catch {\n // Ignore JSON parse errors - we'll fall back to HTTPError\n }\n\n // Check if it's an OData error response\n if (errorBody?.error) {\n const errorCode = errorBody.error.code;\n const errorMessage = errorBody.error.message || response.statusText;\n\n // Check for schema locked error (code 303)\n if (errorCode === \"303\" || errorCode === 303) {\n return new SchemaLockedError(url, errorMessage, errorBody.error);\n }\n\n return new ODataError(\n url,\n errorMessage,\n String(errorCode),\n errorBody.error,\n );\n }\n\n // Fall back to generic HTTPError\n return new HTTPError(url, response.status, response.statusText, errorBody);\n}\n\n\n\n\n\n"],"names":[],"mappings":";;AAkBsB,eAAA,mBACpB,UACA,KAC2B;;AAEvB,MAAA;AAEA,MAAA;AACF,SAAI,cAAS,QAAQ,IAAI,cAAc,MAAnC,mBAAsC,SAAS,qBAAqB;AAC1D,kBAAA,MAAM,cAAgC,QAAQ;AAAA,IAAA;AAAA,EAC5D,QACM;AAAA,EAAA;AAKR,MAAI,uCAAW,OAAO;AACd,UAAA,YAAY,UAAU,MAAM;AAClC,UAAM,eAAe,UAAU,MAAM,WAAW,SAAS;AAGrD,QAAA,cAAc,SAAS,cAAc,KAAK;AAC5C,aAAO,IAAI,kBAAkB,KAAK,cAAc,UAAU,KAAK;AAAA,IAAA;AAGjE,WAAO,IAAI;AAAA,MACT;AAAA,MACA;AAAA,MACA,OAAO,SAAS;AAAA,MAChB,UAAU;AAAA,IACZ;AAAA,EAAA;AAIF,SAAO,IAAI,UAAU,KAAK,SAAS,QAAQ,SAAS,YAAY,SAAS;AAC3E;"}
|
|
1
|
+
{"version":3,"file":"error-parser.js","sources":["../../../src/client/error-parser.ts"],"sourcesContent":["import {\n HTTPError,\n ODataError,\n SchemaLockedError,\n FMODataErrorType,\n} from \"../errors\";\nimport { safeJsonParse } from \"./sanitize-json\";\n\n/**\n * Parses an error response and returns an appropriate error object.\n * This helper is used by builder processResponse methods to handle error responses\n * consistently, particularly important for batch operations where errors need to be\n * properly parsed from the response body.\n *\n * @param response - The Response object (may be from batch or direct request)\n * @param url - The URL that was requested (for error context)\n * @returns An appropriate error object (ODataError, SchemaLockedError, or HTTPError)\n */\nexport async function parseErrorResponse(\n response: Response,\n url: string,\n): Promise<FMODataErrorType> {\n // Try to parse error body if it's JSON\n let errorBody: { error?: { code?: string | number; message?: string } } | undefined;\n \n try {\n if (response.headers.get(\"content-type\")?.includes(\"application/json\")) {\n errorBody = await safeJsonParse<typeof errorBody>(response);\n }\n } catch {\n // Ignore JSON parse errors - we'll fall back to HTTPError\n }\n\n // Check if it's an OData error response\n if (errorBody?.error) {\n const errorCode = errorBody.error.code;\n const errorMessage = errorBody.error.message || response.statusText;\n\n // Check for schema locked error (code 303)\n if (errorCode === \"303\" || errorCode === 303) {\n return new SchemaLockedError(url, errorMessage, errorBody.error);\n }\n\n return new ODataError(\n url,\n errorMessage,\n String(errorCode),\n errorBody.error,\n );\n }\n\n // Fall back to generic HTTPError\n return new HTTPError(url, response.status, response.statusText, errorBody);\n}\n\n\n\n\n\n\n\n\n"],"names":[],"mappings":";;AAkBsB,eAAA,mBACpB,UACA,KAC2B;;AAEvB,MAAA;AAEA,MAAA;AACF,SAAI,cAAS,QAAQ,IAAI,cAAc,MAAnC,mBAAsC,SAAS,qBAAqB;AAC1D,kBAAA,MAAM,cAAgC,QAAQ;AAAA,IAAA;AAAA,EAC5D,QACM;AAAA,EAAA;AAKR,MAAI,uCAAW,OAAO;AACd,UAAA,YAAY,UAAU,MAAM;AAClC,UAAM,eAAe,UAAU,MAAM,WAAW,SAAS;AAGrD,QAAA,cAAc,SAAS,cAAc,KAAK;AAC5C,aAAO,IAAI,kBAAkB,KAAK,cAAc,UAAU,KAAK;AAAA,IAAA;AAGjE,WAAO,IAAI;AAAA,MACT;AAAA,MACA;AAAA,MACA,OAAO,SAAS;AAAA,MAChB,UAAU;AAAA,IACZ;AAAA,EAAA;AAIF,SAAO,IAAI,UAAU,KAAK,SAAS,QAAQ,SAAS,YAAY,SAAS;AAC3E;"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { ExecutionContext, ExecutableBuilder, Result, ExecuteOptions, ConditionallyWithODataAnnotations,
|
|
2
|
-
import { Filter } from '../../filter-types.js';
|
|
1
|
+
import { ExecutionContext, ExecutableBuilder, Result, ExecuteOptions, ConditionallyWithODataAnnotations, ExecuteMethodOptions } from '../../types.js';
|
|
3
2
|
import { Column } from '../../orm/column.js';
|
|
4
3
|
import { FilterExpression, OrderByExpression } from '../../orm/operators.js';
|
|
5
4
|
import { FMTable, InferSchemaOutputFromFMTable, ValidExpandTarget, ExtractTableName } from '../../orm/table.js';
|
|
@@ -7,7 +6,7 @@ import { ExpandedRelations } from '../builders/index.js';
|
|
|
7
6
|
import { TypeSafeOrderBy, QueryReturnType } from './types.js';
|
|
8
7
|
export type { QueryReturnType };
|
|
9
8
|
export type { TypeSafeOrderBy, ExpandedRelations };
|
|
10
|
-
export declare class QueryBuilder<Occ extends FMTable<any, any>, Selected extends keyof InferSchemaOutputFromFMTable<Occ> | Record<string, Column<any, ExtractTableName<Occ>>> = keyof InferSchemaOutputFromFMTable<Occ>, SingleMode extends "exact" | "maybe" | false = false, IsCount extends boolean = false, Expands extends ExpandedRelations = {}> implements ExecutableBuilder<QueryReturnType<InferSchemaOutputFromFMTable<Occ>, Selected, SingleMode, IsCount, Expands>> {
|
|
9
|
+
export declare class QueryBuilder<Occ extends FMTable<any, any>, Selected extends keyof InferSchemaOutputFromFMTable<Occ> | Record<string, Column<any, any, ExtractTableName<Occ>>> = keyof InferSchemaOutputFromFMTable<Occ>, SingleMode extends "exact" | "maybe" | false = false, IsCount extends boolean = false, Expands extends ExpandedRelations = {}> implements ExecutableBuilder<QueryReturnType<InferSchemaOutputFromFMTable<Occ>, Selected, SingleMode, IsCount, Expands>> {
|
|
11
10
|
private queryOptions;
|
|
12
11
|
private expandConfigs;
|
|
13
12
|
private singleMode;
|
|
@@ -58,24 +57,18 @@ export declare class QueryBuilder<Occ extends FMTable<any, any>, Selected extend
|
|
|
58
57
|
* @param fields - Object mapping output keys to column references (container fields excluded)
|
|
59
58
|
* @returns QueryBuilder with updated selected fields
|
|
60
59
|
*/
|
|
61
|
-
select<TSelect extends Record<string, Column<any, ExtractTableName<Occ>, false>>>(fields: TSelect): QueryBuilder<Occ, TSelect, SingleMode, IsCount, Expands>;
|
|
62
|
-
/**
|
|
63
|
-
* Transforms our filter format to odata-query's expected format
|
|
64
|
-
* - Arrays of operators are converted to AND conditions
|
|
65
|
-
* - Single operator objects pass through as-is
|
|
66
|
-
* - Shorthand values are handled by odata-query
|
|
67
|
-
*/
|
|
68
|
-
private transformFilter;
|
|
69
|
-
filter(filter: Filter<ExtractSchemaFromOccurrence<Occ>>): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands>;
|
|
60
|
+
select<TSelect extends Record<string, Column<any, any, ExtractTableName<Occ>, false>>>(fields: TSelect): QueryBuilder<Occ, TSelect, SingleMode, IsCount, Expands>;
|
|
70
61
|
/**
|
|
71
62
|
* Filter results using operator expressions (new ORM-style API).
|
|
72
63
|
* Supports eq, gt, lt, and, or, etc. operators with Column references.
|
|
64
|
+
* Also supports raw OData filter strings as an escape hatch.
|
|
73
65
|
*
|
|
74
66
|
* @example
|
|
75
67
|
* .where(eq(users.hobby, "reading"))
|
|
76
68
|
* .where(and(eq(users.active, true), gt(users.age, 18)))
|
|
69
|
+
* .where("status eq 'active'") // Raw OData string escape hatch
|
|
77
70
|
*/
|
|
78
|
-
where(expression: FilterExpression): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands>;
|
|
71
|
+
where(expression: FilterExpression | string): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands>;
|
|
79
72
|
/**
|
|
80
73
|
* Specify the sort order for query results.
|
|
81
74
|
*
|
|
@@ -101,10 +94,10 @@ export declare class QueryBuilder<Occ extends FMTable<any, any>, Selected extend
|
|
|
101
94
|
* ```
|
|
102
95
|
*/
|
|
103
96
|
orderBy(...orderByArgs: [
|
|
104
|
-
TypeSafeOrderBy<InferSchemaOutputFromFMTable<Occ>> | Column<any, ExtractTableName<Occ>> | OrderByExpression<ExtractTableName<Occ>>
|
|
97
|
+
TypeSafeOrderBy<InferSchemaOutputFromFMTable<Occ>> | Column<any, any, ExtractTableName<Occ>> | OrderByExpression<ExtractTableName<Occ>>
|
|
105
98
|
] | [
|
|
106
|
-
Column<any, ExtractTableName<Occ>>,
|
|
107
|
-
...Array<Column<any, ExtractTableName<Occ>> | OrderByExpression<ExtractTableName<Occ>>>
|
|
99
|
+
Column<any, any, ExtractTableName<Occ>>,
|
|
100
|
+
...Array<Column<any, any, ExtractTableName<Occ>> | OrderByExpression<ExtractTableName<Occ>>>
|
|
108
101
|
]): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands>;
|
|
109
102
|
top(count: number): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands>;
|
|
110
103
|
skip(count: number): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands>;
|
|
@@ -3,7 +3,7 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
|
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
import buildQuery from "odata-query";
|
|
5
5
|
import { RecordCountMismatchError } from "../../errors.js";
|
|
6
|
-
import {
|
|
6
|
+
import { transformOrderByField } from "../../transform.js";
|
|
7
7
|
import { safeJsonParse } from "../sanitize-json.js";
|
|
8
8
|
import { parseErrorResponse } from "../error-parser.js";
|
|
9
9
|
import { isColumn } from "../../orm/column.js";
|
|
@@ -120,90 +120,21 @@ class QueryBuilder {
|
|
|
120
120
|
fieldMapping: Object.keys(fieldMapping).length > 0 ? fieldMapping : void 0
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
|
-
/**
|
|
124
|
-
* Transforms our filter format to odata-query's expected format
|
|
125
|
-
* - Arrays of operators are converted to AND conditions
|
|
126
|
-
* - Single operator objects pass through as-is
|
|
127
|
-
* - Shorthand values are handled by odata-query
|
|
128
|
-
*/
|
|
129
|
-
transformFilter(filter) {
|
|
130
|
-
if (typeof filter === "string") {
|
|
131
|
-
return filter;
|
|
132
|
-
}
|
|
133
|
-
if (Array.isArray(filter)) {
|
|
134
|
-
return filter.map((f) => this.transformFilter(f));
|
|
135
|
-
}
|
|
136
|
-
if ("and" in filter || "or" in filter || "not" in filter) {
|
|
137
|
-
const result2 = {};
|
|
138
|
-
if ("and" in filter && Array.isArray(filter.and)) {
|
|
139
|
-
result2.and = filter.and.map((f) => this.transformFilter(f));
|
|
140
|
-
}
|
|
141
|
-
if ("or" in filter && Array.isArray(filter.or)) {
|
|
142
|
-
result2.or = filter.or.map((f) => this.transformFilter(f));
|
|
143
|
-
}
|
|
144
|
-
if ("not" in filter && filter.not) {
|
|
145
|
-
result2.not = this.transformFilter(filter.not);
|
|
146
|
-
}
|
|
147
|
-
return result2;
|
|
148
|
-
}
|
|
149
|
-
const result = {};
|
|
150
|
-
const andConditions = [];
|
|
151
|
-
for (const [field, value] of Object.entries(filter)) {
|
|
152
|
-
const shouldTransform = this.occurrence && this.databaseUseEntityIds;
|
|
153
|
-
const fieldId = shouldTransform ? transformFieldName(field, this.occurrence) : field;
|
|
154
|
-
if (Array.isArray(value)) {
|
|
155
|
-
if (value.length === 1) {
|
|
156
|
-
result[fieldId] = value[0];
|
|
157
|
-
} else {
|
|
158
|
-
for (const op of value) {
|
|
159
|
-
andConditions.push({ [fieldId]: op });
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
} else if (value && typeof value === "object" && !(value instanceof Date) && !Array.isArray(value)) {
|
|
163
|
-
const operatorKeys = [
|
|
164
|
-
"eq",
|
|
165
|
-
"ne",
|
|
166
|
-
"gt",
|
|
167
|
-
"ge",
|
|
168
|
-
"lt",
|
|
169
|
-
"le",
|
|
170
|
-
"contains",
|
|
171
|
-
"startswith",
|
|
172
|
-
"endswith",
|
|
173
|
-
"in"
|
|
174
|
-
];
|
|
175
|
-
const isOperatorObject = operatorKeys.some((key) => key in value);
|
|
176
|
-
if (isOperatorObject) {
|
|
177
|
-
result[fieldId] = value;
|
|
178
|
-
} else {
|
|
179
|
-
result[fieldId] = value;
|
|
180
|
-
}
|
|
181
|
-
} else {
|
|
182
|
-
result[fieldId] = value;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
if (andConditions.length > 0) {
|
|
186
|
-
if (Object.keys(result).length > 0) {
|
|
187
|
-
return { and: [...andConditions, result] };
|
|
188
|
-
} else {
|
|
189
|
-
return { and: andConditions };
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return result;
|
|
193
|
-
}
|
|
194
|
-
filter(filter) {
|
|
195
|
-
this.queryOptions.filter = this.transformFilter(filter);
|
|
196
|
-
return this;
|
|
197
|
-
}
|
|
198
123
|
/**
|
|
199
124
|
* Filter results using operator expressions (new ORM-style API).
|
|
200
125
|
* Supports eq, gt, lt, and, or, etc. operators with Column references.
|
|
126
|
+
* Also supports raw OData filter strings as an escape hatch.
|
|
201
127
|
*
|
|
202
128
|
* @example
|
|
203
129
|
* .where(eq(users.hobby, "reading"))
|
|
204
130
|
* .where(and(eq(users.active, true), gt(users.age, 18)))
|
|
131
|
+
* .where("status eq 'active'") // Raw OData string escape hatch
|
|
205
132
|
*/
|
|
206
133
|
where(expression) {
|
|
134
|
+
if (typeof expression === "string") {
|
|
135
|
+
this.queryOptions.filter = expression;
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
207
138
|
const filterString = expression.toODataFilter(this.databaseUseEntityIds);
|
|
208
139
|
this.queryOptions.filter = filterString;
|
|
209
140
|
return this;
|