UNPKG

fm-odata-client

Version:

FileMaker OData client developed by Soliant Consulting

1 lines 55.3 kB
{"version":3,"sources":["../src/index.ts","../src/BatchRequest.ts","../src/SchemaManager.ts","../src/Table.ts","../src/Database.ts","../src/Connection.ts","../src/BasicAuth.ts"],"sourcesContent":["export * from \"./Connection.js\";\nexport * from \"./Database.js\";\nexport * from \"./SchemaManager.js\";\nexport * from \"./Table.js\";\n\nexport { default as BasicAuth } from \"./BasicAuth.js\";\nexport { default as Connection } from \"./Connection.js\";\nexport { default as Database } from \"./Database.js\";\nexport { default as SchemaManager } from \"./SchemaManager.js\";\nexport { default as Table } from \"./Table.js\";\n","import * as Crypto from \"node:crypto\";\n\n/**\n * @internal\n */\nclass BatchRequest {\n private readonly operations: Array<Request | Request[]> = [];\n private lastChangeset: Request[] | null = null;\n\n public constructor(\n private readonly serviceEndpoint: string,\n private readonly authorizationHeader: string,\n operations: Request[],\n ) {\n for (const operation of operations) {\n this.addRequest(operation);\n }\n }\n\n public async toRequest(): Promise<Request> {\n const boundary = `batch_${Crypto.randomBytes(16).toString(\"hex\")}`;\n const headers = new Headers({\n Authorization: this.authorizationHeader,\n \"OData-Version\": \"4.0\",\n \"Content-Type\": `multipart/mixed; boundary=${boundary}`,\n });\n\n const body = (\n await Promise.all(\n this.operations.map(async (request) => {\n if (Array.isArray(request)) {\n const changesetBoundary = `changeset_${Crypto.randomBytes(16).toString(\"hex\")}`;\n\n return [\n `--${boundary}`,\n `Content-Type: multipart/mixed; boundary=${changesetBoundary}`,\n \"\",\n ...(await Promise.all(\n request.map(\n async (request) =>\n `--${changesetBoundary}\\r\\n${await BatchRequest.formatRequest(request)}`,\n ),\n )),\n `--${changesetBoundary}--`,\n ].join(\"\\r\\n\");\n }\n\n return `--${boundary}\\r\\n${await BatchRequest.formatRequest(request)}`;\n }),\n )\n ).join(\"\\r\\n\");\n\n return new Request(`${this.serviceEndpoint}/$batch`, {\n method: \"POST\",\n headers,\n body: `${body}\\r\\n--${boundary}--`,\n });\n }\n\n public static parseMultipartResponse(body: string, headers: Headers): Response[] {\n const boundary = BatchRequest.getBoundary(headers);\n const endBoundaryIndex = body.indexOf(`--${boundary}--`);\n let trimmedBody: string;\n\n if (endBoundaryIndex >= 0) {\n trimmedBody = body.slice(0, endBoundaryIndex);\n } else {\n trimmedBody = body;\n }\n\n const parts = trimmedBody.split(`--${boundary}\\r\\n`).slice(1).map(BatchRequest.splitPart);\n\n const responses: Response[] = [];\n\n for (const [headers, body] of parts) {\n const contentType = headers.get(\"Content-Type\");\n\n if (!contentType) {\n throw new Error(\"Multipart part is missing content-type header\");\n }\n\n if (contentType === \"application/http\") {\n responses.push(BatchRequest.parseHttpResponse(body));\n continue;\n }\n\n if (contentType.startsWith(\"multipart/mixed\")) {\n const changesetResponses = BatchRequest.parseMultipartResponse(body, headers);\n responses.push(...changesetResponses);\n continue;\n }\n\n throw new Error(`Unknown content-type: ${contentType}`);\n }\n\n return responses;\n }\n\n public static parseHttpResponse(rawResponse: string): Response {\n const firstBreakIndex = rawResponse.indexOf(\"\\r\\n\");\n const statusLine = rawResponse.slice(0, firstBreakIndex);\n const rawResponseBody = rawResponse.slice(firstBreakIndex + 2);\n\n const [, statusCode, ...statusText] = statusLine.split(\" \");\n const [headers, body] = BatchRequest.splitPart(rawResponseBody);\n\n return new Response(body.trim(), {\n headers,\n status: Number.parseInt(statusCode, 10),\n statusText: statusText.join(\" \"),\n });\n }\n\n public static splitPart(part: string): [Headers, string] {\n if (part.startsWith(\"\\r\\n\")) {\n return [new Headers(), part.replace(/^\\r\\n/, \"\")];\n }\n\n const breakIndex = part.indexOf(\"\\r\\n\\r\\n\");\n const rawHeaders = part.slice(0, breakIndex);\n const rawBody = part.slice(breakIndex + 4);\n\n const headers = new Headers();\n\n for (const rawHeader of rawHeaders.split(\"\\r\\n\")) {\n const separatorIndex = rawHeader.indexOf(\":\");\n headers.append(\n rawHeader.slice(0, separatorIndex),\n rawHeader.slice(separatorIndex + 1).trim(),\n );\n }\n\n return [headers, rawBody];\n }\n\n private static getBoundary(headers: Headers): string {\n const contentType = headers.get(\"Content-Type\");\n\n if (!contentType) {\n throw new Error(\"Response is missing Content-Type header\");\n }\n\n const boundaryPart = contentType\n .split(\";\")\n .map((part) => part.trim())\n .find((part) => part.startsWith(\"boundary=\"));\n\n if (!boundaryPart) {\n throw new Error(\"Content-Type header is missing boundary\");\n }\n\n const [, boundary] = boundaryPart.split(\"=\");\n return boundary;\n }\n\n private static async formatRequest(request: Request): Promise<string> {\n return [\n \"Content-Type: application/http\",\n \"Content-Transfer-Encoding: binary\",\n \"\",\n `${request.method} ${request.url} HTTP/1.1`,\n ...BatchRequest.formatRequestHeaders(request.headers),\n \"\",\n request.body ? await BatchRequest.streamToString(request.body) : \"\",\n ].join(\"\\r\\n\");\n }\n\n private static async streamToString(stream: ReadableStream<Uint8Array>): Promise<string> {\n const chunks = [];\n\n for await (const chunk of stream) {\n chunks.push(Buffer.from(chunk));\n }\n\n return Buffer.concat(chunks).toString(\"utf-8\");\n }\n\n private static formatRequestHeaders(headers: Headers): string[] {\n const result: string[] = [];\n\n for (const [key, value] of headers.entries()) {\n if (key.toLowerCase() === \"authorization\") {\n continue;\n }\n\n result.push(`${key}: ${value}`);\n }\n\n return result;\n }\n\n private addRequest(request: Request): void {\n if (request.method === \"GET\") {\n this.lastChangeset = null;\n this.operations.push(request);\n return;\n }\n\n if (!this.lastChangeset) {\n this.lastChangeset = [];\n this.operations.push(this.lastChangeset);\n }\n\n this.lastChangeset.push(request);\n }\n}\n\nexport default BatchRequest;\n","import type Database from \"./Database.js\";\n\ntype GenericField = {\n name: string;\n nullable?: boolean;\n primary?: boolean;\n unique?: boolean;\n global?: boolean;\n repetitions?: number;\n};\n\ntype StringField = GenericField & {\n type: \"string\";\n maxLength?: number;\n default?: \"USER\" | \"USERNAME\" | \"CURRENT_USER\";\n};\n\ntype NumericField = GenericField & {\n type: \"numeric\";\n};\n\ntype DateField = GenericField & {\n type: \"date\";\n default?: \"CURRENT_DATE\" | \"CURDATE\";\n};\n\ntype TimeField = GenericField & {\n type: \"time\";\n default?: \"CURRENT_TIME\" | \"CURTIME\";\n};\n\ntype TimestampField = GenericField & {\n type: \"timestamp\";\n default?: \"CURRENT_TIMESTAMP\" | \"CURTIMESTAMP\";\n};\n\ntype ContainerField = GenericField & {\n type: \"container\";\n externalSecurePath?: string;\n};\n\nexport type Field =\n | StringField\n | NumericField\n | DateField\n | TimeField\n | TimestampField\n | ContainerField;\n\ntype FileMakerField = Omit<Field, \"type\" | \"repetitions\" | \"maxLength\"> & { type: string };\n\ntype TableDefinition = {\n tableName: string;\n fields: FileMakerField[];\n};\n\nclass SchemaManager {\n public constructor(private readonly database: Database) {}\n\n public async createTable(tableName: string, fields: Field[]): Promise<TableDefinition> {\n return this.database.fetchJson<TableDefinition>(\"/FileMaker_Tables\", {\n method: \"POST\",\n body: JSON.stringify({\n tableName,\n fields: fields.map(SchemaManager.compileFieldDefinition),\n }),\n });\n }\n\n public async addFields(tableName: string, fields: Field[]): Promise<TableDefinition> {\n return this.database.fetchJson<TableDefinition>(`/FileMaker_Tables/${tableName}`, {\n method: \"PATCH\",\n body: JSON.stringify({ fields: fields.map(SchemaManager.compileFieldDefinition) }),\n });\n }\n\n public async deleteTable(tableName: string): Promise<void> {\n return this.database.fetchNone(`/FileMaker_Tables/${tableName}`, { method: \"DELETE\" });\n }\n\n public async deleteField(tableName: string, fieldName: string): Promise<void> {\n return this.database.fetchNone(`/FileMaker_Tables/${tableName}/${fieldName}`, {\n method: \"DELETE\",\n });\n }\n\n public async createIndex(tableName: string, fieldName: string): Promise<{ indexName: string }> {\n return this.database.fetchJson<{ indexName: string }>(`/FileMaker_Indexes/${tableName}`, {\n method: \"POST\",\n body: JSON.stringify({ indexName: fieldName }),\n });\n }\n\n public async deleteIndex(tableName: string, fieldName: string): Promise<void> {\n return this.database.fetchNone(`/FileMaker_Indexes/${tableName}/${fieldName}`, {\n method: \"DELETE\",\n });\n }\n\n private static compileFieldDefinition(field: Field): FileMakerField {\n const fieldCopy = { ...field };\n let type: string = fieldCopy.type;\n\n if (fieldCopy.type === \"string\") {\n type = \"varchar\";\n\n if (fieldCopy.maxLength !== undefined) {\n type += `(${fieldCopy.maxLength})`;\n fieldCopy.maxLength = undefined;\n }\n }\n\n if (field.repetitions !== undefined) {\n type += `[${field.repetitions}]`;\n fieldCopy.repetitions = undefined;\n }\n\n return { ...fieldCopy, type };\n }\n}\n\nexport default SchemaManager;\n","import { URLSearchParams } from \"node:url\";\nimport { FetchError } from \"./Connection.js\";\nimport type { Blob, FetchParams, ServiceDocument } from \"./Connection.js\";\nimport type Database from \"./Database.js\";\n\nexport type FieldValue = string | number | Buffer | null;\nexport type Repetition = {\n repetition: number;\n value: FieldValue;\n};\nexport type RowData = Record<string, FieldValue | Repetition>;\n\nexport type OrderBy = {\n field: string;\n direction?: \"asc\" | \"desc\";\n};\n\nexport type QueryParams = {\n filter?: string;\n orderBy?: string | OrderBy | Array<string | OrderBy>;\n top?: number;\n skip?: number;\n count?: boolean;\n select?: string[];\n relatedTable?:\n | string\n | string[]\n | {\n primaryKey: PrimaryKey;\n table: string | string[];\n };\n};\n\nexport type FetchOneParams = Omit<QueryParams, \"top\" | \"count\">;\n\nexport type CrossJoinParams = Omit<QueryParams, \"relatedTable\" | \"select\"> & {\n select?: Record<string, string[]>;\n};\n\nexport type Row = {\n \"@odata.id\": string;\n \"@odata.editLink\": string;\n} & Record<string, string | number | null>;\nexport type FieldData = {\n value: string | number | null;\n};\n\nexport type QueryResultWithCount = {\n count: number;\n rows: Row[];\n};\n\nexport type CrossJoinRow = Record<string, string | number | null>;\n\nexport type CrossJoinResultWithCount = {\n count: number;\n rows: CrossJoinRow[];\n};\n\nexport type PrimaryKey = string | number | Array<string | number>;\n\nexport const allowedFileTypes = [\n \"image/gif\",\n \"image/png\",\n \"image/jpeg\",\n \"image/tiff\",\n \"application/pdf\",\n];\n\nclass Table<Batched extends boolean = false> {\n public constructor(\n private readonly database: Database<Batched>,\n private readonly name: string,\n private readonly batched: Batched = false as Batched,\n ) {}\n\n public create(data: RowData): Batched extends false ? Promise<Row> : undefined;\n public create(data: RowData): Promise<Row> | undefined {\n const path = \"\";\n const params = (async (): Promise<FetchParams> => ({\n method: \"POST\",\n body: JSON.stringify(await Table.compileRowData(data)),\n }))();\n\n if (this.batched) {\n void this.fetchNone(path, params);\n return;\n }\n\n return this.fetchJson(path, params);\n }\n\n public update(id: PrimaryKey, data: RowData): Batched extends false ? Promise<Row> : undefined;\n public update(id: PrimaryKey, data: RowData): Promise<Row> | undefined {\n const path = `(${Table.compilePrimaryKey(id)})`;\n const params = (async (): Promise<FetchParams> => ({\n method: \"PATCH\",\n body: JSON.stringify(await Table.compileRowData(data)),\n }))();\n\n if (this.batched) {\n void this.fetchNone(path, params);\n return;\n }\n\n return this.fetchJson(path, params);\n }\n\n public updateMany(\n filter: string,\n data: RowData,\n ): Batched extends false ? Promise<Row[]> : undefined;\n public updateMany(filter: string, data: RowData): Promise<Row[]> | undefined {\n const path = \"\";\n const params = (async (): Promise<FetchParams> => ({\n method: \"PATCH\",\n search: new URLSearchParams({ $filter: filter }),\n body: JSON.stringify(await Table.compileRowData(data)),\n }))();\n\n if (this.batched) {\n void this.fetchNone(path, params);\n return;\n }\n\n return this.fetchJson(path, params);\n }\n\n public delete(id: PrimaryKey): Batched extends false ? Promise<void> : void;\n public delete(id: PrimaryKey): Promise<void> | void {\n return this.fetchNone(`(${Table.compilePrimaryKey(id)})`, { method: \"DELETE\" });\n }\n\n public deleteMany(filter: string): Batched extends false ? Promise<void> : void;\n public deleteMany(filter: string): Promise<void> | void {\n return this.fetchNone(\"\", {\n method: \"DELETE\",\n search: new URLSearchParams({ $filter: filter }),\n });\n }\n\n public uploadBinary(\n id: PrimaryKey,\n fieldName: string,\n data: Buffer,\n ): Batched extends false ? Promise<void> : void;\n public uploadBinary(id: PrimaryKey, fieldName: string, data: Buffer): Promise<void> | void {\n return this.fetchNone(\n `(${Table.compilePrimaryKey(id)})/${fieldName}`,\n (async (): Promise<FetchParams> => ({\n method: \"PATCH\",\n body: data,\n contentType: await Table.getMimeType(data),\n }))(),\n );\n }\n\n public async count(filter?: string): Promise<number> {\n return this.fetchJson<number>(\"/$count\", {\n search: !filter\n ? undefined\n : new URLSearchParams({\n $filter: filter,\n }),\n });\n }\n\n public async fetchById(id: PrimaryKey): Promise<Row | null> {\n try {\n return await this.fetchJson(`(${Table.compilePrimaryKey(id)})`);\n } catch (e) {\n if (e instanceof FetchError && e.statusCode === 404 && e.errorCode === \"-1023\") {\n return null;\n }\n\n throw e;\n }\n }\n\n public async fetchFieldValue(\n id: PrimaryKey,\n fieldName: string,\n ): Promise<string | number | null> {\n const fieldData = await this.fetchJson<FieldData>(\n `(${Table.compilePrimaryKey(id)})/${fieldName}`,\n );\n return fieldData.value;\n }\n\n public async fetchFieldBlob(id: PrimaryKey, fieldName: string): Promise<Blob> {\n return this.fetchBlob(`(${Table.compilePrimaryKey(id)})/${fieldName}/$value`);\n }\n\n /**\n * @deprecated Use `fetchFieldBlob` instead.\n */\n public async fetchField(id: PrimaryKey, fieldName: string): Promise<Blob> {\n return this.fetchFieldBlob(id, fieldName);\n }\n\n public async fetchOne(params?: FetchOneParams): Promise<Row | null> {\n const result = await this.query({ ...params, top: 1 });\n\n if (result.length === 0) {\n return null;\n }\n\n return result[0];\n }\n\n public async query(params?: QueryParams & { count: true }): Promise<QueryResultWithCount>;\n public async query(params?: QueryParams & { count?: false }): Promise<Row[]>;\n public async query(params?: QueryParams): Promise<Row[] | QueryResultWithCount> {\n const searchParams: URLSearchParams = params\n ? Table.compileQuerySearch(params)\n : new URLSearchParams();\n const path = params?.relatedTable ? Table.compileRelatedTablePath(params.relatedTable) : \"\";\n\n const response = await this.fetchJson<\n ServiceDocument<Row[]> & {\n \"@odata.count\": number;\n }\n >(path, { search: searchParams });\n\n if (params?.count) {\n return { count: response[\"@odata.count\"], rows: response.value };\n }\n\n return response.value;\n }\n\n public async crossJoin(\n tables: string | string[],\n params?: CrossJoinParams & { count: true },\n ): Promise<CrossJoinResultWithCount>;\n public async crossJoin(\n tables: string | string[],\n params?: CrossJoinParams & { count?: false },\n ): Promise<CrossJoinRow[]>;\n public async crossJoin(\n tables: string | string[],\n params?: CrossJoinParams,\n ): Promise<CrossJoinRow[] | CrossJoinResultWithCount> {\n const searchParams: URLSearchParams = params\n ? Table.compileQuerySearch({ ...params, select: undefined })\n : new URLSearchParams();\n const tableNames = [this.name, ...(Array.isArray(tables) ? tables : [tables])];\n\n if (params?.select) {\n searchParams.set(\n \"$expand\",\n Object.entries(params.select)\n .map(([table, fields]) => `${table}($select=${fields.join(\",\")})`)\n .join(\",\"),\n );\n }\n\n const response = await this.database.fetchJson<\n ServiceDocument<CrossJoinRow[]> & {\n \"@odata.count\": number;\n }\n >(`/$crossjoin(${tableNames.join(\",\")})`, { search: searchParams });\n\n if (params?.count) {\n return { count: response[\"@odata.count\"], rows: response.value };\n }\n\n return response.value;\n }\n\n private async fetchNone(\n path: string,\n params: FetchParams | Promise<FetchParams>,\n ): Promise<void> {\n return this.database.fetchNone(`/${this.name}${path}`, params);\n }\n\n private async fetchJson<T>(\n path: string,\n params: FetchParams | Promise<FetchParams> = {},\n ): Promise<T> {\n return this.database.fetchJson<T>(`/${this.name}${path}`, params);\n }\n\n private async fetchBlob(\n path: string,\n params: FetchParams | Promise<FetchParams> = {},\n ): Promise<Blob> {\n return this.database.fetchBlob(`/${this.name}${path}`, params);\n }\n\n private static async compileRowData(\n data: RowData,\n ): Promise<Record<string, string | number | null>> {\n const result: Record<string, string | number | null> = {};\n\n for (let [key, value] of Object.entries(data)) {\n if (value !== null && typeof value === \"object\" && !(value instanceof Buffer)) {\n key = `${key}[${value.repetition}]`;\n value = value.value;\n }\n\n if (value instanceof Buffer) {\n await Table.getMimeType(value);\n value = value.toString(\"base64\");\n }\n\n result[key] = value;\n }\n\n return result;\n }\n\n private static async getMimeType(data: Buffer): Promise<string> {\n // Asynchronous import for CommonJS support\n // @todo move to top level import when going ESM only\n const { fileTypeFromBuffer } = await import(\"file-type\");\n const fileType = await fileTypeFromBuffer(data);\n\n if (!(fileType && allowedFileTypes.includes(fileType.mime))) {\n throw new Error(\n `Invalid data, must be one of the following types: ${allowedFileTypes.join(\", \")}`,\n );\n }\n\n return fileType.mime;\n }\n\n private static compileQuerySearch(params: QueryParams): URLSearchParams {\n const searchParams = new URLSearchParams();\n\n if (params.filter) {\n searchParams.set(\"$filter\", params.filter);\n }\n\n if (params.orderBy) {\n searchParams.set(\"$orderby\", Table.compileOrderBy(params.orderBy));\n }\n\n if (params.top) {\n searchParams.set(\"$top\", params.top.toString());\n }\n\n if (params.skip) {\n searchParams.set(\"$skip\", params.skip.toString());\n }\n\n if (params.count) {\n searchParams.set(\"$count\", \"true\");\n }\n\n if (params.select) {\n searchParams.set(\"$select\", params.select.join(\",\"));\n }\n\n return searchParams;\n }\n\n private static compileOrderBy(orderBy: string | OrderBy | Array<string | OrderBy>): string {\n if (typeof orderBy === \"string\") {\n return orderBy;\n }\n\n if (Array.isArray(orderBy)) {\n return orderBy.map(Table.compileOrderBy).join(\",\");\n }\n\n if (!orderBy.direction) {\n return orderBy.field;\n }\n\n return `${orderBy.field} ${orderBy.direction}`;\n }\n\n private static compilePrimaryKey(id: PrimaryKey): string {\n if (Array.isArray(id)) {\n return id.map(Table.compilePrimaryKey).join(\",\");\n }\n\n if (typeof id === \"string\") {\n return `'${id}'`;\n }\n\n return id.toString();\n }\n\n private static compileRelatedTablePath(\n relatedTable: Exclude<QueryParams[\"relatedTable\"], undefined>,\n ): string {\n if (Array.isArray(relatedTable)) {\n return `/${relatedTable.map(encodeURIComponent).join(\"/\")}`;\n }\n\n if (typeof relatedTable === \"string\") {\n return `/${encodeURIComponent(relatedTable)}`;\n }\n\n return `(${Table.compilePrimaryKey(\n relatedTable.primaryKey,\n )})${Table.compileRelatedTablePath(relatedTable.table)}`;\n }\n}\n\nexport default Table;\n","import type { Blob, FetchParams, ServiceDocument } from \"./Connection.js\";\nimport type Connection from \"./Connection.js\";\nimport SchemaManager from \"./SchemaManager.js\";\nimport Table from \"./Table.js\";\n\nexport type TableListEntry = {\n name: string;\n kind: \"EntitySet\";\n url: string;\n};\n\nexport type GenericFieldMetadata = {\n $Nullable?: boolean;\n \"@Index\"?: boolean;\n \"@Calculation\"?: boolean;\n \"@Summary\"?: boolean;\n \"@Global\"?: boolean;\n \"@Org.OData.Core.V1.Permissions\"?: \"Org.OData.Core.V1.Permission@Read\";\n};\n\nexport type StringFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.String\";\n $DefaultValue?: \"USER\" | \"USERNAME\" | \"CURRENT_USER\";\n $MaxLength?: number;\n};\n\nexport type DecimalFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.Decimal\";\n \"@AutoGenerated\"?: boolean;\n};\n\nexport type DateFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.Date\";\n $DefaultValue?: \"CURDATE\" | \"CURRENT_DATE\";\n};\n\nexport type TimeOfDayFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.TimeOfDay\";\n $DefaultValue?: \"CURTIME\" | \"CURRENT_TIME\";\n};\n\nexport type DateTimeOffsetFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.Date\";\n $DefaultValue?: \"CURTIMESTAMP\" | \"CURRENT_TIMESTAMP\";\n \"@VersionId\"?: boolean;\n};\n\nexport type StreamFieldMetadata = {\n $Type: \"Edm.Stream\";\n $Nullable?: boolean;\n \"@EnclosedPath\": string;\n \"@ExternalOpenPath\": string;\n \"@ExternalSecurePath\"?: string;\n};\n\nexport type FieldMetadata =\n | StringFieldMetadata\n | DecimalFieldMetadata\n | DateFieldMetadata\n | TimeOfDayFieldMetadata\n | DateTimeOffsetFieldMetadata\n | StreamFieldMetadata;\n\nexport type EntityType = {\n $Kind: \"EntityType\";\n $Key: string[];\n} & Record<string, FieldMetadata>;\n\nexport type EntitySet = {\n $Kind: \"EntitySet\";\n $Type: string;\n};\n\nexport type Metadata = Record<string, EntityType | EntitySet>;\n\nexport type ScriptParam = string | number | Record<string, unknown>;\nexport type ScriptResult = {\n code: number;\n resultParameter: string;\n};\n\n// biome-ignore lint/suspicious/noConfusingVoidType: necessary in this instance\ntype BatchExecutor<T> = (database: Database<true>) => T | void;\n\nclass Database<Batched extends boolean = false> {\n public constructor(\n private readonly connection: Connection,\n private readonly name: string,\n private readonly batched: Batched = false as Batched,\n ) {}\n\n public async batch<T>(executor: BatchExecutor<T[]>): Promise<T[]> {\n const batchConnection = this.connection.batchConnection(this.name);\n const database = new Database(batchConnection, this.name, true);\n const promises = executor(database);\n await batchConnection.executeBatch();\n\n if (!promises) {\n return [];\n }\n\n return Promise.all(promises);\n }\n\n public table(tableName: string): Table<Batched> {\n return new Table(this, tableName, this.batched);\n }\n\n public schemaManager(): SchemaManager {\n if (this.batched) {\n throw new Error(\"Schema alterations are not allowed in a batch operation\");\n }\n\n return new SchemaManager(this as Database);\n }\n\n public async listTables(): Promise<TableListEntry[]> {\n if (this.batched) {\n throw new Error(\"Tables cannot be listed in a batch operation\");\n }\n\n const response = await this.fetchJson<ServiceDocument<TableListEntry[]>>(\"\");\n return response.value;\n }\n\n public async getMetadata(): Promise<Metadata> {\n if (this.batched) {\n throw new Error(\"Metadata cannot be retrieved in a batch operation\");\n }\n\n const response = await this.fetchJson<Record<string, Metadata>>(\"/$metadata\");\n\n if (!(this.name in response)) {\n throw new Error(\"Response did not include any table information\");\n }\n\n return response[this.name];\n }\n\n public async runScript(scriptName: string, scriptParam?: ScriptParam): Promise<ScriptResult> {\n if (this.batched) {\n throw new Error(\"Script execution is not allowed in a batch operation\");\n }\n\n const response = await this.fetchJson<{ scriptResult: ScriptResult }>(\n `/Script.${scriptName}`,\n {\n method: \"POST\",\n body: JSON.stringify({\n scriptParameterValue: scriptParam,\n }),\n },\n );\n\n return response.scriptResult;\n }\n\n /**\n * @internal\n */\n public async fetchNone(\n path: string,\n params: FetchParams | Promise<FetchParams> = {},\n ): Promise<void> {\n return this.connection.fetchNone(`/${this.name}${path}`, params);\n }\n\n /**\n * @internal\n */\n public async fetchJson<T>(\n path: string,\n params: FetchParams | Promise<FetchParams> = {},\n ): Promise<T> {\n return this.connection.fetchJson<T>(`/${this.name}${path}`, params);\n }\n\n /**\n * @internal\n */\n public async fetchBlob(\n path: string,\n params: FetchParams | Promise<FetchParams> = {},\n ): Promise<Blob> {\n return this.connection.fetchBlob(`/${this.name}${path}`, params);\n }\n}\n\nexport default Database;\n","import type { URLSearchParams } from \"node:url\";\nimport BatchRequest from \"./BatchRequest.js\";\nimport Database from \"./Database.js\";\n\nexport type Authentication = {\n getAuthorizationHeader: () => Promise<string>;\n};\n\nexport type FetchParams = {\n search?: URLSearchParams;\n method?: \"GET\" | \"POST\" | \"PATCH\" | \"DELETE\";\n body?: RequestInit[\"body\"];\n contentType?: string;\n};\n\nexport class FetchError extends Error {\n public constructor(\n message: string,\n public readonly errorCode: string,\n public readonly statusCode: number,\n ) {\n super(message);\n }\n}\n\nexport type DatabaseListEntry = {\n name: string;\n kind: \"EntityContainer\";\n url: string;\n};\n\nexport type ServiceDocument<T> = {\n \"@odata.context\": string;\n value: T;\n};\n\nexport type Blob = {\n type: string;\n buffer: Buffer;\n};\n\ntype BatchOperation = {\n path: string;\n params: FetchParams | Promise<FetchParams>;\n resolve: (response: Response) => void;\n reject: (error: Error) => void;\n};\n\ntype Batch = {\n databaseName: string;\n operations: BatchOperation[];\n};\n\nexport type ConnectionOptions = {\n laxParsing?: boolean;\n disableSsl?: boolean;\n};\n\nclass Connection {\n private batch: Batch | null = null;\n private readonly options: ConnectionOptions;\n\n public constructor(\n private readonly hostname: string,\n private readonly authentication: Authentication,\n options: ConnectionOptions | boolean = {},\n ) {\n let finalOptions: ConnectionOptions;\n\n if (typeof options === \"boolean\") {\n console.info(\"Passing laxParsing directly is deprecated, use options object instead.\");\n finalOptions = { laxParsing: options };\n } else {\n finalOptions = options;\n }\n\n this.options = finalOptions;\n }\n\n public async listDatabases(): Promise<DatabaseListEntry[]> {\n if (this.batch) {\n throw new Error(\"Databases cannot be listed from a batched connection\");\n }\n\n const response = await this.fetchJson<ServiceDocument<DatabaseListEntry[]>>(\"\");\n return response.value;\n }\n\n public database(name: string): Database {\n if (this.batch) {\n throw new Error(\"Database objects cannot be created from a batched connection\");\n }\n\n return new Database(this, name);\n }\n\n /**\n * @internal\n */\n public batchConnection(databaseName: string): Connection {\n const connection = new Connection(this.hostname, this.authentication);\n connection.batch = { databaseName, operations: [] };\n return connection;\n }\n\n /**\n * @internal\n */\n public async executeBatch(): Promise<void> {\n if (!this.batch) {\n throw new Error(\"A batch has not been started\");\n }\n\n if (this.batch.operations.length === 0) {\n return;\n }\n\n const batchRequest = new BatchRequest(\n `${this.options.disableSsl ? \"http\" : \"https\"}://${this.hostname}/fmi/odata/v4/${this.batch.databaseName}`,\n await this.authentication.getAuthorizationHeader(),\n await Promise.all(\n this.batch.operations.map(async (operation) =>\n this.createRequest(operation.path, operation.params),\n ),\n ),\n );\n\n const response = await fetch(await batchRequest.toRequest());\n\n if (!response.ok) {\n throw new Error(\"Batch request failed\");\n }\n\n const body = await response.text();\n const responses = BatchRequest.parseMultipartResponse(body, response.headers);\n\n for (let i = 0; i < this.batch.operations.length; ++i) {\n if (!responses[i]) {\n this.batch.operations[i].reject(\n new Error(\"No matching response in batch response\"),\n );\n continue;\n }\n\n this.batch.operations[i].resolve(responses[i]);\n }\n }\n\n /**\n * @internal\n */\n public async fetchNone(\n path: string,\n params: FetchParams | Promise<FetchParams> = {},\n ): Promise<void> {\n await this.fetch(path, params);\n }\n\n /**\n * @internal\n */\n public async fetchJson<T>(\n path: string,\n params: FetchParams | Promise<FetchParams> = {},\n ): Promise<T> {\n const response = await this.fetch(path, params);\n\n if (response.status === 204) {\n throw new Error(\"Response included no content\");\n }\n\n return await this.parseResponseJson<T>(response);\n }\n\n /**\n * @internal\n */\n public async fetchBlob(\n path: string,\n params: FetchParams | Promise<FetchParams> = {},\n ): Promise<Blob> {\n const response = await this.fetch(path, params);\n const contentType = response.headers.get(\"Content-Type\");\n const arrayBuffer = await response.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n\n return { buffer, type: contentType ?? \"application/octet-stream\" };\n }\n\n private async fetch(\n path: string,\n params: FetchParams | Promise<FetchParams>,\n ): Promise<Response> {\n let response: Response;\n\n if (this.batch) {\n const batch = this.batch;\n response = await new Promise((resolve, reject) => {\n batch.operations.push({ path, params, resolve, reject });\n });\n } else {\n const request = await this.createRequest(path, params);\n response = await fetch(request);\n }\n\n if (!response.ok) {\n let errorCode = \"unknown\";\n let errorMessage = \"An unknown error occurred\";\n\n try {\n const data = await this.parseResponseJson<{\n error: {\n code: string;\n message: string;\n };\n }>(response);\n errorCode = data.error.code;\n errorMessage = data.error.message;\n } catch {\n // Ignore error.\n }\n\n throw new FetchError(errorMessage, errorCode, response.status);\n }\n\n return response;\n }\n\n private async createRequest(\n path: string,\n params: FetchParams | Promise<FetchParams>,\n ): Promise<Request> {\n const resolvedParams = await params;\n let url = `${this.options.disableSsl ? \"http\" : \"https\"}://${this.hostname}/fmi/odata/v4${path}`;\n\n if (resolvedParams.search) {\n url += `?${Connection.stringifySearch(resolvedParams.search)}`;\n }\n\n const headers = new Headers({\n Authorization: await this.authentication.getAuthorizationHeader(),\n Accept: \"application/json\",\n });\n\n if (resolvedParams.contentType) {\n headers.set(\"Content-Type\", resolvedParams.contentType);\n } else if (resolvedParams.method === \"POST\" || resolvedParams.method === \"PATCH\") {\n headers.set(\"Content-Type\", \"application/json\");\n }\n\n return new Request(url, {\n keepalive: true,\n method: resolvedParams.method,\n body: resolvedParams.body,\n headers,\n });\n }\n\n private async parseResponseJson<T = unknown>(response: Response): Promise<T> {\n if (!this.options.laxParsing) {\n return (await response.json()) as T;\n }\n\n const json = await response.text();\n const cleanedJson = json\n // biome-ignore lint/suspicious/noControlCharactersInRegex: we must strip control characters\n .replace(/[\\u0000-\\u0009\\u000b-\\u001f]/g, \"\")\n .replace(/\"(?:(?=(\\\\?))\\1.)*?\"/gs, (substring) => {\n return substring.replace(/(?<!\\\\)((?:\\\\\\\\)*)\\n/g, \"$1\\\\n\");\n });\n\n return (await JSON.parse(cleanedJson)) as T;\n }\n\n private static stringifySearch(search: URLSearchParams): string {\n const specialTokens = { \"%24\": \"$\", \"+\": \"%20\", \"%2F\": \"/\", \"%3D\": \"=\", \"%2C\": \",\" };\n return search\n .toString()\n .replace(\n /(%24|\\+|%2F|%3D|%2C)/g,\n (match) => specialTokens[match as keyof typeof specialTokens],\n );\n }\n}\n\nexport default Connection;\n","import type { Authentication } from \"./Connection.js\";\n\nclass BasicAuth implements Authentication {\n private readonly authorizationHeader: Promise<string>;\n\n public constructor(username: string, password: string) {\n this.authorizationHeader = Promise.resolve(\n `Basic ${Buffer.from(`${username}:${password}`).toString(\"base64\")}`,\n );\n }\n\n public async getAuthorizationHeader(): Promise<string> {\n return this.authorizationHeader;\n }\n}\n\nexport default BasicAuth;\n"],"mappings":"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