@dbx-tools/databricks 0.6.186 → 0.6.191

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Best-effort geolocation of a host to the cloud provider and region it
3
+ * runs in. Resolves the host to its IP address(es) via DNS, then matches
4
+ * those addresses against the public IP-range feeds each hyperscaler
5
+ * publishes (AWS, Azure, GCP). Handy for placing a Databricks workspace
6
+ * URL, but works for any host.
7
+ *
8
+ * The range feeds are large and change slowly, so each provider's feed
9
+ * is cached for {@link RANGE_CACHE_TTL_MS} (24 hours) at two layers: an
10
+ * on-disk copy under the OS temp dir (survives process restarts, shared
11
+ * across processes) plus an in-process memoized parse. A provider whose
12
+ * feed fails to load is skipped rather than failing the whole lookup -
13
+ * the other providers still answer.
14
+ *
15
+ * Server-only: DNS resolution needs `node:dns` (via `./net.ts`), the
16
+ * disk cache needs `node:fs` / `node:os` / `node:path`, and the feeds
17
+ * are fetched with the global `fetch`.
18
+ *
19
+ * @module
20
+ */
21
+ import { net } from "@dbx-tools/shared-core";
22
+ /** How long a fetched provider IP-range feed is reused before refetch. */
23
+ export declare const RANGE_CACHE_TTL_MS: number;
24
+ /** Cloud hyperscaler a host resolves into. */
25
+ export declare enum CloudProvider {
26
+ Aws = "aws",
27
+ Azure = "azure",
28
+ Gcp = "gcp"
29
+ }
30
+ /**
31
+ * Where a host lives: the {@link CloudProvider}, the provider-native
32
+ * `region` string (whatever the provider's feed calls it, e.g.
33
+ * `"us-east-1"` on AWS, `"eastus2"` on Azure, `"us-east1"` on GCP), the
34
+ * resolved `ip` that matched, and the `cidr` block it fell in.
35
+ */
36
+ export interface CloudLocation {
37
+ provider: CloudProvider;
38
+ region: string;
39
+ ip: string;
40
+ cidr: string;
41
+ }
42
+ /** A parsed CIDR block tagged with the region it belongs to. */
43
+ interface RegionCidr extends net.Cidr {
44
+ region: string;
45
+ }
46
+ /** One provider's parsed, region-tagged range table. */
47
+ interface ProviderRanges {
48
+ provider: CloudProvider;
49
+ ranges: RegionCidr[];
50
+ }
51
+ /**
52
+ * Resolve `input`'s host to a {@link CloudLocation}, or `null` when the
53
+ * host can't be resolved or none of its IPs match a known cloud range.
54
+ * `input` is any {@link net.UrlLike} - a URL, a bare host, or a `{ url }`
55
+ * wrapper.
56
+ *
57
+ * The provider range feeds are cached for {@link RANGE_CACHE_TTL_MS}, so
58
+ * only the first call in a 24-hour window pays the fetch cost; the DNS
59
+ * lookup happens on every call.
60
+ *
61
+ * @example
62
+ * await resolveCloudLocation("https://adb-1234567890.7.azuredatabricks.net");
63
+ * // { provider: CloudProvider.Azure,
64
+ * // region: "eastus2", ip: "...", cidr: "..." }
65
+ *
66
+ * await resolveCloudLocation("dbc-abc123.cloud.databricks.com");
67
+ * // { provider: CloudProvider.Aws,
68
+ * // region: "us-west-2", ip: "...", cidr: "..." }
69
+ */
70
+ export declare function resolveCloudLocation(input: net.UrlLike): Promise<CloudLocation | null>;
71
+ /**
72
+ * Load every provider's region-tagged range table, each cached for
73
+ * {@link RANGE_CACHE_TTL_MS}. Providers are loaded in parallel and a
74
+ * feed that fails to fetch or parse is dropped (logged, not thrown) so
75
+ * a single flaky feed never sinks the whole lookup. Exposed for callers
76
+ * that want to match many IPs against one cached snapshot without a DNS
77
+ * step per address.
78
+ */
79
+ export declare function loadProviderRanges(): Promise<ProviderRanges[]>;
80
+ export {};
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Best-effort geolocation of a host to the cloud provider and region it
3
+ * runs in. Resolves the host to its IP address(es) via DNS, then matches
4
+ * those addresses against the public IP-range feeds each hyperscaler
5
+ * publishes (AWS, Azure, GCP). Handy for placing a Databricks workspace
6
+ * URL, but works for any host.
7
+ *
8
+ * The range feeds are large and change slowly, so each provider's feed
9
+ * is cached for {@link RANGE_CACHE_TTL_MS} (24 hours) at two layers: an
10
+ * on-disk copy under the OS temp dir (survives process restarts, shared
11
+ * across processes) plus an in-process memoized parse. A provider whose
12
+ * feed fails to load is skipped rather than failing the whole lookup -
13
+ * the other providers still answer.
14
+ *
15
+ * Server-only: DNS resolution needs `node:dns` (via `./net.ts`), the
16
+ * disk cache needs `node:fs` / `node:os` / `node:path`, and the feeds
17
+ * are fetched with the global `fetch`.
18
+ *
19
+ * @module
20
+ */
21
+ import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
22
+ import { tmpdir } from "node:os";
23
+ import { dirname, join } from "node:path";
24
+ import { file } from "@dbx-tools/core";
25
+ import { error, functionModule, hash, http, json, log, net } from "@dbx-tools/shared-core";
26
+ import { resolveHostIps } from "./net.js";
27
+ const logger = log.logger("cloud");
28
+ /** How long a fetched provider IP-range feed is reused before refetch. */
29
+ export const RANGE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
30
+ /** AWS publishes a single stable, unauthenticated ranges feed. */
31
+ const AWS_RANGES_URL = "https://ip-ranges.amazonaws.com/ip-ranges.json";
32
+ /** GCP publishes a single stable, unauthenticated ranges feed. */
33
+ const GCP_RANGES_URL = "https://www.gstatic.com/ipranges/cloud.json";
34
+ /**
35
+ * Azure has no stable feed URL - the JSON download link (with a
36
+ * rotating date stamp) lives on this human-facing download page and
37
+ * must be scraped out. See {@link fetchAzureRanges}.
38
+ */
39
+ const AZURE_DOWNLOAD_PAGE = "https://www.microsoft.com/en-us/download/details.aspx?id=56519";
40
+ /** Regex that plucks the current `ServiceTags_Public_<date>.json` link off the Azure page. */
41
+ const AZURE_JSON_LINK = /https:\/\/download\.microsoft\.com\/download\/[^"']*ServiceTags_Public_\d+\.json/;
42
+ /** Cloud hyperscaler a host resolves into. */
43
+ export var CloudProvider;
44
+ (function (CloudProvider) {
45
+ CloudProvider["Aws"] = "aws";
46
+ CloudProvider["Azure"] = "azure";
47
+ CloudProvider["Gcp"] = "gcp";
48
+ })(CloudProvider || (CloudProvider = {}));
49
+ /**
50
+ * Resolve `input`'s host to a {@link CloudLocation}, or `null` when the
51
+ * host can't be resolved or none of its IPs match a known cloud range.
52
+ * `input` is any {@link net.UrlLike} - a URL, a bare host, or a `{ url }`
53
+ * wrapper.
54
+ *
55
+ * The provider range feeds are cached for {@link RANGE_CACHE_TTL_MS}, so
56
+ * only the first call in a 24-hour window pays the fetch cost; the DNS
57
+ * lookup happens on every call.
58
+ *
59
+ * @example
60
+ * await resolveCloudLocation("https://adb-1234567890.7.azuredatabricks.net");
61
+ * // { provider: CloudProvider.Azure,
62
+ * // region: "eastus2", ip: "...", cidr: "..." }
63
+ *
64
+ * await resolveCloudLocation("dbc-abc123.cloud.databricks.com");
65
+ * // { provider: CloudProvider.Aws,
66
+ * // region: "us-west-2", ip: "...", cidr: "..." }
67
+ */
68
+ export async function resolveCloudLocation(input) {
69
+ const ips = await resolveHostIps(input);
70
+ if (ips.length === 0) {
71
+ logger.debug("no ips resolved", { input: String(input) });
72
+ return null;
73
+ }
74
+ const providers = await loadProviderRanges();
75
+ for (const ip of ips) {
76
+ const parsed = net.parseIp(ip);
77
+ if (!parsed)
78
+ continue;
79
+ for (const { provider, ranges } of providers) {
80
+ const match = net.findContainingCidr(parsed, ranges);
81
+ if (match) {
82
+ return { provider, region: match.region, ip, cidr: match.cidr };
83
+ }
84
+ }
85
+ }
86
+ logger.debug("no cloud range matched", { ips });
87
+ return null;
88
+ }
89
+ /**
90
+ * Load every provider's region-tagged range table, each cached for
91
+ * {@link RANGE_CACHE_TTL_MS}. Providers are loaded in parallel and a
92
+ * feed that fails to fetch or parse is dropped (logged, not thrown) so
93
+ * a single flaky feed never sinks the whole lookup. Exposed for callers
94
+ * that want to match many IPs against one cached snapshot without a DNS
95
+ * step per address.
96
+ */
97
+ export async function loadProviderRanges() {
98
+ const settled = await Promise.allSettled([loadAwsRanges(), loadAzureRanges(), loadGcpRanges()]);
99
+ const out = [];
100
+ for (const result of settled) {
101
+ if (result.status === "fulfilled")
102
+ out.push(result.value);
103
+ else
104
+ logger.warn("provider range load failed", { error: error.errorMessage(result.reason) });
105
+ }
106
+ return out;
107
+ }
108
+ // ────────────────────────────────────────────────────────────────
109
+ // Per-provider cached loaders
110
+ // ────────────────────────────────────────────────────────────────
111
+ const loadAwsRanges = functionModule.memoize(fetchAwsRanges, { ttlMs: RANGE_CACHE_TTL_MS });
112
+ const loadAzureRanges = functionModule.memoize(fetchAzureRanges, { ttlMs: RANGE_CACHE_TTL_MS });
113
+ const loadGcpRanges = functionModule.memoize(fetchGcpRanges, { ttlMs: RANGE_CACHE_TTL_MS });
114
+ /**
115
+ * AWS `ip-ranges.json`: a flat list of IPv4 (`prefixes`) and IPv6
116
+ * (`ipv6_prefixes`) blocks, each already tagged with a `region` such
117
+ * as `"us-east-1"` (or `"GLOBAL"` for edge ranges).
118
+ */
119
+ async function fetchAwsRanges() {
120
+ const feed = await fetchJson(AWS_RANGES_URL);
121
+ const ranges = [];
122
+ for (const entry of feed.prefixes ?? []) {
123
+ addRange(ranges, entry.ip_prefix, entry.region);
124
+ }
125
+ for (const entry of feed.ipv6_prefixes ?? []) {
126
+ addRange(ranges, entry.ipv6_prefix, entry.region);
127
+ }
128
+ logger.debug("loaded aws ranges", { count: ranges.length });
129
+ return { provider: CloudProvider.Aws, ranges };
130
+ }
131
+ /**
132
+ * GCP `cloud.json`: a list of blocks each carrying an `ipv4Prefix` or
133
+ * `ipv6Prefix` and a `scope` that is the region (`"us-east1"`) or
134
+ * `"global"` for non-regional ranges.
135
+ */
136
+ async function fetchGcpRanges() {
137
+ const feed = await fetchJson(GCP_RANGES_URL);
138
+ const ranges = [];
139
+ for (const entry of feed.prefixes ?? []) {
140
+ addRange(ranges, entry.ipv4Prefix ?? entry.ipv6Prefix, entry.scope);
141
+ }
142
+ logger.debug("loaded gcp ranges", { count: ranges.length });
143
+ return { provider: CloudProvider.Gcp, ranges };
144
+ }
145
+ /**
146
+ * Azure service tags: Microsoft ships no stable feed URL, so scrape the
147
+ * current `ServiceTags_Public_<date>.json` link off the download page
148
+ * and fetch it. Only the regional `AzureCloud.<region>` aggregates
149
+ * (which have a non-empty `region`) are kept; the cloud-wide
150
+ * `AzureCloud` tag and per-service tags are ignored so an address maps
151
+ * cleanly to one region.
152
+ */
153
+ async function fetchAzureRanges() {
154
+ const page = await fetchText(AZURE_DOWNLOAD_PAGE);
155
+ const jsonUrl = page.match(AZURE_JSON_LINK)?.[0];
156
+ if (!jsonUrl) {
157
+ throw new Error("could not find ServiceTags JSON link on Azure download page");
158
+ }
159
+ const feed = await fetchJson(jsonUrl);
160
+ const ranges = [];
161
+ for (const value of feed.values ?? []) {
162
+ const region = value.properties?.region;
163
+ if (!region || !value.name?.startsWith("AzureCloud."))
164
+ continue;
165
+ for (const prefix of value.properties?.addressPrefixes ?? []) {
166
+ addRange(ranges, prefix, region);
167
+ }
168
+ }
169
+ logger.debug("loaded azure ranges", { count: ranges.length });
170
+ return { provider: CloudProvider.Azure, ranges };
171
+ }
172
+ // ────────────────────────────────────────────────────────────────
173
+ // Helpers
174
+ // ────────────────────────────────────────────────────────────────
175
+ /** Parse `cidr` and, when valid and `region` is set, push a tagged range. */
176
+ function addRange(ranges, cidr, region) {
177
+ if (!cidr || !region)
178
+ return;
179
+ const parsed = net.parseCidr(cidr);
180
+ if (parsed)
181
+ ranges.push({ ...parsed, region });
182
+ }
183
+ /**
184
+ * Fetch and parse a provider feed. A parse failure names the URL rather than
185
+ * surfacing an opaque `Unexpected token` - the text may come from the on-disk
186
+ * cache, so "which feed" is the part worth knowing. {@link loadProviderRanges}
187
+ * turns the throw into a per-provider warning.
188
+ */
189
+ async function fetchJson(url) {
190
+ const text = await fetchText(url);
191
+ const parsed = json.parse(text);
192
+ if (parsed === undefined) {
193
+ throw new Error(`range feed returned non-JSON content: ${url}`);
194
+ }
195
+ return parsed;
196
+ }
197
+ /**
198
+ * Fetch `url` as text with a 24h on-disk cache under the OS temp dir
199
+ * (keyed by an FNV hash of the URL). A fresh cache file is returned
200
+ * directly; a miss/expiry fetches, writes to a unique temp file, and
201
+ * atomically renames it into place so concurrent callers never observe
202
+ * a half-written cache entry.
203
+ */
204
+ async function fetchText(url) {
205
+ const cacheDir = join(tmpdir(), "dbx-tools", "shared", "cloud");
206
+ const cacheDirCreated = await mkdir(cacheDir, { recursive: true });
207
+ const cachePath = join(cacheDir, `fetch-${hash.fnvHash(url)}.txt`);
208
+ const createdAt = cacheDirCreated ? undefined : await getCreated(cachePath);
209
+ if (createdAt) {
210
+ const expiresAt = new Date(createdAt.getTime() + RANGE_CACHE_TTL_MS);
211
+ if (expiresAt > new Date()) {
212
+ logger.debug("cached fetch hit", { url, cachePath });
213
+ return readFile(cachePath, "utf8");
214
+ }
215
+ }
216
+ let tempPath = join(cacheDir, `${hash.id()}.txt`);
217
+ try {
218
+ const response = await fetch(url);
219
+ if (!response.ok) {
220
+ throw await http.createFetchError(response, url);
221
+ }
222
+ const responseText = await response.text();
223
+ await mkdir(dirname(tempPath), { recursive: true });
224
+ await writeFile(tempPath, responseText);
225
+ await rename(tempPath, cachePath);
226
+ logger.debug("cached fetch load", { url, cachePath });
227
+ tempPath = null;
228
+ return responseText;
229
+ }
230
+ finally {
231
+ if (tempPath) {
232
+ await unlink(tempPath);
233
+ }
234
+ }
235
+ }
236
+ /** Birth time of `path`, or `undefined` when it doesn't exist yet. */
237
+ async function getCreated(path) {
238
+ return (await file.statSync(path))?.birthtime;
239
+ }
240
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xvdWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY2xvdWQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FtQkc7QUFFSCxPQUFPLEVBQUUsS0FBSyxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQzlFLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFDakMsT0FBTyxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFFMUMsT0FBTyxFQUFFLElBQUksRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ3ZDLE9BQU8sRUFBRSxLQUFLLEVBQUUsY0FBYyxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUMzRixPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sVUFBVSxDQUFDO0FBRTFDLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUM7QUFFbkMsMEVBQTBFO0FBQzFFLE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQztBQUV0RCxrRUFBa0U7QUFDbEUsTUFBTSxjQUFjLEdBQUcsZ0RBQWdELENBQUM7QUFDeEUsa0VBQWtFO0FBQ2xFLE1BQU0sY0FBYyxHQUFHLDZDQUE2QyxDQUFDO0FBQ3JFOzs7O0dBSUc7QUFDSCxNQUFNLG1CQUFtQixHQUFHLGdFQUFnRSxDQUFDO0FBQzdGLDhGQUE4RjtBQUM5RixNQUFNLGVBQWUsR0FDbkIsa0ZBQWtGLENBQUM7QUFFckYsOENBQThDO0FBQzlDLE1BQU0sQ0FBTixJQUFZLGFBSVg7QUFKRCxXQUFZLGFBQWE7SUFDdkIsNEJBQVcsQ0FBQTtJQUNYLGdD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QVEsQ0FBQyxDQUFDO1FBQ3pCLENBQUM7SUFDSCxDQUFDO0FBQ0gsQ0FBQztBQUVELHNFQUFzRTtBQUN0RSxLQUFLLFVBQVUsVUFBVSxDQUFDLElBQVk7SUFDcEMsT0FBTyxDQUFDLE1BQU0sSUFBSSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQyxFQUFFLFNBQVMsQ0FBQztBQUNoRCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBCZXN0LWVmZm9ydCBnZW9sb2NhdGlvbiBvZiBhIGhvc3QgdG8gdGhlIGNsb3VkIHByb3ZpZGVyIGFuZCByZWdpb24gaXRcbiAqIHJ1bnMgaW4uIFJlc29sdmVzIHRoZSBob3N0IHRvIGl0cyBJUCBhZGRyZXNzKGVzKSB2aWEgRE5TLCB0aGVuIG1hdGNoZXNcbiAqIHRob3NlIGFkZHJlc3NlcyBhZ2FpbnN0IHRoZSBwdWJsaWMgSVAtcmFuZ2UgZmVlZHMgZWFjaCBoeXBlcnNjYWxlclxuICogcHVibGlzaGVzIChBV1MsIEF6dXJlLCBHQ1ApLiBIYW5keSBmb3IgcGxhY2luZyBhIERhdGFicmlja3Mgd29ya3NwYWNlXG4gKiBVUkwsIGJ1dCB3b3JrcyBmb3IgYW55IGhvc3QuXG4gKlxuICogVGhlIHJhbmdlIGZlZWRzIGFyZSBsYXJnZSBhbmQgY2hhbmdlIHNsb3dseSwgc28gZWFjaCBwcm92aWRlcidzIGZlZWRcbiAqIGlzIGNhY2hlZCBmb3Ige0BsaW5rIFJBTkdFX0NBQ0hFX1RUTF9NU30gKDI0IGhvdXJzKSBhdCB0d28gbGF5ZXJzOiBhblxuICogb24tZGlzayBjb3B5IHVuZGVyIHRoZSBPUyB0ZW1wIGRpciAoc3Vydml2ZXMgcHJvY2VzcyByZXN0YXJ0cywgc2hhcmVkXG4gKiBhY3Jvc3MgcHJvY2Vzc2VzKSBwbHVzIGFuIGluLXByb2Nlc3MgbWVtb2l6ZWQgcGFyc2UuIEEgcHJvdmlkZXIgd2hvc2VcbiAqIGZlZWQgZmFpbHMgdG8gbG9hZCBpcyBza2lwcGVkIHJhdGhlciB0aGFuIGZhaWxpbmcgdGhlIHdob2xlIGxvb2t1cCAtXG4gKiB0aGUgb3RoZXIgcHJvdmlkZXJzIHN0aWxsIGFuc3dlci5cbiAqXG4gKiBTZXJ2ZXItb25seTogRE5TIHJlc29sdXRpb24gbmVlZHMgYG5vZGU6ZG5zYCAodmlhIGAuL25ldC50c2ApLCB0aGVcbiAqIGRpc2sgY2FjaGUgbmVlZHMgYG5vZGU6ZnNgIC8gYG5vZGU6b3NgIC8gYG5vZGU6cGF0aGAsIGFuZCB0aGUgZmVlZHNcbiAqIGFyZSBmZXRjaGVkIHdpdGggdGhlIGdsb2JhbCBgZmV0Y2hgLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBta2RpciwgcmVhZEZpbGUsIHJlbmFtZSwgdW5saW5rLCB3cml0ZUZpbGUgfSBmcm9tIFwibm9kZTpmcy9wcm9taXNlc1wiO1xuaW1wb3J0IHsgdG1wZGlyIH0gZnJvbSBcIm5vZGU6b3NcIjtcbmltcG9ydCB7IGRpcm5hbWUsIGpvaW4gfSBmcm9tIFwibm9kZTpwYXRoXCI7XG5cbmltcG9ydCB7IGZpbGUgfSBmcm9tIFwiQGRieC10b29scy9jb3JlXCI7XG5pbXBvcnQgeyBlcnJvciwgZnVuY3Rpb25Nb2R1bGUsIGhhc2gsIGh0dHAsIGpzb24sIGxvZywgbmV0IH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLWNvcmVcIjtcbmltcG9ydCB7IHJlc29sdmVIb3N0SXBzIH0gZnJvbSBcIi4vbmV0LnRzXCI7XG5cbmNvbnN0IGxvZ2dlciA9IGxvZy5sb2dnZXIoXCJjbG91ZFwiKTtcblxuLyoqIEhvdyBsb25nIGEgZmV0Y2hlZCBwcm92aWRlciBJUC1yYW5nZSBmZWVkIGlzIHJldXNlZCBiZWZvcmUgcmVmZXRjaC4gKi9cbmV4cG9ydCBjb25zdCBSQU5HRV9DQUNIRV9UVExfTVMgPSAyNCAqIDYwICogNjAgKiAxMDAwO1xuXG4vKiogQVdTIHB1Ymxpc2hlcyBhIHNpbmdsZSBzdGFibGUsIHVuYXV0aGVudGljYXRlZCByYW5nZXMgZmVlZC4gKi9cbmNvbnN0IEFXU19SQU5HRVNfVVJMID0gXCJodHRwczovL2lwLXJhbmdlcy5hbWF6b25hd3MuY29tL2lwLXJhbmdlcy5qc29uXCI7XG4vKiogR0NQIHB1Ymxpc2hlcyBhIHNpbmdsZSBzdGFibGUsIHVuYXV0aGVudGljYXRlZCByYW5nZXMgZmVlZC4gKi9cbmNvbnN0IEdDUF9SQU5HRVNfVVJMID0gXCJodHRwczovL3d3dy5nc3RhdGljLmNvbS9pcHJhbmdlcy9jbG91ZC5qc29uXCI7XG4vKipcbiAqIEF6dXJlIGhhcyBubyBzdGFibGUgZmVlZCBVUkwgLSB0aGUgSlNPTiBkb3dubG9hZCBsaW5rICh3aXRoIGFcbiAqIHJvdGF0aW5nIGRhdGUgc3RhbXApIGxpdmVzIG9uIHRoaXMgaHVtYW4tZmFjaW5nIGRvd25sb2FkIHBhZ2UgYW5kXG4gKiBtdXN0IGJlIHNjcmFwZWQgb3V0LiBTZWUge0BsaW5rIGZldGNoQXp1cmVSYW5nZXN9LlxuICovXG5jb25zdCBBWlVSRV9ET1dOTE9BRF9QQUdFID0gXCJodHRwczovL3d3dy5taWNyb3NvZnQuY29tL2VuLXVzL2Rvd25sb2FkL2RldGFpbHMuYXNweD9pZD01NjUxOVwiO1xuLyoqIFJlZ2V4IHRoYXQgcGx1Y2tzIHRoZSBjdXJyZW50IGBTZXJ2aWNlVGFnc19QdWJsaWNfPGRhdGU+Lmpzb25gIGxpbmsgb2ZmIHRoZSBBenVyZSBwYWdlLiAqL1xuY29uc3QgQVpVUkVfSlNPTl9MSU5LID1cbiAgL2h0dHBzOlxcL1xcL2Rvd25sb2FkXFwubWljcm9zb2Z0XFwuY29tXFwvZG93bmxvYWRcXC9bXlwiJ10qU2VydmljZVRhZ3NfUHVibGljX1xcZCtcXC5qc29uLztcblxuLyoqIENsb3VkIGh5cGVyc2NhbGVyIGEgaG9zdCByZXNvbHZlcyBpbnRvLiAqL1xuZXhwb3J0IGVudW0gQ2xvdWRQcm92aWRlciB7XG4gIEF3cyA9IFwiYXdzXCIsXG4gIEF6dXJlID0gXCJhenVyZVwiLFxuICBHY3AgPSBcImdjcFwiLFxufVxuXG4vKipcbiAqIFdoZXJlIGEgaG9zdCBsaXZlczogdGhlIHtAbGluayBDbG91ZFByb3ZpZGVyfSwgdGhlIHByb3ZpZGVyLW5hdGl2ZVxuICogYHJlZ2lvbmAgc3RyaW5nICh3aGF0ZXZlciB0aGUgcHJvdmlkZXIncyBmZWVkIGNhbGxzIGl0LCBlLmcuXG4gKiBgXCJ1cy1lYXN0LTFcImAgb24gQVdTLCBgXCJlYXN0dXMyXCJgIG9uIEF6dXJlLCBgXCJ1cy1lYXN0MVwiYCBvbiBHQ1ApLCB0aGVcbiAqIHJlc29sdmVkIGBpcGAgdGhhdCBtYXRjaGVkLCBhbmQgdGhlIGBjaWRyYCBibG9jayBpdCBmZWxsIGluLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIENsb3VkTG9jYXRpb24ge1xuICBwcm92aWRlcjogQ2xvdWRQcm92aWRlcjtcbiAgcmVnaW9uOiBzdHJpbmc7XG4gIGlwOiBzdHJpbmc7XG4gIGNpZHI6IHN0cmluZztcbn1cblxuLyoqIEEgcGFyc2VkIENJRFIgYmxvY2sgdGFnZ2VkIHdpdGggdGhlIHJlZ2lvbiBpdCBiZWxvbmdzIHRvLiAqL1xuaW50ZXJmYWNlIFJlZ2lvbkNpZHIgZXh0ZW5kcyBuZXQuQ2lkciB7XG4gIHJlZ2lvbjogc3RyaW5nO1xufVxuXG4vKiogT25lIHByb3ZpZGVyJ3MgcGFyc2VkLCByZWdpb24tdGFnZ2VkIHJhbmdlIHRhYmxlLiAqL1xuaW50ZXJmYWNlIFByb3ZpZGVyUmFuZ2VzIHtcbiAgcHJvdmlkZXI6IENsb3VkUHJvdmlkZXI7XG4gIHJhbmdlczogUmVnaW9uQ2lkcltdO1xufVxuXG4vKipcbiAqIFJlc29sdmUgYGlucHV0YCdzIGhvc3QgdG8gYSB7QGxpbmsgQ2xvdWRMb2NhdGlvbn0sIG9yIGBudWxsYCB3aGVuIHRoZVxuICogaG9zdCBjYW4ndCBiZSByZXNvbHZlZCBvciBub25lIG9mIGl0cyBJUHMgbWF0Y2ggYSBrbm93biBjbG91ZCByYW5nZS5cbiAqIGBpbnB1dGAgaXMgYW55IHtAbGluayBuZXQuVXJsTGlrZX0gLSBhIFVSTCwgYSBiYXJlIGhvc3QsIG9yIGEgYHsgdXJsIH1gXG4gKiB3cmFwcGVyLlxuICpcbiAqIFRoZSBwcm92aWRlciByYW5nZSBmZWVkcyBhcmUgY2FjaGVkIGZvciB7QGxpbmsgUkFOR0VfQ0FDSEVfVFRMX01TfSwgc29cbiAqIG9ubHkgdGhlIGZpcnN0IGNhbGwgaW4gYSAyNC1ob3VyIHdpbmRvdyBwYXlzIHRoZSBmZXRjaCBjb3N0OyB0aGUgRE5TXG4gKiBsb29rdXAgaGFwcGVucyBvbiBldmVyeSBjYWxsLlxuICpcbiAqIEBleGFtcGxlXG4gKiBhd2FpdCByZXNvbHZlQ2xvdWRMb2NhdGlvbihcImh0dHBzOi8vYWRiLTEyMzQ1Njc4OTAuNy5henVyZWRhdGFicmlja3MubmV0XCIpO1xuICogLy8geyBwcm92aWRlcjogQ2xvdWRQcm92aWRlci5BenVyZSxcbiAqIC8vICAgcmVnaW9uOiBcImVhc3R1czJcIiwgaXA6IFwiLi4uXCIsIGNpZHI6IFwiLi4uXCIgfVxuICpcbiAqIGF3YWl0IHJlc29sdmVDbG91ZExvY2F0aW9uKFwiZGJjLWFiYzEyMy5jbG91ZC5kYXRhYnJpY2tzLmNvbVwiKTtcbiAqIC8vIHsgcHJvdmlkZXI6IENsb3VkUHJvdmlkZXIuQXdzLFxuICogLy8gICByZWdpb246IFwidXMtd2VzdC0yXCIsIGlwOiBcIi4uLlwiLCBjaWRyOiBcIi4uLlwiIH1cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJlc29sdmVDbG91ZExvY2F0aW9uKGlucHV0OiBuZXQuVXJsTGlrZSk6IFByb21pc2U8Q2xvdWRMb2NhdGlvbiB8IG51bGw+IHtcbiAgY29uc3QgaXBzID0gYXdhaXQgcmVzb2x2ZUhvc3RJcHMoaW5wdXQpO1xuICBpZiAoaXBzLmxlbmd0aCA9PT0gMCkge1xuICAgIGxvZ2dlci5kZWJ1ZyhcIm5vIGlwcyByZXNvbHZlZFwiLCB7IGlucHV0OiBTdHJpbmcoaW5wdXQpIH0pO1xuICAgIHJldHVybiBudWxsO1xuICB9XG4gIGNvbnN0IHByb3ZpZGVycyA9IGF3YWl0IGxvYWRQcm92aWRlclJhbmdlcygpO1xuICBmb3IgKGNvbnN0IGlwIG9mIGlwcykge1xuICAgIGNvbnN0IHBhcnNlZCA9IG5ldC5wYXJzZUlwKGlwKTtcbiAgICBpZiAoIXBhcnNlZCkgY29udGludWU7XG4gICAgZm9yIChjb25zdCB7IHByb3ZpZGVyLCByYW5nZXMgfSBvZiBwcm92aWRlcnMpIHtcbiAgICAgIGNvbnN0IG1hdGNoID0gbmV0LmZpbmRDb250YWluaW5nQ2lkcihwYXJzZWQsIHJhbmdlcyk7XG4gICAgICBpZiAobWF0Y2gpIHtcbiAgICAgICAgcmV0dXJuIHsgcHJvdmlkZXIsIHJlZ2lvbjogbWF0Y2gucmVnaW9uLCBpcCwgY2lkcjogbWF0Y2guY2lkciB9O1xuICAgICAgfVxuICAgIH1cbiAgfVxuICBsb2dnZXIuZGVidWcoXCJubyBjbG91ZCByYW5nZSBtYXRjaGVkXCIsIHsgaXBzIH0pO1xuICByZXR1cm4gbnVsbDtcbn1cblxuLyoqXG4gKiBMb2FkIGV2ZXJ5IHByb3ZpZGVyJ3MgcmVnaW9uLXRhZ2dlZCByYW5nZSB0YWJsZSwgZWFjaCBjYWNoZWQgZm9yXG4gKiB7QGxpbmsgUkFOR0VfQ0FDSEVfVFRMX01TfS4gUHJvdmlkZXJzIGFyZSBsb2FkZWQgaW4gcGFyYWxsZWwgYW5kIGFcbiAqIGZlZWQgdGhhdCBmYWlscyB0byBmZXRjaCBvciBwYXJzZSBpcyBkcm9wcGVkIChsb2dnZWQsIG5vdCB0aHJvd24pIHNvXG4gKiBhIHNpbmdsZSBmbGFreSBmZWVkIG5ldmVyIHNpbmtzIHRoZSB3aG9sZSBsb29rdXAuIEV4cG9zZWQgZm9yIGNhbGxlcnNcbiAqIHRoYXQgd2FudCB0byBtYXRjaCBtYW55IElQcyBhZ2FpbnN0IG9uZSBjYWNoZWQgc25hcHNob3Qgd2l0aG91dCBhIEROU1xuICogc3RlcCBwZXIgYWRkcmVzcy5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxvYWRQcm92aWRlclJhbmdlcygpOiBQcm9taXNlPFByb3ZpZGVyUmFuZ2VzW10+IHtcbiAgY29uc3Qgc2V0dGxlZCA9IGF3YWl0IFByb21pc2UuYWxsU2V0dGxlZChbbG9hZEF3c1JhbmdlcygpLCBsb2FkQXp1cmVSYW5nZXMoKSwgbG9hZEdjcFJhbmdlcygpXSk7XG4gIGNvbnN0IG91dDogUHJvdmlkZXJSYW5nZXNbXSA9IFtdO1xuICBmb3IgKGNvbnN0IHJlc3VsdCBvZiBzZXR0bGVkKSB7XG4gICAgaWYgKHJlc3VsdC5zdGF0dXMgPT09IFwiZnVsZmlsbGVkXCIpIG91dC5wdXNoKHJlc3VsdC52YWx1ZSk7XG4gICAgZWxzZSBsb2dnZXIud2FybihcInByb3ZpZGVyIHJhbmdlIGxvYWQgZmFpbGVkXCIsIHsgZXJyb3I6IGVycm9yLmVycm9yTWVzc2FnZShyZXN1bHQucmVhc29uKSB9KTtcbiAgfVxuICByZXR1cm4gb3V0O1xufVxuXG4vLyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcbi8vIFBlci1wcm92aWRlciBjYWNoZWQgbG9hZGVyc1xuLy8g4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG5cbmNvbnN0IGxvYWRBd3NSYW5nZXMgPSBmdW5jdGlvbk1vZHVsZS5tZW1vaXplKGZldGNoQXdzUmFuZ2VzLCB7IHR0bE1zOiBSQU5HRV9DQUNIRV9UVExfTVMgfSk7XG5jb25zdCBsb2FkQXp1cmVSYW5nZXMgPSBmdW5jdGlvbk1vZHVsZS5tZW1vaXplKGZldGNoQXp1cmVSYW5nZXMsIHsgdHRsTXM6IFJBTkdFX0NBQ0hFX1RUTF9NUyB9KTtcbmNvbnN0IGxvYWRHY3BSYW5nZXMgPSBmdW5jdGlvbk1vZHVsZS5tZW1vaXplKGZldGNoR2NwUmFuZ2VzLCB7IHR0bE1zOiBSQU5HRV9DQUNIRV9UVExfTVMgfSk7XG5cbi8vIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuLy8gUHJvdmlkZXIgZmVlZCBmZXRjaCArIHBhcnNlXG4vLyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcblxuaW50ZXJmYWNlIEF3c0ZlZWQge1xuICBwcmVmaXhlcz86IHsgaXBfcHJlZml4Pzogc3RyaW5nOyByZWdpb24/OiBzdHJpbmcgfVtdO1xuICBpcHY2X3ByZWZpeGVzPzogeyBpcHY2X3ByZWZpeD86IHN0cmluZzsgcmVnaW9uPzogc3RyaW5nIH1bXTtcbn1cblxuLyoqXG4gKiBBV1MgYGlwLXJhbmdlcy5qc29uYDogYSBmbGF0IGxpc3Qgb2YgSVB2NCAoYHByZWZpeGVzYCkgYW5kIElQdjZcbiAqIChgaXB2Nl9wcmVmaXhlc2ApIGJsb2NrcywgZWFjaCBhbHJlYWR5IHRhZ2dlZCB3aXRoIGEgYHJlZ2lvbmAgc3VjaFxuICogYXMgYFwidXMtZWFzdC0xXCJgIChvciBgXCJHTE9CQUxcImAgZm9yIGVkZ2UgcmFuZ2VzKS5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gZmV0Y2hBd3NSYW5nZXMoKTogUHJvbWlzZTxQcm92aWRlclJhbmdlcz4ge1xuICBjb25zdCBmZWVkID0gYXdhaXQgZmV0Y2hKc29uPEF3c0ZlZWQ+KEFXU19SQU5HRVNfVVJMKTtcbiAgY29uc3QgcmFuZ2VzOiBSZWdpb25DaWRyW10gPSBbXTtcbiAgZm9yIChjb25zdCBlbnRyeSBvZiBmZWVkLnByZWZpeGVzID8/IFtdKSB7XG4gICAgYWRkUmFuZ2UocmFuZ2VzLCBlbnRyeS5pcF9wcmVmaXgsIGVudHJ5LnJlZ2lvbik7XG4gIH1cbiAgZm9yIChjb25zdCBlbnRyeSBvZiBmZWVkLmlwdjZfcHJlZml4ZXMgPz8gW10pIHtcbiAgICBhZGRSYW5nZShyYW5nZXMsIGVudHJ5LmlwdjZfcHJlZml4LCBlbnRyeS5yZWdpb24pO1xuICB9XG4gIGxvZ2dlci5kZWJ1ZyhcImxvYWRlZCBhd3MgcmFuZ2VzXCIsIHsgY291bnQ6IHJhbmdlcy5sZW5ndGggfSk7XG4gIHJldHVybiB7IHByb3ZpZGVyOiBDbG91ZFByb3ZpZGVyLkF3cywgcmFuZ2VzIH07XG59XG5cbmludGVyZmFjZSBHY3BGZWVkIHtcbiAgcHJlZml4ZXM/OiB7IGlwdjRQcmVmaXg/OiBzdHJpbmc7IGlwdjZQcmVmaXg/OiBzdHJpbmc7IHNjb3BlPzogc3RyaW5nIH1bXTtcbn1cblxuLyoqXG4gKiBHQ1AgYGNsb3VkLmpzb25gOiBhIGxpc3Qgb2YgYmxvY2tzIGVhY2ggY2FycnlpbmcgYW4gYGlwdjRQcmVmaXhgIG9yXG4gKiBgaXB2NlByZWZpeGAgYW5kIGEgYHNjb3BlYCB0aGF0IGlzIHRoZSByZWdpb24gKGBcInVzLWVhc3QxXCJgKSBvclxuICogYFwiZ2xvYmFsXCJgIGZvciBub24tcmVnaW9uYWwgcmFuZ2VzLlxuICovXG5hc3luYyBmdW5jdGlvbiBmZXRjaEdjcFJhbmdlcygpOiBQcm9taXNlPFByb3ZpZGVyUmFuZ2VzPiB7XG4gIGNvbnN0IGZlZWQgPSBhd2FpdCBmZXRjaEpzb248R2NwRmVlZD4oR0NQX1JBTkdFU19VUkwpO1xuICBjb25zdCByYW5nZXM6IFJlZ2lvbkNpZHJbXSA9IFtdO1xuICBmb3IgKGNvbnN0IGVudHJ5IG9mIGZlZWQucHJlZml4ZXMgPz8gW10pIHtcbiAgICBhZGRSYW5nZShyYW5nZXMsIGVudHJ5LmlwdjRQcmVmaXggPz8gZW50cnkuaXB2NlByZWZpeCwgZW50cnkuc2NvcGUpO1xuICB9XG4gIGxvZ2dlci5kZWJ1ZyhcImxvYWRlZCBnY3AgcmFuZ2VzXCIsIHsgY291bnQ6IHJhbmdlcy5sZW5ndGggfSk7XG4gIHJldHVybiB7IHByb3ZpZGVyOiBDbG91ZFByb3ZpZGVyLkdjcCwgcmFuZ2VzIH07XG59XG5cbmludGVyZmFjZSBBenVyZUZlZWQge1xuICB2YWx1ZXM/OiB7XG4gICAgbmFtZT86IHN0cmluZztcbiAgICBwcm9wZXJ0aWVzPzogeyByZWdpb24/OiBzdHJpbmc7IGFkZHJlc3NQcmVmaXhlcz86IHN0cmluZ1tdIH07XG4gIH1bXTtcbn1cblxuLyoqXG4gKiBBenVyZSBzZXJ2aWNlIHRhZ3M6IE1pY3Jvc29mdCBzaGlwcyBubyBzdGFibGUgZmVlZCBVUkwsIHNvIHNjcmFwZSB0aGVcbiAqIGN1cnJlbnQgYFNlcnZpY2VUYWdzX1B1YmxpY188ZGF0ZT4uanNvbmAgbGluayBvZmYgdGhlIGRvd25sb2FkIHBhZ2VcbiAqIGFuZCBmZXRjaCBpdC4gT25seSB0aGUgcmVnaW9uYWwgYEF6dXJlQ2xvdWQuPHJlZ2lvbj5gIGFnZ3JlZ2F0ZXNcbiAqICh3aGljaCBoYXZlIGEgbm9uLWVtcHR5IGByZWdpb25gKSBhcmUga2VwdDsgdGhlIGNsb3VkLXdpZGVcbiAqIGBBenVyZUNsb3VkYCB0YWcgYW5kIHBlci1zZXJ2aWNlIHRhZ3MgYXJlIGlnbm9yZWQgc28gYW4gYWRkcmVzcyBtYXBzXG4gKiBjbGVhbmx5IHRvIG9uZSByZWdpb24uXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGZldGNoQXp1cmVSYW5nZXMoKTogUHJvbWlzZTxQcm92aWRlclJhbmdlcz4ge1xuICBjb25zdCBwYWdlID0gYXdhaXQgZmV0Y2hUZXh0KEFaVVJFX0RPV05MT0FEX1BBR0UpO1xuICBjb25zdCBqc29uVXJsID0gcGFnZS5tYXRjaChBWlVSRV9KU09OX0xJTkspPy5bMF07XG4gIGlmICghanNvblVybCkge1xuICAgIHRocm93IG5ldyBFcnJvcihcImNvdWxkIG5vdCBmaW5kIFNlcnZpY2VUYWdzIEpTT04gbGluayBvbiBBenVyZSBkb3dubG9hZCBwYWdlXCIpO1xuICB9XG4gIGNvbnN0IGZlZWQgPSBhd2FpdCBmZXRjaEpzb248QXp1cmVGZWVkPihqc29uVXJsKTtcbiAgY29uc3QgcmFuZ2VzOiBSZWdpb25DaWRyW10gPSBbXTtcbiAgZm9yIChjb25zdCB2YWx1ZSBvZiBmZWVkLnZhbHVlcyA/PyBbXSkge1xuICAgIGNvbnN0IHJlZ2lvbiA9IHZhbHVlLnByb3BlcnRpZXM/LnJlZ2lvbjtcbiAgICBpZiAoIXJlZ2lvbiB8fCAhdmFsdWUubmFtZT8uc3RhcnRzV2l0aChcIkF6dXJlQ2xvdWQuXCIpKSBjb250aW51ZTtcbiAgICBmb3IgKGNvbnN0IHByZWZpeCBvZiB2YWx1ZS5wcm9wZXJ0aWVzPy5hZGRyZXNzUHJlZml4ZXMgPz8gW10pIHtcbiAgICAgIGFkZFJhbmdlKHJhbmdlcywgcHJlZml4LCByZWdpb24pO1xuICAgIH1cbiAgfVxuICBsb2dnZXIuZGVidWcoXCJsb2FkZWQgYXp1cmUgcmFuZ2VzXCIsIHsgY291bnQ6IHJhbmdlcy5sZW5ndGggfSk7XG4gIHJldHVybiB7IHByb3ZpZGVyOiBDbG91ZFByb3ZpZGVyLkF6dXJlLCByYW5nZXMgfTtcbn1cblxuLy8g4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG4vLyBIZWxwZXJzXG4vLyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcblxuLyoqIFBhcnNlIGBjaWRyYCBhbmQsIHdoZW4gdmFsaWQgYW5kIGByZWdpb25gIGlzIHNldCwgcHVzaCBhIHRhZ2dlZCByYW5nZS4gKi9cbmZ1bmN0aW9uIGFkZFJhbmdlKHJhbmdlczogUmVnaW9uQ2lkcltdLCBjaWRyPzogc3RyaW5nLCByZWdpb24/OiBzdHJpbmcpOiB2b2lkIHtcbiAgaWYgKCFjaWRyIHx8ICFyZWdpb24pIHJldHVybjtcbiAgY29uc3QgcGFyc2VkID0gbmV0LnBhcnNlQ2lkcihjaWRyKTtcbiAgaWYgKHBhcnNlZCkgcmFuZ2VzLnB1c2goeyAuLi5wYXJzZWQsIHJlZ2lvbiB9KTtcbn1cblxuLyoqXG4gKiBGZXRjaCBhbmQgcGFyc2UgYSBwcm92aWRlciBmZWVkLiBBIHBhcnNlIGZhaWx1cmUgbmFtZXMgdGhlIFVSTCByYXRoZXIgdGhhblxuICogc3VyZmFjaW5nIGFuIG9wYXF1ZSBgVW5leHBlY3RlZCB0b2tlbmAgLSB0aGUgdGV4dCBtYXkgY29tZSBmcm9tIHRoZSBvbi1kaXNrXG4gKiBjYWNoZSwgc28gXCJ3aGljaCBmZWVkXCIgaXMgdGhlIHBhcnQgd29ydGgga25vd2luZy4ge0BsaW5rIGxvYWRQcm92aWRlclJhbmdlc31cbiAqIHR1cm5zIHRoZSB0aHJvdyBpbnRvIGEgcGVyLXByb3ZpZGVyIHdhcm5pbmcuXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGZldGNoSnNvbjxUPih1cmw6IHN0cmluZyk6IFByb21pc2U8VD4ge1xuICBjb25zdCB0ZXh0ID0gYXdhaXQgZmV0Y2hUZXh0KHVybCk7XG4gIGNvbnN0IHBhcnNlZCA9IGpzb24ucGFyc2U8VD4odGV4dCk7XG4gIGlmIChwYXJzZWQgPT09IHVuZGVmaW5lZCkge1xuICAgIHRocm93IG5ldyBFcnJvcihgcmFuZ2UgZmVlZCByZXR1cm5lZCBub24tSlNPTiBjb250ZW50OiAke3VybH1gKTtcbiAgfVxuICByZXR1cm4gcGFyc2VkO1xufVxuXG4vKipcbiAqIEZldGNoIGB1cmxgIGFzIHRleHQgd2l0aCBhIDI0aCBvbi1kaXNrIGNhY2hlIHVuZGVyIHRoZSBPUyB0ZW1wIGRpclxuICogKGtleWVkIGJ5IGFuIEZOViBoYXNoIG9mIHRoZSBVUkwpLiBBIGZyZXNoIGNhY2hlIGZpbGUgaXMgcmV0dXJuZWRcbiAqIGRpcmVjdGx5OyBhIG1pc3MvZXhwaXJ5IGZldGNoZXMsIHdyaXRlcyB0byBhIHVuaXF1ZSB0ZW1wIGZpbGUsIGFuZFxuICogYXRvbWljYWxseSByZW5hbWVzIGl0IGludG8gcGxhY2Ugc28gY29uY3VycmVudCBjYWxsZXJzIG5ldmVyIG9ic2VydmVcbiAqIGEgaGFsZi13cml0dGVuIGNhY2hlIGVudHJ5LlxuICovXG5hc3luYyBmdW5jdGlvbiBmZXRjaFRleHQodXJsOiBzdHJpbmcpOiBQcm9taXNlPHN0cmluZz4ge1xuICBjb25zdCBjYWNoZURpciA9IGpvaW4odG1wZGlyKCksIFwiZGJ4LXRvb2xzXCIsIFwic2hhcmVkXCIsIFwiY2xvdWRcIik7XG4gIGNvbnN0IGNhY2hlRGlyQ3JlYXRlZCA9IGF3YWl0IG1rZGlyKGNhY2hlRGlyLCB7IHJlY3Vyc2l2ZTogdHJ1ZSB9KTtcbiAgY29uc3QgY2FjaGVQYXRoID0gam9pbihjYWNoZURpciwgYGZldGNoLSR7aGFzaC5mbnZIYXNoKHVybCl9LnR4dGApO1xuICBjb25zdCBjcmVhdGVkQXQgPSBjYWNoZURpckNyZWF0ZWQgPyB1bmRlZmluZWQgOiBhd2FpdCBnZXRDcmVhdGVkKGNhY2hlUGF0aCk7XG4gIGlmIChjcmVhdGVkQXQpIHtcbiAgICBjb25zdCBleHBpcmVzQXQgPSBuZXcgRGF0ZShjcmVhdGVkQXQuZ2V0VGltZSgpICsgUkFOR0VfQ0FDSEVfVFRMX01TKTtcbiAgICBpZiAoZXhwaXJlc0F0ID4gbmV3IERhdGUoKSkge1xuICAgICAgbG9nZ2VyLmRlYnVnKFwiY2FjaGVkIGZldGNoIGhpdFwiLCB7IHVybCwgY2FjaGVQYXRoIH0pO1xuICAgICAgcmV0dXJuIHJlYWRGaWxlKGNhY2hlUGF0aCwgXCJ1dGY4XCIpO1xuICAgIH1cbiAgfVxuICBsZXQgdGVtcFBhdGg6IHN0cmluZyB8IG51bGwgPSBqb2luKGNhY2hlRGlyLCBgJHtoYXNoLmlkKCl9LnR4dGApO1xuICB0cnkge1xuICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2godXJsKTtcbiAgICBpZiAoIXJlc3BvbnNlLm9rKSB7XG4gICAgICB0aHJvdyBhd2FpdCBodHRwLmNyZWF0ZUZldGNoRXJyb3IocmVzcG9uc2UsIHVybCk7XG4gICAgfVxuICAgIGNvbnN0IHJlc3BvbnNlVGV4dCA9IGF3YWl0IHJlc3BvbnNlLnRleHQoKTtcbiAgICBhd2FpdCBta2RpcihkaXJuYW1lKHRlbXBQYXRoKSwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgYXdhaXQgd3JpdGVGaWxlKHRlbXBQYXRoLCByZXNwb25zZVRleHQpO1xuXG4gICAgYXdhaXQgcmVuYW1lKHRlbXBQYXRoLCBjYWNoZVBhdGgpO1xuICAgIGxvZ2dlci5kZWJ1ZyhcImNhY2hlZCBmZXRjaCBsb2FkXCIsIHsgdXJsLCBjYWNoZVBhdGggfSk7XG4gICAgdGVtcFBhdGggPSBudWxsO1xuICAgIHJldHVybiByZXNwb25zZVRleHQ7XG4gIH0gZmluYWxseSB7XG4gICAgaWYgKHRlbXBQYXRoKSB7XG4gICAgICBhd2FpdCB1bmxpbmsodGVtcFBhdGgpO1xuICAgIH1cbiAgfVxufVxuXG4vKiogQmlydGggdGltZSBvZiBgcGF0aGAsIG9yIGB1bmRlZmluZWRgIHdoZW4gaXQgZG9lc24ndCBleGlzdCB5ZXQuICovXG5hc3luYyBmdW5jdGlvbiBnZXRDcmVhdGVkKHBhdGg6IHN0cmluZyk6IFByb21pc2U8RGF0ZSB8IHVuZGVmaW5lZD4ge1xuICByZXR1cm4gKGF3YWl0IGZpbGUuc3RhdFN5bmMocGF0aCkpPy5iaXJ0aHRpbWU7XG59XG4iXX0=
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Databricks {@link FileSystem} backed by {@link WorkspaceClient}.
3
+ *
4
+ * Extends {@link BaseFileSystem}. Routes I/O by absolute path:
5
+ * - `/Workspace`, `/Users`, `/Repos`, `/Shared` → workspace objects API
6
+ * - `/Volumes/...` (or `catalog.schema.volume` roots) → Unity Catalog Files API
7
+ * - `/dbfs/...` → DBFS API
8
+ *
9
+ * Every primitive picks its API through {@link DatabricksFileSystem.dispatch}
10
+ * rather than re-branching by hand, and error normalization is inherited from
11
+ * {@link BaseFileSystem}, so a primitive here is just the SDK call.
12
+ *
13
+ * @module
14
+ */
15
+ import { BaseFileSystem, type CopyOptions, type FileEntry, type FileStat, type WriteFileOptions } from "@dbx-tools/shared-fs";
16
+ import { type WorkspaceClient } from "./workspace.ts";
17
+ /** Options for {@link DatabricksFileSystem}. */
18
+ export interface DatabricksFileSystemOptions {
19
+ /** Unique identifier. Defaults to a stable hash of the normalized root. */
20
+ id?: string;
21
+ /**
22
+ * Filesystem root. Accepts:
23
+ * - `/Volumes/catalog/schema/volume` (also `/Volume/...`)
24
+ * - `catalog.schema.volume`
25
+ * - `~` / `~/...` → `/Workspace/Users/<userName>/...` (needs {@link userName},
26
+ * or use {@link DatabricksFileSystem.create})
27
+ * - `/Workspace/...`, `/Users/...`, `/Repos/...`, `/Shared/...`
28
+ * - `/dbfs/...`
29
+ */
30
+ root: string;
31
+ /**
32
+ * Username for expanding `~`. When omitted and {@link root} is home-relative,
33
+ * prefer {@link DatabricksFileSystem.create} which resolves it via
34
+ * `getCurrentUserName`.
35
+ */
36
+ userName?: string;
37
+ /**
38
+ * Databricks workspace client. Defaults to `tryGetWorkspaceClient()`, otherwise
39
+ * a default {@link WorkspaceClient} from env / profile auth.
40
+ */
41
+ client?: WorkspaceClient;
42
+ /** Block all write operations. Defaults to false. */
43
+ readOnly?: boolean;
44
+ /**
45
+ * Create {@link root} (and parents) during init. Defaults to false (volume
46
+ * and workspace roots are usually provisioned out of band).
47
+ */
48
+ createRoot?: boolean;
49
+ }
50
+ /**
51
+ * {@link FileSystem} implementation over Databricks workspace files, UC
52
+ * volumes, and DBFS.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * const fs = new DatabricksFileSystem({ root: "main.default.assets" });
57
+ * await fs.writeFile("notes/hello.txt", "hi");
58
+ *
59
+ * const home = await DatabricksFileSystem.create({ root: "~" });
60
+ * const listing = await home.readdir(".");
61
+ * ```
62
+ */
63
+ export declare class DatabricksFileSystem extends BaseFileSystem<"databricks"> {
64
+ private client;
65
+ private readonly clientOption;
66
+ constructor(options: DatabricksFileSystemOptions);
67
+ /**
68
+ * Construct a {@link DatabricksFileSystem}, resolving `~` roots via the
69
+ * workspace current user when needed.
70
+ */
71
+ static create(options: DatabricksFileSystemOptions): Promise<DatabricksFileSystem>;
72
+ protected onInit(): Promise<void>;
73
+ /** Run the handler for `absolutePath`'s Databricks API with the live client. */
74
+ private dispatch;
75
+ protected createRootDirectory(): Promise<void>;
76
+ protected readBytesAt(resolvedPath: string): Promise<Uint8Array>;
77
+ protected writeBytesAt(resolvedPath: string, content: Uint8Array, options: Required<WriteFileOptions>): Promise<void>;
78
+ protected deleteFileAt(resolvedPath: string): Promise<void>;
79
+ protected createDirectoryAt(resolvedPath: string): Promise<void>;
80
+ protected removeDirectoryAt(resolvedPath: string): Promise<void>;
81
+ protected listDirectoryAt(resolvedPath: string): Promise<FileEntry[]>;
82
+ protected statAt(resolvedPath: string): Promise<Omit<FileStat, "path">>;
83
+ protected tryMoveFileAt(sourcePath: string, destinationPath: string, _options: Required<CopyOptions>): Promise<boolean>;
84
+ /**
85
+ * UC `createDirectory` is single-level, so walk the path creating each level
86
+ * below the volume itself. A level that already exists is not an error.
87
+ */
88
+ private createVolumeDirectories;
89
+ /** Stat a UC path: file metadata first, then a directory probe. */
90
+ private statVolumePath;
91
+ private readDbfsFile;
92
+ private writeDbfsFile;
93
+ }