diff --git a/package-lock.json b/package-lock.json index bd40f1fe..9c640d5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,6 @@ "eslint-config-prettier": "^9.1.0", "playwright": "^1.48.0", "prettier": "^3.2.0", - "turbo": "2.8.20", "typescript": "^5.7.0", "vite": "^6.4.0", "vitest": "^3.2.4" @@ -165,10 +164,6 @@ "resolved": "packages/connections", "link": true }, - "node_modules/@databricks/sdk-core": { - "resolved": "packages/core", - "link": true - }, "node_modules/@databricks/sdk-credentials": { "resolved": "packages/credentials", "link": true @@ -647,7 +642,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -682,7 +676,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -717,7 +710,6 @@ "os": [ "openharmony" ], - "peer": true, "engines": { "node": ">=18" } @@ -1567,90 +1559,6 @@ "@testing-library/dom": ">=7.21.4" } }, - "node_modules/@turbo/darwin-64": { - "version": "2.8.20", - "resolved": "https://registry.npmjs.org/@turbo/darwin-64/-/darwin-64-2.8.20.tgz", - "integrity": "sha512-FQ9EX1xMU5nbwjxXxM3yU88AQQ6Sqc6S44exPRroMcx9XZHqqppl5ymJF0Ig/z3nvQNwDmz1Gsnvxubo+nXWjQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@turbo/darwin-arm64": { - "version": "2.8.20", - "resolved": "https://registry.npmjs.org/@turbo/darwin-arm64/-/darwin-arm64-2.8.20.tgz", - "integrity": "sha512-Gpyh9ATFGThD6/s9L95YWY54cizg/VRWl2B67h0yofG8BpHf67DFAh9nuJVKG7bY0+SBJDAo5cMur+wOl9YOYw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@turbo/linux-64": { - "version": "2.8.20", - "resolved": "https://registry.npmjs.org/@turbo/linux-64/-/linux-64-2.8.20.tgz", - "integrity": "sha512-p2QxWUYyYUgUFG0b0kR+pPi8t7c9uaVlRtjTTI1AbCvVqkpjUfCcReBn6DgG/Hu8xrWdKLuyQFaLYFzQskZbcA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@turbo/linux-arm64": { - "version": "2.8.20", - "resolved": "https://registry.npmjs.org/@turbo/linux-arm64/-/linux-arm64-2.8.20.tgz", - "integrity": "sha512-Gn5yjlZGLRZWarLWqdQzv0wMqyBNIdq1QLi48F1oY5Lo9kiohuf7BPQWtWxeNVS2NgJ1+nb/DzK1JduYC4AWOA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@turbo/windows-64": { - "version": "2.8.20", - "resolved": "https://registry.npmjs.org/@turbo/windows-64/-/windows-64-2.8.20.tgz", - "integrity": "sha512-vyaDpYk/8T6Qz5V/X+ihKvKFEZFUoC0oxYpC1sZanK6gaESJlmV3cMRT3Qhcg4D2VxvtC2Jjs9IRkrZGL+exLw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@turbo/windows-arm64": { - "version": "2.8.20", - "resolved": "https://registry.npmjs.org/@turbo/windows-arm64/-/windows-arm64-2.8.20.tgz", - "integrity": "sha512-voicVULvUV5yaGXo0Iue13BcHGYW3u0VgqSbfQwBaHbpj1zLjYV4KIe+7fYIo6DO8FVUJzxFps3ODCQG/Wy2Qw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -4263,7 +4171,6 @@ "os": [ "aix" ], - "peer": true, "engines": { "node": ">=18" } @@ -4281,7 +4188,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -4299,7 +4205,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -4317,7 +4222,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -4335,7 +4239,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -4353,7 +4256,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -4371,7 +4273,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -4389,7 +4290,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -4407,7 +4307,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -4425,7 +4324,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -4443,7 +4341,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -4461,7 +4358,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -4479,7 +4375,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -4497,7 +4392,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -4515,7 +4409,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -4533,7 +4426,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -4551,7 +4443,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -4569,7 +4460,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -4587,7 +4477,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -4605,7 +4494,6 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=18" } @@ -4623,7 +4511,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -4641,7 +4528,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -4659,7 +4545,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -4717,29 +4602,10 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/turbo": { - "version": "2.8.20", - "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.8.20.tgz", - "integrity": "sha512-Rb4qk5YT8RUwwdXtkLpkVhNEe/lor6+WV7S5tTlLpxSz6MjV5Qi8jGNn4gS6NAvrYGA/rNrE6YUQM85sCZUDbQ==", - "dev": true, - "license": "MIT", - "bin": { - "turbo": "bin/turbo" - }, - "optionalDependencies": { - "@turbo/darwin-64": "2.8.20", - "@turbo/darwin-arm64": "2.8.20", - "@turbo/linux-64": "2.8.20", - "@turbo/linux-arm64": "2.8.20", - "@turbo/windows-64": "2.8.20", - "@turbo/windows-arm64": "2.8.20" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5208,14 +5074,6 @@ "node": ">=22.0.0" } }, - "packages/core": { - "name": "@databricks/sdk-core", - "version": "0.1.0", - "license": "Apache-2.0", - "engines": { - "node": ">=22.0.0" - } - }, "packages/credentials": { "name": "@databricks/sdk-credentials", "version": "0.1.0", @@ -5348,7 +5206,9 @@ "version": "0.1.0", "license": "Apache-2.0", "dependencies": { - "@databricks/sdk-databricks": "*" + "@databricks/sdk-databricks": "*", + "@js-temporal/polyfill": "^0.5.0", + "zod": "^4.3.6" }, "engines": { "node": ">=22.0.0" diff --git a/packages/abacpolicies/src/v1/index.ts b/packages/abacpolicies/src/v1/index.ts index eedb19be..a21aad7b 100644 --- a/packages/abacpolicies/src/v1/index.ts +++ b/packages/abacpolicies/src/v1/index.ts @@ -6,6 +6,7 @@ export {PolicyType, SecurableType} from './model'; export type { ColumnMaskOptions, + ColumnTagValueExtraction, CreatePolicy, DeletePolicy, DeletePolicy_Response, @@ -16,7 +17,9 @@ export type { ListPolicies, ListPolicies_Response, MatchColumn, + MetadataExtractionExpression, PolicyInfo, RowFilterOptions, + TagValueExtraction, UpdatePolicy, } from './model'; diff --git a/packages/abacpolicies/src/v1/model.ts b/packages/abacpolicies/src/v1/model.ts index ea141069..383bd2bc 100644 --- a/packages/abacpolicies/src/v1/model.ts +++ b/packages/abacpolicies/src/v1/model.ts @@ -54,6 +54,14 @@ export interface ColumnMaskOptions { using?: FunctionArgument[] | undefined; } +/** Extracts the value of a column-level tag: get_column_tag_value(col, "tagKey"). */ +export interface ColumnTagValueExtraction { + /** The alias from MATCH COLUMNS that identifies the column. */ + columnAlias?: string | undefined; + /** 1024 matches the max_length on FunctionArgument.constant above. */ + tagKey?: string | undefined; +} + export interface CreatePolicy { /** Required. The policy to create. */ policyInfo?: PolicyInfo | undefined; @@ -86,6 +94,8 @@ export interface FunctionArgument { alias?: string | undefined; /** A constant literal. */ constant?: string | undefined; + /** A metadata extraction expression resolved at query time. */ + metadataExtraction?: MetadataExtractionExpression | undefined; } export interface GetPolicy { @@ -145,6 +155,14 @@ export interface MatchColumn { alias?: string | undefined; } +/** An expression that extracts metadata at query time. */ +export interface MetadataExtractionExpression { + /** Extracts the value of a securable-level tag. */ + tagValue?: TagValueExtraction | undefined; + /** Extracts the value of a column-level tag. */ + columnTagValue?: ColumnTagValueExtraction | undefined; +} + export interface PolicyInfo { /** Unique identifier of the policy. This field is output only and is generated by the system. */ id?: string | undefined; @@ -247,6 +265,12 @@ export interface RowFilterOptions { using?: FunctionArgument[] | undefined; } +/** Extracts the value of a securable-level tag: get_tag_value("tagKey"). */ +export interface TagValueExtraction { + /** 1024 matches the max_length on FunctionArgument.constant above. */ + tagKey?: string | undefined; +} + export interface UpdatePolicy { /** Required. The type of the securable to update the policy for. */ onSecurableType?: string | undefined; @@ -283,6 +307,17 @@ export const unmarshalColumnMaskOptionsSchema: z.ZodType = z using: d.using, })); +export const unmarshalColumnTagValueExtractionSchema: z.ZodType = + z + .object({ + column_alias: z.string().optional(), + tag_key: z.string().optional(), + }) + .transform(d => ({ + columnAlias: d.column_alias, + tagKey: d.tag_key, + })); + export const unmarshalCreatePolicySchema: z.ZodType = z .object({ policy_info: z.lazy(() => unmarshalPolicyInfoSchema).optional(), @@ -319,10 +354,14 @@ export const unmarshalFunctionArgumentSchema: z.ZodType = z .object({ alias: z.string().optional(), constant: z.string().optional(), + metadata_extraction: z + .lazy(() => unmarshalMetadataExtractionExpressionSchema) + .optional(), }) .transform(d => ({ alias: d.alias, constant: d.constant, + metadataExtraction: d.metadata_extraction, })); export const unmarshalGetPolicySchema: z.ZodType = z @@ -383,6 +422,19 @@ export const unmarshalMatchColumnSchema: z.ZodType = z alias: d.alias, })); +export const unmarshalMetadataExtractionExpressionSchema: z.ZodType = + z + .object({ + tag_value: z.lazy(() => unmarshalTagValueExtractionSchema).optional(), + column_tag_value: z + .lazy(() => unmarshalColumnTagValueExtractionSchema) + .optional(), + }) + .transform(d => ({ + tagValue: d.tag_value, + columnTagValue: d.column_tag_value, + })); + export const unmarshalPolicyInfoSchema: z.ZodType = z .object({ id: z.string().optional(), @@ -439,6 +491,15 @@ export const unmarshalRowFilterOptionsSchema: z.ZodType = z using: d.using, })); +export const unmarshalTagValueExtractionSchema: z.ZodType = + z + .object({ + tag_key: z.string().optional(), + }) + .transform(d => ({ + tagKey: d.tag_key, + })); + export const unmarshalUpdatePolicySchema: z.ZodType = z .object({ on_securable_type: z.string().optional(), @@ -467,6 +528,16 @@ export const marshalColumnMaskOptionsSchema: z.ZodType = z using: d.using, })); +export const marshalColumnTagValueExtractionSchema: z.ZodType = z + .object({ + columnAlias: z.string().optional(), + tagKey: z.string().optional(), + }) + .transform(d => ({ + column_alias: d.columnAlias, + tag_key: d.tagKey, + })); + export const marshalCreatePolicySchema: z.ZodType = z .object({ policyInfo: z.lazy(() => marshalPolicyInfoSchema).optional(), @@ -502,10 +573,14 @@ export const marshalFunctionArgumentSchema: z.ZodType = z .object({ alias: z.string().optional(), constant: z.string().optional(), + metadataExtraction: z + .lazy(() => marshalMetadataExtractionExpressionSchema) + .optional(), }) .transform(d => ({ alias: d.alias, constant: d.constant, + metadata_extraction: d.metadataExtraction, })); export const marshalGetPolicySchema: z.ZodType = z @@ -565,6 +640,18 @@ export const marshalMatchColumnSchema: z.ZodType = z alias: d.alias, })); +export const marshalMetadataExtractionExpressionSchema: z.ZodType = z + .object({ + tagValue: z.lazy(() => marshalTagValueExtractionSchema).optional(), + columnTagValue: z + .lazy(() => marshalColumnTagValueExtractionSchema) + .optional(), + }) + .transform(d => ({ + tag_value: d.tagValue, + column_tag_value: d.columnTagValue, + })); + export const marshalPolicyInfoSchema: z.ZodType = z .object({ id: z.string().optional(), @@ -621,6 +708,14 @@ export const marshalRowFilterOptionsSchema: z.ZodType = z using: d.using, })); +export const marshalTagValueExtractionSchema: z.ZodType = z + .object({ + tagKey: z.string().optional(), + }) + .transform(d => ({ + tag_key: d.tagKey, + })); + export const marshalUpdatePolicySchema: z.ZodType = z .object({ onSecurableType: z.string().optional(), diff --git a/packages/features/src/v1/model.ts b/packages/features/src/v1/model.ts index 9fdf8b0c..45cee41b 100644 --- a/packages/features/src/v1/model.ts +++ b/packages/features/src/v1/model.ts @@ -477,6 +477,8 @@ export interface MaterializedFeature { lastMaterializationTime?: Temporal.Instant | undefined; /** The quartz cron expression that defines the schedule of the materialization pipeline. The schedule is evaluated in the UTC timezone. */ cronSchedule?: string | undefined; + /** True if this is an online materialized feature. False if it is an offline materialized feature. */ + isOnline?: boolean | undefined; } /** Computes the maximum value. */ @@ -1169,6 +1171,7 @@ export const unmarshalMaterializedFeatureSchema: z.ZodType .transform(s => Temporal.Instant.from(s)) .optional(), cron_schedule: z.string().optional(), + is_online: z.boolean().optional(), }) .transform(d => ({ materializedFeatureId: d.materialized_feature_id, @@ -1179,6 +1182,7 @@ export const unmarshalMaterializedFeatureSchema: z.ZodType pipelineScheduleState: d.pipeline_schedule_state, lastMaterializationTime: d.last_materialization_time, cronSchedule: d.cron_schedule, + isOnline: d.is_online, })); export const unmarshalMaxFunctionSchema: z.ZodType = z @@ -1872,6 +1876,7 @@ export const marshalMaterializedFeatureSchema: z.ZodType = z .transform((d: Temporal.Instant) => d.toString()) .optional(), cronSchedule: z.string().optional(), + isOnline: z.boolean().optional(), }) .transform(d => ({ materialized_feature_id: d.materializedFeatureId, @@ -1882,6 +1887,7 @@ export const marshalMaterializedFeatureSchema: z.ZodType = z pipeline_schedule_state: d.pipelineScheduleState, last_materialization_time: d.lastMaterializationTime, cron_schedule: d.cronSchedule, + is_online: d.isOnline, })); export const marshalMaxFunctionSchema: z.ZodType = z diff --git a/packages/files/package.json b/packages/files/package.json index eef0d8b5..7d1ae448 100644 --- a/packages/files/package.json +++ b/packages/files/package.json @@ -1,12 +1,12 @@ { "name": "@databricks/sdk-files", "version": "0.1.0", - "description": "Databricks Files service client", + "description": "", "type": "module", "exports": { - "./v1": { - "types": "./dist/v1/index.d.ts", - "import": "./dist/v1/index.js" + "./v2": { + "types": "./dist/v2/index.d.ts", + "import": "./dist/v2/index.js" } }, "files": [ @@ -15,19 +15,21 @@ ], "scripts": { "build": "tsc -b", - "lint": "eslint src tests --ext .ts", - "lint:fix": "eslint src tests --ext .ts --fix", - "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"", - "format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"", + "lint": "eslint src --ext .ts", + "lint:fix": "eslint src --ext .ts --fix", + "format": "prettier --write \"src/**/*.ts\"", + "format:check": "prettier --check \"src/**/*.ts\"", "typecheck": "tsc --noEmit", "clean": "rm -rf dist tsconfig.tsbuildinfo", - "test": "vitest run", - "test:browser": "vitest run --config vitest.config.browser.ts" + "test": "echo 'no tests'", + "test:browser": "echo 'no tests'" }, "author": "Databricks", "license": "Apache-2.0", "dependencies": { - "@databricks/sdk-databricks": "*" + "@databricks/sdk-databricks": "*", + "@js-temporal/polyfill": "^0.5.0", + "zod": "^4.3.6" }, "engines": { "node": ">=22.0.0" diff --git a/packages/files/src/v2/client.ts b/packages/files/src/v2/client.ts new file mode 100644 index 00000000..ae565c13 --- /dev/null +++ b/packages/files/src/v2/client.ts @@ -0,0 +1,723 @@ +// Code generated from API definition by Databricks SDK Generator. DO NOT EDIT. + +import type {Call, Options} from '@databricks/sdk-databricks/api'; +import {execute} from '@databricks/sdk-databricks/api'; +import type {Logger} from '@databricks/sdk-databricks/logger'; +import {NoOpLogger} from '@databricks/sdk-databricks/logger'; +import type {ClientOptions} from '@databricks/sdk-databricks/options'; +import type { + HttpClient, + HttpRequest, +} from '@databricks/sdk-databricks/transport'; +import {newHttpClient} from '@databricks/sdk-databricks/transport'; +import { + buildHttpRequest, + executeHttpCall, + marshalRequest, + parseResponse, + sendAndCheckError, +} from './utils'; +import type { + AddBlock, + AddBlock_Response, + Close, + Close_Response, + Create, + CreateDirectoryRequest, + CreateDirectoryResponse, + Create_Response, + Delete, + DeleteDirectoryRequest, + DeleteDirectoryResponse, + DeleteFileRequest, + DeleteFileResponse, + Delete_Response, + DirectoryEntry, + DownloadFileRequest, + DownloadFileResponse, + GetDirectoryMetadataRequest, + GetDirectoryMetadataResponse, + GetFileMetadataRequest, + GetFileMetadataResponse, + GetStatus, + GetStatus_Response, + ListDirectoryContentsRequest, + ListDirectoryResponse, + ListStatus, + ListStatus_Response, + MkDirs, + MkDirs_Response, + Move, + Move_Response, + Put, + Put_Response, + Read, + Read_Response, + UploadFileRequest, + UploadFileResponse, +} from './model'; +import { + marshalAddBlockSchema, + marshalCloseSchema, + marshalCreateSchema, + marshalDeleteSchema, + marshalMkDirsSchema, + marshalMoveSchema, + marshalPutSchema, + unmarshalAddBlock_ResponseSchema, + unmarshalClose_ResponseSchema, + unmarshalCreateDirectoryResponseSchema, + unmarshalCreate_ResponseSchema, + unmarshalDeleteDirectoryResponseSchema, + unmarshalDeleteFileResponseSchema, + unmarshalDelete_ResponseSchema, + unmarshalGetDirectoryMetadataResponseSchema, + unmarshalGetFileMetadataResponseSchema, + unmarshalGetStatus_ResponseSchema, + unmarshalListDirectoryResponseSchema, + unmarshalListStatus_ResponseSchema, + unmarshalMkDirs_ResponseSchema, + unmarshalMove_ResponseSchema, + unmarshalPut_ResponseSchema, + unmarshalRead_ResponseSchema, + unmarshalUploadFileResponseSchema, +} from './model'; + +export class Client { + private readonly host: string; + private readonly httpClient: HttpClient; + private readonly logger: Logger; + + constructor(options: ClientOptions) { + if (options.host === undefined) { + throw new Error('Host is required.'); + } + this.host = options.host.replace(/\/$/, ''); + this.logger = options.logger ?? new NoOpLogger(); + this.httpClient = newHttpClient(options); + } + + /** + * Appends a block of data to the stream specified by the input handle. If the handle does not + * exist, this call will throw an exception with ``RESOURCE_DOES_NOT_EXIST``. + * + * If the block of data exceeds 1 MB, this call will throw an exception with ``MAX_BLOCK_SIZE_EXCEEDED``. + */ + async addBlock( + signal: AbortSignal | undefined, + req: AddBlock, + options?: Options + ): Promise { + const url = `${this.host}/api/2.0/dbfs/add-block`; + const body = marshalRequest(req, marshalAddBlockSchema); + let resp: AddBlock_Response | undefined; + const call: Call = async (callSignal?: AbortSignal): Promise => { + const httpReq = buildHttpRequest('POST', url, callSignal, body); + const respBody = await executeHttpCall({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + resp = parseResponse(respBody, unmarshalAddBlock_ResponseSchema); + }; + await execute(signal, call, options); + if (resp === undefined) { + throw new Error('API call completed without a result.'); + } + return resp; + } + + /** + * Closes the stream specified by the input handle. If the handle does not exist, this call + * throws an exception with ``RESOURCE_DOES_NOT_EXIST``. + */ + async close( + signal: AbortSignal | undefined, + req: Close, + options?: Options + ): Promise { + const url = `${this.host}/api/2.0/dbfs/close`; + const body = marshalRequest(req, marshalCloseSchema); + let resp: Close_Response | undefined; + const call: Call = async (callSignal?: AbortSignal): Promise => { + const httpReq = buildHttpRequest('POST', url, callSignal, body); + const respBody = await executeHttpCall({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + resp = parseResponse(respBody, unmarshalClose_ResponseSchema); + }; + await execute(signal, call, options); + if (resp === undefined) { + throw new Error('API call completed without a result.'); + } + return resp; + } + + /** + * Opens a stream to write to a file and returns a handle to this stream. + * There is a 10 minute idle timeout on this handle. If a file or directory already exists on the given path + * and __overwrite__ is set to false, this call will throw an exception with ``RESOURCE_ALREADY_EXISTS``. + * + * A typical workflow for file upload would be: + * + * 1. Issue a ``create`` call and get a handle. + * 2. Issue one or more ``add-block`` calls with the handle you have. + * 3. Issue a ``close`` call with the handle you have. + */ + async create( + signal: AbortSignal | undefined, + req: Create, + options?: Options + ): Promise { + const url = `${this.host}/api/2.0/dbfs/create`; + const body = marshalRequest(req, marshalCreateSchema); + let resp: Create_Response | undefined; + const call: Call = async (callSignal?: AbortSignal): Promise => { + const httpReq = buildHttpRequest('POST', url, callSignal, body); + const respBody = await executeHttpCall({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + resp = parseResponse(respBody, unmarshalCreate_ResponseSchema); + }; + await execute(signal, call, options); + if (resp === undefined) { + throw new Error('API call completed without a result.'); + } + return resp; + } + + /** + * Delete the file or directory (optionally recursively delete all files in the directory). + * This call throws an exception with `IO_ERROR` if the path is a non-empty directory and `recursive` is set to + * `false` or on other similar errors. + * + * When you delete a large number of files, the delete operation is done in increments. The call returns + * a response after approximately 45 seconds with an error message (503 Service Unavailable) asking you to + * re-invoke the delete operation until the directory structure is fully deleted. + * + * For operations that delete more than 10K files, we discourage using the DBFS REST API, but advise you to + * perform such operations in the context of a cluster, using + * the [File system utility (dbutils.fs)](/dev-tools/databricks-utils.html#dbutils-fs). `dbutils.fs` + * covers the functional scope of the DBFS REST API, but from notebooks. Running such operations using notebooks + * provides better control and manageability, such as selective deletes, and the possibility to automate periodic + * delete jobs. + */ + async delete( + signal: AbortSignal | undefined, + req: Delete, + options?: Options + ): Promise { + const url = `${this.host}/api/2.0/dbfs/delete`; + const body = marshalRequest(req, marshalDeleteSchema); + let resp: Delete_Response | undefined; + const call: Call = async (callSignal?: AbortSignal): Promise => { + const httpReq = buildHttpRequest('POST', url, callSignal, body); + const respBody = await executeHttpCall({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + resp = parseResponse(respBody, unmarshalDelete_ResponseSchema); + }; + await execute(signal, call, options); + if (resp === undefined) { + throw new Error('API call completed without a result.'); + } + return resp; + } + + /** + * Gets the file information for a file or directory. + * If the file or directory does not exist, this call throws an exception with `RESOURCE_DOES_NOT_EXIST`. + */ + async getStatus( + signal: AbortSignal | undefined, + req: GetStatus, + options?: Options + ): Promise { + const url = `${this.host}/api/2.0/dbfs/get-status`; + const params = new URLSearchParams(); + if (req.path !== undefined) { + params.append('path', req.path); + } + const query = params.toString(); + const fullUrl = query !== '' ? `${url}?${query}` : url; + let resp: GetStatus_Response | undefined; + const call: Call = async (callSignal?: AbortSignal): Promise => { + const httpReq = buildHttpRequest('GET', fullUrl, callSignal); + const respBody = await executeHttpCall({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + resp = parseResponse(respBody, unmarshalGetStatus_ResponseSchema); + }; + await execute(signal, call, options); + if (resp === undefined) { + throw new Error('API call completed without a result.'); + } + return resp; + } + + /** + * List the contents of a directory, or details of the file. If the file or directory does not exist, this call + * throws an exception with `RESOURCE_DOES_NOT_EXIST`. + * + * When calling list on a large directory, the list operation will time out after approximately 60 seconds. + * We strongly recommend using list only on directories containing less than 10K files and discourage using + * the DBFS REST API for operations that list more than 10K files. Instead, we recommend that you perform such + * operations in the context of a cluster, using + * the [File system utility (dbutils.fs)](/dev-tools/databricks-utils.html#dbutils-fs), which provides the same + * functionality without timing out. + */ + async list( + signal: AbortSignal | undefined, + req: ListStatus, + options?: Options + ): Promise { + const url = `${this.host}/api/2.0/dbfs/list`; + const params = new URLSearchParams(); + if (req.path !== undefined) { + params.append('path', req.path); + } + const query = params.toString(); + const fullUrl = query !== '' ? `${url}?${query}` : url; + let resp: ListStatus_Response | undefined; + const call: Call = async (callSignal?: AbortSignal): Promise => { + const httpReq = buildHttpRequest('GET', fullUrl, callSignal); + const respBody = await executeHttpCall({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + resp = parseResponse(respBody, unmarshalListStatus_ResponseSchema); + }; + await execute(signal, call, options); + if (resp === undefined) { + throw new Error('API call completed without a result.'); + } + return resp; + } + + /** + * Creates the given directory and necessary parent directories if they do not exist. + * If a file (not a directory) exists at any prefix of the input path, this call throws an exception with `RESOURCE_ALREADY_EXISTS`. + * **Note**: If this operation fails, it might have succeeded in creating some of the necessary parent directories. + */ + async mkdirs( + signal: AbortSignal | undefined, + req: MkDirs, + options?: Options + ): Promise { + const url = `${this.host}/api/2.0/dbfs/mkdirs`; + const body = marshalRequest(req, marshalMkDirsSchema); + let resp: MkDirs_Response | undefined; + const call: Call = async (callSignal?: AbortSignal): Promise => { + const httpReq = buildHttpRequest('POST', url, callSignal, body); + const respBody = await executeHttpCall({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + resp = parseResponse(respBody, unmarshalMkDirs_ResponseSchema); + }; + await execute(signal, call, options); + if (resp === undefined) { + throw new Error('API call completed without a result.'); + } + return resp; + } + + /** + * Moves a file from one location to another location within DBFS. + * If the source file does not exist, this call throws an exception with `RESOURCE_DOES_NOT_EXIST`. + * If a file already exists in the destination path, this call throws an exception with `RESOURCE_ALREADY_EXISTS`. + * If the given source path is a directory, this call always recursively moves all files. + */ + async move( + signal: AbortSignal | undefined, + req: Move, + options?: Options + ): Promise { + const url = `${this.host}/api/2.0/dbfs/move`; + const body = marshalRequest(req, marshalMoveSchema); + let resp: Move_Response | undefined; + const call: Call = async (callSignal?: AbortSignal): Promise => { + const httpReq = buildHttpRequest('POST', url, callSignal, body); + const respBody = await executeHttpCall({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + resp = parseResponse(respBody, unmarshalMove_ResponseSchema); + }; + await execute(signal, call, options); + if (resp === undefined) { + throw new Error('API call completed without a result.'); + } + return resp; + } + + /** + * Uploads a file through the use of multipart form post. + * It is mainly used for streaming uploads, but can also be used as a convenient single call for data upload. + * + * Alternatively you can pass contents as base64 string. + * + * The amount of data that can be passed (when not streaming) using the __contents__ parameter is limited to 1 MB. + * `MAX_BLOCK_SIZE_EXCEEDED` will be thrown if this limit is exceeded. + * + * If you want to upload large files, use the streaming upload. For details, see :method:dbfs/create, + * :method:dbfs/addBlock, :method:dbfs/close. + */ + async put( + signal: AbortSignal | undefined, + req: Put, + options?: Options + ): Promise { + const url = `${this.host}/api/2.0/dbfs/put`; + const body = marshalRequest(req, marshalPutSchema); + let resp: Put_Response | undefined; + const call: Call = async (callSignal?: AbortSignal): Promise => { + const httpReq = buildHttpRequest('POST', url, callSignal, body); + const respBody = await executeHttpCall({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + resp = parseResponse(respBody, unmarshalPut_ResponseSchema); + }; + await execute(signal, call, options); + if (resp === undefined) { + throw new Error('API call completed without a result.'); + } + return resp; + } + + /** + * Returns the contents of a file. If the file does not exist, this call throws an exception with `RESOURCE_DOES_NOT_EXIST`. + * If the path is a directory, the read length is negative, or if the offset is negative, this call throws an exception with + * `INVALID_PARAMETER_VALUE`. If the read length exceeds 1 MB, this call throws an + * exception with `MAX_READ_SIZE_EXCEEDED`. + * + * If `offset + length` exceeds the number of bytes in a file, it reads the contents until the end of file. + */ + async read( + signal: AbortSignal | undefined, + req: Read, + options?: Options + ): Promise { + const url = `${this.host}/api/2.0/dbfs/read`; + const params = new URLSearchParams(); + if (req.path !== undefined) { + params.append('path', req.path); + } + if (req.offset !== undefined) { + params.append('offset', String(req.offset)); + } + if (req.length !== undefined) { + params.append('length', String(req.length)); + } + const query = params.toString(); + const fullUrl = query !== '' ? `${url}?${query}` : url; + let resp: Read_Response | undefined; + const call: Call = async (callSignal?: AbortSignal): Promise => { + const httpReq = buildHttpRequest('GET', fullUrl, callSignal); + const respBody = await executeHttpCall({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + resp = parseResponse(respBody, unmarshalRead_ResponseSchema); + }; + await execute(signal, call, options); + if (resp === undefined) { + throw new Error('API call completed without a result.'); + } + return resp; + } + + /** + * Creates an empty directory. If necessary, also creates any parent directories of the + * new, empty directory (like the shell command `mkdir -p`). If called on an existing + * directory, returns a success response; this method is idempotent (it will succeed if the directory already + * exists). + */ + async createDirectory( + signal: AbortSignal | undefined, + req: CreateDirectoryRequest, + options?: Options + ): Promise { + const url = `${this.host}/api/2.0/fs/directories${(req.directoryPath ?? '').split('/').map(encodeURIComponent).join('/')}`; + let resp: CreateDirectoryResponse | undefined; + const call: Call = async (callSignal?: AbortSignal): Promise => { + const httpReq = buildHttpRequest('PUT', url, callSignal); + const respBody = await executeHttpCall({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + resp = parseResponse(respBody, unmarshalCreateDirectoryResponseSchema); + }; + await execute(signal, call, options); + if (resp === undefined) { + throw new Error('API call completed without a result.'); + } + return resp; + } + + /** + * Deletes an empty directory. + * + * To delete a non-empty directory, first delete all of its contents. This can be done + * by listing the directory contents and deleting each file and subdirectory recursively. + */ + async deleteDirectory( + signal: AbortSignal | undefined, + req: DeleteDirectoryRequest, + options?: Options + ): Promise { + const url = `${this.host}/api/2.0/fs/directories${(req.directoryPath ?? '').split('/').map(encodeURIComponent).join('/')}`; + let resp: DeleteDirectoryResponse | undefined; + const call: Call = async (callSignal?: AbortSignal): Promise => { + const httpReq = buildHttpRequest('DELETE', url, callSignal); + const respBody = await executeHttpCall({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + resp = parseResponse(respBody, unmarshalDeleteDirectoryResponseSchema); + }; + await execute(signal, call, options); + if (resp === undefined) { + throw new Error('API call completed without a result.'); + } + return resp; + } + + /** Deletes a file. If the request is successful, there is no response body. */ + async deleteFile( + signal: AbortSignal | undefined, + req: DeleteFileRequest, + options?: Options + ): Promise { + const url = `${this.host}/api/2.0/fs/files${(req.filePath ?? '').split('/').map(encodeURIComponent).join('/')}`; + let resp: DeleteFileResponse | undefined; + const call: Call = async (callSignal?: AbortSignal): Promise => { + const httpReq = buildHttpRequest('DELETE', url, callSignal); + const respBody = await executeHttpCall({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + resp = parseResponse(respBody, unmarshalDeleteFileResponseSchema); + }; + await execute(signal, call, options); + if (resp === undefined) { + throw new Error('API call completed without a result.'); + } + return resp; + } + + /** + * Downloads a file. The file contents are the response body. This is a + * standard HTTP file download, not a JSON RPC. It supports the + * Range and If-Unmodified-Since HTTP headers. + */ + async downloadFile( + signal: AbortSignal | undefined, + req: DownloadFileRequest + ): Promise { + const url = `${this.host}/api/2.0/fs/files${(req.filePath ?? '').split('/').map(encodeURIComponent).join('/')}`; + const params = new URLSearchParams(); + if (req.range !== undefined) { + params.append('Range', req.range); + } + if (req.ifUnmodifiedSince !== undefined) { + params.append('If-Unmodified-Since', req.ifUnmodifiedSince); + } + const query = params.toString(); + const fullUrl = query !== '' ? `${url}?${query}` : url; + const headers = new Headers(); + const httpReq: HttpRequest = { + url: fullUrl, + method: 'GET', + headers, + ...(signal !== undefined && {signal}), + }; + const resp = await sendAndCheckError({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + return { + contentLength: resp.headers.get('content-length') ?? undefined, + contentType: resp.headers.get('content-type') ?? undefined, + lastModified: resp.headers.get('last-modified') ?? undefined, + contents: resp.body ?? undefined, + }; + } + + /** + * Get the metadata of a directory. The response HTTP headers contain the metadata. + * There is no response body. + * + * This method is useful to check if a directory exists and the caller has access to it. + * + * If you wish to ensure the directory exists, you can instead use `PUT`, which will create + * the directory if it does not exist, and is idempotent (it will succeed if the directory + * already exists). + */ + async getDirectoryMetadata( + signal: AbortSignal | undefined, + req: GetDirectoryMetadataRequest, + options?: Options + ): Promise { + const url = `${this.host}/api/2.0/fs/directories${(req.directoryPath ?? '').split('/').map(encodeURIComponent).join('/')}`; + let resp: GetDirectoryMetadataResponse | undefined; + const call: Call = async (callSignal?: AbortSignal): Promise => { + const httpReq = buildHttpRequest('head', url, callSignal); + const respBody = await executeHttpCall({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + resp = parseResponse( + respBody, + unmarshalGetDirectoryMetadataResponseSchema + ); + }; + await execute(signal, call, options); + if (resp === undefined) { + throw new Error('API call completed without a result.'); + } + return resp; + } + + /** + * Get the metadata of a file. The response HTTP headers contain the metadata. There is no + * response body. + */ + async getFileMetadata( + signal: AbortSignal | undefined, + req: GetFileMetadataRequest, + options?: Options + ): Promise { + const url = `${this.host}/api/2.0/fs/files${(req.filePath ?? '').split('/').map(encodeURIComponent).join('/')}`; + const params = new URLSearchParams(); + if (req.range !== undefined) { + params.append('Range', req.range); + } + if (req.ifUnmodifiedSince !== undefined) { + params.append('If-Unmodified-Since', req.ifUnmodifiedSince); + } + const query = params.toString(); + const fullUrl = query !== '' ? `${url}?${query}` : url; + let resp: GetFileMetadataResponse | undefined; + const call: Call = async (callSignal?: AbortSignal): Promise => { + const httpReq = buildHttpRequest('head', fullUrl, callSignal); + const respBody = await executeHttpCall({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + resp = parseResponse(respBody, unmarshalGetFileMetadataResponseSchema); + }; + await execute(signal, call, options); + if (resp === undefined) { + throw new Error('API call completed without a result.'); + } + return resp; + } + + /** + * Returns the contents of a directory. + * If there is no directory at the specified path, the API returns a HTTP 404 error. + */ + async listDirectoryContents( + signal: AbortSignal | undefined, + req: ListDirectoryContentsRequest, + options?: Options + ): Promise { + const url = `${this.host}/api/2.0/fs/directories${(req.directoryPath ?? '').split('/').map(encodeURIComponent).join('/')}`; + const params = new URLSearchParams(); + if (req.pageSize !== undefined) { + params.append('page_size', String(req.pageSize)); + } + if (req.pageToken !== undefined) { + params.append('page_token', req.pageToken); + } + const query = params.toString(); + const fullUrl = query !== '' ? `${url}?${query}` : url; + let resp: ListDirectoryResponse | undefined; + const call: Call = async (callSignal?: AbortSignal): Promise => { + const httpReq = buildHttpRequest('GET', fullUrl, callSignal); + const respBody = await executeHttpCall({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + resp = parseResponse(respBody, unmarshalListDirectoryResponseSchema); + }; + await execute(signal, call, options); + if (resp === undefined) { + throw new Error('API call completed without a result.'); + } + return resp; + } + + async *listDirectoryContentsIter( + signal: AbortSignal | undefined, + req: ListDirectoryContentsRequest, + options?: Options + ): AsyncGenerator { + const pageReq: ListDirectoryContentsRequest = {...req}; + for (;;) { + const resp = await this.listDirectoryContents(signal, pageReq, options); + for (const item of resp.contents ?? []) { + yield item; + } + if (resp.nextPageToken === undefined || resp.nextPageToken === '') { + return; + } + pageReq.pageToken = resp.nextPageToken; + } + } + + /** + * Uploads a file of up to 5 GiB. The file contents should be sent as the request body as + * raw bytes (an octet stream); do not encode or otherwise modify the bytes before sending. + * The contents of the resulting file will be exactly the bytes sent in the request body. + * If the request is successful, there is no response body. + */ + async uploadFile( + signal: AbortSignal | undefined, + req: UploadFileRequest + ): Promise { + const url = `${this.host}/api/2.0/fs/files${(req.filePath ?? '').split('/').map(encodeURIComponent).join('/')}`; + const params = new URLSearchParams(); + if (req.overwrite !== undefined) { + params.append('overwrite', String(req.overwrite)); + } + const query = params.toString(); + const fullUrl = query !== '' ? `${url}?${query}` : url; + const headers = new Headers(); + headers.set('Content-Type', 'application/octet-stream'); + const httpReq: HttpRequest = { + url: fullUrl, + method: 'PUT', + headers, + body: req.contents, + ...(signal !== undefined && {signal}), + }; + const resp = await sendAndCheckError({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + } +} diff --git a/packages/files/src/v2/index.ts b/packages/files/src/v2/index.ts new file mode 100644 index 00000000..537047f3 --- /dev/null +++ b/packages/files/src/v2/index.ts @@ -0,0 +1,46 @@ +// Code generated from API definition by Databricks SDK Generator. DO NOT EDIT. + +export {Client} from './client'; + +export {} from './model'; + +export type { + AddBlock, + AddBlock_Response, + Close, + Close_Response, + Create, + Create_Response, + CreateDirectoryRequest, + CreateDirectoryResponse, + Delete, + Delete_Response, + DeleteDirectoryRequest, + DeleteDirectoryResponse, + DeleteFileRequest, + DeleteFileResponse, + DirectoryEntry, + DownloadFileRequest, + DownloadFileResponse, + FileInfo, + GetDirectoryMetadataRequest, + GetDirectoryMetadataResponse, + GetFileMetadataRequest, + GetFileMetadataResponse, + GetStatus, + GetStatus_Response, + ListDirectoryContentsRequest, + ListDirectoryResponse, + ListStatus, + ListStatus_Response, + MkDirs, + MkDirs_Response, + Move, + Move_Response, + Put, + Put_Response, + Read, + Read_Response, + UploadFileRequest, + UploadFileResponse, +} from './model'; diff --git a/packages/files/src/v2/model.ts b/packages/files/src/v2/model.ts new file mode 100644 index 00000000..6ed563b2 --- /dev/null +++ b/packages/files/src/v2/model.ts @@ -0,0 +1,981 @@ +// Code generated from API definition by Databricks SDK Generator. DO NOT EDIT. + +import {z} from 'zod'; + +export interface AddBlock { + /** The handle on an open stream. */ + handle?: number | undefined; + /** The base64-encoded data to append to the stream. This has a limit of 1 MB. */ + data?: Uint8Array | undefined; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-object-type -- Proto-style nested message name. +export interface AddBlock_Response {} + +export interface Close { + /** The handle on an open stream. */ + handle?: number | undefined; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-object-type -- Proto-style nested message name. +export interface Close_Response {} + +export interface Create { + /** The path of the new file. The path should be the absolute DBFS path. */ + path?: string | undefined; + /** The flag that specifies whether to overwrite existing file/files. */ + overwrite?: boolean | undefined; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export interface Create_Response { + /** Handle which should subsequently be passed into the AddBlock and Close calls when writing to a file through a stream. */ + handle?: number | undefined; +} + +/** Create a directory */ +export interface CreateDirectoryRequest { + /** The absolute path of a directory. */ + directoryPath?: string | undefined; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface CreateDirectoryResponse {} + +export interface Delete { + /** The path of the file or directory to delete. The path should be the absolute DBFS path. */ + path?: string | undefined; + /** Whether or not to recursively delete the directory's contents. Deleting empty directories can be done without providing the recursive flag. */ + recursive?: boolean | undefined; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-object-type -- Proto-style nested message name. +export interface Delete_Response {} + +/** Delete a directory */ +export interface DeleteDirectoryRequest { + /** The absolute path of a directory. */ + directoryPath?: string | undefined; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface DeleteDirectoryResponse {} + +/** Delete a file */ +export interface DeleteFileRequest { + /** The absolute path of the file. */ + filePath?: string | undefined; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface DeleteFileResponse {} + +export interface DirectoryEntry { + /** The length of the file in bytes. This field is omitted for directories. */ + fileSize?: number | undefined; + /** True if the path is a directory. */ + isDirectory?: boolean | undefined; + /** Last modification time of given file in milliseconds since unix epoch. */ + lastModified?: number | undefined; + /** The name of the file or directory. This is the last component of the path. */ + name?: string | undefined; + /** The absolute path of the file or directory. */ + path?: string | undefined; +} + +/** Download a file */ +export interface DownloadFileRequest { + /** The absolute path of the file. */ + filePath?: string | undefined; + /** + * The range of bytes to retrieve. + * The range is inclusive and zero-based, see + * [RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110#name-range) for further details. + */ + range?: string | undefined; + /** + * Download the file only if it has not been modified since the specified timestamp. + * If it has, a 412 Precondition Failed error will be returned. + * See [RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110#name-if-unmodified-since) for further details. + */ + ifUnmodifiedSince?: string | undefined; +} + +export interface DownloadFileResponse { + /** The length of the HTTP response body in bytes. */ + contentLength?: number | undefined; + contentType?: string | undefined; + contents?: Uint8Array | undefined; + /** The last modified time of the file in HTTP-date (RFC 7231) format. */ + lastModified?: string | undefined; +} + +/** Stores the attributes of a file or directory. */ +export interface FileInfo { + /** The absolute path of the file or directory. */ + path?: string | undefined; + /** True if the path is a directory. */ + isDir?: boolean | undefined; + /** The length of the file in bytes. This field is omitted for directories. */ + fileSize?: number | undefined; + /** Last modification time of given file in milliseconds since epoch. */ + modificationTime?: number | undefined; +} + +/** Get directory metadata */ +export interface GetDirectoryMetadataRequest { + /** The absolute path of a directory. */ + directoryPath?: string | undefined; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface GetDirectoryMetadataResponse {} + +/** Get file metadata */ +export interface GetFileMetadataRequest { + /** The absolute path of the file. */ + filePath?: string | undefined; + /** + * The range of bytes to retrieve. + * The range is inclusive and zero-based, see + * [RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110#name-range) for further details. + */ + range?: string | undefined; + /** + * Download the file only if it has not been modified since the specified timestamp. + * If it has, a 412 Precondition Failed error will be returned. + * See [RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110#name-if-unmodified-since) for further details. + */ + ifUnmodifiedSince?: string | undefined; +} + +export interface GetFileMetadataResponse { + /** The length of the HTTP response body in bytes. */ + contentLength?: number | undefined; + contentType?: string | undefined; + /** The last modified time of the file in HTTP-date (RFC 7231) format. */ + lastModified?: string | undefined; +} + +export interface GetStatus { + /** The path of the file or directory. The path should be the absolute DBFS path. */ + path?: string | undefined; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export interface GetStatus_Response { + /** The absolute path of the file or directory. */ + path?: string | undefined; + /** True if the path is a directory. */ + isDir?: boolean | undefined; + /** The length of the file in bytes. This field is omitted for directories. */ + fileSize?: number | undefined; + /** Last modification time of given file in milliseconds since epoch. */ + modificationTime?: number | undefined; +} + +/** List directory contents */ +export interface ListDirectoryContentsRequest { + /** The absolute path of a directory. */ + directoryPath?: string | undefined; + /** + * The maximum number of directory entries to return. The response may contain fewer + * entries. If the response contains a `next_page_token`, there may be more entries, + * even if fewer than `page_size` entries are in the response. + * + * We recommend not to set this value unless you are intentionally listing less than + * the complete directory contents. + * + * If unspecified, at most 1000 directory entries will be returned. + * The maximum value is 1000. Values above 1000 will be coerced to 1000. + */ + pageSize?: number | undefined; + /** + * An opaque page token which was the `next_page_token` in the response of the previous + * request to list the contents of this directory. Provide this token to retrieve the + * next page of directory entries. + * When providing a `page_token`, all other parameters provided to the request must match + * the previous request. + * To list all of the entries in a directory, it is necessary to continue requesting + * pages of entries until the response contains no `next_page_token`. Note that the + * number of entries returned must not be used to determine when the listing is complete. + */ + pageToken?: string | undefined; +} + +export interface ListDirectoryResponse { + /** Array of DirectoryEntry. */ + contents?: DirectoryEntry[] | undefined; + /** A token, which can be sent as `page_token` to retrieve the next page. */ + nextPageToken?: string | undefined; +} + +export interface ListStatus { + /** The path of the file or directory. The path should be the absolute DBFS path. */ + path?: string | undefined; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export interface ListStatus_Response { + /** A list of FileInfo's that describe contents of directory or file. See example above. */ + files?: FileInfo[] | undefined; +} + +export interface MkDirs { + /** The path of the new directory. The path should be the absolute DBFS path. */ + path?: string | undefined; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-object-type -- Proto-style nested message name. +export interface MkDirs_Response {} + +export interface Move { + /** The source path of the file or directory. The path should be the absolute DBFS path. */ + sourcePath?: string | undefined; + /** The destination path of the file or directory. The path should be the absolute DBFS path. */ + destinationPath?: string | undefined; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-object-type -- Proto-style nested message name. +export interface Move_Response {} + +export interface Put { + /** The path of the new file. The path should be the absolute DBFS path. */ + path?: string | undefined; + /** This parameter might be absent, and instead a posted file will be used. */ + contents?: Uint8Array | undefined; + /** The flag that specifies whether to overwrite existing file/files. */ + overwrite?: boolean | undefined; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-object-type -- Proto-style nested message name. +export interface Put_Response {} + +export interface Read { + /** The path of the file to read. The path should be the absolute DBFS path. */ + path?: string | undefined; + /** The offset to read from in bytes. */ + offset?: number | undefined; + /** + * The number of bytes to read starting from the offset. This has a limit of 1 MB, and a default + * value of 0.5 MB. + */ + length?: number | undefined; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export interface Read_Response { + /** + * The number of bytes read (could be less than ``length`` if we hit end of file). This refers to + * number of bytes read in unencoded version (response data is base64-encoded). + */ + bytesRead?: number | undefined; + /** The base64-encoded contents of the file read. */ + data?: Uint8Array | undefined; +} + +/** Upload a file */ +export interface UploadFileRequest { + /** The absolute path of the file. */ + filePath?: string | undefined; + contents?: Uint8Array | undefined; + /** If true or unspecified, an existing file will be overwritten. If false, an error will be returned if the path points to an existing file. */ + overwrite?: boolean | undefined; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface UploadFileResponse {} + +export const unmarshalAddBlockSchema: z.ZodType = z + .object({ + handle: z.number().optional(), + data: z + .string() + .transform(s => Uint8Array.from(atob(s), c => c.charCodeAt(0))) + .optional(), + }) + .transform(d => ({ + handle: d.handle, + data: d.data, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const unmarshalAddBlock_ResponseSchema: z.ZodType = + z.object({}); + +export const unmarshalCloseSchema: z.ZodType = z + .object({ + handle: z.number().optional(), + }) + .transform(d => ({ + handle: d.handle, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const unmarshalClose_ResponseSchema: z.ZodType = + z.object({}); + +export const unmarshalCreateSchema: z.ZodType = z + .object({ + path: z.string().optional(), + overwrite: z.boolean().optional(), + }) + .transform(d => ({ + path: d.path, + overwrite: d.overwrite, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const unmarshalCreate_ResponseSchema: z.ZodType = z + .object({ + handle: z.number().optional(), + }) + .transform(d => ({ + handle: d.handle, + })); + +export const unmarshalCreateDirectoryRequestSchema: z.ZodType = + z + .object({ + directory_path: z.string().optional(), + }) + .transform(d => ({ + directoryPath: d.directory_path, + })); + +export const unmarshalCreateDirectoryResponseSchema: z.ZodType = + z.object({}); + +export const unmarshalDeleteSchema: z.ZodType = z + .object({ + path: z.string().optional(), + recursive: z.boolean().optional(), + }) + .transform(d => ({ + path: d.path, + recursive: d.recursive, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const unmarshalDelete_ResponseSchema: z.ZodType = + z.object({}); + +export const unmarshalDeleteDirectoryRequestSchema: z.ZodType = + z + .object({ + directory_path: z.string().optional(), + }) + .transform(d => ({ + directoryPath: d.directory_path, + })); + +export const unmarshalDeleteDirectoryResponseSchema: z.ZodType = + z.object({}); + +export const unmarshalDeleteFileRequestSchema: z.ZodType = z + .object({ + file_path: z.string().optional(), + }) + .transform(d => ({ + filePath: d.file_path, + })); + +export const unmarshalDeleteFileResponseSchema: z.ZodType = + z.object({}); + +export const unmarshalDirectoryEntrySchema: z.ZodType = z + .object({ + file_size: z.number().optional(), + is_directory: z.boolean().optional(), + last_modified: z.number().optional(), + name: z.string().optional(), + path: z.string().optional(), + }) + .transform(d => ({ + fileSize: d.file_size, + isDirectory: d.is_directory, + lastModified: d.last_modified, + name: d.name, + path: d.path, + })); + +export const unmarshalDownloadFileRequestSchema: z.ZodType = + z + .object({ + file_path: z.string().optional(), + Range: z.string().optional(), + 'If-Unmodified-Since': z.string().optional(), + }) + .transform(d => ({ + filePath: d.file_path, + range: d.Range, + ifUnmodifiedSince: d['If-Unmodified-Since'], + })); + +export const unmarshalDownloadFileResponseSchema: z.ZodType = + z + .object({ + 'content-length': z.number().optional(), + 'content-type': z.string().optional(), + contents: z + .string() + .transform(s => Uint8Array.from(atob(s), c => c.charCodeAt(0))) + .optional(), + 'last-modified': z.string().optional(), + }) + .transform(d => ({ + contentLength: d['content-length'], + contentType: d['content-type'], + contents: d.contents, + lastModified: d['last-modified'], + })); + +export const unmarshalFileInfoSchema: z.ZodType = z + .object({ + path: z.string().optional(), + is_dir: z.boolean().optional(), + file_size: z.number().optional(), + modification_time: z.number().optional(), + }) + .transform(d => ({ + path: d.path, + isDir: d.is_dir, + fileSize: d.file_size, + modificationTime: d.modification_time, + })); + +export const unmarshalGetDirectoryMetadataRequestSchema: z.ZodType = + z + .object({ + directory_path: z.string().optional(), + }) + .transform(d => ({ + directoryPath: d.directory_path, + })); + +export const unmarshalGetDirectoryMetadataResponseSchema: z.ZodType = + z.object({}); + +export const unmarshalGetFileMetadataRequestSchema: z.ZodType = + z + .object({ + file_path: z.string().optional(), + Range: z.string().optional(), + 'If-Unmodified-Since': z.string().optional(), + }) + .transform(d => ({ + filePath: d.file_path, + range: d.Range, + ifUnmodifiedSince: d['If-Unmodified-Since'], + })); + +export const unmarshalGetFileMetadataResponseSchema: z.ZodType = + z + .object({ + 'content-length': z.number().optional(), + 'content-type': z.string().optional(), + 'last-modified': z.string().optional(), + }) + .transform(d => ({ + contentLength: d['content-length'], + contentType: d['content-type'], + lastModified: d['last-modified'], + })); + +export const unmarshalGetStatusSchema: z.ZodType = z + .object({ + path: z.string().optional(), + }) + .transform(d => ({ + path: d.path, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const unmarshalGetStatus_ResponseSchema: z.ZodType = + z + .object({ + path: z.string().optional(), + is_dir: z.boolean().optional(), + file_size: z.number().optional(), + modification_time: z.number().optional(), + }) + .transform(d => ({ + path: d.path, + isDir: d.is_dir, + fileSize: d.file_size, + modificationTime: d.modification_time, + })); + +export const unmarshalListDirectoryContentsRequestSchema: z.ZodType = + z + .object({ + directory_path: z.string().optional(), + page_size: z.number().optional(), + page_token: z.string().optional(), + }) + .transform(d => ({ + directoryPath: d.directory_path, + pageSize: d.page_size, + pageToken: d.page_token, + })); + +export const unmarshalListDirectoryResponseSchema: z.ZodType = + z + .object({ + contents: z.array(z.lazy(() => unmarshalDirectoryEntrySchema)).optional(), + next_page_token: z.string().optional(), + }) + .transform(d => ({ + contents: d.contents, + nextPageToken: d.next_page_token, + })); + +export const unmarshalListStatusSchema: z.ZodType = z + .object({ + path: z.string().optional(), + }) + .transform(d => ({ + path: d.path, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const unmarshalListStatus_ResponseSchema: z.ZodType = + z + .object({ + files: z.array(z.lazy(() => unmarshalFileInfoSchema)).optional(), + }) + .transform(d => ({ + files: d.files, + })); + +export const unmarshalMkDirsSchema: z.ZodType = z + .object({ + path: z.string().optional(), + }) + .transform(d => ({ + path: d.path, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const unmarshalMkDirs_ResponseSchema: z.ZodType = + z.object({}); + +export const unmarshalMoveSchema: z.ZodType = z + .object({ + source_path: z.string().optional(), + destination_path: z.string().optional(), + }) + .transform(d => ({ + sourcePath: d.source_path, + destinationPath: d.destination_path, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const unmarshalMove_ResponseSchema: z.ZodType = z.object( + {} +); + +export const unmarshalPutSchema: z.ZodType = z + .object({ + path: z.string().optional(), + contents: z + .string() + .transform(s => Uint8Array.from(atob(s), c => c.charCodeAt(0))) + .optional(), + overwrite: z.boolean().optional(), + }) + .transform(d => ({ + path: d.path, + contents: d.contents, + overwrite: d.overwrite, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const unmarshalPut_ResponseSchema: z.ZodType = z.object( + {} +); + +export const unmarshalReadSchema: z.ZodType = z + .object({ + path: z.string().optional(), + offset: z.number().optional(), + length: z.number().optional(), + }) + .transform(d => ({ + path: d.path, + offset: d.offset, + length: d.length, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const unmarshalRead_ResponseSchema: z.ZodType = z + .object({ + bytes_read: z.number().optional(), + data: z + .string() + .transform(s => Uint8Array.from(atob(s), c => c.charCodeAt(0))) + .optional(), + }) + .transform(d => ({ + bytesRead: d.bytes_read, + data: d.data, + })); + +export const unmarshalUploadFileRequestSchema: z.ZodType = z + .object({ + file_path: z.string().optional(), + contents: z + .string() + .transform(s => Uint8Array.from(atob(s), c => c.charCodeAt(0))) + .optional(), + overwrite: z.boolean().optional(), + }) + .transform(d => ({ + filePath: d.file_path, + contents: d.contents, + overwrite: d.overwrite, + })); + +export const unmarshalUploadFileResponseSchema: z.ZodType = + z.object({}); + +export const marshalAddBlockSchema: z.ZodType = z + .object({ + handle: z.number().optional(), + data: z + .any() + .transform((d: Uint8Array) => + btoa(Array.from(d, b => String.fromCharCode(b)).join('')) + ) + .optional(), + }) + .transform(d => ({ + handle: d.handle, + data: d.data, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const marshalAddBlock_ResponseSchema: z.ZodType = z.object({}); + +export const marshalCloseSchema: z.ZodType = z + .object({ + handle: z.number().optional(), + }) + .transform(d => ({ + handle: d.handle, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const marshalClose_ResponseSchema: z.ZodType = z.object({}); + +export const marshalCreateSchema: z.ZodType = z + .object({ + path: z.string().optional(), + overwrite: z.boolean().optional(), + }) + .transform(d => ({ + path: d.path, + overwrite: d.overwrite, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const marshalCreate_ResponseSchema: z.ZodType = z + .object({ + handle: z.number().optional(), + }) + .transform(d => ({ + handle: d.handle, + })); + +export const marshalCreateDirectoryRequestSchema: z.ZodType = z + .object({ + directoryPath: z.string().optional(), + }) + .transform(d => ({ + directory_path: d.directoryPath, + })); + +export const marshalCreateDirectoryResponseSchema: z.ZodType = z.object({}); + +export const marshalDeleteSchema: z.ZodType = z + .object({ + path: z.string().optional(), + recursive: z.boolean().optional(), + }) + .transform(d => ({ + path: d.path, + recursive: d.recursive, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const marshalDelete_ResponseSchema: z.ZodType = z.object({}); + +export const marshalDeleteDirectoryRequestSchema: z.ZodType = z + .object({ + directoryPath: z.string().optional(), + }) + .transform(d => ({ + directory_path: d.directoryPath, + })); + +export const marshalDeleteDirectoryResponseSchema: z.ZodType = z.object({}); + +export const marshalDeleteFileRequestSchema: z.ZodType = z + .object({ + filePath: z.string().optional(), + }) + .transform(d => ({ + file_path: d.filePath, + })); + +export const marshalDeleteFileResponseSchema: z.ZodType = z.object({}); + +export const marshalDirectoryEntrySchema: z.ZodType = z + .object({ + fileSize: z.number().optional(), + isDirectory: z.boolean().optional(), + lastModified: z.number().optional(), + name: z.string().optional(), + path: z.string().optional(), + }) + .transform(d => ({ + file_size: d.fileSize, + is_directory: d.isDirectory, + last_modified: d.lastModified, + name: d.name, + path: d.path, + })); + +export const marshalDownloadFileRequestSchema: z.ZodType = z + .object({ + filePath: z.string().optional(), + range: z.string().optional(), + ifUnmodifiedSince: z.string().optional(), + }) + .transform(d => ({ + file_path: d.filePath, + Range: d.range, + 'If-Unmodified-Since': d.ifUnmodifiedSince, + })); + +export const marshalDownloadFileResponseSchema: z.ZodType = z + .object({ + contentLength: z.number().optional(), + contentType: z.string().optional(), + contents: z + .any() + .transform((d: Uint8Array) => + btoa(Array.from(d, b => String.fromCharCode(b)).join('')) + ) + .optional(), + lastModified: z.string().optional(), + }) + .transform(d => ({ + 'content-length': d.contentLength, + 'content-type': d.contentType, + contents: d.contents, + 'last-modified': d.lastModified, + })); + +export const marshalFileInfoSchema: z.ZodType = z + .object({ + path: z.string().optional(), + isDir: z.boolean().optional(), + fileSize: z.number().optional(), + modificationTime: z.number().optional(), + }) + .transform(d => ({ + path: d.path, + is_dir: d.isDir, + file_size: d.fileSize, + modification_time: d.modificationTime, + })); + +export const marshalGetDirectoryMetadataRequestSchema: z.ZodType = z + .object({ + directoryPath: z.string().optional(), + }) + .transform(d => ({ + directory_path: d.directoryPath, + })); + +export const marshalGetDirectoryMetadataResponseSchema: z.ZodType = z.object( + {} +); + +export const marshalGetFileMetadataRequestSchema: z.ZodType = z + .object({ + filePath: z.string().optional(), + range: z.string().optional(), + ifUnmodifiedSince: z.string().optional(), + }) + .transform(d => ({ + file_path: d.filePath, + Range: d.range, + 'If-Unmodified-Since': d.ifUnmodifiedSince, + })); + +export const marshalGetFileMetadataResponseSchema: z.ZodType = z + .object({ + contentLength: z.number().optional(), + contentType: z.string().optional(), + lastModified: z.string().optional(), + }) + .transform(d => ({ + 'content-length': d.contentLength, + 'content-type': d.contentType, + 'last-modified': d.lastModified, + })); + +export const marshalGetStatusSchema: z.ZodType = z + .object({ + path: z.string().optional(), + }) + .transform(d => ({ + path: d.path, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const marshalGetStatus_ResponseSchema: z.ZodType = z + .object({ + path: z.string().optional(), + isDir: z.boolean().optional(), + fileSize: z.number().optional(), + modificationTime: z.number().optional(), + }) + .transform(d => ({ + path: d.path, + is_dir: d.isDir, + file_size: d.fileSize, + modification_time: d.modificationTime, + })); + +export const marshalListDirectoryContentsRequestSchema: z.ZodType = z + .object({ + directoryPath: z.string().optional(), + pageSize: z.number().optional(), + pageToken: z.string().optional(), + }) + .transform(d => ({ + directory_path: d.directoryPath, + page_size: d.pageSize, + page_token: d.pageToken, + })); + +export const marshalListDirectoryResponseSchema: z.ZodType = z + .object({ + contents: z.array(z.lazy(() => marshalDirectoryEntrySchema)).optional(), + nextPageToken: z.string().optional(), + }) + .transform(d => ({ + contents: d.contents, + next_page_token: d.nextPageToken, + })); + +export const marshalListStatusSchema: z.ZodType = z + .object({ + path: z.string().optional(), + }) + .transform(d => ({ + path: d.path, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const marshalListStatus_ResponseSchema: z.ZodType = z + .object({ + files: z.array(z.lazy(() => marshalFileInfoSchema)).optional(), + }) + .transform(d => ({ + files: d.files, + })); + +export const marshalMkDirsSchema: z.ZodType = z + .object({ + path: z.string().optional(), + }) + .transform(d => ({ + path: d.path, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const marshalMkDirs_ResponseSchema: z.ZodType = z.object({}); + +export const marshalMoveSchema: z.ZodType = z + .object({ + sourcePath: z.string().optional(), + destinationPath: z.string().optional(), + }) + .transform(d => ({ + source_path: d.sourcePath, + destination_path: d.destinationPath, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const marshalMove_ResponseSchema: z.ZodType = z.object({}); + +export const marshalPutSchema: z.ZodType = z + .object({ + path: z.string().optional(), + contents: z + .any() + .transform((d: Uint8Array) => + btoa(Array.from(d, b => String.fromCharCode(b)).join('')) + ) + .optional(), + overwrite: z.boolean().optional(), + }) + .transform(d => ({ + path: d.path, + contents: d.contents, + overwrite: d.overwrite, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const marshalPut_ResponseSchema: z.ZodType = z.object({}); + +export const marshalReadSchema: z.ZodType = z + .object({ + path: z.string().optional(), + offset: z.number().optional(), + length: z.number().optional(), + }) + .transform(d => ({ + path: d.path, + offset: d.offset, + length: d.length, + })); + +// eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested message name. +export const marshalRead_ResponseSchema: z.ZodType = z + .object({ + bytesRead: z.number().optional(), + data: z + .any() + .transform((d: Uint8Array) => + btoa(Array.from(d, b => String.fromCharCode(b)).join('')) + ) + .optional(), + }) + .transform(d => ({ + bytes_read: d.bytesRead, + data: d.data, + })); + +export const marshalUploadFileRequestSchema: z.ZodType = z + .object({ + filePath: z.string().optional(), + contents: z + .any() + .transform((d: Uint8Array) => + btoa(Array.from(d, b => String.fromCharCode(b)).join('')) + ) + .optional(), + overwrite: z.boolean().optional(), + }) + .transform(d => ({ + file_path: d.filePath, + contents: d.contents, + overwrite: d.overwrite, + })); + +export const marshalUploadFileResponseSchema: z.ZodType = z.object({}); diff --git a/packages/files/src/v2/utils.ts b/packages/files/src/v2/utils.ts new file mode 100644 index 00000000..7d70114c --- /dev/null +++ b/packages/files/src/v2/utils.ts @@ -0,0 +1,165 @@ +// Code generated from API definition by Databricks SDK Generator. DO NOT EDIT. + +import {APIError} from '@databricks/sdk-databricks/apierror'; +import type {Logger} from '@databricks/sdk-databricks/logger'; +import type { + HttpClient, + HttpRequest, + HttpResponse, +} from '@databricks/sdk-databricks/transport'; +import type {z} from 'zod'; + +export interface HttpCallOptions { + readonly request: HttpRequest; + readonly httpClient: HttpClient; + readonly logger: Logger; +} + +async function readAll( + body: ReadableStream | null +): Promise { + if (body === null) { + return new Uint8Array(0); + } + const reader = body.getReader(); + const chunks: Uint8Array[] = []; + for (;;) { + const {done, value} = await reader.read(); + if (done) { + break; + } + chunks.push(value); + } + const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0); + const result = new Uint8Array(totalLength); + let offset = 0; + for (const chunk of chunks) { + result.set(chunk, offset); + offset += chunk.length; + } + return result; +} + +export async function executeHttpCall( + opts: HttpCallOptions +): Promise { + opts.logger.debug('HTTP request', { + method: opts.request.method, + url: opts.request.url, + }); + + let resp: HttpResponse; + try { + resp = await opts.httpClient.send(opts.request); + } catch (e: unknown) { + opts.logger.debug('HTTP request failed'); + throw e; + } + + const body = await readAll(resp.body); + + opts.logger.debug('HTTP response', { + statusCode: resp.statusCode, + body: new TextDecoder().decode(body), + }); + + const apiErr = APIError.fromHttpError(resp.statusCode, resp.headers, body); + if (apiErr !== undefined) { + throw apiErr; + } + + return body; +} + +export function buildHttpRequest( + method: string, + url: string, + signal?: AbortSignal, + body?: string +): HttpRequest { + const headers = new Headers(); + headers.set('Content-Type', 'application/json'); + + const req: HttpRequest = {url, method, headers}; + if (body !== undefined) { + req.body = body; + } + if (signal !== undefined) { + req.signal = signal; + } + return req; +} + +export function parseResponse(body: Uint8Array, schema: z.ZodType): T { + const text = new TextDecoder().decode(body); + const parsed: unknown = JSON.parse(text); + return schema.parse(parsed); +} + +export function marshalRequest(data: unknown, schema: z.ZodType): string { + return JSON.stringify(schema.parse(data)); +} + +export function flattenQueryParams( + prefix: string, + value: unknown, + params: URLSearchParams +): void { + if (value === null || value === undefined) { + return; + } + if (Array.isArray(value)) { + // arrays of objects are not yet supported + for (const item of value) { + params.append(prefix, String(item)); + } + } else if (typeof value === 'object') { + for (const [key, val] of Object.entries(value as Record)) { + flattenQueryParams(`${prefix}.${key}`, val, params); + } + } else if ( + typeof value === 'string' || + typeof value === 'number' || + typeof value === 'boolean' || + typeof value === 'bigint' + ) { + params.append(prefix, String(value)); + } else { + throw new Error(`Unsupported query parameter type: ${typeof value}`); + } +} + +/** + * Sends an HTTP request and checks for API errors. On non-2xx responses the + * body is buffered and parsed into an APIError. On 2xx the raw HttpResponse + * is returned with the body stream untouched. + */ +export async function sendAndCheckError( + opts: HttpCallOptions +): Promise { + opts.logger.debug('HTTP request', { + method: opts.request.method, + url: opts.request.url, + }); + + let resp: HttpResponse; + try { + resp = await opts.httpClient.send(opts.request); + } catch (e: unknown) { + opts.logger.debug('HTTP request failed'); + throw e; + } + + opts.logger.debug('HTTP response', {statusCode: resp.statusCode}); + + if (resp.statusCode < 200 || resp.statusCode >= 300) { + const body = await readAll(resp.body); + const apiErr = APIError.fromHttpError(resp.statusCode, resp.headers, body); + if (apiErr !== undefined) { + throw apiErr; + } + throw new Error(`unexpected HTTP status ${String(resp.statusCode)}`); + } + + return resp; +} diff --git a/packages/postgres/src/v1/client.ts b/packages/postgres/src/v1/client.ts index dca7c85e..b2960fa6 100644 --- a/packages/postgres/src/v1/client.ts +++ b/packages/postgres/src/v1/client.ts @@ -78,6 +78,7 @@ import type { SyncedTable, SyncedTableOperationMetadata, Table, + UndeleteBranchRequest, UpdateBranchRequest, UpdateDatabaseRequest, UpdateEndpointRequest, @@ -94,6 +95,7 @@ import { marshalRoleSchema, marshalSyncedTableSchema, marshalTableSchema, + marshalUndeleteBranchRequestSchema, unmarshalBranchOperationMetadataSchema, unmarshalBranchSchema, unmarshalCatalogOperationMetadataSchema, @@ -457,6 +459,9 @@ export class Client { if (req.purge !== undefined) { params.append('purge', String(req.purge)); } + if (req.allowMissing !== undefined) { + params.append('allow_missing', String(req.allowMissing)); + } const query = params.toString(); const fullUrl = query !== '' ? `${url}?${query}` : url; let resp: Operation | undefined; @@ -1097,6 +1102,9 @@ export class Client { if (req.pageSize !== undefined) { params.append('page_size', String(req.pageSize)); } + if (req.showDeleted !== undefined) { + params.append('show_deleted', String(req.showDeleted)); + } const query = params.toString(); const fullUrl = query !== '' ? `${url}?${query}` : url; let resp: ListBranchesResponse | undefined; @@ -1398,6 +1406,40 @@ export class Client { } } + /** Undeletes the specified database branch. */ + async undeleteBranch( + signal: AbortSignal | undefined, + req: UndeleteBranchRequest, + options?: Options + ): Promise { + const url = `${this.host}/api/2.0/postgres/${req.name ?? ''}/undelete`; + const body = marshalRequest(req, marshalUndeleteBranchRequestSchema); + let resp: Operation | undefined; + const call: Call = async (callSignal?: AbortSignal): Promise => { + const httpReq = buildHttpRequest('POST', url, callSignal, body); + const respBody = await executeHttpCall({ + request: httpReq, + httpClient: this.httpClient, + logger: this.logger, + }); + resp = parseResponse(respBody, unmarshalOperationSchema); + }; + await execute(signal, call, options); + if (resp === undefined) { + throw new Error('API call completed without a result.'); + } + return resp; + } + + async undeleteBranchOperation( + signal: AbortSignal | undefined, + req: UndeleteBranchRequest, + options?: Options + ): Promise { + const op = await this.undeleteBranch(signal, req, options); + return new UndeleteBranchOperation(this, op); + } + /** Updates the specified database branch. You can set this branch as the project's default branch, or protect/unprotect it. */ async updateBranch( signal: AbortSignal | undefined, @@ -2922,6 +2964,95 @@ export class DeleteSyncedTableOperation { } } +export class UndeleteBranchOperation { + constructor( + private readonly client: Client, + private operation: Operation + ) {} + + /** Returns the server-assigned name of the long-running operation. */ + name(): Promise { + return Promise.resolve(this.operation.name); + } + + /** Returns metadata associated with the long-running operation. */ + metadata(): Promise { + if (this.operation.metadata === undefined) { + return Promise.resolve(undefined); + } + return Promise.resolve( + z + .lazy(() => unmarshalBranchOperationMetadataSchema) + .parse(this.operation.metadata) + ); + } + + /** + * Polls the operation until it completes. + * + * Throws if the operation failed. + */ + async wait( + signal: AbortSignal | undefined, + options?: Options + ): Promise { + const errStillRunning = new Error('operation still in progress'); + + const call: Call = async (callSignal?: AbortSignal): Promise => { + const op = await this.client.getOperation( + callSignal, + { + name: this.operation.name, + }, + options + ); + this.operation = op; + if (op.done === undefined) { + throw new Error('operation is missing the done field'); + } + if (!op.done) { + throw errStillRunning; + } + + if (op.error !== undefined) { + const msg = + op.error.message !== undefined && op.error.message !== '' + ? op.error.message + : 'unknown error'; + const errorMsg = + op.error.errorCode !== undefined + ? `[${op.error.errorCode}] ${msg}` + : msg; + throw new Error(`operation failed: ${errorMsg}`, { + cause: op.error, + }); + } + }; + + const retryOptions: Options = { + retrier: () => + retryOn({}, (err: Error) => { + return err.message.includes('operation still in progress'); + }), + }; + await execute(signal, call, retryOptions); + } + + /** Checks whether the operation has completed */ + async done( + signal: AbortSignal | undefined, + options?: Options + ): Promise { + const op = await this.client.getOperation( + signal, + {name: this.operation.name}, + options + ); + this.operation = op; + return op.done; + } +} + export class UpdateBranchOperation { constructor( private readonly client: Client, diff --git a/packages/postgres/src/v1/index.ts b/packages/postgres/src/v1/index.ts index ffac7291..df1993b1 100644 --- a/packages/postgres/src/v1/index.ts +++ b/packages/postgres/src/v1/index.ts @@ -16,6 +16,7 @@ export { DeleteProjectOperation, DeleteRoleOperation, DeleteSyncedTableOperation, + UndeleteBranchOperation, UpdateBranchOperation, UpdateDatabaseOperation, UpdateEndpointOperation, @@ -140,6 +141,7 @@ export type { SyncedTablePipelineProgress, SyncedTablePosition, Table, + UndeleteBranchRequest, UpdateBranchRequest, UpdateDatabaseRequest, UpdateEndpointRequest, diff --git a/packages/postgres/src/v1/model.ts b/packages/postgres/src/v1/model.ts index 5a79cae6..bed051d3 100644 --- a/packages/postgres/src/v1/model.ts +++ b/packages/postgres/src/v1/model.ts @@ -592,6 +592,8 @@ export enum BranchStatus_State { READY = 'READY', /** The branch is stored in cost-effective archival storage. Expect slow query response times. */ ARCHIVED = 'ARCHIVED', + /** The branch is deleted and is not available for querying, but can be undeleted. */ + DELETED = 'DELETED', } // eslint-disable-next-line @typescript-eslint/naming-convention -- Proto-style nested enum name. @@ -798,6 +800,16 @@ export interface BranchStatus { * which follows the `projects/{project_id}/branches/{branch_id}` format and is not user-friendly. */ branchId?: string | undefined; + /** + * A timestamp indicating when the branch was deleted. + * Empty if the branch is not deleted. + */ + deleteTime?: Temporal.Instant | undefined; + /** + * A timestamp indicating when the branch is scheduled to be purged. + * Empty if the branch is not deleted, otherwise set to a timestamp in the future. + */ + purgeTime?: Temporal.Instant | undefined; } export interface Catalog { @@ -1120,6 +1132,11 @@ export interface DeleteBranchRequest { * Soft deletion (purge=false) is not supported yet. */ purge?: boolean | undefined; + /** + * If true, if branch does not exists, the request will succeed and no action will be taken. + * If false (default value) and branch does not exists, the request will fail with NOT_FOUND error. + */ + allowMissing?: boolean | undefined; } export interface DeleteCatalogRequest { @@ -1601,6 +1618,12 @@ export interface ListBranchesRequest { pageToken?: string | undefined; /** Upper bound for items returned. Cannot be negative. */ pageSize?: number | undefined; + /** + * Whether to include soft-deleted branches in the response. + * When true, deleted branches are included alongside active branches. + * Purged branches are never returned. + */ + showDeleted?: boolean | undefined; } export interface ListBranchesResponse { @@ -2196,6 +2219,14 @@ export interface Table { tableServingUrl?: string | undefined; } +export interface UndeleteBranchRequest { + /** + * The full resource path of the branch to undelete. + * Format: projects/{project_id}/branches/{branch_id} + */ + name?: string | undefined; +} + export interface UpdateBranchRequest { /** * The Branch to update. @@ -2339,6 +2370,14 @@ export const unmarshalBranchStatusSchema: z.ZodType = z .transform(s => Temporal.Instant.from(s)) .optional(), branch_id: z.string().optional(), + delete_time: z + .string() + .transform(s => Temporal.Instant.from(s)) + .optional(), + purge_time: z + .string() + .transform(s => Temporal.Instant.from(s)) + .optional(), }) .transform(d => ({ sourceBranch: d.source_branch, @@ -2352,6 +2391,8 @@ export const unmarshalBranchStatusSchema: z.ZodType = z logicalSizeBytes: d.logical_size_bytes, expireTime: d.expire_time, branchId: d.branch_id, + deleteTime: d.delete_time, + purgeTime: d.purge_time, })); export const unmarshalCatalogSchema: z.ZodType = z @@ -2619,10 +2660,12 @@ export const unmarshalDeleteBranchRequestSchema: z.ZodType .object({ name: z.string().optional(), purge: z.boolean().optional(), + allow_missing: z.boolean().optional(), }) .transform(d => ({ name: d.name, purge: d.purge, + allowMissing: d.allow_missing, })); export const unmarshalDeleteCatalogRequestSchema: z.ZodType = @@ -3122,11 +3165,13 @@ export const unmarshalListBranchesRequestSchema: z.ZodType parent: z.string().optional(), page_token: z.string().optional(), page_size: z.number().optional(), + show_deleted: z.boolean().optional(), }) .transform(d => ({ parent: d.parent, pageToken: d.page_token, pageSize: d.page_size, + showDeleted: d.show_deleted, })); export const unmarshalListBranchesResponseSchema: z.ZodType = @@ -3692,6 +3737,15 @@ export const unmarshalTableSchema: z.ZodType = z tableServingUrl: d.table_serving_url, })); +export const unmarshalUndeleteBranchRequestSchema: z.ZodType = + z + .object({ + name: z.string().optional(), + }) + .transform(d => ({ + name: d.name, + })); + export const unmarshalUpdateBranchRequestSchema: z.ZodType = z .object({ @@ -3825,6 +3879,14 @@ export const marshalBranchStatusSchema: z.ZodType = z .transform((d: Temporal.Instant) => d.toString()) .optional(), branchId: z.string().optional(), + deleteTime: z + .any() + .transform((d: Temporal.Instant) => d.toString()) + .optional(), + purgeTime: z + .any() + .transform((d: Temporal.Instant) => d.toString()) + .optional(), }) .transform(d => ({ source_branch: d.sourceBranch, @@ -3838,6 +3900,8 @@ export const marshalBranchStatusSchema: z.ZodType = z logical_size_bytes: d.logicalSizeBytes, expire_time: d.expireTime, branch_id: d.branchId, + delete_time: d.deleteTime, + purge_time: d.purgeTime, })); export const marshalCatalogSchema: z.ZodType = z @@ -4090,10 +4154,12 @@ export const marshalDeleteBranchRequestSchema: z.ZodType = z .object({ name: z.string().optional(), purge: z.boolean().optional(), + allowMissing: z.boolean().optional(), }) .transform(d => ({ name: d.name, purge: d.purge, + allow_missing: d.allowMissing, })); export const marshalDeleteCatalogRequestSchema: z.ZodType = z @@ -4566,11 +4632,13 @@ export const marshalListBranchesRequestSchema: z.ZodType = z parent: z.string().optional(), pageToken: z.string().optional(), pageSize: z.number().optional(), + showDeleted: z.boolean().optional(), }) .transform(d => ({ parent: d.parent, page_token: d.pageToken, page_size: d.pageSize, + show_deleted: d.showDeleted, })); export const marshalListBranchesResponseSchema: z.ZodType = z @@ -5108,6 +5176,14 @@ export const marshalTableSchema: z.ZodType = z table_serving_url: d.tableServingUrl, })); +export const marshalUndeleteBranchRequestSchema: z.ZodType = z + .object({ + name: z.string().optional(), + }) + .transform(d => ({ + name: d.name, + })); + export const marshalUpdateBranchRequestSchema: z.ZodType = z .object({ branch: z.lazy(() => marshalBranchSchema).optional(), diff --git a/packages/sdk/src/model.ts b/packages/sdk/src/model.ts index e21d935b..3ef9d0b7 100644 --- a/packages/sdk/src/model.ts +++ b/packages/sdk/src/model.ts @@ -79,6 +79,13 @@ export interface FieldMetadata { * single path segment. */ isMultiSegment?: boolean | undefined; + /** + * When true, the field carries a raw byte stream (e.g., file contents) + * rather than a small binary blob. SDK generators use this to send/receive + * the data as a streaming body (application/octet-stream) instead of + * base64-encoding it into JSON. + */ + isStream?: boolean | undefined; } /** @@ -413,9 +420,11 @@ export interface WaitForState_StateInfo { export const unmarshalFieldMetadataSchema: z.ZodType = z .object({ is_multi_segment: z.boolean().optional(), + is_stream: z.boolean().optional(), }) .transform(d => ({ isMultiSegment: d.is_multi_segment, + isStream: d.is_stream, })); export const unmarshalLongRunningOperationSchema: z.ZodType = @@ -574,9 +583,11 @@ export const unmarshalWaitForState_StateInfoSchema: z.ZodType ({ is_multi_segment: d.isMultiSegment, + is_stream: d.isStream, })); export const marshalLongRunningOperationSchema: z.ZodType = z diff --git a/packages/tables/src/v1/model.ts b/packages/tables/src/v1/model.ts index 63e24af5..4899a7b0 100644 --- a/packages/tables/src/v1/model.ts +++ b/packages/tables/src/v1/model.ts @@ -75,7 +75,7 @@ export enum DataSourceFormat { ICEBERG = 'ICEBERG', } -/** Latest kind: CONNECTION_API_SOURCE = 310; Next id: 311 */ +/** Latest kind: CONNECTION_VEEVA_VAULT_OAUTH_M2M = 311; Next id: 312 */ export enum SecurableKind { TABLE_STANDARD = 'TABLE_STANDARD', TABLE_EXTERNAL = 'TABLE_EXTERNAL',