@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.
- package/lib/builders/api-assignment.d.mts +125 -4
- package/lib/builders/api_builder.d.mts +104 -7
- package/lib/builders/builder.d.mts +82 -1
- package/lib/builders/modes-processor.d.mts +66 -3
- package/lib/errors.d.mts +114 -19
- package/lib/factories/component-base.d.mts +171 -8
- package/lib/factories/context.d.mts +22 -4
- package/lib/handlers/api-cache-manager.d.mts +209 -20
- package/lib/handlers/api-manager.d.mts +539 -38
- package/lib/handlers/context-async.d.mts +92 -25
- package/lib/handlers/context-live.d.mts +117 -30
- package/lib/handlers/framework-internals.d.mts +33 -2
- package/lib/handlers/hook-manager.d.mts +306 -73
- package/lib/handlers/lifecycle-token.d.mts +48 -3
- package/lib/handlers/lifecycle.d.mts +86 -5
- package/lib/handlers/materialize-manager.d.mts +76 -8
- package/lib/handlers/metadata.d.mts +238 -18
- package/lib/handlers/module-manager.d.mts +169 -21
- package/lib/handlers/ownership.d.mts +376 -45
- package/lib/handlers/permission-manager.d.mts +283 -46
- package/lib/handlers/routine-manager.d.mts +425 -0
- package/lib/handlers/trusted-root.d.mts +45 -4
- package/lib/handlers/unified-wrapper.d.mts +287 -26
- package/lib/handlers/version-manager.d.mts +236 -29
- package/lib/helpers/caller-pinning.d.mts +21 -2
- package/lib/helpers/class-instance-wrapper.d.mts +56 -2
- package/lib/helpers/config.d.mts +311 -161
- package/lib/helpers/defaults.d.mts +39 -0
- package/lib/helpers/eventemitter-context.d.mts +29 -3
- package/lib/helpers/eventtarget-context.d.mts +19 -1
- package/lib/helpers/eventtarget-property-context.d.mts +21 -0
- package/lib/helpers/generate-manifest.d.mts +174 -7
- package/lib/helpers/hint-detector.d.mts +22 -2
- package/lib/helpers/manifest-resolver.d.mts +100 -1
- package/lib/helpers/modes-utils.d.mts +30 -3
- package/lib/helpers/module-discovery.d.mts +80 -7
- package/lib/helpers/module-manifest-validator.d.mts +36 -13
- package/lib/helpers/module-sort.d.mts +64 -1
- package/lib/helpers/observer-context.d.mts +21 -0
- package/lib/helpers/pattern-matcher.d.mts +43 -3
- package/lib/helpers/platform.d.mts +109 -10
- package/lib/helpers/resolve-from-caller.d.mts +27 -3
- package/lib/helpers/sanitize.d.mts +92 -4
- package/lib/helpers/scheduler-context.d.mts +21 -1
- package/lib/helpers/utilities.d.mts +52 -4
- package/lib/i18n/translations.d.mts +50 -5
- package/lib/modes/eager.d.mts +46 -8
- package/lib/modes/lazy.d.mts +57 -10
- package/lib/processors/flatten.d.mts +116 -56
- package/lib/processors/loader.d.mts +77 -10
- package/lib/processors/type-generator.d.mts +16 -2
- package/lib/processors/typescript.d.mts +169 -13
- package/lib/runtime/runtime-asynclocalstorage.d.mts +71 -3
- package/lib/runtime/runtime-livebindings.d.mts +37 -2
- package/lib/runtime/runtime.d.mts +39 -3
- package/lib/typegen/typegen.d.mts +34 -2
- package/package.json +5 -23
- package/slothlet.d.mts +428 -3
|
@@ -1,12 +1,179 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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[];
|