@quereus/quereus 0.6.3 → 0.6.5
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/README.md +20 -2
- package/dist/src/common/logger.d.ts +59 -0
- package/dist/src/common/logger.d.ts.map +1 -1
- package/dist/src/common/logger.js +68 -0
- package/dist/src/common/logger.js.map +1 -1
- package/dist/src/func/builtins/datetime.d.ts.map +1 -1
- package/dist/src/func/builtins/datetime.js +10 -5
- package/dist/src/func/builtins/datetime.js.map +1 -1
- package/dist/src/index.d.ts +5 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/planner/building/constraint-builder.d.ts.map +1 -1
- package/dist/src/planner/building/constraint-builder.js +4 -0
- package/dist/src/planner/building/constraint-builder.js.map +1 -1
- package/dist/src/planner/building/delete.d.ts.map +1 -1
- package/dist/src/planner/building/delete.js +2 -1
- package/dist/src/planner/building/delete.js.map +1 -1
- package/dist/src/planner/building/insert.d.ts.map +1 -1
- package/dist/src/planner/building/insert.js +4 -1
- package/dist/src/planner/building/insert.js.map +1 -1
- package/dist/src/planner/building/update.d.ts.map +1 -1
- package/dist/src/planner/building/update.js +4 -2
- package/dist/src/planner/building/update.js.map +1 -1
- package/dist/src/planner/nodes/dml-executor-node.d.ts +8 -2
- package/dist/src/planner/nodes/dml-executor-node.d.ts.map +1 -1
- package/dist/src/planner/nodes/dml-executor-node.js +11 -2
- package/dist/src/planner/nodes/dml-executor-node.js.map +1 -1
- package/dist/src/planner/validation/determinism-validator.d.ts +29 -0
- package/dist/src/planner/validation/determinism-validator.d.ts.map +1 -0
- package/dist/src/planner/validation/determinism-validator.js +47 -0
- package/dist/src/planner/validation/determinism-validator.js.map +1 -0
- package/dist/src/runtime/emit/add-constraint.d.ts.map +1 -1
- package/dist/src/runtime/emit/add-constraint.js +3 -0
- package/dist/src/runtime/emit/add-constraint.js.map +1 -1
- package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
- package/dist/src/runtime/emit/dml-executor.js +84 -8
- package/dist/src/runtime/emit/dml-executor.js.map +1 -1
- package/dist/src/runtime/types.d.ts +1 -0
- package/dist/src/runtime/types.d.ts.map +1 -1
- package/dist/src/runtime/types.js.map +1 -1
- package/dist/src/schema/manager.d.ts.map +1 -1
- package/dist/src/schema/manager.js +41 -0
- package/dist/src/schema/manager.js.map +1 -1
- package/dist/src/util/ast-stringify.d.ts.map +1 -1
- package/dist/src/util/ast-stringify.js +14 -1
- package/dist/src/util/ast-stringify.js.map +1 -1
- package/dist/src/util/mutation-statement.d.ts +16 -0
- package/dist/src/util/mutation-statement.d.ts.map +1 -0
- package/dist/src/util/mutation-statement.js +92 -0
- package/dist/src/util/mutation-statement.js.map +1 -0
- package/dist/src/util/plugin-helper.d.ts +45 -0
- package/dist/src/util/plugin-helper.d.ts.map +1 -0
- package/dist/src/util/plugin-helper.js +85 -0
- package/dist/src/util/plugin-helper.js.map +1 -0
- package/dist/src/util/sql-literal.d.ts +11 -0
- package/dist/src/util/sql-literal.d.ts.map +1 -0
- package/dist/src/util/sql-literal.js +18 -0
- package/dist/src/util/sql-literal.js.map +1 -0
- package/dist/src/vtab/memory/table.d.ts +1 -2
- package/dist/src/vtab/memory/table.d.ts.map +1 -1
- package/dist/src/vtab/memory/table.js +3 -3
- package/dist/src/vtab/memory/table.js.map +1 -1
- package/dist/src/vtab/table.d.ts +23 -5
- package/dist/src/vtab/table.d.ts.map +1 -1
- package/dist/src/vtab/table.js +6 -0
- package/dist/src/vtab/table.js.map +1 -1
- package/package.json +4 -2
- package/src/common/logger.ts +75 -1
- package/src/func/builtins/datetime.ts +10 -5
- package/src/index.ts +13 -16
- package/src/planner/building/constraint-builder.ts +5 -0
- package/src/planner/building/delete.ts +5 -1
- package/src/planner/building/insert.ts +8 -1
- package/src/planner/building/update.ts +10 -2
- package/src/planner/nodes/dml-executor-node.ts +8 -2
- package/src/planner/validation/determinism-validator.ts +76 -0
- package/src/runtime/emit/add-constraint.ts +3 -0
- package/src/runtime/emit/dml-executor.ts +105 -9
- package/src/runtime/types.ts +3 -0
- package/src/schema/manager.ts +45 -0
- package/src/util/ast-stringify.ts +24 -1
- package/src/util/hash.ts +90 -90
- package/src/util/mutation-statement.ts +129 -0
- package/src/util/plugin-helper.ts +110 -0
- package/src/util/sql-literal.ts +22 -0
- package/src/vtab/memory/table.ts +3 -8
- package/src/vtab/table.ts +25 -10
- package/dist/src/config/loader.d.ts +0 -41
- package/dist/src/config/loader.d.ts.map +0 -1
- package/dist/src/config/loader.js +0 -102
- package/dist/src/config/loader.js.map +0 -1
- package/dist/src/planner/nodes/physical-access-nodes.d.ts +0 -83
- package/dist/src/planner/nodes/physical-access-nodes.d.ts.map +0 -1
- package/dist/src/planner/nodes/physical-access-nodes.js +0 -226
- package/dist/src/planner/nodes/physical-access-nodes.js.map +0 -1
- package/dist/src/planner/nodes/scan.d.ts +0 -27
- package/dist/src/planner/nodes/scan.d.ts.map +0 -1
- package/dist/src/planner/nodes/scan.js +0 -78
- package/dist/src/planner/nodes/scan.js.map +0 -1
- package/dist/src/planner/nodes/update-executor-node.d.ts +0 -24
- package/dist/src/planner/nodes/update-executor-node.d.ts.map +0 -1
- package/dist/src/planner/nodes/update-executor-node.js +0 -57
- package/dist/src/planner/nodes/update-executor-node.js.map +0 -1
- package/dist/src/planner/physical-utils.d.ts +0 -36
- package/dist/src/planner/physical-utils.d.ts.map +0 -1
- package/dist/src/planner/physical-utils.js +0 -122
- package/dist/src/planner/physical-utils.js.map +0 -1
- package/dist/src/planner/rules/physical/rule-filter-optimization.d.ts +0 -11
- package/dist/src/planner/rules/physical/rule-filter-optimization.d.ts.map +0 -1
- package/dist/src/planner/rules/physical/rule-filter-optimization.js +0 -49
- package/dist/src/planner/rules/physical/rule-filter-optimization.js.map +0 -1
- package/dist/src/planner/rules/physical/rule-mark-physical.d.ts +0 -11
- package/dist/src/planner/rules/physical/rule-mark-physical.d.ts.map +0 -1
- package/dist/src/planner/rules/physical/rule-mark-physical.js +0 -29
- package/dist/src/planner/rules/physical/rule-mark-physical.js.map +0 -1
- package/dist/src/planner/rules/physical/rule-project-optimization.d.ts +0 -11
- package/dist/src/planner/rules/physical/rule-project-optimization.d.ts.map +0 -1
- package/dist/src/planner/rules/physical/rule-project-optimization.js +0 -44
- package/dist/src/planner/rules/physical/rule-project-optimization.js.map +0 -1
- package/dist/src/planner/rules/physical/rule-sort-optimization.d.ts +0 -11
- package/dist/src/planner/rules/physical/rule-sort-optimization.d.ts.map +0 -1
- package/dist/src/planner/rules/physical/rule-sort-optimization.js +0 -53
- package/dist/src/planner/rules/physical/rule-sort-optimization.js.map +0 -1
- package/dist/src/planner/rules/rewrite/rule-constant-folding.d.ts +0 -11
- package/dist/src/planner/rules/rewrite/rule-constant-folding.d.ts.map +0 -1
- package/dist/src/planner/rules/rewrite/rule-constant-folding.js +0 -59
- package/dist/src/planner/rules/rewrite/rule-constant-folding.js.map +0 -1
- package/dist/src/planner/util/deferred-constraint.d.ts +0 -14
- package/dist/src/planner/util/deferred-constraint.d.ts.map +0 -1
- package/dist/src/planner/util/deferred-constraint.js +0 -85
- package/dist/src/planner/util/deferred-constraint.js.map +0 -1
- package/dist/src/runtime/emit/table-reference.d.ts +0 -5
- package/dist/src/runtime/emit/table-reference.d.ts.map +0 -1
- package/dist/src/runtime/emit/table-reference.js +0 -67
- package/dist/src/runtime/emit/table-reference.js.map +0 -1
- package/dist/src/runtime/emit/update-executor.d.ts +0 -5
- package/dist/src/runtime/emit/update-executor.d.ts.map +0 -1
- package/dist/src/runtime/emit/update-executor.js +0 -54
- package/dist/src/runtime/emit/update-executor.js.map +0 -1
- package/dist/src/util/plugin-loader.d.ts +0 -52
- package/dist/src/util/plugin-loader.d.ts.map +0 -1
- package/dist/src/util/plugin-loader.js +0 -307
- package/dist/src/util/plugin-loader.js.map +0 -1
- package/src/config/loader.ts +0 -140
- package/src/util/plugin-loader.ts +0 -387
|
@@ -1,387 +0,0 @@
|
|
|
1
|
-
import type { Database } from '../core/database.js';
|
|
2
|
-
import type { PluginManifest, PluginRegistrations } from '../vtab/manifest.js';
|
|
3
|
-
import { StatusCode, type SqlValue } from '../common/types.js';
|
|
4
|
-
import { quereusError } from '../common/errors.js';
|
|
5
|
-
import { createLogger } from '../common/logger.js';
|
|
6
|
-
|
|
7
|
-
const log = createLogger('util:plugin-loader');
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Plugin module interface - what we expect from a plugin module
|
|
11
|
-
*/
|
|
12
|
-
export interface PluginModule {
|
|
13
|
-
/** Default export - the plugin registration function */
|
|
14
|
-
default: (db: Database, config: Record<string, SqlValue>) => Promise<PluginRegistrations> | PluginRegistrations;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Extracts plugin manifest from package.json metadata
|
|
19
|
-
* Looks for metadata in package.json root fields and quereus.provides/settings
|
|
20
|
-
*/
|
|
21
|
-
function extractManifestFromPackageJson(pkg: any): PluginManifest {
|
|
22
|
-
const quereus = pkg.quereus || {};
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
name: pkg.name || 'Unknown Plugin',
|
|
26
|
-
version: pkg.version || '0.0.0',
|
|
27
|
-
author: pkg.author,
|
|
28
|
-
description: pkg.description,
|
|
29
|
-
pragmaPrefix: quereus.pragmaPrefix,
|
|
30
|
-
settings: quereus.settings,
|
|
31
|
-
provides: quereus.provides,
|
|
32
|
-
capabilities: quereus.capabilities
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Dynamically loads and registers a plugin module
|
|
38
|
-
*
|
|
39
|
-
* @param url The URL to the ES module (can be https:// or file:// URL)
|
|
40
|
-
* @param db The Database instance to register the module with
|
|
41
|
-
* @param config Configuration values to pass to the module
|
|
42
|
-
* @returns The plugin's manifest if available
|
|
43
|
-
*/
|
|
44
|
-
export async function dynamicLoadModule(
|
|
45
|
-
url: string,
|
|
46
|
-
db: Database,
|
|
47
|
-
config: Record<string, SqlValue> = {}
|
|
48
|
-
): Promise<PluginManifest | undefined> {
|
|
49
|
-
try {
|
|
50
|
-
// Add cache-busting timestamp for development
|
|
51
|
-
const moduleUrl = new URL(url);
|
|
52
|
-
if (moduleUrl.protocol === 'file:' || moduleUrl.hostname === 'localhost') {
|
|
53
|
-
moduleUrl.searchParams.set('t', Date.now().toString());
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Dynamic import with Vite ignore comment for bundler compatibility
|
|
57
|
-
const mod = await import(/* @vite-ignore */ moduleUrl.toString()) as PluginModule;
|
|
58
|
-
|
|
59
|
-
// Validate module structure
|
|
60
|
-
if (typeof mod.default !== 'function') {
|
|
61
|
-
quereusError(`Module at ${url} has no default export function`, StatusCode.FORMAT);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Call the module's register function with the database and config
|
|
65
|
-
const registrations = await mod.default(db, config);
|
|
66
|
-
|
|
67
|
-
// Register all the items the plugin provides
|
|
68
|
-
await registerPluginItems(db, registrations);
|
|
69
|
-
|
|
70
|
-
log('Successfully loaded plugin from %s', url);
|
|
71
|
-
if (registrations.vtables?.length) {
|
|
72
|
-
log(' Registered %d vtable module(s): %s', registrations.vtables.length,
|
|
73
|
-
registrations.vtables.map(v => v.name).join(', '));
|
|
74
|
-
}
|
|
75
|
-
if (registrations.functions?.length) {
|
|
76
|
-
log(' Registered %d function(s): %s', registrations.functions.length,
|
|
77
|
-
registrations.functions.map(f => `${f.schema.name}/${f.schema.numArgs}`).join(', '));
|
|
78
|
-
}
|
|
79
|
-
if (registrations.collations?.length) {
|
|
80
|
-
log(' Registered %d collation(s): %s', registrations.collations.length,
|
|
81
|
-
registrations.collations.map(c => c.name).join(', '));
|
|
82
|
-
}
|
|
83
|
-
if (registrations.types?.length) {
|
|
84
|
-
log(' Registered %d type(s): %s', registrations.types.length,
|
|
85
|
-
registrations.types.map(t => t.name).join(', '));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Try to extract manifest from package.json
|
|
89
|
-
let manifest: PluginManifest | undefined;
|
|
90
|
-
try {
|
|
91
|
-
const packageJsonUrl = new URL('package.json', moduleUrl);
|
|
92
|
-
const packageJsonResponse = await fetch(packageJsonUrl.toString());
|
|
93
|
-
if (packageJsonResponse.ok) {
|
|
94
|
-
const pkg = await packageJsonResponse.json();
|
|
95
|
-
manifest = extractManifestFromPackageJson(pkg);
|
|
96
|
-
}
|
|
97
|
-
} catch {
|
|
98
|
-
// package.json not found or not accessible - that's okay
|
|
99
|
-
log('Could not load package.json for plugin at %s', url);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return manifest;
|
|
103
|
-
} catch (error) {
|
|
104
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
105
|
-
quereusError(`Failed to load plugin from ${url}: ${message}`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Registers all items provided by a plugin
|
|
111
|
-
*
|
|
112
|
-
* @param db Database instance to register with
|
|
113
|
-
* @param registrations The items to register
|
|
114
|
-
*/
|
|
115
|
-
async function registerPluginItems(db: Database, registrations: PluginRegistrations): Promise<void> {
|
|
116
|
-
// Register virtual table modules
|
|
117
|
-
if (registrations.vtables) {
|
|
118
|
-
for (const vtable of registrations.vtables) {
|
|
119
|
-
try {
|
|
120
|
-
db.registerVtabModule(vtable.name, vtable.module, vtable.auxData);
|
|
121
|
-
} catch (error) {
|
|
122
|
-
quereusError(`Failed to register vtable module '${vtable.name}': ${error instanceof Error ? error.message : String(error)}`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Register functions
|
|
128
|
-
if (registrations.functions) {
|
|
129
|
-
for (const func of registrations.functions) {
|
|
130
|
-
try {
|
|
131
|
-
db.registerFunction(func.schema);
|
|
132
|
-
} catch (error) {
|
|
133
|
-
quereusError(`Failed to register function '${func.schema.name}/${func.schema.numArgs}': ${error instanceof Error ? error.message : String(error)}`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Register collations
|
|
139
|
-
if (registrations.collations) {
|
|
140
|
-
for (const collation of registrations.collations) {
|
|
141
|
-
try {
|
|
142
|
-
db.registerCollation(collation.name, collation.func);
|
|
143
|
-
} catch (error) {
|
|
144
|
-
quereusError(`Failed to register collation '${collation.name}': ${error instanceof Error ? error.message : String(error)}`);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Register types
|
|
150
|
-
if (registrations.types) {
|
|
151
|
-
for (const type of registrations.types) {
|
|
152
|
-
try {
|
|
153
|
-
db.registerType(type.name, type.definition);
|
|
154
|
-
} catch (error) {
|
|
155
|
-
quereusError(`Failed to register type '${type.name}': ${error instanceof Error ? error.message : String(error)}`);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Validates that a URL is likely to be a valid plugin module
|
|
163
|
-
*
|
|
164
|
-
* @param url The URL to validate
|
|
165
|
-
* @returns true if the URL appears valid
|
|
166
|
-
*/
|
|
167
|
-
export function validatePluginUrl(url: string): boolean {
|
|
168
|
-
try {
|
|
169
|
-
const parsed = new URL(url);
|
|
170
|
-
|
|
171
|
-
// Allow https:// and file:// protocols
|
|
172
|
-
if (!['https:', 'file:'].includes(parsed.protocol)) {
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Must end with .js or .mjs
|
|
177
|
-
if (!/\.(m?js)$/i.test(parsed.pathname)) {
|
|
178
|
-
return false;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return true;
|
|
182
|
-
} catch {
|
|
183
|
-
return false;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
/** Loader options for loadPlugin */
|
|
189
|
-
export interface LoadPluginOptions {
|
|
190
|
-
/**
|
|
191
|
-
* Environment hint. Defaults to auto-detection.
|
|
192
|
-
* 'browser' enables optional CDN resolution when allowCdn is true.
|
|
193
|
-
*/
|
|
194
|
-
env?: 'auto' | 'browser' | 'node';
|
|
195
|
-
/**
|
|
196
|
-
* Allow resolving npm: specs to a public CDN in browser contexts.
|
|
197
|
-
* Disabled by default (opt-in).
|
|
198
|
-
*/
|
|
199
|
-
allowCdn?: boolean;
|
|
200
|
-
/** Which CDN to use when allowCdn is true. Defaults to 'jsdelivr'. */
|
|
201
|
-
cdn?: 'jsdelivr' | 'unpkg' | 'esm.sh';
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* High-level plugin loader that accepts npm specs or direct URLs.
|
|
206
|
-
*
|
|
207
|
-
* Examples:
|
|
208
|
-
* - npm:@scope/quereus-plugin-foo@^1
|
|
209
|
-
* - @scope/quereus-plugin-foo (npm package name)
|
|
210
|
-
* - https://raw.githubusercontent.com/user/repo/main/plugin.js
|
|
211
|
-
* - file:///path/to/plugin.js (Node only)
|
|
212
|
-
*/
|
|
213
|
-
export async function loadPlugin(
|
|
214
|
-
spec: string,
|
|
215
|
-
db: Database,
|
|
216
|
-
config: Record<string, SqlValue> = {},
|
|
217
|
-
options: LoadPluginOptions = {}
|
|
218
|
-
): Promise<PluginManifest | undefined> {
|
|
219
|
-
const env = options.env && options.env !== 'auto' ? options.env : (isBrowserEnv() ? 'browser' : 'node');
|
|
220
|
-
|
|
221
|
-
// Direct URL or file path via dynamicLoadModule
|
|
222
|
-
if (isUrlLike(spec)) {
|
|
223
|
-
return await dynamicLoadModule(spec, db, config);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Interpret as npm spec or bare package name
|
|
227
|
-
const npm = parseNpmSpec(spec);
|
|
228
|
-
if (!npm) {
|
|
229
|
-
quereusError(`Invalid plugin spec: ${spec}. Use a URL, file://, or npm package (e.g., npm:@scope/name@version).`, StatusCode.FORMAT);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (env === 'node') {
|
|
233
|
-
// Resolve using Node ESM resolution. Prefer exported subpath './plugin'.
|
|
234
|
-
const subpathImport = `${npm.name}/plugin${npm.subpath ?? ''}`;
|
|
235
|
-
const candidates = [subpathImport, `${npm.name}${npm.subpath ?? ''}`];
|
|
236
|
-
|
|
237
|
-
let mod: PluginModule | undefined;
|
|
238
|
-
let lastErr: unknown = undefined;
|
|
239
|
-
for (const target of candidates) {
|
|
240
|
-
try {
|
|
241
|
-
mod = await import(/* @vite-ignore */ target) as PluginModule;
|
|
242
|
-
break;
|
|
243
|
-
} catch (e) {
|
|
244
|
-
lastErr = e;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (!mod) {
|
|
249
|
-
quereusError(
|
|
250
|
-
`Failed to resolve plugin package '${npm.name}'. Ensure it exports './plugin' or a default module. Last error: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (typeof mod.default !== 'function') {
|
|
255
|
-
quereusError(`Resolved module for '${npm.name}' has no default export function`, StatusCode.FORMAT);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const registrations = await mod.default(db, config);
|
|
259
|
-
await registerPluginItems(db, registrations);
|
|
260
|
-
log('Successfully loaded plugin from package %s', npm.name);
|
|
261
|
-
if (registrations.vtables?.length) {
|
|
262
|
-
log(' Registered %d vtable module(s): %s', registrations.vtables.length,
|
|
263
|
-
registrations.vtables.map(v => v.name).join(', '));
|
|
264
|
-
}
|
|
265
|
-
if (registrations.functions?.length) {
|
|
266
|
-
log(' Registered %d function(s): %s', registrations.functions.length,
|
|
267
|
-
registrations.functions.map(f => `${f.schema.name}/${f.schema.numArgs}`).join(', '));
|
|
268
|
-
}
|
|
269
|
-
if (registrations.collations?.length) {
|
|
270
|
-
log(' Registered %d collation(s): %s', registrations.collations.length,
|
|
271
|
-
registrations.collations.map(c => c.name).join(', '));
|
|
272
|
-
}
|
|
273
|
-
if (registrations.types?.length) {
|
|
274
|
-
log(' Registered %d type(s): %s', registrations.types.length,
|
|
275
|
-
registrations.types.map(t => t.name).join(', '));
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Try to extract manifest from package.json
|
|
279
|
-
let manifest: PluginManifest | undefined;
|
|
280
|
-
try {
|
|
281
|
-
// Try to import package.json directly
|
|
282
|
-
const pkg = await import(`${npm.name}/package.json`, { assert: { type: 'json' } });
|
|
283
|
-
manifest = extractManifestFromPackageJson(pkg.default);
|
|
284
|
-
} catch {
|
|
285
|
-
// package.json not found - that's okay
|
|
286
|
-
log('Could not load package.json for plugin %s', npm.name);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return manifest;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Browser path: npm spec requires CDN; only if explicitly allowed
|
|
293
|
-
if (!options.allowCdn) {
|
|
294
|
-
quereusError(
|
|
295
|
-
`Loading npm packages in the browser requires allowCdn=true. Received spec '${spec}'. ` +
|
|
296
|
-
`Either provide a direct https:// URL to the ESM plugin or enable CDN resolution.`,
|
|
297
|
-
StatusCode.MISUSE
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const cdnUrl = toCdnUrl(npm, options.cdn ?? 'jsdelivr');
|
|
302
|
-
return await dynamicLoadModule(cdnUrl, db, config);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function isBrowserEnv(): boolean {
|
|
306
|
-
// Heuristic: presence of document on globalThis implies browser
|
|
307
|
-
return typeof globalThis !== 'undefined' && typeof (globalThis as unknown as { document?: unknown }).document !== 'undefined';
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function isUrlLike(s: string): boolean {
|
|
311
|
-
try {
|
|
312
|
-
const u = new URL(s);
|
|
313
|
-
return u.protocol === 'https:' || u.protocol === 'http:' || u.protocol === 'file:';
|
|
314
|
-
} catch {
|
|
315
|
-
return false;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
interface NpmSpec {
|
|
320
|
-
name: string; // @scope/pkg or pkg
|
|
321
|
-
version?: string; // optional semver or tag
|
|
322
|
-
subpath?: string; // optional subpath after package root (rarely used)
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function parseNpmSpec(input: string): NpmSpec | null {
|
|
326
|
-
// Remove optional npm: prefix
|
|
327
|
-
const raw = input.startsWith('npm:') ? input.slice(4) : input;
|
|
328
|
-
|
|
329
|
-
// Quick reject if contains spaces or empty
|
|
330
|
-
if (!raw || /\s/.test(raw)) return null;
|
|
331
|
-
|
|
332
|
-
// Support patterns like:
|
|
333
|
-
// @scope/name@1.2.3/path name@^1 name name/path
|
|
334
|
-
// Split off subpath (first '/' that is not part of scope)
|
|
335
|
-
let nameAndVersion = raw;
|
|
336
|
-
let subpath: string | undefined;
|
|
337
|
-
if (raw.startsWith('@')) {
|
|
338
|
-
// Scoped: look for second '/'
|
|
339
|
-
const secondSlash = raw.indexOf('/', raw.indexOf('/') + 1);
|
|
340
|
-
if (secondSlash !== -1) {
|
|
341
|
-
nameAndVersion = raw.slice(0, secondSlash);
|
|
342
|
-
subpath = raw.slice(secondSlash);
|
|
343
|
-
}
|
|
344
|
-
} else {
|
|
345
|
-
const firstSlash = raw.indexOf('/');
|
|
346
|
-
if (firstSlash !== -1) {
|
|
347
|
-
nameAndVersion = raw.slice(0, firstSlash);
|
|
348
|
-
subpath = raw.slice(firstSlash);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Now split name@version
|
|
353
|
-
const atIndex = nameAndVersion.lastIndexOf('@');
|
|
354
|
-
if (nameAndVersion.startsWith('@')) {
|
|
355
|
-
// Scoped: the first '@' is part of the scope
|
|
356
|
-
if (atIndex > 0) {
|
|
357
|
-
const name = nameAndVersion.slice(0, atIndex);
|
|
358
|
-
const version = nameAndVersion.slice(atIndex + 1) || undefined;
|
|
359
|
-
return { name, version, subpath };
|
|
360
|
-
}
|
|
361
|
-
return { name: nameAndVersion, subpath };
|
|
362
|
-
} else {
|
|
363
|
-
if (atIndex > 0) {
|
|
364
|
-
const name = nameAndVersion.slice(0, atIndex);
|
|
365
|
-
const version = nameAndVersion.slice(atIndex + 1) || undefined;
|
|
366
|
-
return { name, version, subpath };
|
|
367
|
-
}
|
|
368
|
-
return { name: nameAndVersion, subpath };
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
function toCdnUrl(spec: NpmSpec, cdn: 'jsdelivr' | 'unpkg' | 'esm.sh'): string {
|
|
373
|
-
const versionSegment = spec.version ? `@${spec.version}` : '';
|
|
374
|
-
const subpath = spec.subpath ? spec.subpath.replace(/^\//, '') : 'plugin';
|
|
375
|
-
switch (cdn) {
|
|
376
|
-
case 'unpkg':
|
|
377
|
-
return `https://unpkg.com/${spec.name}${versionSegment}/${subpath}`;
|
|
378
|
-
case 'esm.sh':
|
|
379
|
-
// esm.sh expects ?path=/subpath or direct subpath after package
|
|
380
|
-
// Use direct subpath; esm.sh will transform to ESM
|
|
381
|
-
return `https://esm.sh/${spec.name}${versionSegment}/${subpath}`;
|
|
382
|
-
case 'jsdelivr':
|
|
383
|
-
default:
|
|
384
|
-
return `https://cdn.jsdelivr.net/npm/${spec.name}${versionSegment}/${subpath}`;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|