@proofkit/fmodata 0.1.0-alpha.10 → 0.1.0-alpha.12
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 +32 -22
- package/dist/esm/client/base-table.d.ts +8 -5
- package/dist/esm/client/base-table.js.map +1 -1
- package/dist/esm/client/batch-builder.d.ts +1 -1
- package/dist/esm/client/batch-builder.js +2 -2
- package/dist/esm/client/batch-builder.js.map +1 -1
- package/dist/esm/client/build-occurrences.d.ts +74 -0
- package/dist/esm/client/build-occurrences.js +31 -0
- package/dist/esm/client/build-occurrences.js.map +1 -0
- package/dist/esm/client/delete-builder.d.ts +1 -1
- package/dist/esm/client/delete-builder.js +3 -2
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +3 -3
- package/dist/esm/client/entity-set.js +38 -1
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/filemaker-odata.js +25 -9
- package/dist/esm/client/filemaker-odata.js.map +1 -1
- package/dist/esm/client/insert-builder.d.ts +1 -5
- package/dist/esm/client/insert-builder.js +7 -23
- package/dist/esm/client/insert-builder.js.map +1 -1
- package/dist/esm/client/query-builder.d.ts +3 -7
- package/dist/esm/client/query-builder.js +69 -68
- package/dist/esm/client/query-builder.js.map +1 -1
- package/dist/esm/client/record-builder.d.ts +2 -7
- package/dist/esm/client/record-builder.js +38 -25
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/client/response-processor.d.ts +0 -5
- package/dist/esm/client/sanitize-json.d.ts +35 -0
- package/dist/esm/client/sanitize-json.js +27 -0
- package/dist/esm/client/sanitize-json.js.map +1 -0
- package/dist/esm/client/table-occurrence.d.ts +26 -12
- package/dist/esm/client/table-occurrence.js +14 -30
- package/dist/esm/client/table-occurrence.js.map +1 -1
- package/dist/esm/client/update-builder.d.ts +1 -1
- package/dist/esm/client/update-builder.js +3 -2
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/errors.d.ts +11 -1
- package/dist/esm/errors.js +15 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +6 -3
- package/dist/esm/index.js +7 -6
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types.d.ts +8 -1
- package/dist/esm/types.js +7 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/validation.js +3 -4
- package/dist/esm/validation.js.map +1 -1
- package/package.json +3 -2
- package/src/client/base-table.ts +8 -5
- package/src/client/batch-builder.ts +2 -2
- package/src/client/build-occurrences.ts +155 -0
- package/src/client/delete-builder.ts +3 -2
- package/src/client/entity-set.ts +60 -7
- package/src/client/filemaker-odata.ts +39 -10
- package/src/client/insert-builder.ts +8 -33
- package/src/client/query-builder.ts +82 -90
- package/src/client/record-builder.ts +51 -42
- package/src/client/response-processor.ts +0 -13
- package/src/client/sanitize-json.ts +66 -0
- package/src/client/table-occurrence.ts +50 -69
- package/src/client/update-builder.ts +3 -2
- package/src/errors.ts +24 -1
- package/src/index.ts +10 -8
- package/src/types.ts +13 -1
- package/src/validation.ts +4 -5
package/README.md
CHANGED
|
@@ -127,13 +127,9 @@ const connection = new FMServerConnection({
|
|
|
127
127
|
|
|
128
128
|
### Schema Definitions
|
|
129
129
|
|
|
130
|
-
This library relies on a schema-first approach for good type-safety and optional runtime validation. These are abstracted into BaseTable and TableOccurrence
|
|
130
|
+
This library relies on a schema-first approach for good type-safety and optional runtime validation. These are abstracted into BaseTable and TableOccurrence types to match FileMaker concepts.
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
- **`defineBaseTable()`** and **`defineTableOccurrence()`** - Recommended for better type inference, especially when using entity IDs (FMFID/FMTID). These functions provide improved TypeScript type inference for field names in queries.
|
|
135
|
-
|
|
136
|
-
- **`new BaseTable()`** and **`new TableOccurrence()`** - Still supported for backward compatibility, but may have slightly less precise type inference in some cases.
|
|
132
|
+
Use **`defineBaseTable()`** and **`defineTableOccurrence()`** to create your schemas. These functions provide full TypeScript type inference for field names in queries.
|
|
137
133
|
|
|
138
134
|
A `BaseTable` defines the schema for your FileMaker table using Standard Schema. These examples show zod, but you can use any other validation library that supports Standard Schema.
|
|
139
135
|
|
|
@@ -155,9 +151,7 @@ const contactsBase = defineBaseTable({
|
|
|
155
151
|
});
|
|
156
152
|
```
|
|
157
153
|
|
|
158
|
-
A `TableOccurrence` is the actual entry point for the OData service on the FileMaker server. It
|
|
159
|
-
|
|
160
|
-
**Recommended:** Use `defineTableOccurrence()` for better type inference. You can also use `new TableOccurrence()` directly.
|
|
154
|
+
A `TableOccurrence` is the actual entry point for the OData service on the FileMaker server. It allows you to reference the same base table multiple times with different names.
|
|
161
155
|
|
|
162
156
|
```typescript
|
|
163
157
|
import { defineTableOccurrence } from "@proofkit/fmodata";
|
|
@@ -640,9 +634,15 @@ const result = await db
|
|
|
640
634
|
|
|
641
635
|
### Defining Navigation
|
|
642
636
|
|
|
643
|
-
|
|
637
|
+
Use `buildOccurrences()` to define relationships between tables. This function takes an array of table occurrences and a configuration object that specifies navigation relationships using type-safe string references:
|
|
644
638
|
|
|
645
639
|
```typescript
|
|
640
|
+
import {
|
|
641
|
+
defineBaseTable,
|
|
642
|
+
defineTableOccurrence,
|
|
643
|
+
buildOccurrences,
|
|
644
|
+
} from "@proofkit/fmodata";
|
|
645
|
+
|
|
646
646
|
const contactsBase = defineBaseTable({
|
|
647
647
|
schema: {
|
|
648
648
|
id: z.string(),
|
|
@@ -661,8 +661,7 @@ const usersBase = defineBaseTable({
|
|
|
661
661
|
idField: "id",
|
|
662
662
|
});
|
|
663
663
|
|
|
664
|
-
//
|
|
665
|
-
// Create base occurrences first, then add navigation
|
|
664
|
+
// Step 1: Define base table occurrences (without navigation)
|
|
666
665
|
const _contactsTO = defineTableOccurrence({
|
|
667
666
|
name: "contacts",
|
|
668
667
|
baseTable: contactsBase,
|
|
@@ -673,21 +672,32 @@ const _usersTO = defineTableOccurrence({
|
|
|
673
672
|
baseTable: usersBase,
|
|
674
673
|
});
|
|
675
674
|
|
|
676
|
-
//
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
675
|
+
// Step 2: Build occurrences with navigation using string references
|
|
676
|
+
// The strings autocomplete to valid table occurrence names!
|
|
677
|
+
const occurrences = buildOccurrences({
|
|
678
|
+
occurrences: [_contactsTO, _usersTO],
|
|
679
|
+
navigation: {
|
|
680
|
+
contacts: ["users"],
|
|
681
|
+
users: ["contacts"],
|
|
682
|
+
},
|
|
683
683
|
});
|
|
684
684
|
|
|
685
|
-
//
|
|
686
|
-
const
|
|
687
|
-
|
|
685
|
+
// Use with your database
|
|
686
|
+
const db = connection.database("MyDB", {
|
|
687
|
+
occurrences: occurrences,
|
|
688
688
|
});
|
|
689
689
|
```
|
|
690
690
|
|
|
691
|
+
The `buildOccurrences` function accepts an object with:
|
|
692
|
+
|
|
693
|
+
- `occurrences` - Array of TableOccurrences to build
|
|
694
|
+
- `navigation` - Optional object mapping TO names to arrays of navigation targets
|
|
695
|
+
|
|
696
|
+
It returns a tuple in the same order as the input array, with full autocomplete for navigation target names. Self-navigation is prevented at the type level.
|
|
697
|
+
|
|
698
|
+
- Handles circular references automatically
|
|
699
|
+
- Returns fully typed `TableOccurrence` instances with resolved navigation
|
|
700
|
+
|
|
691
701
|
### Navigating Between Tables
|
|
692
702
|
|
|
693
703
|
Navigate to related records:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
2
|
/**
|
|
3
3
|
* BaseTable defines the schema and configuration for a table.
|
|
4
|
+
* Use `defineBaseTable()` to create instances with proper type inference.
|
|
4
5
|
*
|
|
5
6
|
* @template Schema - Record of field names to StandardSchemaV1 validators
|
|
6
7
|
* @template IdField - The name of the primary key field (optional, automatically read-only)
|
|
@@ -10,8 +11,9 @@ import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
|
10
11
|
* @example Basic table with auto-inferred required fields
|
|
11
12
|
* ```ts
|
|
12
13
|
* import { z } from "zod";
|
|
14
|
+
* import { defineBaseTable } from "@proofkit/fmodata";
|
|
13
15
|
*
|
|
14
|
-
* const usersTable =
|
|
16
|
+
* const usersTable = defineBaseTable({
|
|
15
17
|
* schema: {
|
|
16
18
|
* id: z.string(), // Auto-required (not nullable), auto-readOnly (idField)
|
|
17
19
|
* name: z.string(), // Auto-required (not nullable)
|
|
@@ -26,8 +28,9 @@ import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
|
26
28
|
* @example Table with additional required and readOnly fields
|
|
27
29
|
* ```ts
|
|
28
30
|
* import { z } from "zod";
|
|
31
|
+
* import { defineBaseTable } from "@proofkit/fmodata";
|
|
29
32
|
*
|
|
30
|
-
* const usersTable =
|
|
33
|
+
* const usersTable = defineBaseTable({
|
|
31
34
|
* schema: {
|
|
32
35
|
* id: z.string(), // Auto-required, auto-readOnly (idField)
|
|
33
36
|
* createdAt: z.string(), // Read-only system field
|
|
@@ -46,8 +49,9 @@ import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
|
46
49
|
* @example Table with multiple read-only fields
|
|
47
50
|
* ```ts
|
|
48
51
|
* import { z } from "zod";
|
|
52
|
+
* import { defineBaseTable } from "@proofkit/fmodata";
|
|
49
53
|
*
|
|
50
|
-
* const usersTable =
|
|
54
|
+
* const usersTable = defineBaseTable({
|
|
51
55
|
* schema: {
|
|
52
56
|
* id: z.string(),
|
|
53
57
|
* createdAt: z.string(),
|
|
@@ -94,8 +98,7 @@ export declare class BaseTable<Schema extends Record<string, StandardSchemaV1> =
|
|
|
94
98
|
/**
|
|
95
99
|
* Creates a BaseTable with proper TypeScript type inference.
|
|
96
100
|
*
|
|
97
|
-
*
|
|
98
|
-
* field names are properly typed throughout the library.
|
|
101
|
+
* Use this function to create BaseTable instances with full type safety.
|
|
99
102
|
*
|
|
100
103
|
* @example Without entity IDs
|
|
101
104
|
* ```ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-table.js","sources":["../../../src/client/base-table.ts"],"sourcesContent":["import { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * BaseTable defines the schema and configuration for a table.\n *\n * @template Schema - Record of field names to StandardSchemaV1 validators\n * @template IdField - The name of the primary key field (optional, automatically read-only)\n * @template Required - Additional field names to require on insert (beyond auto-inferred required fields)\n * @template ReadOnly - Field names that cannot be modified via insert/update (idField is automatically read-only)\n *\n * @example Basic table with auto-inferred required fields\n * ```ts\n * import { z } from \"zod\";\n *\n * const usersTable =
|
|
1
|
+
{"version":3,"file":"base-table.js","sources":["../../../src/client/base-table.ts"],"sourcesContent":["import { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * BaseTable defines the schema and configuration for a table.\n * Use `defineBaseTable()` to create instances with proper type inference.\n *\n * @template Schema - Record of field names to StandardSchemaV1 validators\n * @template IdField - The name of the primary key field (optional, automatically read-only)\n * @template Required - Additional field names to require on insert (beyond auto-inferred required fields)\n * @template ReadOnly - Field names that cannot be modified via insert/update (idField is automatically read-only)\n *\n * @example Basic table with auto-inferred required fields\n * ```ts\n * import { z } from \"zod\";\n * import { defineBaseTable } from \"@proofkit/fmodata\";\n *\n * const usersTable = defineBaseTable({\n * schema: {\n * id: z.string(), // Auto-required (not nullable), auto-readOnly (idField)\n * name: z.string(), // Auto-required (not nullable)\n * email: z.string().nullable(), // Optional (nullable)\n * },\n * idField: \"id\",\n * });\n * // On insert: name is required, email is optional (id is excluded - readOnly)\n * // On update: name and email available (id is excluded - readOnly)\n * ```\n *\n * @example Table with additional required and readOnly fields\n * ```ts\n * import { z } from \"zod\";\n * import { defineBaseTable } from \"@proofkit/fmodata\";\n *\n * const usersTable = defineBaseTable({\n * schema: {\n * id: z.string(), // Auto-required, auto-readOnly (idField)\n * createdAt: z.string(), // Read-only system field\n * name: z.string(), // Auto-required\n * email: z.string().nullable(), // Optional by default...\n * legacyField: z.string().nullable(), // Optional by default...\n * },\n * idField: \"id\",\n * required: [\"legacyField\"], // Make legacyField required for new inserts\n * readOnly: [\"createdAt\"], // Exclude from insert/update\n * });\n * // On insert: name and legacyField required; email optional (id and createdAt excluded)\n * // On update: all fields optional (id and createdAt excluded)\n * ```\n *\n * @example Table with multiple read-only fields\n * ```ts\n * import { z } from \"zod\";\n * import { defineBaseTable } from \"@proofkit/fmodata\";\n *\n * const usersTable = defineBaseTable({\n * schema: {\n * id: z.string(),\n * createdAt: z.string(),\n * modifiedAt: z.string(),\n * createdBy: z.string(),\n * notes: z.string().nullable(),\n * },\n * idField: \"id\",\n * readOnly: [\"createdAt\", \"modifiedAt\", \"createdBy\"],\n * });\n * // On insert/update: only notes is available (id and system fields excluded)\n * ```\n */\nexport class BaseTable<\n Schema extends Record<string, StandardSchemaV1> = any,\n IdField extends keyof Schema | undefined = undefined,\n Required extends readonly (keyof Schema | (string & {}))[] = readonly [],\n ReadOnly extends readonly (keyof Schema | (string & {}))[] = readonly [],\n> {\n public readonly schema: Schema;\n public readonly idField?: IdField;\n public readonly required?: Required;\n public readonly readOnly?: ReadOnly;\n public readonly fmfIds?: Record<\n keyof Schema | (string & {}),\n `FMFID:${string}`\n >;\n\n constructor(config: {\n schema: Schema;\n idField?: IdField;\n required?: Required;\n readOnly?: ReadOnly;\n fmfIds?: Record<string, `FMFID:${string}`>;\n }) {\n this.schema = config.schema;\n this.idField = config.idField;\n this.required = config.required;\n this.readOnly = config.readOnly;\n this.fmfIds = config.fmfIds as\n | Record<keyof Schema, `FMFID:${string}`>\n | undefined;\n }\n\n /**\n * Returns the FileMaker field ID (FMFID) for a given field name, or the field name itself if not using IDs.\n * @param fieldName - The field name to get the ID for\n * @returns The FMFID string or the original field name\n */\n getFieldId(fieldName: keyof Schema): string {\n if (this.fmfIds && fieldName in this.fmfIds) {\n return this.fmfIds[fieldName];\n }\n return String(fieldName);\n }\n\n /**\n * Returns the field name for a given FileMaker field ID (FMFID), or the ID itself if not found.\n * @param fieldId - The FMFID to get the field name for\n * @returns The field name or the original ID\n */\n getFieldName(fieldId: string): string {\n if (this.fmfIds) {\n // Search for the field name that corresponds to this FMFID\n for (const [fieldName, fmfId] of Object.entries(this.fmfIds)) {\n if (fmfId === fieldId) {\n return fieldName;\n }\n }\n }\n return fieldId;\n }\n\n /**\n * Returns true if this BaseTable is using FileMaker field IDs.\n */\n isUsingFieldIds(): boolean {\n return this.fmfIds !== undefined;\n }\n}\n\n/**\n * Creates a BaseTable with proper TypeScript type inference.\n *\n * Use this function to create BaseTable instances with full type safety.\n *\n * @example Without entity IDs\n * ```ts\n * const users = defineBaseTable({\n * schema: { id: z.string(), name: z.string() },\n * idField: \"id\",\n * });\n * ```\n *\n * @example With entity IDs (FileMaker field IDs)\n * ```ts\n * const products = defineBaseTable({\n * schema: { id: z.string(), name: z.string() },\n * idField: \"id\",\n * fmfIds: { id: \"FMFID:1\", name: \"FMFID:2\" },\n * });\n * ```\n */\nexport function defineBaseTable<\n const Schema extends Record<string, StandardSchemaV1>,\n IdField extends keyof Schema | undefined = undefined,\n const Required extends readonly (\n | keyof Schema\n | (string & {})\n )[] = readonly [],\n const ReadOnly extends readonly (\n | keyof Schema\n | (string & {})\n )[] = readonly [],\n>(config: {\n schema: Schema;\n idField?: IdField;\n required?: Required;\n readOnly?: ReadOnly;\n fmfIds?: { [K in keyof Schema | (string & {})]: `FMFID:${string}` };\n}): BaseTable<Schema, IdField, Required, ReadOnly> {\n return new BaseTable(config);\n}\n"],"names":[],"mappings":";;;AAoEO,MAAM,UAKX;AAAA,EAUA,YAAY,QAMT;AAfa;AACA;AACA;AACA;AACA;AAYd,SAAK,SAAS,OAAO;AACrB,SAAK,UAAU,OAAO;AACtB,SAAK,WAAW,OAAO;AACvB,SAAK,WAAW,OAAO;AACvB,SAAK,SAAS,OAAO;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUvB,WAAW,WAAiC;AAC1C,QAAI,KAAK,UAAU,aAAa,KAAK,QAAQ;AACpC,aAAA,KAAK,OAAO,SAAS;AAAA,IAAA;AAE9B,WAAO,OAAO,SAAS;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQzB,aAAa,SAAyB;AACpC,QAAI,KAAK,QAAQ;AAEJ,iBAAA,CAAC,WAAW,KAAK,KAAK,OAAO,QAAQ,KAAK,MAAM,GAAG;AAC5D,YAAI,UAAU,SAAS;AACd,iBAAA;AAAA,QAAA;AAAA,MACT;AAAA,IACF;AAEK,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMT,kBAA2B;AACzB,WAAO,KAAK,WAAW;AAAA,EAAA;AAE3B;AAwBO,SAAS,gBAWd,QAMiD;AAC1C,SAAA,IAAI,UAAU,MAAM;AAC7B;"}
|
|
@@ -41,7 +41,7 @@ export declare class BatchBuilder<Builders extends readonly ExecutableBuilder<an
|
|
|
41
41
|
url: string;
|
|
42
42
|
body?: any;
|
|
43
43
|
};
|
|
44
|
-
toRequest(baseUrl: string): Request;
|
|
44
|
+
toRequest(baseUrl: string, options?: ExecuteOptions): Request;
|
|
45
45
|
processResponse(response: Response, options?: ExecuteOptions): Promise<Result<any>>;
|
|
46
46
|
/**
|
|
47
47
|
* Execute the batch operation.
|
|
@@ -61,7 +61,7 @@ class BatchBuilder {
|
|
|
61
61
|
// Body is constructed in execute()
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
|
-
toRequest(baseUrl) {
|
|
64
|
+
toRequest(baseUrl, options) {
|
|
65
65
|
const fullUrl = `${baseUrl}/${this.databaseName}/$batch`;
|
|
66
66
|
return new Request(fullUrl, {
|
|
67
67
|
method: "POST",
|
|
@@ -102,7 +102,7 @@ class BatchBuilder {
|
|
|
102
102
|
}
|
|
103
103
|
try {
|
|
104
104
|
const requests = this.builders.map(
|
|
105
|
-
(builder) => builder.toRequest(baseUrl)
|
|
105
|
+
(builder) => builder.toRequest(baseUrl, options)
|
|
106
106
|
);
|
|
107
107
|
const { body, boundary } = await formatBatchRequestFromNative(
|
|
108
108
|
requests,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batch-builder.js","sources":["../../../src/client/batch-builder.ts"],"sourcesContent":["import type {\n ExecutableBuilder,\n ExecutionContext,\n Result,\n ExecuteOptions,\n} from \"../types\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport {\n formatBatchRequestFromNative,\n parseBatchResponse,\n type ParsedBatchResponse,\n} from \"./batch-request\";\n\n/**\n * Helper type to extract result types from a tuple of ExecutableBuilders.\n * Uses a mapped type which TypeScript 4.1+ can handle for tuples.\n */\ntype ExtractTupleTypes<T extends readonly ExecutableBuilder<any>[]> = {\n [K in keyof T]: T[K] extends ExecutableBuilder<infer U> ? U : never;\n};\n\n/**\n * Converts a ParsedBatchResponse to a native Response object\n * @param parsed - The parsed batch response\n * @returns A native Response object\n */\nfunction parsedToResponse(parsed: ParsedBatchResponse): Response {\n const headers = new Headers(parsed.headers);\n\n // Handle null body\n if (parsed.body === null || parsed.body === undefined) {\n return new Response(null, {\n status: parsed.status,\n statusText: parsed.statusText,\n headers,\n });\n }\n\n // Convert body to string if it's not already\n const bodyString =\n typeof parsed.body === \"string\" ? parsed.body : JSON.stringify(parsed.body);\n\n // Handle 204 No Content status - it cannot have a body per HTTP spec\n // If FileMaker returns 204 with a body, treat it as 200\n let status = parsed.status;\n if (status === 204 && bodyString && bodyString.trim() !== \"\") {\n status = 200;\n }\n\n return new Response(status === 204 ? null : bodyString, {\n status: status,\n statusText: parsed.statusText,\n headers,\n });\n}\n\n/**\n * Builder for batch operations that allows multiple queries to be executed together\n * in a single transactional request.\n */\nexport class BatchBuilder<Builders extends readonly ExecutableBuilder<any>[]>\n implements ExecutableBuilder<ExtractTupleTypes<Builders>>\n{\n private builders: ExecutableBuilder<any>[];\n private readonly originalBuilders: Builders;\n\n constructor(\n builders: Builders,\n private readonly databaseName: string,\n private readonly context: ExecutionContext,\n ) {\n // Convert readonly tuple to mutable array for dynamic additions\n this.builders = [...builders];\n // Store original tuple for type preservation\n this.originalBuilders = builders;\n }\n\n /**\n * Add a request to the batch dynamically.\n * This allows building up batch operations programmatically.\n *\n * @param builder - An executable builder to add to the batch\n * @returns This BatchBuilder for method chaining\n * @example\n * ```ts\n * const batch = db.batch([]);\n * batch.addRequest(db.from('contacts').list());\n * batch.addRequest(db.from('users').list());\n * const result = await batch.execute();\n * ```\n */\n addRequest<T>(builder: ExecutableBuilder<T>): this {\n this.builders.push(builder);\n return this;\n }\n\n /**\n * Get the request configuration for this batch operation.\n * This is used internally by the execution system.\n */\n getRequestConfig(): { method: string; url: string; body?: any } {\n // Note: This method is kept for compatibility but batch operations\n // should use execute() directly which handles the full Request/Response flow\n return {\n method: \"POST\",\n url: `/${this.databaseName}/$batch`,\n body: undefined, // Body is constructed in execute()\n };\n }\n\n toRequest(baseUrl: string): Request {\n // Batch operations are not designed to be nested, but we provide\n // a basic implementation for interface compliance\n const fullUrl = `${baseUrl}/${this.databaseName}/$batch`;\n return new Request(fullUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"multipart/mixed\",\n \"OData-Version\": \"4.0\",\n },\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<Result<any>> {\n // This should not typically be called for batch operations\n // as they handle their own response processing\n return {\n data: undefined,\n error: {\n name: \"NotImplementedError\",\n message: \"Batch operations handle response processing internally\",\n timestamp: new Date(),\n } as any,\n };\n }\n\n /**\n * Execute the batch operation.\n *\n * @param options - Optional fetch options and batch-specific options (includes beforeRequest hook)\n * @returns A tuple of results matching the input builders\n */\n async execute<EO extends ExecuteOptions>(\n options?: RequestInit & FFetchOptions & EO,\n ): Promise<Result<ExtractTupleTypes<Builders>>> {\n const baseUrl = this.context._getBaseUrl?.();\n if (!baseUrl) {\n return {\n data: undefined,\n error: {\n name: \"ConfigurationError\",\n message:\n \"Base URL not available - execution context must implement _getBaseUrl()\",\n timestamp: new Date(),\n } as any,\n };\n }\n\n try {\n // Convert builders to native Request objects\n const requests: Request[] = this.builders.map((builder) =>\n builder.toRequest(baseUrl),\n );\n\n // Format batch request (automatically groups mutations into changesets)\n const { body, boundary } = await formatBatchRequestFromNative(\n requests,\n baseUrl,\n );\n\n // Execute the batch request\n const response = await this.context._makeRequest<string>(\n `/${this.databaseName}/$batch`,\n {\n ...options,\n method: \"POST\",\n headers: {\n ...options?.headers,\n \"Content-Type\": `multipart/mixed; boundary=${boundary}`,\n \"OData-Version\": \"4.0\",\n },\n body,\n },\n );\n\n if (response.error) {\n return { data: undefined, error: response.error };\n }\n\n // Extract the actual boundary from the response\n // FileMaker uses its own boundary, not the one we sent\n const firstLine =\n response.data.split(\"\\r\\n\")[0] || response.data.split(\"\\n\")[0] || \"\";\n const actualBoundary = firstLine.startsWith(\"--\")\n ? firstLine.substring(2)\n : boundary;\n\n // Parse the multipart response\n const contentTypeHeader = `multipart/mixed; boundary=${actualBoundary}`;\n const parsedResponses = parseBatchResponse(\n response.data,\n contentTypeHeader,\n );\n\n // Check if we got the expected number of responses\n if (parsedResponses.length !== this.builders.length) {\n return {\n data: undefined,\n error: {\n name: \"BatchError\",\n message: `Expected ${this.builders.length} responses but got ${parsedResponses.length}`,\n timestamp: new Date(),\n } as any,\n };\n }\n\n // Process each response using the corresponding builder\n // Build tuple by processing each builder in order\n type ResultTuple = ExtractTupleTypes<Builders>;\n\n // Process builders sequentially to preserve tuple order\n const processedResults: any[] = [];\n for (let i = 0; i < this.originalBuilders.length; i++) {\n const builder = this.originalBuilders[i];\n const parsed = parsedResponses[i];\n\n if (!builder || !parsed) {\n processedResults.push(undefined);\n continue;\n }\n\n // Convert parsed response to native Response\n const nativeResponse = parsedToResponse(parsed);\n\n // Let the builder process its own response\n const result = await builder.processResponse(nativeResponse, options);\n\n if (result.error) {\n processedResults.push(undefined);\n } else {\n processedResults.push(result.data);\n }\n }\n\n // Use a type assertion that TypeScript will respect\n // ExtractTupleTypes ensures this is a proper tuple type\n return {\n data: processedResults as unknown as ResultTuple,\n error: undefined,\n };\n } catch (err) {\n return {\n data: undefined,\n error: {\n name: \"BatchError\",\n message: err instanceof Error ? err.message : \"Unknown error\",\n timestamp: new Date(),\n } as any,\n };\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"batch-builder.js","sources":["../../../src/client/batch-builder.ts"],"sourcesContent":["import type {\n ExecutableBuilder,\n ExecutionContext,\n Result,\n ExecuteOptions,\n} from \"../types\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport {\n formatBatchRequestFromNative,\n parseBatchResponse,\n type ParsedBatchResponse,\n} from \"./batch-request\";\n\n/**\n * Helper type to extract result types from a tuple of ExecutableBuilders.\n * Uses a mapped type which TypeScript 4.1+ can handle for tuples.\n */\ntype ExtractTupleTypes<T extends readonly ExecutableBuilder<any>[]> = {\n [K in keyof T]: T[K] extends ExecutableBuilder<infer U> ? U : never;\n};\n\n/**\n * Converts a ParsedBatchResponse to a native Response object\n * @param parsed - The parsed batch response\n * @returns A native Response object\n */\nfunction parsedToResponse(parsed: ParsedBatchResponse): Response {\n const headers = new Headers(parsed.headers);\n\n // Handle null body\n if (parsed.body === null || parsed.body === undefined) {\n return new Response(null, {\n status: parsed.status,\n statusText: parsed.statusText,\n headers,\n });\n }\n\n // Convert body to string if it's not already\n const bodyString =\n typeof parsed.body === \"string\" ? parsed.body : JSON.stringify(parsed.body);\n\n // Handle 204 No Content status - it cannot have a body per HTTP spec\n // If FileMaker returns 204 with a body, treat it as 200\n let status = parsed.status;\n if (status === 204 && bodyString && bodyString.trim() !== \"\") {\n status = 200;\n }\n\n return new Response(status === 204 ? null : bodyString, {\n status: status,\n statusText: parsed.statusText,\n headers,\n });\n}\n\n/**\n * Builder for batch operations that allows multiple queries to be executed together\n * in a single transactional request.\n */\nexport class BatchBuilder<Builders extends readonly ExecutableBuilder<any>[]>\n implements ExecutableBuilder<ExtractTupleTypes<Builders>>\n{\n private builders: ExecutableBuilder<any>[];\n private readonly originalBuilders: Builders;\n\n constructor(\n builders: Builders,\n private readonly databaseName: string,\n private readonly context: ExecutionContext,\n ) {\n // Convert readonly tuple to mutable array for dynamic additions\n this.builders = [...builders];\n // Store original tuple for type preservation\n this.originalBuilders = builders;\n }\n\n /**\n * Add a request to the batch dynamically.\n * This allows building up batch operations programmatically.\n *\n * @param builder - An executable builder to add to the batch\n * @returns This BatchBuilder for method chaining\n * @example\n * ```ts\n * const batch = db.batch([]);\n * batch.addRequest(db.from('contacts').list());\n * batch.addRequest(db.from('users').list());\n * const result = await batch.execute();\n * ```\n */\n addRequest<T>(builder: ExecutableBuilder<T>): this {\n this.builders.push(builder);\n return this;\n }\n\n /**\n * Get the request configuration for this batch operation.\n * This is used internally by the execution system.\n */\n getRequestConfig(): { method: string; url: string; body?: any } {\n // Note: This method is kept for compatibility but batch operations\n // should use execute() directly which handles the full Request/Response flow\n return {\n method: \"POST\",\n url: `/${this.databaseName}/$batch`,\n body: undefined, // Body is constructed in execute()\n };\n }\n\n toRequest(baseUrl: string, options?: ExecuteOptions): Request {\n // Batch operations are not designed to be nested, but we provide\n // a basic implementation for interface compliance\n const fullUrl = `${baseUrl}/${this.databaseName}/$batch`;\n return new Request(fullUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"multipart/mixed\",\n \"OData-Version\": \"4.0\",\n },\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<Result<any>> {\n // This should not typically be called for batch operations\n // as they handle their own response processing\n return {\n data: undefined,\n error: {\n name: \"NotImplementedError\",\n message: \"Batch operations handle response processing internally\",\n timestamp: new Date(),\n } as any,\n };\n }\n\n /**\n * Execute the batch operation.\n *\n * @param options - Optional fetch options and batch-specific options (includes beforeRequest hook)\n * @returns A tuple of results matching the input builders\n */\n async execute<EO extends ExecuteOptions>(\n options?: RequestInit & FFetchOptions & EO,\n ): Promise<Result<ExtractTupleTypes<Builders>>> {\n const baseUrl = this.context._getBaseUrl?.();\n if (!baseUrl) {\n return {\n data: undefined,\n error: {\n name: \"ConfigurationError\",\n message:\n \"Base URL not available - execution context must implement _getBaseUrl()\",\n timestamp: new Date(),\n } as any,\n };\n }\n\n try {\n // Convert builders to native Request objects\n const requests: Request[] = this.builders.map((builder) =>\n builder.toRequest(baseUrl, options),\n );\n\n // Format batch request (automatically groups mutations into changesets)\n const { body, boundary } = await formatBatchRequestFromNative(\n requests,\n baseUrl,\n );\n\n // Execute the batch request\n const response = await this.context._makeRequest<string>(\n `/${this.databaseName}/$batch`,\n {\n ...options,\n method: \"POST\",\n headers: {\n ...options?.headers,\n \"Content-Type\": `multipart/mixed; boundary=${boundary}`,\n \"OData-Version\": \"4.0\",\n },\n body,\n },\n );\n\n if (response.error) {\n return { data: undefined, error: response.error };\n }\n\n // Extract the actual boundary from the response\n // FileMaker uses its own boundary, not the one we sent\n const firstLine =\n response.data.split(\"\\r\\n\")[0] || response.data.split(\"\\n\")[0] || \"\";\n const actualBoundary = firstLine.startsWith(\"--\")\n ? firstLine.substring(2)\n : boundary;\n\n // Parse the multipart response\n const contentTypeHeader = `multipart/mixed; boundary=${actualBoundary}`;\n const parsedResponses = parseBatchResponse(\n response.data,\n contentTypeHeader,\n );\n\n // Check if we got the expected number of responses\n if (parsedResponses.length !== this.builders.length) {\n return {\n data: undefined,\n error: {\n name: \"BatchError\",\n message: `Expected ${this.builders.length} responses but got ${parsedResponses.length}`,\n timestamp: new Date(),\n } as any,\n };\n }\n\n // Process each response using the corresponding builder\n // Build tuple by processing each builder in order\n type ResultTuple = ExtractTupleTypes<Builders>;\n\n // Process builders sequentially to preserve tuple order\n const processedResults: any[] = [];\n for (let i = 0; i < this.originalBuilders.length; i++) {\n const builder = this.originalBuilders[i];\n const parsed = parsedResponses[i];\n\n if (!builder || !parsed) {\n processedResults.push(undefined);\n continue;\n }\n\n // Convert parsed response to native Response\n const nativeResponse = parsedToResponse(parsed);\n\n // Let the builder process its own response\n const result = await builder.processResponse(nativeResponse, options);\n\n if (result.error) {\n processedResults.push(undefined);\n } else {\n processedResults.push(result.data);\n }\n }\n\n // Use a type assertion that TypeScript will respect\n // ExtractTupleTypes ensures this is a proper tuple type\n return {\n data: processedResults as unknown as ResultTuple,\n error: undefined,\n };\n } catch (err) {\n return {\n data: undefined,\n error: {\n name: \"BatchError\",\n message: err instanceof Error ? err.message : \"Unknown error\",\n timestamp: new Date(),\n } as any,\n };\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { TableOccurrence } from './table-occurrence.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extract the name type from a TableOccurrence
|
|
4
|
+
*/
|
|
5
|
+
type ExtractName<TO> = TO extends TableOccurrence<any, infer Name, any, any> ? Name : never;
|
|
6
|
+
/**
|
|
7
|
+
* Extract all names from an array of TableOccurrences as a union
|
|
8
|
+
*/
|
|
9
|
+
type ExtractNames<TOs extends readonly TableOccurrence<any, any, any, any>[]> = ExtractName<TOs[number]>;
|
|
10
|
+
/**
|
|
11
|
+
* Find a TableOccurrence by name from an array
|
|
12
|
+
*/
|
|
13
|
+
type FindByName<TOs extends readonly TableOccurrence<any, any, any, any>[], Name extends string> = Extract<TOs[number], TableOccurrence<any, Name, any, any>>;
|
|
14
|
+
/**
|
|
15
|
+
* Navigation configuration - maps TO names to arrays of navigation target names.
|
|
16
|
+
* A table occurrence cannot navigate to itself.
|
|
17
|
+
*/
|
|
18
|
+
type NavigationConfig<TOs extends readonly TableOccurrence<any, any, any, any>[]> = {
|
|
19
|
+
[K in ExtractNames<TOs>]?: Exclude<ExtractNames<TOs>, K>[];
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Resolve navigation config to actual TO record for a given TO name
|
|
23
|
+
*/
|
|
24
|
+
type ResolveNavForTO<TOs extends readonly TableOccurrence<any, any, any, any>[], Nav extends NavigationConfig<TOs> | undefined, Name extends ExtractNames<TOs>> = Nav extends NavigationConfig<TOs> ? Nav[Name] extends infer NavNames extends string[] ? {
|
|
25
|
+
[K in NavNames[number]]: FindByName<TOs, K>;
|
|
26
|
+
} : {} : {};
|
|
27
|
+
/**
|
|
28
|
+
* Build the result type - a tuple of TOs with navigation resolved
|
|
29
|
+
*/
|
|
30
|
+
type BuildResult<TOs extends readonly TableOccurrence<any, any, any, any>[], Nav extends NavigationConfig<TOs> | undefined> = {
|
|
31
|
+
[K in keyof TOs]: TOs[K] extends TableOccurrence<infer BT, infer Name, any, infer DefSelect> ? Name extends ExtractNames<TOs> ? TableOccurrence<BT, Name, ResolveNavForTO<TOs, Nav, Name>, DefSelect> : TOs[K] : TOs[K];
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Builds TableOccurrences with navigation relationships resolved.
|
|
35
|
+
*
|
|
36
|
+
* This is the second phase of TO definition - after defining base TOs,
|
|
37
|
+
* use this function to link them with navigation relationships.
|
|
38
|
+
*
|
|
39
|
+
* @example Full navigation
|
|
40
|
+
* ```ts
|
|
41
|
+
* const [contacts, users] = buildOccurrences({
|
|
42
|
+
* occurrences: [_contacts, _users],
|
|
43
|
+
* navigation: {
|
|
44
|
+
* contacts: ["users"],
|
|
45
|
+
* users: ["contacts"],
|
|
46
|
+
* },
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @example Partial navigation
|
|
51
|
+
* ```ts
|
|
52
|
+
* const [contacts, users] = buildOccurrences({
|
|
53
|
+
* occurrences: [_contacts, _users],
|
|
54
|
+
* navigation: {
|
|
55
|
+
* contacts: ["users"],
|
|
56
|
+
* },
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @example No navigation
|
|
61
|
+
* ```ts
|
|
62
|
+
* const [contacts, users] = buildOccurrences({
|
|
63
|
+
* occurrences: [_contacts, _users],
|
|
64
|
+
* });
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* @param config - Configuration object with occurrences array and optional navigation
|
|
68
|
+
* @returns Tuple of TableOccurrences with navigation resolved (same order as input)
|
|
69
|
+
*/
|
|
70
|
+
export declare function buildOccurrences<const TOs extends readonly TableOccurrence<any, any, any, any>[], const Nav extends NavigationConfig<TOs> | undefined>(config: {
|
|
71
|
+
occurrences: TOs;
|
|
72
|
+
navigation?: Nav;
|
|
73
|
+
}): BuildResult<TOs, Nav>;
|
|
74
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { TableOccurrence } from "./table-occurrence.js";
|
|
2
|
+
function buildOccurrences(config) {
|
|
3
|
+
const { occurrences, navigation } = config;
|
|
4
|
+
const toByName = /* @__PURE__ */ new Map();
|
|
5
|
+
for (const to of occurrences) {
|
|
6
|
+
toByName.set(to.name, to);
|
|
7
|
+
}
|
|
8
|
+
const result = occurrences.map((to) => {
|
|
9
|
+
const navNames = navigation == null ? void 0 : navigation[to.name];
|
|
10
|
+
const resolvedNav = {};
|
|
11
|
+
if (navNames) {
|
|
12
|
+
for (const navName of navNames) {
|
|
13
|
+
if (navName === to.name) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`TableOccurrence "${to.name}" cannot navigate to itself`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
const targetTO = toByName.get(navName);
|
|
19
|
+
if (targetTO) {
|
|
20
|
+
resolvedNav[navName] = targetTO;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return TableOccurrence._withNavigation(to, resolvedNav);
|
|
25
|
+
});
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
buildOccurrences
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=build-occurrences.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-occurrences.js","sources":["../../../src/client/build-occurrences.ts"],"sourcesContent":["import { TableOccurrence } from \"./table-occurrence\";\n\n/**\n * Extract the name type from a TableOccurrence\n */\ntype ExtractName<TO> =\n TO extends TableOccurrence<any, infer Name, any, any> ? Name : never;\n\n/**\n * Extract all names from an array of TableOccurrences as a union\n */\ntype ExtractNames<TOs extends readonly TableOccurrence<any, any, any, any>[]> =\n ExtractName<TOs[number]>;\n\n/**\n * Find a TableOccurrence by name from an array\n */\ntype FindByName<\n TOs extends readonly TableOccurrence<any, any, any, any>[],\n Name extends string,\n> = Extract<TOs[number], TableOccurrence<any, Name, any, any>>;\n\n/**\n * Navigation configuration - maps TO names to arrays of navigation target names.\n * A table occurrence cannot navigate to itself.\n */\ntype NavigationConfig<\n TOs extends readonly TableOccurrence<any, any, any, any>[],\n> = {\n [K in ExtractNames<TOs>]?: Exclude<ExtractNames<TOs>, K>[];\n};\n\n/**\n * Resolve navigation config to actual TO record for a given TO name\n */\ntype ResolveNavForTO<\n TOs extends readonly TableOccurrence<any, any, any, any>[],\n Nav extends NavigationConfig<TOs> | undefined,\n Name extends ExtractNames<TOs>,\n> =\n Nav extends NavigationConfig<TOs>\n ? Nav[Name] extends infer NavNames extends string[]\n ? {\n [K in NavNames[number]]: FindByName<TOs, K>;\n }\n : {}\n : {};\n\n/**\n * Build the result type - a tuple of TOs with navigation resolved\n */\ntype BuildResult<\n TOs extends readonly TableOccurrence<any, any, any, any>[],\n Nav extends NavigationConfig<TOs> | undefined,\n> = {\n [K in keyof TOs]: TOs[K] extends TableOccurrence<\n infer BT,\n infer Name,\n any,\n infer DefSelect\n >\n ? Name extends ExtractNames<TOs>\n ? TableOccurrence<BT, Name, ResolveNavForTO<TOs, Nav, Name>, DefSelect>\n : TOs[K]\n : TOs[K];\n};\n\n/**\n * Configuration object for buildOccurrences\n */\ntype BuildOccurrencesConfig<\n TOs extends readonly TableOccurrence<any, any, any, any>[],\n> = {\n occurrences: TOs;\n navigation?: NavigationConfig<TOs>;\n};\n\n/**\n * Builds TableOccurrences with navigation relationships resolved.\n *\n * This is the second phase of TO definition - after defining base TOs,\n * use this function to link them with navigation relationships.\n *\n * @example Full navigation\n * ```ts\n * const [contacts, users] = buildOccurrences({\n * occurrences: [_contacts, _users],\n * navigation: {\n * contacts: [\"users\"],\n * users: [\"contacts\"],\n * },\n * });\n * ```\n *\n * @example Partial navigation\n * ```ts\n * const [contacts, users] = buildOccurrences({\n * occurrences: [_contacts, _users],\n * navigation: {\n * contacts: [\"users\"],\n * },\n * });\n * ```\n *\n * @example No navigation\n * ```ts\n * const [contacts, users] = buildOccurrences({\n * occurrences: [_contacts, _users],\n * });\n * ```\n *\n * @param config - Configuration object with occurrences array and optional navigation\n * @returns Tuple of TableOccurrences with navigation resolved (same order as input)\n */\nexport function buildOccurrences<\n const TOs extends readonly TableOccurrence<any, any, any, any>[],\n const Nav extends NavigationConfig<TOs> | undefined,\n>(config: { occurrences: TOs; navigation?: Nav }): BuildResult<TOs, Nav> {\n const { occurrences, navigation } = config;\n\n // Build a map of name -> TO for quick lookup\n const toByName = new Map<string, TableOccurrence<any, any, any, any>>();\n for (const to of occurrences) {\n toByName.set(to.name, to);\n }\n\n // Build result array with navigation resolved\n const result = occurrences.map((to) => {\n const navNames = navigation?.[to.name as keyof typeof navigation] as\n | string[]\n | undefined;\n\n // Resolve navigation names to actual TOs\n const resolvedNav: Record<string, TableOccurrence<any, any, any, any>> = {};\n if (navNames) {\n for (const navName of navNames) {\n // Prevent self-navigation\n if (navName === to.name) {\n throw new Error(\n `TableOccurrence \"${to.name}\" cannot navigate to itself`,\n );\n }\n const targetTO = toByName.get(navName);\n if (targetTO) {\n resolvedNav[navName] = targetTO;\n }\n }\n }\n\n // Create new TO with navigation using internal method\n return TableOccurrence._withNavigation(to, resolvedNav);\n });\n\n return result as BuildResult<TOs, Nav>;\n}\n"],"names":[],"mappings":";AAkHO,SAAS,iBAGd,QAAuE;AACjE,QAAA,EAAE,aAAa,WAAA,IAAe;AAG9B,QAAA,+BAAe,IAAiD;AACtE,aAAW,MAAM,aAAa;AACnB,aAAA,IAAI,GAAG,MAAM,EAAE;AAAA,EAAA;AAI1B,QAAM,SAAS,YAAY,IAAI,CAAC,OAAO;AAC/B,UAAA,WAAW,yCAAa,GAAG;AAKjC,UAAM,cAAmE,CAAC;AAC1E,QAAI,UAAU;AACZ,iBAAW,WAAW,UAAU;AAE1B,YAAA,YAAY,GAAG,MAAM;AACvB,gBAAM,IAAI;AAAA,YACR,oBAAoB,GAAG,IAAI;AAAA,UAC7B;AAAA,QAAA;AAEI,cAAA,WAAW,SAAS,IAAI,OAAO;AACrC,YAAI,UAAU;AACZ,sBAAY,OAAO,IAAI;AAAA,QAAA;AAAA,MACzB;AAAA,IACF;AAIK,WAAA,gBAAgB,gBAAgB,IAAI,WAAW;AAAA,EAAA,CACvD;AAEM,SAAA;AACT;"}
|
|
@@ -73,7 +73,7 @@ export declare class ExecutableDeleteBuilder<T extends Record<string, any>> impl
|
|
|
73
73
|
url: string;
|
|
74
74
|
body?: any;
|
|
75
75
|
};
|
|
76
|
-
toRequest(baseUrl: string): Request;
|
|
76
|
+
toRequest(baseUrl: string, options?: ExecuteOptions): Request;
|
|
77
77
|
processResponse(response: Response, options?: ExecuteOptions): Promise<Result<{
|
|
78
78
|
deletedCount: number;
|
|
79
79
|
}>>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
import { getAcceptHeader } from "../types.js";
|
|
4
5
|
import { QueryBuilder } from "./query-builder.js";
|
|
5
6
|
import { getTableIdentifiers } from "../transform.js";
|
|
6
7
|
class DeleteBuilder {
|
|
@@ -151,13 +152,13 @@ class ExecutableDeleteBuilder {
|
|
|
151
152
|
url
|
|
152
153
|
};
|
|
153
154
|
}
|
|
154
|
-
toRequest(baseUrl) {
|
|
155
|
+
toRequest(baseUrl, options) {
|
|
155
156
|
const config = this.getRequestConfig();
|
|
156
157
|
const fullUrl = `${baseUrl}${config.url}`;
|
|
157
158
|
return new Request(fullUrl, {
|
|
158
159
|
method: config.method,
|
|
159
160
|
headers: {
|
|
160
|
-
Accept:
|
|
161
|
+
Accept: getAcceptHeader(options == null ? void 0 : options.includeODataAnnotations)
|
|
161
162
|
}
|
|
162
163
|
});
|
|
163
164
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delete-builder.js","sources":["../../../src/client/delete-builder.ts"],"sourcesContent":["import type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n WithSystemFields,\n ExecuteOptions,\n} from \"../types\";\nimport type { TableOccurrence } from \"./table-occurrence\";\nimport { QueryBuilder } from \"./query-builder\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport { getTableIdentifiers } from \"../transform\";\n\n/**\n * Initial delete builder returned from EntitySet.delete()\n * Requires calling .byId() or .where() before .execute() is available\n */\nexport class DeleteBuilder<T extends Record<string, any>> {\n private tableName: string;\n private databaseName: string;\n private context: ExecutionContext;\n private occurrence?: TableOccurrence<any, any, any, any>;\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence?: TableOccurrence<any, any, any, any>;\n tableName: string;\n databaseName: string;\n context: ExecutionContext;\n databaseUseEntityIds?: boolean;\n }) {\n this.occurrence = config.occurrence;\n this.tableName = config.tableName;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n }\n\n /**\n * Delete a single record by ID\n */\n byId(id: string | number): ExecutableDeleteBuilder<T> {\n return new ExecutableDeleteBuilder<T>({\n occurrence: this.occurrence,\n tableName: this.tableName,\n databaseName: this.databaseName,\n context: this.context,\n mode: \"byId\",\n recordId: id,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n /**\n * Delete records matching a filter query\n * @param fn Callback that receives a QueryBuilder for building the filter\n */\n where(\n fn: (\n q: QueryBuilder<WithSystemFields<T>>,\n ) => QueryBuilder<WithSystemFields<T>>,\n ): ExecutableDeleteBuilder<T> {\n // Create a QueryBuilder for the user to configure\n const queryBuilder = new QueryBuilder<\n WithSystemFields<T>,\n keyof WithSystemFields<T>,\n false,\n false,\n undefined\n >({\n occurrence: undefined,\n tableName: this.tableName,\n databaseName: this.databaseName,\n context: this.context,\n });\n\n // Let the user configure it\n const configuredBuilder = fn(queryBuilder);\n\n return new ExecutableDeleteBuilder<T>({\n occurrence: this.occurrence,\n tableName: this.tableName,\n databaseName: this.databaseName,\n context: this.context,\n mode: \"byFilter\",\n queryBuilder: configuredBuilder,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n}\n\n/**\n * Executable delete builder - has execute() method\n * Returned after calling .byId() or .where()\n */\nexport class ExecutableDeleteBuilder<T extends Record<string, any>>\n implements ExecutableBuilder<{ deletedCount: number }>\n{\n private tableName: string;\n private databaseName: string;\n private context: ExecutionContext;\n private occurrence?: TableOccurrence<any, any, any, any>;\n private mode: \"byId\" | \"byFilter\";\n private recordId?: string | number;\n private queryBuilder?: QueryBuilder<any>;\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence?: TableOccurrence<any, any, any, any>;\n tableName: string;\n databaseName: string;\n context: ExecutionContext;\n mode: \"byId\" | \"byFilter\";\n recordId?: string | number;\n queryBuilder?: QueryBuilder<any>;\n databaseUseEntityIds?: boolean;\n }) {\n this.occurrence = config.occurrence;\n this.tableName = config.tableName;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.mode = config.mode;\n this.recordId = config.recordId;\n this.queryBuilder = config.queryBuilder;\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n }\n\n /**\n * Helper to merge database-level useEntityIds with per-request options\n */\n private mergeExecuteOptions(\n options?: RequestInit & FFetchOptions & ExecuteOptions,\n ): RequestInit & FFetchOptions & { useEntityIds?: boolean } {\n // If useEntityIds is not set in options, use the database-level setting\n return {\n ...options,\n useEntityIds: options?.useEntityIds ?? this.databaseUseEntityIds,\n };\n }\n\n /**\n * Gets the table ID (FMTID) if using entity IDs, otherwise returns the table name\n * @param useEntityIds - Optional override for entity ID usage\n */\n private getTableId(useEntityIds?: boolean): string {\n if (!this.occurrence) {\n return this.tableName;\n }\n\n const contextDefault = this.context._getUseEntityIds?.() ?? false;\n const shouldUseIds = useEntityIds ?? contextDefault;\n\n if (shouldUseIds) {\n const identifiers = getTableIdentifiers(this.occurrence);\n if (!identifiers.id) {\n throw new Error(\n `useEntityIds is true but TableOccurrence \"${identifiers.name}\" does not have an fmtId defined`,\n );\n }\n return identifiers.id;\n }\n\n return this.occurrence.getTableName();\n }\n\n async execute(\n options?: RequestInit & FFetchOptions & { useEntityIds?: boolean },\n ): Promise<Result<{ deletedCount: number }>> {\n // Merge database-level useEntityIds with per-request options\n const mergedOptions = this.mergeExecuteOptions(options);\n\n // Get table identifier with override support\n const tableId = this.getTableId(mergedOptions.useEntityIds);\n\n let url: string;\n\n if (this.mode === \"byId\") {\n // Delete single record by ID: DELETE /{database}/{table}('id')\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n } else {\n // Delete by filter: DELETE /{database}/{table}?$filter=...\n if (!this.queryBuilder) {\n throw new Error(\"Query builder is required for filter-based delete\");\n }\n\n // Get the query string from the configured QueryBuilder\n const queryString = this.queryBuilder.getQueryString();\n // Remove the leading \"/\" and table name from the query string as we'll build our own URL\n const queryParams = queryString.startsWith(`/${tableId}`)\n ? queryString.slice(`/${tableId}`.length)\n : queryString.startsWith(`/${this.tableName}`)\n ? queryString.slice(`/${this.tableName}`.length)\n : queryString;\n\n url = `/${this.databaseName}/${tableId}${queryParams}`;\n }\n\n // Make DELETE request\n const result = await this.context._makeRequest(url, {\n method: \"DELETE\",\n ...mergedOptions,\n });\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n const response = result.data;\n\n // OData returns 204 No Content with fmodata.affected_rows header\n // The _makeRequest should handle extracting the header value\n // For now, we'll check if response contains the count\n let deletedCount = 0;\n\n if (typeof response === \"number\") {\n deletedCount = response;\n } else if (response && typeof response === \"object\") {\n // Check if the response has a count property (fallback)\n deletedCount = (response as any).deletedCount || 0;\n }\n\n return { data: { deletedCount }, error: undefined };\n }\n\n getRequestConfig(): { method: string; url: string; body?: any } {\n // For batch operations, use database-level setting (no per-request override available here)\n const tableId = this.getTableId(this.databaseUseEntityIds);\n\n let url: string;\n\n if (this.mode === \"byId\") {\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n } else {\n if (!this.queryBuilder) {\n throw new Error(\"Query builder is required for filter-based delete\");\n }\n\n const queryString = this.queryBuilder.getQueryString();\n const queryParams = queryString.startsWith(`/${tableId}`)\n ? queryString.slice(`/${tableId}`.length)\n : queryString.startsWith(`/${this.tableName}`)\n ? queryString.slice(`/${this.tableName}`.length)\n : queryString;\n\n url = `/${this.databaseName}/${tableId}${queryParams}`;\n }\n\n return {\n method: \"DELETE\",\n url,\n };\n }\n\n toRequest(baseUrl: string): Request {\n const config = this.getRequestConfig();\n const fullUrl = `${baseUrl}${config.url}`;\n\n return new Request(fullUrl, {\n method: config.method,\n headers: {\n Accept: \"application/json\",\n },\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<Result<{ deletedCount: number }>> {\n // Check for empty response (204 No Content)\n const text = await response.text();\n if (!text || text.trim() === \"\") {\n // For 204 No Content, check the fmodata.affected_rows header\n const affectedRows = response.headers.get(\"fmodata.affected_rows\");\n const deletedCount = affectedRows ? parseInt(affectedRows, 10) : 1;\n return { data: { deletedCount }, error: undefined };\n }\n\n const rawResponse = JSON.parse(text);\n\n // OData returns 204 No Content with fmodata.affected_rows header\n // The _makeRequest should handle extracting the header value\n // For now, we'll check if response contains the count\n let deletedCount = 0;\n\n if (typeof rawResponse === \"number\") {\n deletedCount = rawResponse;\n } else if (rawResponse && typeof rawResponse === \"object\") {\n // Check if the response has a count property (fallback)\n deletedCount = (rawResponse as any).deletedCount || 0;\n }\n\n return { data: { deletedCount }, error: undefined };\n }\n}\n"],"names":["deletedCount"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"delete-builder.js","sources":["../../../src/client/delete-builder.ts"],"sourcesContent":["import type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n WithSystemFields,\n ExecuteOptions,\n} from \"../types\";\nimport { getAcceptHeader } from \"../types\";\nimport type { TableOccurrence } from \"./table-occurrence\";\nimport { QueryBuilder } from \"./query-builder\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport { getTableIdentifiers } from \"../transform\";\n\n/**\n * Initial delete builder returned from EntitySet.delete()\n * Requires calling .byId() or .where() before .execute() is available\n */\nexport class DeleteBuilder<T extends Record<string, any>> {\n private tableName: string;\n private databaseName: string;\n private context: ExecutionContext;\n private occurrence?: TableOccurrence<any, any, any, any>;\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence?: TableOccurrence<any, any, any, any>;\n tableName: string;\n databaseName: string;\n context: ExecutionContext;\n databaseUseEntityIds?: boolean;\n }) {\n this.occurrence = config.occurrence;\n this.tableName = config.tableName;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n }\n\n /**\n * Delete a single record by ID\n */\n byId(id: string | number): ExecutableDeleteBuilder<T> {\n return new ExecutableDeleteBuilder<T>({\n occurrence: this.occurrence,\n tableName: this.tableName,\n databaseName: this.databaseName,\n context: this.context,\n mode: \"byId\",\n recordId: id,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n /**\n * Delete records matching a filter query\n * @param fn Callback that receives a QueryBuilder for building the filter\n */\n where(\n fn: (\n q: QueryBuilder<WithSystemFields<T>>,\n ) => QueryBuilder<WithSystemFields<T>>,\n ): ExecutableDeleteBuilder<T> {\n // Create a QueryBuilder for the user to configure\n const queryBuilder = new QueryBuilder<\n WithSystemFields<T>,\n keyof WithSystemFields<T>,\n false,\n false,\n undefined\n >({\n occurrence: undefined,\n tableName: this.tableName,\n databaseName: this.databaseName,\n context: this.context,\n });\n\n // Let the user configure it\n const configuredBuilder = fn(queryBuilder);\n\n return new ExecutableDeleteBuilder<T>({\n occurrence: this.occurrence,\n tableName: this.tableName,\n databaseName: this.databaseName,\n context: this.context,\n mode: \"byFilter\",\n queryBuilder: configuredBuilder,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n}\n\n/**\n * Executable delete builder - has execute() method\n * Returned after calling .byId() or .where()\n */\nexport class ExecutableDeleteBuilder<T extends Record<string, any>>\n implements ExecutableBuilder<{ deletedCount: number }>\n{\n private tableName: string;\n private databaseName: string;\n private context: ExecutionContext;\n private occurrence?: TableOccurrence<any, any, any, any>;\n private mode: \"byId\" | \"byFilter\";\n private recordId?: string | number;\n private queryBuilder?: QueryBuilder<any>;\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence?: TableOccurrence<any, any, any, any>;\n tableName: string;\n databaseName: string;\n context: ExecutionContext;\n mode: \"byId\" | \"byFilter\";\n recordId?: string | number;\n queryBuilder?: QueryBuilder<any>;\n databaseUseEntityIds?: boolean;\n }) {\n this.occurrence = config.occurrence;\n this.tableName = config.tableName;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.mode = config.mode;\n this.recordId = config.recordId;\n this.queryBuilder = config.queryBuilder;\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n }\n\n /**\n * Helper to merge database-level useEntityIds with per-request options\n */\n private mergeExecuteOptions(\n options?: RequestInit & FFetchOptions & ExecuteOptions,\n ): RequestInit & FFetchOptions & { useEntityIds?: boolean } {\n // If useEntityIds is not set in options, use the database-level setting\n return {\n ...options,\n useEntityIds: options?.useEntityIds ?? this.databaseUseEntityIds,\n };\n }\n\n /**\n * Gets the table ID (FMTID) if using entity IDs, otherwise returns the table name\n * @param useEntityIds - Optional override for entity ID usage\n */\n private getTableId(useEntityIds?: boolean): string {\n if (!this.occurrence) {\n return this.tableName;\n }\n\n const contextDefault = this.context._getUseEntityIds?.() ?? false;\n const shouldUseIds = useEntityIds ?? contextDefault;\n\n if (shouldUseIds) {\n const identifiers = getTableIdentifiers(this.occurrence);\n if (!identifiers.id) {\n throw new Error(\n `useEntityIds is true but TableOccurrence \"${identifiers.name}\" does not have an fmtId defined`,\n );\n }\n return identifiers.id;\n }\n\n return this.occurrence.getTableName();\n }\n\n async execute(\n options?: RequestInit & FFetchOptions & { useEntityIds?: boolean },\n ): Promise<Result<{ deletedCount: number }>> {\n // Merge database-level useEntityIds with per-request options\n const mergedOptions = this.mergeExecuteOptions(options);\n\n // Get table identifier with override support\n const tableId = this.getTableId(mergedOptions.useEntityIds);\n\n let url: string;\n\n if (this.mode === \"byId\") {\n // Delete single record by ID: DELETE /{database}/{table}('id')\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n } else {\n // Delete by filter: DELETE /{database}/{table}?$filter=...\n if (!this.queryBuilder) {\n throw new Error(\"Query builder is required for filter-based delete\");\n }\n\n // Get the query string from the configured QueryBuilder\n const queryString = this.queryBuilder.getQueryString();\n // Remove the leading \"/\" and table name from the query string as we'll build our own URL\n const queryParams = queryString.startsWith(`/${tableId}`)\n ? queryString.slice(`/${tableId}`.length)\n : queryString.startsWith(`/${this.tableName}`)\n ? queryString.slice(`/${this.tableName}`.length)\n : queryString;\n\n url = `/${this.databaseName}/${tableId}${queryParams}`;\n }\n\n // Make DELETE request\n const result = await this.context._makeRequest(url, {\n method: \"DELETE\",\n ...mergedOptions,\n });\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n const response = result.data;\n\n // OData returns 204 No Content with fmodata.affected_rows header\n // The _makeRequest should handle extracting the header value\n // For now, we'll check if response contains the count\n let deletedCount = 0;\n\n if (typeof response === \"number\") {\n deletedCount = response;\n } else if (response && typeof response === \"object\") {\n // Check if the response has a count property (fallback)\n deletedCount = (response as any).deletedCount || 0;\n }\n\n return { data: { deletedCount }, error: undefined };\n }\n\n getRequestConfig(): { method: string; url: string; body?: any } {\n // For batch operations, use database-level setting (no per-request override available here)\n const tableId = this.getTableId(this.databaseUseEntityIds);\n\n let url: string;\n\n if (this.mode === \"byId\") {\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n } else {\n if (!this.queryBuilder) {\n throw new Error(\"Query builder is required for filter-based delete\");\n }\n\n const queryString = this.queryBuilder.getQueryString();\n const queryParams = queryString.startsWith(`/${tableId}`)\n ? queryString.slice(`/${tableId}`.length)\n : queryString.startsWith(`/${this.tableName}`)\n ? queryString.slice(`/${this.tableName}`.length)\n : queryString;\n\n url = `/${this.databaseName}/${tableId}${queryParams}`;\n }\n\n return {\n method: \"DELETE\",\n url,\n };\n }\n\n toRequest(baseUrl: string, options?: ExecuteOptions): Request {\n const config = this.getRequestConfig();\n const fullUrl = `${baseUrl}${config.url}`;\n\n return new Request(fullUrl, {\n method: config.method,\n headers: {\n Accept: getAcceptHeader(options?.includeODataAnnotations),\n },\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<Result<{ deletedCount: number }>> {\n // Check for empty response (204 No Content)\n const text = await response.text();\n if (!text || text.trim() === \"\") {\n // For 204 No Content, check the fmodata.affected_rows header\n const affectedRows = response.headers.get(\"fmodata.affected_rows\");\n const deletedCount = affectedRows ? parseInt(affectedRows, 10) : 1;\n return { data: { deletedCount }, error: undefined };\n }\n\n const rawResponse = JSON.parse(text);\n\n // OData returns 204 No Content with fmodata.affected_rows header\n // The _makeRequest should handle extracting the header value\n // For now, we'll check if response contains the count\n let deletedCount = 0;\n\n if (typeof rawResponse === \"number\") {\n deletedCount = rawResponse;\n } else if (rawResponse && typeof rawResponse === \"object\") {\n // Check if the response has a count property (fallback)\n deletedCount = (rawResponse as any).deletedCount || 0;\n }\n\n return { data: { deletedCount }, error: undefined };\n }\n}\n"],"names":["deletedCount"],"mappings":"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"}
|
|
@@ -11,8 +11,7 @@ import { Database } from './database.js';
|
|
|
11
11
|
type ExtractNavigationNames<O extends TableOccurrence<any, any, any, any> | undefined> = O extends TableOccurrence<any, any, infer Nav, any> ? Nav extends Record<string, any> ? keyof Nav & string : never : never;
|
|
12
12
|
type ExtractSchemaFromOccurrence<O> = O extends TableOccurrence<infer BT, any, any, any> ? BT extends BaseTable<infer S, any, any, any> ? S : never : never;
|
|
13
13
|
type ExtractDefaultSelect<O> = O extends TableOccurrence<infer BT, any, any, infer DefSelect> ? BT extends BaseTable<infer S, any, any, any> ? DefSelect extends "all" ? keyof S : DefSelect extends "schema" ? keyof S : DefSelect extends readonly (infer K)[] ? K & keyof S : keyof S : never : never;
|
|
14
|
-
type
|
|
15
|
-
type FindNavigationTarget<O extends TableOccurrence<any, any, any, any> | undefined, Name extends string> = O extends TableOccurrence<any, any, infer Nav, any> ? Nav extends Record<string, any> ? Name extends keyof Nav ? ResolveNavigationItem<Nav[Name]> : TableOccurrence<BaseTable<Record<string, StandardSchemaV1>, any, any, any>, any, any, any> : TableOccurrence<BaseTable<Record<string, StandardSchemaV1>, any, any, any>, any, any, any> : TableOccurrence<BaseTable<Record<string, StandardSchemaV1>, any, any, any>, any, any, any>;
|
|
14
|
+
type FindNavigationTarget<O extends TableOccurrence<any, any, any, any> | undefined, Name extends string> = O extends TableOccurrence<any, any, infer Nav, any> ? Nav extends Record<string, any> ? Name extends keyof Nav ? Nav[Name] : TableOccurrence<BaseTable<Record<string, StandardSchemaV1>, any, any, any>, any, any, any> : TableOccurrence<BaseTable<Record<string, StandardSchemaV1>, any, any, any>, any, any, any> : TableOccurrence<BaseTable<Record<string, StandardSchemaV1>, any, any, any>, any, any, any>;
|
|
16
15
|
export declare class EntitySet<Schema extends Record<string, StandardSchemaV1> = any, Occ extends TableOccurrence<any, any, any, any> | undefined = undefined> {
|
|
17
16
|
private occurrence?;
|
|
18
17
|
private tableName;
|
|
@@ -22,6 +21,7 @@ export declare class EntitySet<Schema extends Record<string, StandardSchemaV1> =
|
|
|
22
21
|
private isNavigateFromEntitySet?;
|
|
23
22
|
private navigateRelation?;
|
|
24
23
|
private navigateSourceTableName?;
|
|
24
|
+
private navigateBasePath?;
|
|
25
25
|
constructor(config: {
|
|
26
26
|
occurrence?: Occ;
|
|
27
27
|
tableName: string;
|
|
@@ -37,7 +37,7 @@ export declare class EntitySet<Schema extends Record<string, StandardSchemaV1> =
|
|
|
37
37
|
database: Database<any>;
|
|
38
38
|
}): EntitySet<OccurrenceSchema, Occ>;
|
|
39
39
|
list(): QueryBuilder<InferSchemaType<Schema>, Occ extends TableOccurrence<any, any, any, any> ? ExtractDefaultSelect<Occ> : keyof InferSchemaType<Schema>, false, false, Occ>;
|
|
40
|
-
get(id: string | number): RecordBuilder<InferSchemaType<Schema>, false, keyof InferSchemaType<Schema>, Occ, keyof InferSchemaType<Schema>, {}>;
|
|
40
|
+
get(id: string | number): RecordBuilder<InferSchemaType<Schema>, false, keyof InferSchemaType<Schema>, Occ, Occ extends TableOccurrence<any, any, any, any> ? ExtractDefaultSelect<Occ> : keyof InferSchemaType<Schema>, {}>;
|
|
41
41
|
insert(data: Occ extends TableOccurrence<infer BT, any, any, any> ? BT extends BaseTable<any, any, any, any> ? InsertData<BT> : Partial<InferSchemaType<Schema>> : Partial<InferSchemaType<Schema>>, options: {
|
|
42
42
|
returnFullRecord: false;
|
|
43
43
|
}): InsertBuilder<InferSchemaType<Schema>, Occ, "minimal">;
|
|
@@ -7,6 +7,7 @@ import { InsertBuilder } from "./insert-builder.js";
|
|
|
7
7
|
import { DeleteBuilder } from "./delete-builder.js";
|
|
8
8
|
import { UpdateBuilder } from "./update-builder.js";
|
|
9
9
|
class EntitySet {
|
|
10
|
+
// Full base path for chained navigations
|
|
10
11
|
constructor(config) {
|
|
11
12
|
__publicField(this, "occurrence");
|
|
12
13
|
__publicField(this, "tableName");
|
|
@@ -17,6 +18,7 @@ class EntitySet {
|
|
|
17
18
|
__publicField(this, "isNavigateFromEntitySet");
|
|
18
19
|
__publicField(this, "navigateRelation");
|
|
19
20
|
__publicField(this, "navigateSourceTableName");
|
|
21
|
+
__publicField(this, "navigateBasePath");
|
|
20
22
|
this.occurrence = config.occurrence;
|
|
21
23
|
this.tableName = config.tableName;
|
|
22
24
|
this.databaseName = config.databaseName;
|
|
@@ -60,6 +62,7 @@ class EntitySet {
|
|
|
60
62
|
builder.isNavigate = true;
|
|
61
63
|
builder.navigateRelation = this.navigateRelation;
|
|
62
64
|
builder.navigateSourceTableName = this.navigateSourceTableName;
|
|
65
|
+
builder.navigateBasePath = this.navigateBasePath;
|
|
63
66
|
}
|
|
64
67
|
return builder.top(1e3);
|
|
65
68
|
}
|
|
@@ -73,6 +76,32 @@ class EntitySet {
|
|
|
73
76
|
recordId: id,
|
|
74
77
|
databaseUseEntityIds: ((_a = this.database) == null ? void 0 : _a.isUsingEntityIds()) ?? false
|
|
75
78
|
});
|
|
79
|
+
if (this.occurrence) {
|
|
80
|
+
const defaultSelect = this.occurrence.defaultSelect;
|
|
81
|
+
if (defaultSelect === "schema") {
|
|
82
|
+
const schema = this.occurrence.baseTable.schema;
|
|
83
|
+
const fields = Object.keys(schema);
|
|
84
|
+
const uniqueFields = [...new Set(fields)];
|
|
85
|
+
const selectedBuilder = builder.select(...uniqueFields);
|
|
86
|
+
if (this.isNavigateFromEntitySet) {
|
|
87
|
+
selectedBuilder.isNavigateFromEntitySet = true;
|
|
88
|
+
selectedBuilder.navigateRelation = this.navigateRelation;
|
|
89
|
+
selectedBuilder.navigateSourceTableName = this.navigateSourceTableName;
|
|
90
|
+
}
|
|
91
|
+
return selectedBuilder;
|
|
92
|
+
} else if (Array.isArray(defaultSelect)) {
|
|
93
|
+
const uniqueFields = [
|
|
94
|
+
...new Set(defaultSelect)
|
|
95
|
+
];
|
|
96
|
+
const selectedBuilder = builder.select(...uniqueFields);
|
|
97
|
+
if (this.isNavigateFromEntitySet) {
|
|
98
|
+
selectedBuilder.isNavigateFromEntitySet = true;
|
|
99
|
+
selectedBuilder.navigateRelation = this.navigateRelation;
|
|
100
|
+
selectedBuilder.navigateSourceTableName = this.navigateSourceTableName;
|
|
101
|
+
}
|
|
102
|
+
return selectedBuilder;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
76
105
|
if (this.isNavigateFromEntitySet) {
|
|
77
106
|
builder.isNavigateFromEntitySet = true;
|
|
78
107
|
builder.navigateRelation = this.navigateRelation;
|
|
@@ -130,7 +159,15 @@ class EntitySet {
|
|
|
130
159
|
});
|
|
131
160
|
entitySet.isNavigateFromEntitySet = true;
|
|
132
161
|
entitySet.navigateRelation = relationName;
|
|
133
|
-
|
|
162
|
+
if (this.isNavigateFromEntitySet && this.navigateBasePath) {
|
|
163
|
+
entitySet.navigateBasePath = `${this.navigateBasePath}/${this.navigateRelation}`;
|
|
164
|
+
entitySet.navigateSourceTableName = this.navigateSourceTableName;
|
|
165
|
+
} else if (this.isNavigateFromEntitySet && this.navigateRelation) {
|
|
166
|
+
entitySet.navigateBasePath = `${this.navigateSourceTableName}/${this.navigateRelation}`;
|
|
167
|
+
entitySet.navigateSourceTableName = this.navigateSourceTableName;
|
|
168
|
+
} else {
|
|
169
|
+
entitySet.navigateSourceTableName = this.tableName;
|
|
170
|
+
}
|
|
134
171
|
return entitySet;
|
|
135
172
|
}
|
|
136
173
|
}
|