dirsql 0.4.46 → 0.4.48

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.
@@ -1,17 +1,17 @@
1
1
  // Launcher-side resolution of the TOML configs' `[[dirsql.extension]]` entries.
2
2
  //
3
- // The compiled `dirsql` binary loads config extensions literally — it has no
4
- // `require.resolve`, so it cannot resolve a bare **package name**. When any
5
- // TOML config in argv names an extension by package name, the shared SDK
6
- // resolver resolves every config's entries and this launcher passes the
7
- // resolved literal paths to the binary via repeatable `--extension` flags;
8
- // the binary then loads those and ignores the configs' own extension entries.
3
+ // The core loads config extensions literally — it has no `require.resolve`,
4
+ // so it cannot resolve a bare **package name**. When any TOML config in argv
5
+ // names an extension by package name, the shared SDK resolver resolves every
6
+ // config's entries and this launcher passes the resolved literal paths on via
7
+ // repeatable `--extension` flags; the core then loads those and ignores the
8
+ // configs' own extension entries.
9
9
  //
10
10
  // Native-language configs (`.py`/`.js`/`.mjs`/`.cjs`) are untouched: the binary
11
11
  // dispatches those to `dirsql interpret`, whose handshake already carries
12
12
  // resolved paths.
13
- import { existsSync } from "node:fs";
14
13
  import { getCore } from "../core.js";
14
+ import { resolveConfigsExtensionSpecs } from "../resolve-config-extensions.js";
15
15
  // Config extensions the binary dispatches to `dirsql interpret`; never
16
16
  // pre-resolved here (that path resolves via the handshake).
17
17
  const NATIVE_CONFIG_SUFFIXES = [".py", ".js", ".mjs", ".cjs"];
