@dbx-tools/core 0.9.153 → 0.9.157

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/bundle.ts CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { opendir, realpath, stat } from "node:fs/promises";
13
13
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
14
- import { json, object, stringUtils } from "@dbx-tools/shared-core";
14
+ import { parseRecord, isRecord, optional, trimToNull } from "@dbx-tools/shared-core";
15
15
  import { spawn } from "./exec.ts";
16
16
  import { resolveWorkingDirectory, root } from "./project-utils.ts";
17
17
 
@@ -68,17 +68,14 @@ export async function* appResources(
68
68
  for await (const bundlePath of bundlePaths(boundary, startDirectory)) {
69
69
  const validated = await validateBundle(bundlePath, profile);
70
70
  for (const [key, config] of Object.entries(bundleApps(validated.data))) {
71
- if (!object.isRecord(config)) continue;
71
+ if (!isRecord(config)) continue;
72
72
  yield {
73
73
  bundlePath,
74
74
  key,
75
75
  config,
76
76
  data: validated.data,
77
- ...object.optional(
78
- "sourceCodePath",
79
- resolveSourceCodePath(bundlePath, config.source_code_path),
80
- ),
81
- ...object.optional("bundleFailure", validated.bundleFailure),
77
+ ...optional("sourceCodePath", resolveSourceCodePath(bundlePath, config.source_code_path)),
78
+ ...optional("bundleFailure", validated.bundleFailure),
82
79
  };
83
80
  }
84
81
  }
@@ -174,7 +171,7 @@ async function validateBundle(
174
171
  stderr: "capture",
175
172
  },
176
173
  );
177
- const detail = stringUtils.trimToNull(result.stderr) ?? stringUtils.trimToNull(result.stdout);
174
+ const detail = trimToNull(result.stderr) ?? trimToNull(result.stdout);
178
175
  const bundleFailure =
179
176
  result.exitCode === 0
180
177
  ? undefined
