@cldmv/slothlet-types 3.15.3 → 3.16.1

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.
Files changed (58) hide show
  1. package/lib/builders/api-assignment.d.mts +125 -4
  2. package/lib/builders/api_builder.d.mts +104 -7
  3. package/lib/builders/builder.d.mts +82 -1
  4. package/lib/builders/modes-processor.d.mts +66 -3
  5. package/lib/errors.d.mts +114 -19
  6. package/lib/factories/component-base.d.mts +171 -8
  7. package/lib/factories/context.d.mts +22 -4
  8. package/lib/handlers/api-cache-manager.d.mts +209 -20
  9. package/lib/handlers/api-manager.d.mts +539 -38
  10. package/lib/handlers/context-async.d.mts +92 -25
  11. package/lib/handlers/context-live.d.mts +117 -30
  12. package/lib/handlers/framework-internals.d.mts +33 -2
  13. package/lib/handlers/hook-manager.d.mts +306 -73
  14. package/lib/handlers/lifecycle-token.d.mts +48 -3
  15. package/lib/handlers/lifecycle.d.mts +86 -5
  16. package/lib/handlers/materialize-manager.d.mts +76 -8
  17. package/lib/handlers/metadata.d.mts +238 -18
  18. package/lib/handlers/module-manager.d.mts +169 -21
  19. package/lib/handlers/ownership.d.mts +376 -45
  20. package/lib/handlers/permission-manager.d.mts +283 -46
  21. package/lib/handlers/routine-manager.d.mts +425 -0
  22. package/lib/handlers/trusted-root.d.mts +45 -4
  23. package/lib/handlers/unified-wrapper.d.mts +287 -26
  24. package/lib/handlers/version-manager.d.mts +236 -29
  25. package/lib/helpers/caller-pinning.d.mts +21 -2
  26. package/lib/helpers/class-instance-wrapper.d.mts +56 -2
  27. package/lib/helpers/config.d.mts +311 -161
  28. package/lib/helpers/defaults.d.mts +39 -0
  29. package/lib/helpers/eventemitter-context.d.mts +29 -3
  30. package/lib/helpers/eventtarget-context.d.mts +19 -1
  31. package/lib/helpers/eventtarget-property-context.d.mts +21 -0
  32. package/lib/helpers/generate-manifest.d.mts +174 -7
  33. package/lib/helpers/hint-detector.d.mts +22 -2
  34. package/lib/helpers/manifest-resolver.d.mts +100 -1
  35. package/lib/helpers/modes-utils.d.mts +30 -3
  36. package/lib/helpers/module-discovery.d.mts +80 -7
  37. package/lib/helpers/module-manifest-validator.d.mts +36 -13
  38. package/lib/helpers/module-sort.d.mts +64 -1
  39. package/lib/helpers/observer-context.d.mts +21 -0
  40. package/lib/helpers/pattern-matcher.d.mts +43 -3
  41. package/lib/helpers/platform.d.mts +109 -10
  42. package/lib/helpers/resolve-from-caller.d.mts +27 -3
  43. package/lib/helpers/sanitize.d.mts +92 -4
  44. package/lib/helpers/scheduler-context.d.mts +21 -1
  45. package/lib/helpers/utilities.d.mts +52 -4
  46. package/lib/i18n/translations.d.mts +50 -5
  47. package/lib/modes/eager.d.mts +46 -8
  48. package/lib/modes/lazy.d.mts +57 -10
  49. package/lib/processors/flatten.d.mts +116 -56
  50. package/lib/processors/loader.d.mts +77 -10
  51. package/lib/processors/type-generator.d.mts +16 -2
  52. package/lib/processors/typescript.d.mts +169 -13
  53. package/lib/runtime/runtime-asynclocalstorage.d.mts +71 -3
  54. package/lib/runtime/runtime-livebindings.d.mts +37 -2
  55. package/lib/runtime/runtime.d.mts +39 -3
  56. package/lib/typegen/typegen.d.mts +34 -2
  57. package/package.json +5 -23
  58. package/slothlet.d.mts +428 -3
