Skip to content

Commit d73df95

Browse files
committed
update
1 parent 9efbf7f commit d73df95

26 files changed

Lines changed: 2352 additions & 0 deletions

package-lock.json

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/databricks/package.json

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"name": "@databricks/sdk-databricks",
3+
"version": "0.1.0",
4+
"description": "Databricks SDK core infrastructure for JavaScript/TypeScript",
5+
"type": "module",
6+
"main": "./dist/index.js",
7+
"types": "./dist/index.d.ts",
8+
"exports": {
9+
".": {
10+
"types": "./dist/index.d.ts",
11+
"import": "./dist/index.js"
12+
}
13+
},
14+
"files": [
15+
"dist",
16+
"src"
17+
],
18+
"scripts": {
19+
"build": "tsc",
20+
"lint": "eslint src tests --ext .ts",
21+
"lint:fix": "eslint src tests --ext .ts --fix",
22+
"format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
23+
"format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"",
24+
"test": "vitest run",
25+
"test:node": "vitest run",
26+
"test:watch": "vitest",
27+
"test:coverage": "vitest run --coverage",
28+
"typecheck": "tsc --noEmit",
29+
"clean": "rm -rf dist"
30+
},
31+
"dependencies": {
32+
"@databricks/sdk-auth": "0.1.0"
33+
},
34+
"devDependencies": {
35+
"@types/node": "^20.11.0",
36+
"@typescript-eslint/eslint-plugin": "^7.0.0",
37+
"@typescript-eslint/parser": "^7.0.0",
38+
"@vitest/coverage-v8": "^2.1.0",
39+
"eslint": "^8.57.0",
40+
"eslint-config-prettier": "^9.1.0",
41+
"prettier": "^3.2.0",
42+
"typescript": "^5.3.0",
43+
"vitest": "^2.1.0"
44+
},
45+
"author": "Databricks",
46+
"license": "Apache-2.0",
47+
"engines": {
48+
"node": ">=18.0.0"
49+
}
50+
}