@@ -28,14 +28,6 @@ export async function withResolvedExtensions(argv) {
28
28
  const configPaths = getCore()
29
29
  .configPathsFromArgv(argv)
30
30
  .filter((p) => !NATIVE_CONFIG_SUFFIXES.some((s) => p.endsWith(s)));
31
- // The shared resolver pulls in smol-toml, which only a TOML config on disk
32
- // can need. Guarding on the same `existsSync` the resolver itself starts
33
- // with keeps the parser off the common launch path entirely (#720); the
34
- // resolver skips any individually-missing config on its own.
35
- if (!configPaths.some((p) => existsSync(p))) {
36
- return argv;
37
- }
38
- const { resolveConfigsExtensionSpecs } = await import("../resolve-config-extensions.js");
39
31
  const specs = resolveConfigsExtensionSpecs(configPaths);
40
32
  if (specs === null) {
41
33
  return argv;
package/dist/core.d.ts CHANGED
@@ -10,8 +10,32 @@ export interface NativeDirSQL {
10
10
  export interface NativeDirSQLConstructor {
11
11
  openAsync(root: string | null, tables: TableDef[] | null, ignore: string[] | null, config: string[] | null, persist: boolean | null, persistPath: string | null, extensions: ExtensionSpec[] | null, suppressConfigExtensions: boolean | null, noIgnore: boolean | null): Promise<NativeDirSQL>;
12
12
  }
13
+ export interface ConfigSource {
14
+ path: string;
15
+ contents?: string;
16
+ }
17
+ /** One planned `[[dirsql.extension]]` entry, as the core plans it.
18
+ *
19
+ * Exactly one of `path` and `package` is set: a `path` is ready to load, a
20
+ * `package` must be located with `require.resolve` first — unless `shadow`
21
+ * names an existing file, which takes precedence over the package.
22
+ */
23
+ export type ExtensionPlanEntry = {
24
+ path: string;
25
+ package?: undefined;
26
+ shadow?: undefined;
27
+ entrypoint?: string;
28
+ } | {
29
+ path?: undefined;
30
+ package: string;
31
+ shadow: string;
32
+ entrypoint?: string;
33
+ };
13
34
  export interface CoreModule {
14
35
  DirSQL: NativeDirSQLConstructor;
15
36
  configPathsFromArgv(argv: string[]): string[];
37
+ planConfigExtensions(configs: ConfigSource[]): ExtensionPlanEntry[] | null;
38
+ selectLoadable(name: string, dirs: string[], candidates: string[]): string;
39
+ planExtensionPath(path: string, base: string, resolveRelative: boolean): ExtensionPlanEntry;
16
40
  }
17
41
  export declare function getCore(): CoreModule;
@@ -0,0 +1,8 @@
1
+ import type { ExtensionPlanEntry } from "./core.js";
2
+ import type { PackageResolver } from "./package-dir.js";
3
+ /** Resolve a planned entry to a concrete file.
4
+ *
5
+ * A literal entry is already resolved. A package entry prefers a same-named
6
+ * file shadowing it next to the config, then the installed package.
7
+ */
8
+ export declare function planEntryPath(entry: ExtensionPlanEntry, resolver?: PackageResolver): string;
@@ -0,0 +1,18 @@
1
+ // The loadable path one planned extension entry resolves to.
2
+ import { existsSync, statSync } from "node:fs";
3
+ import { defaultResolver } from "./default-resolver.js";
4
+ import { resolvePackage } from "./resolve-package.js";
5
+ /** Resolve a planned entry to a concrete file.
6
+ *
7
+ * A literal entry is already resolved. A package entry prefers a same-named
8
+ * file shadowing it next to the config, then the installed package.
9
+ */
10
+ export function planEntryPath(entry, resolver = defaultResolver()) {
11
+ if (entry.path !== undefined) {
12
+ return entry.path;
13
+ }
14
+ if (existsSync(entry.shadow) && statSync(entry.shadow).isFile()) {
15
+ return entry.shadow;
16
+ }
17
+ return resolvePackage(entry.package, resolver);
18
+ }
@@ -0,0 +1,5 @@
1
+ /** The config's text, or `undefined` when it cannot be read.
2
+ *
3
+ * A missing or unreadable config is left for the core to report.
4
+ */
5
+ export declare function readConfig(configPath: string): string | undefined;
@@ -0,0 +1,12 @@
1
+ // Reading one TOML config off disk for the core's extension planner.
2
+ import { readFileSync } from "node:fs";
3
+ /** The config's text, or `undefined` when it cannot be read.
4
+ *
5
+ * A missing or unreadable config is left for the core to report.
6
+ */
7
+ export function readConfig(configPath) {
8
+ try {
9
+ return readFileSync(configPath, "utf8");
10
+ }
11
+ catch { }
12
+ }
@@ -1,15 +1,16 @@
1
- // SDK-side resolution of a TOML config's `[[dirsql.extension]]` entries.
1
+ // SDK-side resolution of the TOML configs' `[[dirsql.extension]]` entries.
2
2
  //
3
- // The Rust core loads config extensions literally — it has no
4
- // `require.resolve`, so it cannot resolve a bare **package name**. When a
5
- // config names an extension by package name, the SDK resolves every entry
6
- // here, hands the core the resolved literal paths, and suppresses the core's
7
- // own config-extension loading so the entries are not loaded twice.
3
+ // The planning — which configs parse, whether any entry names a package rather
4
+ // than a file, and what each literal path resolves to against its own config's
5
+ // directory — lives in the Rust core (`planConfigExtensions`). This module
6
+ // supplies the two host-specific halves the core cannot have: reading the
7
+ // files, and locating an installed package with `require.resolve`.
8
8
  //
9
9
  // Shared by the `DirSQL` constructor (`config` option) and the CLI launcher.
10
- import { hasBareName } from "./has-bare-name.js";
11
- import { loadExtensionEntries } from "./load-extension-entries.js";
12
- import { resolveEntries } from "./resolve-entries.js";
10
+ import { resolve as resolvePath } from "node:path";
11
+ import { getCore } from "./core.js";
12
+ import { planEntryPath } from "./plan-entry-path.js";
13
+ import { readConfig } from "./read-config.js";
13
14
  /**
14
15
  * Resolve the `[[dirsql.extension]]` entries of several configs, in order.
15
16
  *
@@ -22,15 +23,15 @@ import { resolveEntries } from "./resolve-entries.js";
22
23
  * the core.
23
24
  */
24
25
  export function resolveConfigsExtensionSpecs(configPaths) {
25
- const loaded = configPaths.map(loadExtensionEntries);
26
- if (!loaded.some((item) => item !== null && hasBareName(item.entries))) {
26
+ const plan = getCore().planConfigExtensions(configPaths.map((path) => ({
27
+ path: resolvePath(path),
28
+ contents: readConfig(path),
29
+ })));
30
+ if (plan === null) {
27
31
  return null;
28
32
  }
29
- const specs = [];
30
- for (const item of loaded) {
31
- if (item !== null) {
32
- specs.push(...resolveEntries(item.entries, item.base));
33
- }
34
- }
35
- return specs;
33
+ return plan.map((entry) => ({
34
+ path: planEntryPath(entry),
35
+ entrypoint: entry.entrypoint,
36
+ }));
36
37
  }
@@ -1,6 +1,8 @@
1
1
  // Resolve an extension entry's `path` to a concrete loadable file.
2
2
  //
3
- // Resolution is an ordered probe (file-first, then package):
3
+ // Resolution is an ordered probe (file-first, then package), planned by the
4
+ // core (`planExtensionPath`) and carried out here: only locating an installed
5
+ // package is host-specific.
4
6
  //
5
7
  // 1. Path-looking (contains a separator, or ends in `.so` / `.dylib` /
6
8
  // `.dll` / `.node`) -> returned as a file path: made absolute against
@@ -8,14 +10,10 @@
8
10
  // verbatim (programmatic entries).
9
11
  // 2. Bare package name -> a same-named local file under `base` shadows the
10
12
  // package; otherwise the package dir is located via `require.resolve`
11
- // and the current platform's loadable is globbed from inside it. Zero
12
- // matches and multiple matches are both hard errors -- disambiguate
13
- // with a literal path.
14
- import { existsSync, statSync } from "node:fs";
15
- import { isAbsolute, resolve as resolvePath } from "node:path";
13
+ // and the current platform's loadable is picked from inside it.
14
+ import { getCore } from "./core.js";
16
15
  import { defaultResolver } from "./default-resolver.js";
17
- import { isBareName } from "./is-bare-name.js";
18
- import { resolvePackage } from "./resolve-package.js";
16
+ import { planEntryPath } from "./plan-entry-path.js";
19
17
  /**
20
18
  * Resolve an extension `path` to a concrete file. `base` is the directory a
21
19
  * relative path and the bare-name shadow probe resolve against (a config file's
@@ -24,15 +22,5 @@ import { resolvePackage } from "./resolve-package.js";
24
22
  * when false it is returned verbatim (programmatic semantics).
25
23
  */
26
24
  export function resolveExtensionPath(path, base, resolveRelative, resolver = defaultResolver()) {
27
- if (!isBareName(path)) {
28
- if (resolveRelative && !isAbsolute(path)) {
29
- return resolvePath(base, path);
30
- }
31
- return path;
32
- }
33
- const local = resolvePath(base, path);
34
- if (existsSync(local) && statSync(local).isFile()) {
35
- return local;
36
- }
37
- return resolvePackage(path, resolver);
25
+ return planEntryPath(getCore().planExtensionPath(path, base, resolveRelative), resolver);
38
26
  }
@@ -1,3 +1,3 @@
1
1
  import { type PackageResolver } from "./package-dir.js";
2
- /** Glob the platform loadable inside a bare name's package dir. */
2
+ /** Pick the platform loadable inside a bare name's package dir. */
3
3
  export declare function resolvePackage(name: string, resolver: PackageResolver): string;
@@ -1,22 +1,15 @@
1
- // Glob a bare package name's platform loadable out of its installed directory.
1
+ // Locate a bare package name's platform loadable inside its installed dir.
2
+ //
3
+ // `require.resolve` finds the package's directory; the core picks the one
4
+ // loadable file inside it (and throws on zero or several — the caller must
5
+ // then disambiguate with a literal path).
2
6
  import { readdirSync } from "node:fs";
3
7
  import { join } from "node:path";
8
+ import { getCore } from "./core.js";
4
9
  import { packageDir } from "./package-dir.js";
5
- import { platformSuffixes } from "./platform-suffixes.js";
6
- /** Glob the platform loadable inside a bare name's package dir. */
10
+ /** Pick the platform loadable inside a bare name's package dir. */
7
11
  export function resolvePackage(name, resolver) {
8
12
  const dir = packageDir(name, resolver);
9
- const suffixes = platformSuffixes();
10
- const matches = readdirSync(dir, { recursive: true })
11
- .filter((entry) => suffixes.some((s) => entry.endsWith(s)))
12
- .map((entry) => join(dir, entry))
13
- .sort();
14
- const desc = suffixes.join(" / ");
15
- if (matches.length === 0) {
16
- throw new Error(`no loadable extension file (${desc}) found in package '${name}' (searched ${dir})`);
17
- }
18
- if (matches.length > 1) {
19
- throw new Error(`multiple loadable extension files found in package '${name}': ${matches.join(", ")}; disambiguate with a literal path`);
20
- }
21
- return matches[0];
13
+ const candidates = readdirSync(dir, { recursive: true }).map((entry) => join(dir, entry));
14
+ return getCore().selectLoadable(name, [dir], candidates);
22
15
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dirsql",
3
- "version": "0.4.46",
3
+ "version": "0.4.48",
4
4
  "description": "Ephemeral SQL index over a local directory",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/thekevinscott/dirsql",
@@ -199,8 +199,7 @@
199
199
  }
200
200
  },
201
201
  "dependencies": {
202
- "bin-shim": "^0.2.0",
203
- "smol-toml": "^1.6.1"
202
+ "bin-shim": "^0.2.0"
204
203
  },
205
204
  "devDependencies": {
206
205
  "@biomejs/biome": "^1.9.0",
@@ -222,10 +221,10 @@
222
221
  ]
223
222
  },
224
223
  "optionalDependencies": {
225
- "@dirsql/lib-linux-x64-gnu": "0.4.46",
226
- "@dirsql/lib-linux-arm64-gnu": "0.4.46",
227
- "@dirsql/lib-darwin-x64": "0.4.46",
228
- "@dirsql/lib-darwin-arm64": "0.4.46",
229
- "@dirsql/lib-win32-x64-msvc": "0.4.46"
224
+ "@dirsql/lib-linux-x64-gnu": "0.4.48",
225
+ "@dirsql/lib-linux-arm64-gnu": "0.4.48",
226
+ "@dirsql/lib-darwin-x64": "0.4.48",
227
+ "@dirsql/lib-darwin-arm64": "0.4.48",
228
+ "@dirsql/lib-win32-x64-msvc": "0.4.48"
230
229
  }
231
230
  }
@@ -1,3 +0,0 @@
1
- import type { Toml } from "./load-extension-entries.js";
2
- /** True when some entry's `path` names an extension by bare package name. */
3
- export declare function hasBareName(entries: Toml[]): boolean;
@@ -1,5 +0,0 @@
1
- import { isBareName } from "./is-bare-name.js";
2
- /** True when some entry's `path` names an extension by bare package name. */
3
- export function hasBareName(entries) {
4
- return entries.some((e) => typeof e.path === "string" && isBareName(e.path));
5
- }
@@ -1,2 +0,0 @@
1
- /** True when `path` is a bare package name rather than a file path. */
2
- export declare function isBareName(path: string): boolean;
@@ -1,10 +0,0 @@
1
- /** True when `path` is a bare package name rather than a file path. */
2
- export function isBareName(path) {
3
- // Suffixes that mark a value as "already a file path" (so package resolution is
4
- // never attempted).
5
- const loadableSuffixes = [".so", ".dylib", ".dll", ".node"];
6
- if (path.includes("/") || path.includes("\\")) {
7
- return false;
8
- }
9
- return !loadableSuffixes.some((s) => path.endsWith(s));
10
- }
@@ -1,10 +0,0 @@
1
- export type Toml = Record<string, any>;
2
- /** Load a config's `[[dirsql.extension]]` entries with its base directory.
3
- *
4
- * `null` when the config is missing, unreadable/malformed, or declares no
5
- * extension array — the caller leaves such configs to the core.
6
- */
7
- export declare function loadExtensionEntries(configPath: string): {
8
- entries: Toml[];
9
- base: string;
10
- } | null;
@@ -1,27 +0,0 @@
1
- // Read a TOML config's `[[dirsql.extension]]` array off disk.
2
- import { existsSync, readFileSync } from "node:fs";
3
- import { dirname, resolve as resolvePath } from "node:path";
4
- import { parse as parseToml } from "smol-toml";
5
- /** Load a config's `[[dirsql.extension]]` entries with its base directory.
6
- *
7
- * `null` when the config is missing, unreadable/malformed, or declares no
8
- * extension array — the caller leaves such configs to the core.
9
- */
10
- export function loadExtensionEntries(configPath) {
11
- if (!existsSync(configPath)) {
12
- return null;
13
- }
14
- let doc;
15
- try {
16
- doc = parseToml(readFileSync(configPath, "utf8"));
17
- }
18
- catch {
19
- // Leave a malformed config for the core to report.
20
- return null;
21
- }
22
- const dirsql = (doc.dirsql ?? {});
23
- if (!Array.isArray(dirsql.extension)) {
24
- return null;
25
- }
26
- return { entries: dirsql.extension, base: dirname(resolvePath(configPath)) };
27
- }
@@ -1,2 +0,0 @@
1
- /** Loadable-file glob suffix(es) for the current platform. */
2
- export declare function platformSuffixes(): string[];
@@ -1,10 +0,0 @@
1
- /** Loadable-file glob suffix(es) for the current platform. */
2
- export function platformSuffixes() {
3
- if (process.platform === "darwin") {
4
- return [".dylib", ".node"];
5
- }
6
- if (process.platform === "win32") {
7
- return [".dll", ".node"];
8
- }
9
- return [".so", ".node"];
10
- }
@@ -1,13 +0,0 @@
1
- import type { ExtensionSpec } from "./dirsql.js";
2
- /**
3
- * Resolve a TOML config's `[[dirsql.extension]]` entries to literal paths.
4
- *
5
- * Returns the resolved specs — every entry resolved via
6
- * `resolveExtensionPath` against the config file's parent directory — when at
7
- * least one entry's `path` is a bare package name. Returns `null` when the
8
- * caller should not intervene: the config is missing, malformed, declares no
9
- * extensions, or uses only literal paths — leaving the core's own loading
10
- * (and error reporting) untouched. Throws if a package name cannot be
11
- * resolved.
12
- */
13
- export declare function resolveConfigExtensionSpecs(configPath: string): ExtensionSpec[] | null;
@@ -1,21 +0,0 @@
1
- import { hasBareName } from "./has-bare-name.js";
2
- import { loadExtensionEntries } from "./load-extension-entries.js";
3
- import { resolveEntries } from "./resolve-entries.js";
4
- /**
5
- * Resolve a TOML config's `[[dirsql.extension]]` entries to literal paths.
6
- *
7
- * Returns the resolved specs — every entry resolved via
8
- * `resolveExtensionPath` against the config file's parent directory — when at
9
- * least one entry's `path` is a bare package name. Returns `null` when the
10
- * caller should not intervene: the config is missing, malformed, declares no
11
- * extensions, or uses only literal paths — leaving the core's own loading
12
- * (and error reporting) untouched. Throws if a package name cannot be
13
- * resolved.
14
- */
15
- export function resolveConfigExtensionSpecs(configPath) {
16
- const loaded = loadExtensionEntries(configPath);
17
- if (loaded === null || !hasBareName(loaded.entries)) {
18
- return null;
19
- }
20
- return resolveEntries(loaded.entries, loaded.base);
21
- }
@@ -1,4 +0,0 @@
1
- import type { ExtensionSpec } from "./dirsql.js";
2
- import type { Toml } from "./load-extension-entries.js";
3
- /** Resolve a config's entries to specs, against that config's own directory. */
4
- export declare function resolveEntries(entries: Toml[], base: string): ExtensionSpec[];
@@ -1,8 +0,0 @@
1
- import { resolveExtensionPath } from "./resolve-extension.js";
2
- /** Resolve a config's entries to specs, against that config's own directory. */
3
- export function resolveEntries(entries, base) {
4
- return entries.map((e) => ({
5
- path: resolveExtensionPath(e.path, base, true),
6
- entrypoint: typeof e.entrypoint === "string" ? e.entrypoint : undefined,
7
- }));
8
- }