@@ -1,12 +1,179 @@
1
- export function collectPackageSpecifiers(packageRoot: any): Promise<Map<any, any>>;
2
- export function collectSlothletSpecifiers(root: any): Promise<Set<string>>;
3
- export function generateBrowserAssets(apiDir: any, options?: {}): Promise<{
4
- manifest: any;
1
+ /**
2
+ * Generate a slothlet browser manifest by scanning a directory at build time.
3
+ *
4
+ * This is the primary entry point for producing the `manifest` object required by
5
+ * `slothlet({ manifest, resolveModuleSpecifier })`. Call this once during your build
6
+ * step and embed the result in your browser bundle.
7
+ *
8
+ * @param {string} dir - Absolute or relative path to the API root directory.
9
+ * @returns {Promise<{ files: Array<{path:string,name:string,fullName:string}>, directories: Array }>}
10
+ * Manifest object ready to pass to `slothlet()`.
11
+ *
12
+ * @throws {SlothletError} `GENERATE_MANIFEST_DIR_INVALID` if `dir` is not a non-empty string.
13
+ * @throws {SlothletError} `GENERATE_MANIFEST_DIR_UNREADABLE` if `dir` cannot be read (missing path, permission denied); the underlying reason is surfaced in the message.
14
+ * @throws {SlothletError} `GENERATE_MANIFEST_NOT_DIRECTORY` if `dir` exists but is not a directory.
15
+ *
16
+ * @example
17
+ * // Build script — produces a manifest and writes it to disk
18
+ * import { generateManifest } from "@cldmv/slothlet/helpers/generate-manifest";
19
+ * import { writeFileSync } from "node:fs";
20
+ *
21
+ * const manifest = await generateManifest("./src/api");
22
+ * writeFileSync("./dist/api-manifest.json", JSON.stringify(manifest, null, 2));
23
+ *
24
+ * @example
25
+ * // Vite plugin — inline manifest into the browser bundle
26
+ * import { generateManifest } from "@cldmv/slothlet/helpers/generate-manifest";
27
+ *
28
+ * export function slothletManifestPlugin(apiDir) {
29
+ * return {
30
+ * name: "slothlet-manifest",
31
+ * async buildStart() {
32
+ * const manifest = await generateManifest(apiDir);
33
+ * this.emitFile({
34
+ * type: "asset",
35
+ * fileName: "slothlet-manifest.json",
36
+ * source: JSON.stringify(manifest)
37
+ * });
38
+ * }
39
+ * };
40
+ * }
41
+ */
42
+ export function generateManifest(dir: string): Promise<{
43
+ files: Array<{
44
+ path: string;
45
+ name: string;
46
+ fullName: string;
47
+ }>;
48
+ directories: any[];
49
+ }>;
50
+ /**
51
+ * Generate everything the browser needs to run slothlet, in one build-time call.
52
+ *
53
+ * Returns both halves of a browser-mode setup:
54
+ * - `manifest` — the API-directory listing passed to `slothlet({ manifest })` (replaces the
55
+ * filesystem `readdir` slothlet uses in Node).
56
+ * - `importmap` — the `<script type="importmap">` content that lets the browser resolve slothlet's
57
+ * own module graph AND the third-party packages the registered API leaves import.
58
+ *
59
+ * Run this in your build step (or, for Electron, in the main process) and send both to the
60
+ * renderer: inline `importmap` into the page's importmap script tag, and pass `manifest` (plus a
61
+ * `resolveModuleSpecifier` for your API base) to `slothlet()`.
62
+ *
63
+ * The importmap covers two surfaces. First, slothlet's own modules (rebased onto `slothletBase`).
64
+ * Second — and this is what the registered API leaves need — the **exact `exports` subpaths** of the
65
+ * other packages in the browser graph: the generator scans the `apiDir` leaves for the packages they
66
+ * import, reads each package's `package.json` `exports`, and emits the redirected subpath keys a
67
+ * plain prefix map can't produce (`@scope/ext/errors` → `…/@scope/ext/src/lib/errors.mjs`). Without
68
+ * these, a subpath the `exports` map redirects resolves to a literal URL and 404s in the browser, so
69
+ * consumers previously hand-maintained allowlists. Those sibling packages are served next to
70
+ * `@cldmv/slothlet` under a base **derived** from `slothletBase` (its node_modules/CDN parent). (#297)
71
+ *
72
+ * @param {string} apiDir - Absolute or relative path to the API root directory.
73
+ * @param {object} [options] - Options.
74
+ * @param {string} [options.slothletBase="/node_modules/@cldmv/slothlet/"] - URL/path prefix where
75
+ * the `@cldmv/slothlet` package is served in the browser. Defaults to the conventional
76
+ * node_modules location (slothlet installed as a dependency, node_modules served at the web
77
+ * root). Override with a CDN URL, an Electron protocol path, or `"/"` when the package is served
78
+ * at the web root.
79
+ * @returns {Promise<{ manifest: { files: Array, directories: Array }, importmap: { imports: Object<string,string> } }>}
80
+ * The API manifest and slothlet's own browser importmap.
81
+ *
82
+ * @throws {SlothletError} `GENERATE_BROWSER_ASSETS_SLOTHLET_BASE_INVALID` if `options.slothletBase` is provided but is not a string.
83
+ *
84
+ * @example
85
+ * // Build step — slothlet installed in node_modules (default base), ship both to the renderer.
86
+ * import { generateBrowserAssets } from "@cldmv/slothlet/helpers/generate-manifest";
87
+ * const { manifest, importmap } = await generateBrowserAssets("./src/api");
88
+ * // → inline importmap: `<script type="importmap">${JSON.stringify(importmap)}</script>`
89
+ * // → pass manifest to slothlet({ manifest, resolveModuleSpecifier })
90
+ *
91
+ * @example
92
+ * // Override the base for a CDN (or "/" when the package is served at the web root).
93
+ * const { manifest, importmap } = await generateBrowserAssets("./src/api", {
94
+ * slothletBase: "https://cdn.example.com/@cldmv/slothlet@3/"
95
+ * });
96
+ */
97
+ export function generateBrowserAssets(apiDir: string, options?: {
98
+ slothletBase?: string | undefined;
99
+ }): Promise<{
100
+ manifest: {
101
+ files: any[];
102
+ directories: any[];
103
+ };
5
104
  importmap: {
6
- imports: {};
105
+ imports: {
106
+ [x: string]: string;
107
+ };
7
108
  };
8
109
  }>;
110
+ /**
111
+ * Generate the browser importmap for slothlet's OWN modules.
112
+ *
113
+ * In a browser, slothlet's internal imports (`@cldmv/slothlet`, `@cldmv/slothlet/helpers/*`, …)
114
+ * are static and resolved by the page's importmap **before slothlet runs** — they cannot route
115
+ * through `resolveModuleSpecifier` (which only governs API-leaf loads). This produces that
116
+ * importmap from slothlet's public export surface so consumers never hand-roll it.
117
+ *
118
+ * Each specifier is resolved via `import.meta.resolve`, which automatically picks the dev
119
+ * (`slothlet-dev` → `src/`) or published (`default` → `dist/`) files based on the conditions of
120
+ * the build process — then rebased onto `slothletBase` (where the package is served).
121
+ *
122
+ * @param {string} [slothletBase="/node_modules/@cldmv/slothlet/"] - URL/path prefix where the
123
+ * `@cldmv/slothlet` package is served in the browser. Defaults to the conventional node_modules
124
+ * location; override with a CDN URL, an Electron protocol path, or `"/"` when the package is
125
+ * served at the web root.
126
+ * @returns {Promise<{ imports: Object<string,string> }>} An importmap object ready to inline as
127
+ * `<script type="importmap">`.
128
+ */
9
129
  export function generateImportMap(slothletBase?: string): Promise<{
10
- imports: {};
130
+ imports: {
131
+ [x: string]: string;
132
+ };
11
133
  }>;
12
- export function generateManifest(dir: any): Promise<any>;
134
+ /**
135
+ * Collect the full set of `@cldmv/slothlet[/sub]` specifiers the browser importmap must cover.
136
+ *
137
+ * Three sources, unioned so the map mirrors slothlet's public export surface: (1) declared flat entry points from package.json `exports` — so
138
+ * every flat (non-wildcard) public module specifier a consumer can import resolves via the importmap, including public aggregators that
139
+ * slothlet's own internals never import directly (notably the bare `@cldmv/slothlet/runtime`, whose
140
+ * `/runtime/async` + `/runtime/live` variants are the only ones internally referenced); (2) a per-file
141
+ * enumeration of every wildcard `exports` directory (`./helpers/*`, `./handlers/*`, …) so EVERY exported
142
+ * subpath gets an entry by construction — not just the modules slothlet itself imports, so a browser can
143
+ * never hit a wildcard endpoint the map lacks; and (3) a recursive source scan as a backstop for any
144
+ * imported specifier the first two miss. i18n locales are handled separately — they are dynamic-template imports the
145
+ * static scan can't see, and are enumerated separately from the languages directory. Inclusion here is about
146
+ * specifier resolution, not runtime compatibility — some public exports (e.g. `typegen`, `devcheck`) are
147
+ * Node-only and won't execute in a browser even though their specifier resolves. JSON exports (the
148
+ * module-manifest schema) are tooling-only and excluded too — they aren't browser module imports. (#137)
149
+ *
150
+ * @param {string} root - The slothlet package root (holds package.json and the shipped source).
151
+ * @returns {Promise<Set<string>>} The set of bare specifiers, always including `@cldmv/slothlet` and
152
+ * its flat (non-wildcard) public exports.
153
+ */
154
+ export function collectSlothletSpecifiers(root: string): Promise<Set<string>>;
155
+ /**
156
+ * Collect the exact importmap subpath keys for ANY package from its `package.json` `exports`.
157
+ *
158
+ * The package-agnostic counterpart to {@link collectSlothletSpecifiers}: given a package's root
159
+ * directory, read its `exports` map and return the bare specifier → relative-target pairs a browser
160
+ * importmap needs. Import maps do plain prefix substitution and never consult a package's `exports`,
161
+ * so a subpath the `exports` map *redirects* (`@scope/pkg/errors` → `./src/lib/errors.mjs`) 404s
162
+ * unless the importmap carries that exact key. This produces those keys.
163
+ *
164
+ * Handles the same shapes the self-collector does, generalized: the package root (`.`), flat
165
+ * (non-wildcard) subpaths, wildcard directories (`./x/*` → every module file under the declared
166
+ * target dir), conditional `exports` (via {@link pickBrowserTarget} — browser/import/default, never
167
+ * node/require), and the string-exports and conditions-only (`.` sugar) forms. Only ES-module
168
+ * targets are emitted (see {@link isBrowserModuleTarget}); a package with no `exports` (or an
169
+ * unreadable `package.json`) yields an empty map — the prefix map already covers those.
170
+ *
171
+ * The returned targets are the paths the `exports` map itself declares, so the caller rebases them
172
+ * onto wherever the package is served — no `import.meta.resolve` (which resolves from slothlet's own
173
+ * scope, not the consumer's) is involved.
174
+ *
175
+ * @param {string} packageRoot - Absolute path to the package's root (the dir holding its package.json).
176
+ * @returns {Promise<Map<string,string>>} Map of bare specifier → target path relative to `packageRoot`.
177
+ * @public
178
+ */
179
+ export function collectPackageSpecifiers(packageRoot: string): Promise<Map<string, string>>;
@@ -1,6 +1,26 @@
1
+ /**
2
+ * Hint detection for providing helpful error hints
3
+ * @class HintDetector
4
+ * @extends ComponentBase
5
+ * @package
6
+ */
1
7
  export class HintDetector extends ComponentBase {
2
8
  static slothletProperty: string;
3
- detectHint(error: any, errorCode: any): string;
9
+ /**
10
+ * Detect appropriate hint key based on error
11
+ * @param {Error} error - The original error
12
+ * @param {string} errorCode - The SlothletError code
13
+ * @returns {string|undefined} Hint key for i18n translation, or undefined
14
+ * @public
15
+ */
16
+ public detectHint(error: Error, errorCode: string): string | undefined;
4
17
  }
5
- export function detectHint(error: any, errorCode: any): string;
18
+ /**
19
+ * Detect appropriate hint key based on error
20
+ * @param {Error} error - The original error
21
+ * @param {string} errorCode - The SlothletError code
22
+ * @returns {string|undefined} Hint key for i18n translation, or undefined
23
+ * @public
24
+ */
25
+ export function detectHint(error: Error, errorCode: string): string | undefined;
6
26
  import { ComponentBase } from "#factories/component-base";
@@ -1 +1,100 @@
1
- export function createManifestResolver(base: any): (entry: any) => string;
1
+ /**
2
+ * @Project: @cldmv/slothlet
3
+ * @Filename: /src/lib/helpers/manifest-resolver.mjs
4
+ * @Date: 2026-05-28 00:00:00 -07:00 (1748419200)
5
+ * @Author: Nate Corcoran <CLDMV>
6
+ * @Email: <Shinrai@users.noreply.github.com>
7
+ * -----
8
+ * @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
9
+ * @Last modified time: 2026-05-28 08:10:28 -07:00 (1779981028)
10
+ * -----
11
+ * @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
12
+ */
13
+ /**
14
+ * @fileoverview Browser-safe factory for the `resolveModuleSpecifier` callback.
15
+ *
16
+ * @description
17
+ * `createManifestResolver(base)` produces a `resolveModuleSpecifier` function that
18
+ * resolves manifest-relative file paths to absolute URLs using the standard
19
+ * `new URL(path, base)` algorithm. This is the correct resolver for any deployment
20
+ * where API modules are served from a known base URL (the vast majority of use cases).
21
+ *
22
+ * This file has **no Node.js-specific imports** — it is safe to include in a browser
23
+ * bundle directly. For the build-time `generateManifest` utility (which uses `node:fs`)
24
+ * see `@cldmv/slothlet/helpers/generate-manifest`.
25
+ *
26
+ * ### Typical browser workflow
27
+ *
28
+ * ```text
29
+ * Build time → generateManifest("./src/api") → api-manifest.json
30
+ * Bundle time → import api-manifest.json
31
+ * Runtime → createManifestResolver(import.meta.url) → pass to slothlet()
32
+ * ```
33
+ *
34
+ * @example
35
+ * // API modules live next to the current module
36
+ * import { createManifestResolver } from "@cldmv/slothlet/helpers/manifest-resolver";
37
+ * import manifest from "./api-manifest.json" assert { type: "json" };
38
+ * import { slothlet } from "@cldmv/slothlet";
39
+ *
40
+ * const api = await slothlet({
41
+ * manifest,
42
+ * resolveModuleSpecifier: createManifestResolver(import.meta.url)
43
+ * });
44
+ *
45
+ * @example
46
+ * // API modules live in a sub-directory relative to the current module
47
+ * import { createManifestResolver } from "@cldmv/slothlet/helpers/manifest-resolver";
48
+ *
49
+ * const api = await slothlet({
50
+ * manifest,
51
+ * resolveModuleSpecifier: createManifestResolver(new URL("./api/", import.meta.url))
52
+ * });
53
+ *
54
+ * @module @cldmv/slothlet/helpers/manifest-resolver
55
+ * @public
56
+ */
57
+ /**
58
+ * Create a `resolveModuleSpecifier` callback that resolves manifest file entries
59
+ * to absolute URLs using a fixed base URL.
60
+ *
61
+ * The returned function implements the exact signature expected by
62
+ * `slothlet({ resolveModuleSpecifier })`: it receives a manifest file entry object
63
+ * and returns an importable URL string.
64
+ *
65
+ * @param {string|URL} base - The base URL that all manifest paths are relative to.
66
+ * Pass `import.meta.url` when API modules sit next to your entry point, or
67
+ * `new URL("./api/", import.meta.url)` when they are in a sub-directory.
68
+ * @returns {(entry: { path: string, name: string, fullName: string }) => string}
69
+ * A `resolveModuleSpecifier` function ready to pass to `slothlet()`.
70
+ *
71
+ * @throws {TypeError} If `base` is not a string or URL instance.
72
+ *
73
+ * @example
74
+ * // Modules in the same directory as the current file
75
+ * const resolver = createManifestResolver(import.meta.url);
76
+ * // resolver({ path: "math.mjs", name: "math", fullName: "math.mjs" })
77
+ * // => "https://example.com/app/math.mjs"
78
+ *
79
+ * @example
80
+ * // Modules in an ./api/ sub-directory
81
+ * const resolver = createManifestResolver(new URL("./api/", import.meta.url));
82
+ * // resolver({ path: "auth.mjs", name: "auth", fullName: "auth.mjs" })
83
+ * // => "https://example.com/app/api/auth.mjs"
84
+ *
85
+ * @example
86
+ * // Full slothlet integration
87
+ * import manifest from "./api-manifest.json" assert { type: "json" };
88
+ * import { slothlet } from "@cldmv/slothlet";
89
+ * import { createManifestResolver } from "@cldmv/slothlet/helpers/manifest-resolver";
90
+ *
91
+ * const api = await slothlet({
92
+ * manifest,
93
+ * resolveModuleSpecifier: createManifestResolver(new URL("./api/", import.meta.url))
94
+ * });
95
+ */
96
+ export function createManifestResolver(base: string | URL): (entry: {
97
+ path: string;
98
+ name: string;
99
+ fullName: string;
100
+ }) => string;
@@ -1,7 +1,34 @@
1
+ /**
2
+ * Mode processing utilities component class
3
+ * @extends ComponentBase
4
+ */
1
5
  export class ModesUtils extends ComponentBase {
2
6
  static slothletProperty: string;
3
- ensureNamedExportFunction(fn: any, ____nameHint: any): any;
4
- cloneWrapperImpl(value: any, mode: any): any;
5
- getOwnershipCollisionMode(config: any, collisionContext?: string): any;
7
+ /**
8
+ * Create a named wrapper for default export functions when they are anonymous.
9
+ * NOTE: This function is now a pass-through since UnifiedWrapper handles name/length/toString
10
+ * through its proxy get trap. Wrapping is no longer needed and causes toString mismatches.
11
+ * @param {Function} fn - Original function.
12
+ * @param {string} nameHint - Name to apply if fn is anonymous or named "default" (unused).
13
+ * @returns {Function} Original function unmodified.
14
+ * @public
15
+ */
16
+ public ensureNamedExportFunction(fn: Function, ____nameHint: any): Function;
17
+ /**
18
+ * Clone eager-mode module exports to avoid mutating import cache objects.
19
+ * @param {unknown} value - Value to clone for wrapping
20
+ * @param {string} mode - Current mode ("eager" or "lazy")
21
+ * @returns {unknown} Cloned value for eager mode, original otherwise
22
+ * @public
23
+ */
24
+ public cloneWrapperImpl(value: unknown, mode: string): unknown;
25
+ /**
26
+ * Helper to determine collision mode for ownership conflicts
27
+ * @param {Object} config - Slothlet configuration
28
+ * @param {string} collisionContext - Either 'initial' or 'api'
29
+ * @returns {string} Collision mode from config
30
+ * @public
31
+ */
32
+ public getOwnershipCollisionMode(config: Object, collisionContext?: string): string;
6
33
  }
7
34
  import { ComponentBase } from "#factories/component-base";
@@ -1,7 +1,80 @@
1
- export function discoverModules(options?: {}): Promise<Readonly<{
2
- packageName: any;
3
- packageRoot: any;
4
- mountPath: readonly any[];
5
- apiDir: any;
6
- manifest: any;
7
- }>[]>;
1
+ /**
2
+ * @typedef {object} DiscoverOptions
3
+ * @property {string|string[]} [scanRoot] - Filesystem path(s) to scan. Default: upward-walk from process.cwd() to nearest node_modules ancestor.
4
+ * @property {string} [manifest="slothlet.module.json"] - Manifest filename, or `<file>#<dotted.key>` locator pointing at a subkey of another file (e.g. `"package.json#slothlet"`).
5
+ * @property {Record<string, string>} [schema] - Field-name remap for legacy manifests. Maps canonical name → legacy name.
6
+ * @property {string|string[]} [prefix] - Name-prefix filter applied BEFORE manifest read. Matches against full package name including scope.
7
+ * @property {(manifest: object, packageName: string) => boolean} [filter] - Content-based filter applied AFTER manifest validation.
8
+ */
9
+ /**
10
+ * @typedef {object} DiscoverResult
11
+ * @property {string} packageName - Package name from package.json.
12
+ * @property {string} packageRoot - Resolved absolute filesystem path to the package directory.
13
+ * @property {string[]} mountPath - Normalized mountPath segments (always an array).
14
+ * @property {string} apiDir - Absolute resolved filesystem path to the apiDir inside the package.
15
+ * @property {object} manifest - Normalized + deep-frozen manifest (per M3).
16
+ */
17
+ /**
18
+ * Walk the filesystem and return validated module candidates.
19
+ *
20
+ * @param {DiscoverOptions} [options] - Discovery options.
21
+ * @returns {Promise<DiscoverResult[]>} Discovered modules in walk order (apply `sort()` for deterministic ordering).
22
+ * @throws {SlothletError} `MODULE_*` codes on manifest validation failures or G7 duplicate-name-version-mismatch.
23
+ *
24
+ * @example
25
+ * import { discoverModules } from "@cldmv/slothlet/helpers/module-discovery";
26
+ *
27
+ * const found = await discoverModules();
28
+ * // Default upward-walk scanRoot, default slothlet.module.json manifest.
29
+ *
30
+ * @example
31
+ * const drivers = await discoverModules({
32
+ * prefix: "@cldmv/packrat-driver-",
33
+ * filter: (m) => m.kind === "driver"
34
+ * });
35
+ */
36
+ export function discoverModules(options?: DiscoverOptions): Promise<DiscoverResult[]>;
37
+ export type DiscoverOptions = {
38
+ /**
39
+ * - Filesystem path(s) to scan. Default: upward-walk from process.cwd() to nearest node_modules ancestor.
40
+ */
41
+ scanRoot?: string | string[] | undefined;
42
+ /**
43
+ * - Manifest filename, or `<file>#<dotted.key>` locator pointing at a subkey of another file (e.g. `"package.json#slothlet"`).
44
+ */
45
+ manifest?: string | undefined;
46
+ /**
47
+ * - Field-name remap for legacy manifests. Maps canonical name → legacy name.
48
+ */
49
+ schema?: Record<string, string> | undefined;
50
+ /**
51
+ * - Name-prefix filter applied BEFORE manifest read. Matches against full package name including scope.
52
+ */
53
+ prefix?: string | string[] | undefined;
54
+ /**
55
+ * - Content-based filter applied AFTER manifest validation.
56
+ */
57
+ filter?: ((manifest: object, packageName: string) => boolean) | undefined;
58
+ };
59
+ export type DiscoverResult = {
60
+ /**
61
+ * - Package name from package.json.
62
+ */
63
+ packageName: string;
64
+ /**
65
+ * - Resolved absolute filesystem path to the package directory.
66
+ */
67
+ packageRoot: string;
68
+ /**
69
+ * - Normalized mountPath segments (always an array).
70
+ */
71
+ mountPath: string[];
72
+ /**
73
+ * - Absolute resolved filesystem path to the apiDir inside the package.
74
+ */
75
+ apiDir: string;
76
+ /**
77
+ * - Normalized + deep-frozen manifest (per M3).
78
+ */
79
+ manifest: object;
80
+ };
@@ -1,13 +1,36 @@
1
- export function validateModuleManifest(manifest: any, packageContext: any): {
2
- schemaVersion: number;
3
- name: any;
4
- version: any;
5
- description: any;
6
- mountPath: any[];
7
- apiDir: any;
8
- kind: any;
9
- priority: any;
10
- dependencies: any;
11
- permissions: any;
12
- metadata: any;
13
- };
1
+ /**
2
+ * Validate a parsed slothlet.module.json manifest and return a normalized form.
3
+ *
4
+ * @param {object} manifest - Parsed JSON manifest object (must already be valid JSON).
5
+ * @param {object} packageContext - Context derived from the host package.
6
+ * @param {string} packageContext.packageName - npm package `name` from package.json.
7
+ * @param {string} packageContext.packageVersion - npm package `version` from package.json.
8
+ * @param {string} [packageContext.packageDescription] - npm package `description` from package.json.
9
+ * @param {string} packageContext.packageRoot - Absolute filesystem path to the package root.
10
+ * @param {string} packageContext.manifestPath - Path to the manifest file (used in error context for diagnostics).
11
+ * @returns {object} Normalized manifest with the following shape:
12
+ * - All optional fields filled in from defaults / package.json fallbacks
13
+ * - `name`, `version` always present (from package.json if absent in manifest)
14
+ * - `description` from manifest (override) or package.json (fallback)
15
+ * - `priority` defaults to 0 if absent
16
+ * - `mountPath` normalized to an array of segments
17
+ * @throws {SlothletError} with `MODULE_*` code on any validation failure.
18
+ *
19
+ * @example
20
+ * const manifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
21
+ * const pkgJson = JSON.parse(await fs.readFile(path.join(packageRoot, "package.json"), "utf8"));
22
+ * const normalized = validateModuleManifest(manifest, {
23
+ * packageName: pkgJson.name,
24
+ * packageVersion: pkgJson.version,
25
+ * packageDescription: pkgJson.description,
26
+ * packageRoot,
27
+ * manifestPath
28
+ * });
29
+ */
30
+ export function validateModuleManifest(manifest: object, packageContext: {
31
+ packageName: string;
32
+ packageVersion: string;
33
+ packageDescription?: string | undefined;
34
+ packageRoot: string;
35
+ manifestPath: string;
36
+ }): object;
@@ -1 +1,64 @@
1
- export function sortModules(results: any, comparator: any): any[];
1
+ /**
2
+ * @Project: @cldmv/slothlet
3
+ * @Filename: /src/lib/helpers/module-sort.mjs
4
+ * @Date: 2026-05-27T11:22:33-07:00 (1779906153)
5
+ * @Author: Nate Corcoran <CLDMV>
6
+ * @Email: <Shinrai@users.noreply.github.com>
7
+ * -----
8
+ * @Last modified by: Nate Corcoran <CLDMV> (Shinrai@users.noreply.github.com)
9
+ * @Last modified time: 2026-05-27 18:57:20 -07:00 (1779933440)
10
+ * -----
11
+ * @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
12
+ */
13
+ /**
14
+ * @fileoverview Pure sort function for `DiscoverResult[]`.
15
+ * @module @cldmv/slothlet/helpers/module-sort
16
+ * @internal
17
+ *
18
+ * @description
19
+ * Default comparator sorts by `manifest.priority` descending (higher first)
20
+ * with `packageName` ascending as the tiebreak. Pass any custom comparator
21
+ * matching `Array.prototype.sort`'s `(a, b) => number` signature to override.
22
+ *
23
+ * Pure function: returns a new array; never mutates the input. The
24
+ * `DiscoverResult` entries are already deep-frozen by `discoverModules()`
25
+ * per M3, so the comparator cannot mutate them through this surface either.
26
+ */
27
+ /**
28
+ * @typedef {object} DiscoverResult
29
+ * @property {string} packageName
30
+ * @property {string} packageRoot
31
+ * @property {string[]} mountPath
32
+ * @property {string} apiDir
33
+ * @property {object} manifest
34
+ */
35
+ /**
36
+ * Sort a `DiscoverResult[]` and return a new array. Pure function.
37
+ *
38
+ * @param {DiscoverResult[]} results - Discovery results to sort.
39
+ * @param {(a: DiscoverResult, b: DiscoverResult) => number} [comparator] - Custom comparator. Defaults to priority desc + alphabetical tiebreak.
40
+ * @returns {DiscoverResult[]} New array sorted by the chosen comparator. Input is not mutated.
41
+ *
42
+ * @example
43
+ * import { sortModules } from "@cldmv/slothlet/helpers/module-sort";
44
+ *
45
+ * const sorted = sortModules(found);
46
+ * // Default: priority desc, then packageName asc.
47
+ *
48
+ * @example
49
+ * // Custom: alphabetical only.
50
+ * const alpha = sortModules(found, (a, b) => a.packageName.localeCompare(b.packageName));
51
+ *
52
+ * @example
53
+ * // Topological over manifest.dependencies (caller's responsibility to
54
+ * // implement; slothlet ships the plumbing, not the topo sort itself).
55
+ * const topo = sortModules(found, makeDependencyComparator(found));
56
+ */
57
+ export function sortModules(results: DiscoverResult[], comparator?: (a: DiscoverResult, b: DiscoverResult) => number): DiscoverResult[];
58
+ export type DiscoverResult = {
59
+ packageName: string;
60
+ packageRoot: string;
61
+ mountPath: string[];
62
+ apiDir: string;
63
+ manifest: object;
64
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Pin observer callbacks to the module that constructs them.
3
+ *
4
+ * Called once globally when the first instance is created; later calls are ignored, matching how the
5
+ * other boundary patches behave. Costs nothing when no runtime registered a pinning strategy — the
6
+ * wrapper hands the callback straight through.
7
+ *
8
+ * @returns {void}
9
+ * @public
10
+ */
11
+ export function enableObserverPatching(): void;
12
+ /**
13
+ * Restore the original observer constructors.
14
+ *
15
+ * Restores a constructor only when the wrapper installed here is still in place, so anything that
16
+ * replaced it afterwards keeps ownership of its own restore.
17
+ *
18
+ * @returns {void}
19
+ * @public
20
+ */
21
+ export function disableObserverPatching(): void;
@@ -1,3 +1,43 @@
1
- export function compilePattern(pattern: any, options?: {}): any;
2
- export function expandBraces(pattern: any, depth?: number, maxDepth?: number, options?: {}): any;
3
- export function splitBraceAlternatives(content: any): string[];
1
+ /**
2
+ * Compile a glob pattern into a matcher function.
3
+ * Supports: * (any chars except .), ** (any chars including .), ? (single char),
4
+ * {a,b} brace expansion, !pattern negation
5
+ *
6
+ * @param {string} pattern - Glob pattern
7
+ * @param {object} [options={}] - Options
8
+ * @param {Function} [options.onMaxDepth] - Called when brace expansion exceeds max depth.
9
+ * Should throw an error. If not provided, a SlothletError("BRACE_EXPANSION_MAX_DEPTH") is thrown.
10
+ * @returns {function} Matcher function that takes a path and returns boolean
11
+ * @example
12
+ * const matcher = compilePattern("payments.**");
13
+ * matcher("payments.charge"); // true
14
+ * matcher("admin.users"); // false
15
+ */
16
+ export function compilePattern(pattern: string, options?: {
17
+ onMaxDepth?: Function | undefined;
18
+ }): Function;
19
+ /**
20
+ * Expand brace patterns {a,b,c} into multiple patterns.
21
+ * Supports nested braces with configurable depth limit.
22
+ *
23
+ * @param {string} pattern - Pattern with braces to expand
24
+ * @param {number} [depth=0] - Current recursion depth
25
+ * @param {number} [maxDepth=10] - Maximum nesting depth
26
+ * @param {object} [options={}] - Options
27
+ * @param {Function} [options.onMaxDepth] - Called when max depth exceeded. Should throw.
28
+ * @returns {string[]} Array of expanded patterns
29
+ * @example
30
+ * expandBraces("{a,b}.path"); // ["a.path", "b.path"]
31
+ */
32
+ export function expandBraces(pattern: string, depth?: number, maxDepth?: number, options?: {
33
+ onMaxDepth?: Function | undefined;
34
+ }): string[];
35
+ /**
36
+ * Split brace alternatives on commas, respecting nested braces.
37
+ *
38
+ * @param {string} content - Content inside braces
39
+ * @returns {string[]} Array of alternatives
40
+ * @example
41
+ * splitBraceAlternatives("a,b,c"); // ["a", "b", "c"]
42
+ */
43
+ export function splitBraceAlternatives(content: string): string[];