packages/databricks/src/api/api.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/**
2+
* Utilities to make API calls against the Databricks API with retry, timeout,
3+
* and rate limiting support.
4+
*/
5+
6+
import type {Option, Options} from './options';
7+
import type {Retrier} from './retrier';
8+
9+
/**
10+
* A function representing a single API call attempt. Throw an error to
11+
* indicate failure; return normally to indicate success.
12+
*/
13+
export type Call = (signal: AbortSignal) => Promise<void>;
14+
15+
/**
16+
* Makes an API call using the given options for retry, timeout, and rate
17+
* limiting behavior.
18+
*/
19+
export async function execute(call: Call, ...opts: Option[]): Promise<void> {
20+
const options: Options = {};
21+
for (const opt of opts) {
22+
opt.apply(options);
23+
}
24+
25+
await executeImpl(call, options, sleepMs);
26+
}
27+
28+
/**
29+
* Sleeps for the given duration in milliseconds. Rejects with an
30+
* AbortError if the signal is aborted before the sleep completes.
31+
*/
32+
function sleepMs(ms: number, signal: AbortSignal): Promise<void> {
33+
return new Promise<void>((resolve, reject) => {
34+
if (signal.aborted) {
35+
reject(signal.reason as Error);
36+
return;
37+
}
38+
39+
const timer = setTimeout(() => {
40+
signal.removeEventListener('abort', onAbort);
41+
resolve();
42+
}, ms);
43+
44+
function onAbort(): void {
45+
clearTimeout(timer);
46+
reject(signal.reason as Error);
47+
}
48+
49+
signal.addEventListener('abort', onAbort, {once: true});
50+
});
51+
}
52+
53+
// Convenience type for readability and testability.
54+
type Sleeper = (ms: number, signal: AbortSignal) => Promise<void>;
55+
56+
/**
57+
* The actual implementation of execute, separated for testability.
58+
*/
59+
async function executeImpl(
60+
apiCall: Call,
61+
opts: Options,
62+
sleep: Sleeper
63+
): Promise<void> {
64+
// Set up abort controller with optional timeout.
65+
const controller = new AbortController();
66+
const {signal} = controller;
67+
68+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
69+
if (opts.timeoutMs !== undefined && opts.timeoutMs > 0) {
70+
timeoutId = setTimeout(() => {
71+
controller.abort(
72+
new DOMException('The operation timed out.', 'TimeoutError')
73+
);
74+
}, opts.timeoutMs);
75+
}
76+
77+
try {
78+
// Lazily instantiated retrier.
79+
let retrier: Retrier | undefined;
80+
81+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-constant-condition
82+
while (true) {
83+
if (opts.rateLimiter !== undefined) {
84+
await opts.rateLimiter.wait(signal);
85+
}
86+
87+
try {
88+
await apiCall(signal);
89+
return; // Success — nothing to retry.
90+
} catch (err: unknown) {
91+
if (retrier === undefined) {
92+
if (opts.retrier !== undefined) {
93+
retrier = opts.retrier();
94+
}
95+
if (retrier === undefined) {
96+
throw err; // No retrier — no retry.
97+
}
98+
}
99+
100+
const decision = retrier.isRetriable(err);
101+
if (!decision.retriable) {
102+
throw err; // Not retriable.
103+
}
104+
105+
await sleep(decision.delayMs, signal);
106+
}
107+
}
108+
} finally {
109+
if (timeoutId !== undefined) {
110+
clearTimeout(timeoutId);
111+
}
112+
}
113+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// API call execution.
2+
export type {Call} from './api';
3+
export {execute} from './api';
4+
5+
// Options for API calls.
6+
export type {Option, Options} from './options';
7+
export {
8+
withRetrier,
9+
withDisableRetry,
10+
withTimeout,
11+
withLimiter,
12+
} from './options';
13+
14+
// Rate limiting.
15+
export type {Limiter} from './limiter';
16+
17+
// Retry logic.
18+
export type {RetryDecision, Retrier, BackoffPolicyOptions} from './retrier';
19+
export {retryOnCodes, retryOn, BackoffPolicy} from './retrier';
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Rate limiting interface for API calls.
3+
*/
4+
5+
/**
6+
* A limiter controls the rate of API calls. Implementations must be safe
7+
* to call concurrently from multiple async contexts.
8+
*/
9+
export interface Limiter {
10+
/**
11+
* Waits until the call is allowed to proceed. Rejects if the signal is
12+
* aborted before the wait completes.
13+
*/
14+
wait(signal?: AbortSignal): Promise<void>;
15+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* Options to control the behavior of an API call.
3+
*/
4+
5+
import type {Limiter} from './limiter';
6+
import type {Retrier} from './retrier';
7+
8+
/**
9+
* An option that configures the behavior of an API call via Execute.
10+
*/
11+
export interface Option {
12+
/**
13+
* Applies this option to the given options object.
14+
* Throws if the option is invalid.
15+
*/
16+
apply(opts: Options): void;
17+
}
18+
19+
/**
20+
* Collected options that control the behavior of an API call.
21+
*/
22+
export interface Options {
23+
/**
24+
* Provides a new Retrier for each Execute call. The function must be safe
25+
* to call from multiple async contexts. The retrier must be fresh within
26+
* the context of an Execute call.
27+
*/
28+
retrier?: () => Retrier;
29+
30+
/** Rate limiter applied before each API call attempt. */
31+
rateLimiter?: Limiter;
32+
33+
/** Timeout in milliseconds for the entire Execute invocation. */
34+
timeoutMs?: number;
35+
}
36+
37+
/**
38+
* Configures a retrier provider for the API call. If no retrier is provided,
39+
* the call is not retried on failure.
40+
*
41+
* The provider function must be safe to call from multiple async contexts.
42+
*/
43+
export function withRetrier(provider: () => Retrier): Option {
44+
return {
45+
apply(opts: Options): void {
46+
opts.retrier = provider;
47+
},
48+
};
49+
}
50+
51+
/**
52+
* Convenience option to disable retries.
53+
*/
54+
export function withDisableRetry(): Option {
55+
return {
56+
apply(opts: Options): void {
57+
opts.retrier = undefined;
58+
},
59+
};
60+
}
61+
62+
/**
63+
* Sets the timeout duration for the entire Execute invocation.
64+
*
65+
* If the underlying signal already has a deadline, the effective timeout is
66+
* the minimum of both.
67+
*/
68+
export function withTimeout(ms: number): Option {
69+
return {
70+
apply(opts: Options): void {
71+
opts.timeoutMs = ms;
72+
},
73+
};
74+
}
75+
76+
/**
77+
* Configures a rate limiter for the API call. The limiter is invoked before
78+
* each call attempt. If no limiter is provided, the call is not rate limited.
79+
*/
80+
export function withLimiter(limiter: Limiter): Option {
81+
return {
82+
apply(opts: Options): void {
83+
opts.rateLimiter = limiter;
84+
},
85+
};
86+
}

0 commit comments

Comments
 (0)