@@ -184,17 +181,17 @@ async function validateBundle(
184
181
  }`,
185
182
  );
186
183
 
187
- const parsed = json.parseRecord(result.stdout);
184
+ const parsed = parseRecord(result.stdout);
188
185
  if (!parsed) {
189
186
  if (bundleFailure) return { data: {}, bundleFailure };
190
187
  throw new Error(`Databricks CLI returned invalid bundle JSON for ${bundlePath}`);
191
188
  }
192
- return { data: parsed, ...object.optional("bundleFailure", bundleFailure) };
189
+ return { data: parsed, ...optional("bundleFailure", bundleFailure) };
193
190
  }
194
191
 
195
192
  function bundleApps(bundle: Record<string, unknown>): Record<string, unknown> {
196
- const resources = object.isRecord(bundle.resources) ? bundle.resources : undefined;
197
- return object.isRecord(resources?.apps) ? resources.apps : {};
193
+ const resources = isRecord(bundle.resources) ? bundle.resources : undefined;
194
+ return isRecord(resources?.apps) ? resources.apps : {};
198
195
  }
199
196
 
200
197
  async function isFile(path: string): Promise<boolean> {
@@ -36,12 +36,19 @@ import { readFileSync } from "node:fs";
36
36
  import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
37
37
  import { parseEnv } from "node:util";
38
38
  import {
39
- environmentUtils,
40
- json,
41
39
  log,
42
- object,
43
- options as sharedOptions,
44
- stringUtils,
40
+ isDatabricksAppEnv,
41
+ parseRecord,
42
+ isRecord,
43
+ sequence,
44
+ type Sequence,
45
+ toBoolean,
46
+ tcpPortSchema,
47
+ tokenize,
48
+ trimToEmpty,
49
+ trimToNull,
50
+ parseList,
51
+ toNumber,
45
52
  } from "@dbx-tools/shared-core";
46
53
  import { parse as parseYaml } from "yaml";
47
54
  import { z } from "zod";
@@ -106,9 +113,6 @@ const NODE_ENV_ALTERNATIVES = {
106
113
  development: ["dev"],
107
114
  } as const satisfies Record<string, readonly string[]>;
108
115
 
109
- /** Highest valid TCP port number. */
110
- export const MAX_TCP_PORT = sharedOptions.MAX_TCP_PORT;
111
-
112
116
  /** Boolean environment override for project `.env` reads. */
113
117
  const CONFIG_DOTENV_KEY = "DBX_TOOLS_CONFIG_DOTENV";
114
118
 
@@ -130,9 +134,9 @@ export const valueSchema = z
130
134
  message: "Interpolated values are not allowed",
131
135
  });
132
136
 
133
- const portValueSchema = sharedOptions.tcpPortSchema;
137
+ const portValueSchema = tcpPortSchema;
134
138
 
135
- const positiveNumberValue = z.preprocess((input) => object.toNumber(input), z.number().positive());
139
+ const positiveNumberValue = z.preprocess((input) => toNumber(input), z.number().positive());
136
140
  const positiveIntValue = positiveNumberValue.transform(Math.floor);
137
141
 
138
142
  /** A named bundle or App resource; concrete resource fields pass through. */
@@ -228,7 +232,7 @@ function resourceValue(resource: unknown): string | undefined {
228
232
  ["postgres", "branch"],
229
233
  ] as const) {
230
234
  let value = resource;
231
- for (const part of path) value = object.isRecord(value) ? value[part] : undefined;
235
+ for (const part of path) value = isRecord(value) ? value[part] : undefined;
232
236
  const parsed = valueSchema.safeParse(value);
233
237
  if (parsed.success) return parsed.data;
234
238
  }
@@ -241,12 +245,12 @@ export function getBundlePath(data: Record<string, unknown>, path: string): stri
241
245
  if (parts.length === 0) return undefined;
242
246
  let current: unknown = data;
243
247
  for (let index = 0; index < parts.length; index++) {
244
- if (!object.isRecord(current)) return undefined;
248
+ if (!isRecord(current)) return undefined;
245
249
  const next = current[parts[index]!];
246
250
  if (index === parts.length - 1) {
247
251
  const direct = valueSchema.safeParse(next);
248
252
  if (direct.success) return direct.data;
249
- if (object.isRecord(next)) {
253
+ if (isRecord(next)) {
250
254
  const nested = valueSchema.safeParse(next.value);
251
255
  return nested.success ? nested.data : undefined;
252
256
  }
@@ -261,12 +265,11 @@ export function getBundlePath(data: Record<string, unknown>, path: string): stri
261
265
  export function environmentKeys(name: string): readonly string[] {
262
266
  const trimmed = name.trim();
263
267
  if (!trimmed) return [];
264
- return object
265
- .sequence(
266
- trimmed,
267
- trimmed.toUpperCase(),
268
- Array.from(stringUtils.tokenize(trimmed)).join("_").toUpperCase(),
269
- )
268
+ return sequence(
269
+ trimmed,
270
+ trimmed.toUpperCase(),
271
+ Array.from(tokenize(trimmed)).join("_").toUpperCase(),
272
+ )
270
273
  .filter(Boolean)
271
274
  .distinct()
272
275
  .toArray();
@@ -277,24 +280,17 @@ function keys(
277
280
  input: ConfigKey,
278
281
  options: Pick<ConfigOptions, "scope" | "prefix"> = {},
279
282
  ): readonly string[] {
280
- const scopes = object
281
- .sequence(options.scope ?? DEFAULT_SCOPE)
282
- .map(stringUtils.trimToEmpty)
283
+ const scopes = sequence(options.scope ?? DEFAULT_SCOPE)
284
+ .map(trimToEmpty)
283
285
  .filter(Boolean)
284
286
  .distinct()
285
287
  .toArray();
286
288
  const prefixes =
287
289
  options.prefix === undefined
288
290
  ? []
289
- : object
290
- .sequence(options.prefix)
291
- .map(stringUtils.trimToEmpty)
292
- .filter(Boolean)
293
- .distinct()
294
- .toArray();
295
- return object
296
- .sequence(input)
297
- .map(stringUtils.trimToEmpty)
291
+ : sequence(options.prefix).map(trimToEmpty).filter(Boolean).distinct().toArray();
292
+ return sequence(input)
293
+ .map(trimToEmpty)
298
294
  .filter(Boolean)
299
295
  .distinct()
300
296
  .flatMap((key) => {
@@ -316,10 +312,10 @@ function keys(
316
312
  * already answered.
317
313
  *
318
314
  */
319
- function values(input: ConfigKey, options: ConfigOptions = {}): object.Sequence<ConfigValue> {
315
+ function values(input: ConfigKey, options: ConfigOptions = {}): Sequence<ConfigValue> {
320
316
  const candidates = keys(input, options);
321
317
  const sources = configSources(options);
322
- return object.sequence({
318
+ return sequence({
323
319
  *[Symbol.iterator](): Generator<ConfigValue> {
324
320
  for (const source of sources) {
325
321
  for (const map of read(source, options)) {
@@ -335,7 +331,7 @@ function values(input: ConfigKey, options: ConfigOptions = {}): object.Sequence<
335
331
 
336
332
  function configSources(options: ConfigOptions): readonly ConfigSource[] {
337
333
  const sources: ConfigSource[] = [
338
- ...object.sequence<ConfigSource>(options.sources ?? DEFAULT_SOURCES).distinct(),
334
+ ...sequence<ConfigSource>(options.sources ?? DEFAULT_SOURCES).distinct(),
339
335
  ];
340
336
  if (options.data !== undefined && options.sources !== undefined && !sources.includes("config")) {
341
337
  sources.push("config");
@@ -346,12 +342,12 @@ function configSources(options: ConfigOptions): readonly ConfigSource[] {
346
342
  function configMapValue(value: ConfigMapValue | unknown): string | null {
347
343
  if (Array.isArray(value)) {
348
344
  for (const entry of value) {
349
- const resolved = stringUtils.trimToNull(entry);
345
+ const resolved = trimToNull(entry);
350
346
  if (resolved !== null) return resolved;
351
347
  }
352
348
  return null;
353
349
  }
354
- return stringUtils.trimToNull(value);
350
+ return trimToNull(value);
355
351
  }
356
352
 
357
353
  /**
@@ -389,12 +385,12 @@ export function name(
389
385
  * Known long and short names are interchangeable; unknown names pass through.
390
386
  */
391
387
  function nodeEnvNames(nodeEnv: unknown): string[] {
392
- const name = stringUtils.trimToNull(nodeEnv)?.toLowerCase();
388
+ const name = trimToNull(nodeEnv)?.toLowerCase();
393
389
  if (!name || !/^[a-z0-9_-]+$/.test(name)) return [];
394
390
  for (const [canonical, alternatives] of Object.entries(NODE_ENV_ALTERNATIVES)) {
395
391
  const names: readonly string[] = alternatives;
396
392
  if (name === canonical || names.includes(name)) {
397
- return [...object.sequence(name, canonical, alternatives).distinct()];
393
+ return [...sequence(name, canonical, alternatives).distinct()];
398
394
  }
399
395
  }
400
396
  return [name];
@@ -414,7 +410,7 @@ export function string(
414
410
  input: ConfigKey,
415
411
  options?: ConfigOptions,
416
412
  ): string | undefined {
417
- return stringUtils.trimToNull(configured) ?? text(input, options);
413
+ return trimToNull(configured) ?? text(input, options);
418
414
  }
419
415
 
420
416
  /**
@@ -426,12 +422,9 @@ export function boolean(
426
422
  input: ConfigKey,
427
423
  options?: ConfigOptions,
428
424
  ): boolean | undefined {
429
- return object.toBoolean(configured) ?? object.toBoolean(text(input, options));
425
+ return toBoolean(configured) ?? toBoolean(text(input, options));
430
426
  }
431
427
 
432
- /** Parse a finite number with the shared scalar coercion rules. */
433
- export const toNumber = object.toNumber;
434
-
435
428
  /**
436
429
  * Resolve a positive number that may be fractional (a score threshold, a ratio).
437
430
  * Use {@link positiveInt} for a count, port, or timeout.
@@ -492,10 +485,8 @@ export function list(
492
485
  transform?: (entry: string) => string,
493
486
  options?: ConfigOptions,
494
487
  ): string[] {
495
- const fromConfig = stringUtils.parseList(configured, transform);
496
- return fromConfig.length > 0
497
- ? fromConfig
498
- : stringUtils.parseList(text(input, options), transform);
488
+ const fromConfig = parseList(configured, transform);
489
+ return fromConfig.length > 0 ? fromConfig : parseList(text(input, options), transform);
499
490
  }
500
491
 
501
492
  /**
@@ -510,23 +501,21 @@ export function list(
510
501
  *
511
502
  */
512
503
  export function bundleFile(cwd?: string | null, profile?: string | null): ConfigFile | undefined {
513
- const override = object.toBoolean(process.env[CONFIG_BUNDLE_KEY]);
504
+ const override = toBoolean(process.env[CONFIG_BUNDLE_KEY]);
514
505
  if (override === false) return undefined;
515
506
  if (
516
507
  override === undefined &&
517
- (process.env.NODE_ENV?.trim().toLowerCase() === "production" ||
518
- environmentUtils.isDatabricksAppEnv())
508
+ (process.env.NODE_ENV?.trim().toLowerCase() === "production" || isDatabricksAppEnv())
519
509
  ) {
520
510
  return undefined;
521
511
  }
522
512
  const resolved = resolveWorkingDirectory(cwd);
523
- return loadBundleFile(resolved, stringUtils.trimToNull(profile));
513
+ return loadBundleFile(resolved, trimToNull(profile));
524
514
  }
525
515
 
526
516
  /** The parsed `app.yaml` / `app.yml` for `cwd`, when local App config reads are enabled. */
527
517
  export function appFile(cwd?: string | null): ConfigFile | undefined {
528
- const enabled =
529
- object.toBoolean(process.env[CONFIG_APP_KEY]) ?? !environmentUtils.isDatabricksAppEnv();
518
+ const enabled = toBoolean(process.env[CONFIG_APP_KEY]) ?? !isDatabricksAppEnv();
530
519
  if (!enabled) return undefined;
531
520
  return loadAppFile(resolveWorkingDirectory(cwd));
532
521
  }
@@ -547,7 +536,7 @@ function sourceData(
547
536
  source: ConfigFile | Record<string, unknown> | undefined,
548
537
  ): Record<string, unknown> | undefined {
549
538
  if (!source) return undefined;
550
- if ("path" in source && typeof source.path === "string" && object.isRecord(source.data)) {
539
+ if ("path" in source && typeof source.path === "string" && isRecord(source.data)) {
551
540
  return source.data;
552
541
  }
553
542
  return source as Record<string, unknown>;
@@ -555,8 +544,7 @@ function sourceData(
555
544
 
556
545
  /** Parsed `.env` for `cwd`, or `{}`; parsed file data is cached by path. */
557
546
  function dotenv(cwd?: string | null): Record<string, string | undefined> {
558
- const enabled =
559
- object.toBoolean(process.env[CONFIG_DOTENV_KEY]) ?? !environmentUtils.isDatabricksAppEnv();
547
+ const enabled = toBoolean(process.env[CONFIG_DOTENV_KEY]) ?? !isDatabricksAppEnv();
560
548
  if (!enabled) return {};
561
549
  const resolved = resolveWorkingDirectory(cwd);
562
550
  return loadDotenv(resolved, nodeEnvNames(process.env.NODE_ENV));
@@ -566,8 +554,8 @@ function dotenv(cwd?: string | null): Record<string, string | undefined> {
566
554
  function read(
567
555
  source: ConfigSource,
568
556
  options: ConfigOptions,
569
- ): object.Sequence<Readonly<Record<string, unknown>>> {
570
- return object.sequence({
557
+ ): Sequence<Readonly<Record<string, unknown>>> {
558
+ return sequence({
571
559
  *[Symbol.iterator](): Generator<Readonly<Record<string, unknown>>> {
572
560
  switch (source) {
573
561
  case "config": {
@@ -672,13 +660,13 @@ function loadBundleFile(cwd: string, profile: string | null): ConfigFile | undef
672
660
  encoding: "utf8",
673
661
  stdio: ["ignore", "pipe", "pipe"],
674
662
  });
675
- const output = stringUtils.trimToNull(result.stdout);
676
- const error = stringUtils.trimToNull(result.stderr);
663
+ const output = trimToNull(result.stdout);
664
+ const error = trimToNull(result.stderr);
677
665
  if (output === null) {
678
666
  logger.debug("bundle validate produced no JSON", { path, status: result.status, error });
679
667
  return undefined;
680
668
  }
681
- const parsed = json.parseRecord(output);
669
+ const parsed = parseRecord(output);
682
670
  if (!parsed) {
683
671
  logger.warn("failed to parse bundle validate output", { path, status: result.status });
684
672
  return undefined;
@@ -697,7 +685,7 @@ function loadAppFile(cwd: string): ConfigFile | undefined {
697
685
  const data = cachedRecord(JSON.stringify(["app", path]), () => {
698
686
  try {
699
687
  const parsed = parseYaml(readFileSync(path, "utf8"));
700
- return object.isRecord(parsed) ? parsed : undefined;
688
+ return isRecord(parsed) ? parsed : undefined;
701
689
  } catch {
702
690
  logger.warn("failed to parse app yaml", { path });
703
691
  return undefined;
@@ -11,7 +11,7 @@
11
11
  * @module
12
12
  */
13
13
 
14
- import { json, object } from "@dbx-tools/shared-core";
14
+ import { parse, isRecord } from "@dbx-tools/shared-core";
15
15
  import { maxSatisfying, satisfies, valid, validRange } from "semver";
16
16
 
17
17
  import { npmRegistry } from "./project-utils.ts";
@@ -330,7 +330,7 @@ function normalizeDependency(request: DependencyInfo): DependencyInfo {
330
330
 
331
331
  function parseSpecifierInput(name: string, value: unknown): DependencyInfo | undefined {
332
332
  if (typeof value === "string") return specifierToDependency(name, value);
333
- if (!object.isRecord(value)) return undefined;
333
+ if (!isRecord(value)) return undefined;
334
334
  const version = typeof value.version === "string" ? value.version : undefined;
335
335
  const semverValue = typeof value.semver === "string" ? value.semver : undefined;
336
336
  const range = typeof value.range === "string" ? value.range : undefined;
@@ -347,7 +347,7 @@ function dependencyMap(
347
347
  }
348
348
 
349
349
  function parseDependencyField(value: unknown): Record<string, DependencyInfo> {
350
- if (!object.isRecord(value)) return {};
350
+ if (!isRecord(value)) return {};
351
351
  const out: Record<string, DependencyInfo> = {};
352
352
  for (const [name, spec] of Object.entries(value)) {
353
353
  const parsed = parseSpecifierInput(name, spec);
@@ -563,7 +563,7 @@ async function fetchRegistryJson(url: string): Promise<unknown> {
563
563
  }).catch(() => undefined);
564
564
  if (!response?.ok) return undefined;
565
565
  const text = await response.text();
566
- return json.parse(text);
566
+ return parse(text);
567
567
  }
568
568
 
569
569
  function createPackageInfoLookup(
@@ -587,7 +587,7 @@ async function loadPackageInfo(
587
587
  registryUrl?: string | URL,
588
588
  ): Promise<PackageInfo | undefined> {
589
589
  const packument = await fetchRegistryJson(packumentUrl(name, registryUrl));
590
- if (!object.isRecord(packument)) return undefined;
590
+ if (!isRecord(packument)) return undefined;
591
591
  const versions = publishedVersions(packument);
592
592
  if (versions.length === 0) return undefined;
593
593
  return {
@@ -606,7 +606,7 @@ async function loadVersionDependencies(
606
606
  const doc = await fetchRegistryJson(
607
607
  `${packumentUrl(name, registryUrl)}/${encodeURIComponent(version)}`,
608
608
  );
609
- if (!object.isRecord(doc)) return {};
609
+ if (!isRecord(doc)) return {};
610
610
  return stringMap(doc.dependencies);
611
611
  }
612
612
 
@@ -618,13 +618,13 @@ function publishedVersions(packument: Record<string, unknown>): string[] {
618
618
  if (Array.isArray(versions)) {
619
619
  return versions.filter((value): value is string => typeof value === "string");
620
620
  }
621
- if (object.isRecord(versions)) return Object.keys(versions);
621
+ if (isRecord(versions)) return Object.keys(versions);
622
622
  return [];
623
623
  }
624
624
 
625
625
  function latestTag(packument: Record<string, unknown>): string | undefined {
626
626
  const tags = packument["dist-tags"];
627
- if (!object.isRecord(tags) || typeof tags.latest !== "string") return undefined;
627
+ if (!isRecord(tags) || typeof tags.latest !== "string") return undefined;
628
628
  return tags.latest;
629
629
  }
630
630
 
@@ -632,20 +632,20 @@ function versionDependencies(
632
632
  packument: Record<string, unknown>,
633
633
  ): Record<string, Record<string, string>> {
634
634
  const versions = packument.versions;
635
- if (!object.isRecord(versions)) {
635
+ if (!isRecord(versions)) {
636
636
  return typeof packument.version === "string"
637
637
  ? { [packument.version]: stringMap(packument.dependencies) }
638
638
  : {};
639
639
  }
640
640
  const out: Record<string, Record<string, string>> = {};
641
641
  for (const [version, value] of Object.entries(versions)) {
642
- if (object.isRecord(value)) out[version] = stringMap(value.dependencies);
642
+ if (isRecord(value)) out[version] = stringMap(value.dependencies);
643
643
  }
644
644
  return out;
645
645
  }
646
646
 
647
647
  function stringMap(value: unknown): Record<string, string> {
648
- if (!object.isRecord(value)) return {};
648
+ if (!isRecord(value)) return {};
649
649
  const out: Record<string, string> = {};
650
650
  for (const [name, spec] of Object.entries(value)) {
651
651
  if (typeof spec === "string") out[name] = spec;
package/src/exec.ts CHANGED
@@ -54,7 +54,7 @@ import * as readline from "node:readline";
54
54
  import { Readable } from "node:stream";
55
55
  import { text } from "node:stream/consumers";
56
56
  import { finished } from "node:stream/promises";
57
- import { errorUtils, functionUtils, log } from "@dbx-tools/shared-core";
57
+ import { log, toError, memoize } from "@dbx-tools/shared-core";
58
58
 
59
59
  /** Shell-compatible exit code returned when an executable cannot be found. */
60
60
  export const COMMAND_NOT_FOUND_EXIT_CODE = 127;
@@ -252,7 +252,7 @@ interface ProcessWithSignalExitEmitter extends NodeJS.Process {
252
252
 
253
253
  type ParentSignalShutdown = (signal: NodeJS.Signals) => Promise<void>;
254
254
 
255
- const parentSignalForwarderState = functionUtils.memoize((): ParentSignalForwarderState => ({
255
+ const parentSignalForwarderState = memoize((): ParentSignalForwarderState => ({
256
256
  children: new Map(),
257
257
  handlers: new Map(),
258
258
  forwardingSignals: new Set(),
@@ -607,7 +607,7 @@ async function tryProcessSnapshot(): Promise<ProcessSnapshotEntry[] | undefined>
607
607
  try {
608
608
  return await processSnapshot();
609
609
  } catch (error) {
610
- logger.debug("process snapshot failed", { error: errorUtils.toError(error) });
610
+ logger.debug("process snapshot failed", { error: toError(error) });
611
611
  return undefined;
612
612
  }
613
613
  }
package/src/file-lock.ts CHANGED
@@ -26,12 +26,16 @@ import { mkdir, open } from "node:fs/promises";
26
26
  import { tmpdir } from "node:os";
27
27
  import { dirname, join } from "node:path";
28
28
 
29
- import * as asyncUtils from "@dbx-tools/shared-core/async-utils";
30
- import * as errorUtils from "@dbx-tools/shared-core/error-utils";
31
- import * as functionUtils from "@dbx-tools/shared-core/function-utils";
32
- import * as hash from "@dbx-tools/shared-core/hash";
33
- import * as log from "@dbx-tools/shared-core/log";
34
- import * as object from "@dbx-tools/shared-core/object";
29
+ import {
30
+ sleep,
31
+ toError,
32
+ memoize,
33
+ fnvHash,
34
+ log,
35
+ toNumber,
36
+ toOneOrMany,
37
+ toStableKey,
38
+ } from "@dbx-tools/shared-core";
35
39
  import lockfile from "proper-lockfile";
36
40
 
37
41
  const logger = log.logger("core:file-lock");
@@ -49,10 +53,10 @@ const POLL_MS = 50;
49
53
  * Matches `proper-lockfile`'s default. Not caller-configurable: every process
50
54
  * must share the same threshold or a live holder can be mistaken for stale.
51
55
  */
52
- const STALE_MS = object.toNumber(process.env.FILE_LOCK_STALE_MS) ?? 10_000;
56
+ const STALE_MS = toNumber(process.env.FILE_LOCK_STALE_MS) ?? 10_000;
53
57
 
54
58
  /** Heartbeat interval for refreshing lock-directory mtime (`STALE_MS / 2`). */
55
- const UPDATE_MS = object.toNumber(process.env.FILE_LOCK_UPDATE_MS) ?? STALE_MS / 2;
59
+ const UPDATE_MS = toNumber(process.env.FILE_LOCK_UPDATE_MS) ?? STALE_MS / 2;
56
60
 
57
61
  /** `flock(2)` operation bits (Linux / macOS / BSD). */
58
62
  const LOCK_EX = 2;
@@ -208,13 +212,12 @@ export async function acquireFileLock(
208
212
 
209
213
  /** Canonical filesystem-safe id for a lock key. */
210
214
  function lockId(key: unknown): string {
211
- const stable = object
212
- .toOneOrMany(key)
213
- .map((part) => object.toStableKey(part))
215
+ const stable = toOneOrMany(key)
216
+ .map((part) => toStableKey(part))
214
217
  .join("\u0000");
215
218
  // Short digest so paths stay well under OS limits; collisions are fine — they
216
219
  // only merge critical sections that already shared a key string.
217
- return hash.fnvHash(stable);
220
+ return fnvHash(stable);
218
221
  }
219
222
 
220
223
  /**
@@ -225,7 +228,7 @@ function lockId(key: unknown): string {
225
228
  * callers fall through to the next backend. A miss is cached too, so a process
226
229
  * that cannot flock never retries the load.
227
230
  */
228
- const resolveFlock = functionUtils.memoize(async (): Promise<FlockFn | undefined> => {
231
+ const resolveFlock = memoize(async (): Promise<FlockFn | undefined> => {
229
232
  if (process.platform === "win32") return undefined;
230
233
  if (!process.versions.bun) return undefined;
231
234
 
@@ -240,7 +243,7 @@ const resolveFlock = functionUtils.memoize(async (): Promise<FlockFn | undefined
240
243
  });
241
244
  return lib.symbols.flock;
242
245
  } catch (cause) {
243
- logger.debug("flock FFI unavailable", { error: errorUtils.toError(cause) });
246
+ logger.debug("flock FFI unavailable", { error: toError(cause) });
244
247
  return undefined;
245
248
  }
246
249
  });
@@ -282,7 +285,7 @@ async function waitForFlock(
282
285
  onWait();
283
286
  }
284
287
  assertBeforeDeadline(lockPath, deadline);
285
- await asyncUtils.sleep(POLL_MS);
288
+ await sleep(POLL_MS);
286
289
  }
287
290
  }
288
291
 
@@ -333,13 +336,13 @@ async function acquireLockDirectory(
333
336
  });
334
337
  } catch (cause) {
335
338
  const err = cause as NodeJS.ErrnoException;
336
- if (err.code !== "ELOCKED") throw errorUtils.toError(cause);
339
+ if (err.code !== "ELOCKED") throw toError(cause);
337
340
  if (!waiting) {
338
341
  waiting = true;
339
342
  onWait();
340
343
  }
341
344
  assertBeforeDeadline(lockPath, deadline);
342
- await asyncUtils.sleep(POLL_MS);
345
+ await sleep(POLL_MS);
343
346
  }
344
347
  }
345
348
  }
@@ -38,7 +38,7 @@ import {
38
38
  type Worker,
39
39
  type WorkerOptions,
40
40
  } from "node:worker_threads";
41
- import { errorUtils, hash, object } from "@dbx-tools/shared-core";
41
+ import { toError, id, toOneOrMany, toStableKey, isRecord } from "@dbx-tools/shared-core";
42
42
 
43
43
  /**
44
44
  * `workerData` slot carrying the coordinator port into a worker, and the
@@ -242,7 +242,7 @@ class LockClient {
242
242
  if (this.closed) {
243
243
  throw new Error("Process lock port is closed");
244
244
  }
245
- const requestId = hash.id();
245
+ const requestId = id();
246
246
  this.retain();
247
247
  try {
248
248
  await new Promise<void>((resolve, reject) => {
@@ -251,7 +251,7 @@ class LockClient {
251
251
  this.port.postMessage({ type: "acquire", requestId, key } satisfies LockRequest);
252
252
  } catch (cause) {
253
253
  this.pending.delete(requestId);
254
- reject(errorUtils.toError(cause));
254
+ reject(toError(cause));
255
255
  }
256
256
  });
257
257
  } catch (cause) {
@@ -330,9 +330,8 @@ export function withProcessLock<T>(key: unknown, fn: () => T | Promise<T>): Prom
330
330
 
331
331
  /** Canonical string identity for a lock key (see `object.toStableKey`). */
332
332
  function lockKey(key: unknown): string {
333
- return object
334
- .toOneOrMany(key)
335
- .map((part) => object.toStableKey(part))
333
+ return toOneOrMany(key)
334
+ .map((part) => toStableKey(part))
336
335
  .join("\u0000");
337
336
  }
338
337
 
@@ -348,7 +347,7 @@ function lockKey(key: unknown): string {
348
347
  */
349
348
  export function processLockWorkerOptions(options: WorkerOptions = {}): WorkerOptions {
350
349
  const port = createCoordinatorPort("processLockWorkerOptions");
351
- const existing = object.isRecord(options.workerData) ? options.workerData : {};
350
+ const existing = isRecord(options.workerData) ? options.workerData : {};
352
351
  return {
353
352
  ...options,
354
353
  workerData: { ...existing, [LOCK_PORT_KEY]: port },
@@ -404,7 +403,7 @@ export function processLockAttached(): Promise<void> {
404
403
  }
405
404
  return new Promise<void>((resolve) => {
406
405
  const onMessage = (message: unknown): void => {
407
- if (!object.isRecord(message) || message.type !== ATTACH_MESSAGE_TYPE) return;
406
+ if (!isRecord(message) || message.type !== ATTACH_MESSAGE_TYPE) return;
408
407
  port.off("message", onMessage);
409
408
  client = new LockClient((message as unknown as AttachMessage).port);
410
409
  resolve();