@quereus/plugin-loader 0.1.0
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 +69 -0
- package/dist/src/config-loader.d.ts +41 -0
- package/dist/src/config-loader.d.ts.map +1 -0
- package/dist/src/config-loader.js +102 -0
- package/dist/src/config-loader.js.map +1 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +13 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/manifest.d.ts +72 -0
- package/dist/src/manifest.d.ts.map +1 -0
- package/dist/src/manifest.js +2 -0
- package/dist/src/manifest.js.map +1 -0
- package/dist/src/plugin-loader.d.ts +51 -0
- package/dist/src/plugin-loader.d.ts.map +1 -0
- package/dist/src/plugin-loader.js +227 -0
- package/dist/src/plugin-loader.js.map +1 -0
- package/package.json +39 -0
- package/src/config-loader.ts +139 -0
- package/src/index.ts +34 -0
- package/src/manifest.ts +83 -0
- package/src/plugin-loader.ts +296 -0
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# @quereus/plugin-loader
|
|
2
|
+
|
|
3
|
+
Plugin loading system for Quereus. This package provides dynamic module loading capabilities for extending Quereus with custom virtual tables, functions, collations, and types.
|
|
4
|
+
|
|
5
|
+
**Note:** This package uses dynamic `import()` which is not compatible with React Native. For React Native environments, use static imports and manual plugin registration instead.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @quereus/plugin-loader
|
|
11
|
+
# or
|
|
12
|
+
yarn add @quereus/plugin-loader
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { Database } from '@quereus/quereus';
|
|
19
|
+
import { loadPlugin, dynamicLoadModule } from '@quereus/plugin-loader';
|
|
20
|
+
|
|
21
|
+
const db = new Database();
|
|
22
|
+
|
|
23
|
+
// Load from npm package (Node.js)
|
|
24
|
+
await loadPlugin('npm:@acme/quereus-plugin-foo@^1', db, { api_key: '...' });
|
|
25
|
+
|
|
26
|
+
// Load from URL (Node.js or Browser)
|
|
27
|
+
await dynamicLoadModule('https://example.com/plugin.js', db, { timeout: 10000 });
|
|
28
|
+
|
|
29
|
+
// Browser with CDN (opt-in)
|
|
30
|
+
await loadPlugin('npm:@acme/quereus-plugin-foo@^1', db, {}, { allowCdn: true });
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## React Native
|
|
34
|
+
|
|
35
|
+
This package is **not compatible** with React Native due to its use of dynamic `import()`. For React Native apps:
|
|
36
|
+
|
|
37
|
+
1. Exclude this package from your dependencies
|
|
38
|
+
2. Use static imports for plugins
|
|
39
|
+
3. Manually register plugins with the database
|
|
40
|
+
|
|
41
|
+
Example for React Native:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { Database } from '@quereus/quereus';
|
|
45
|
+
import myPlugin from './plugins/my-plugin';
|
|
46
|
+
|
|
47
|
+
const db = new Database();
|
|
48
|
+
|
|
49
|
+
// Manually register the plugin
|
|
50
|
+
const registrations = await myPlugin(db, { /* config */ });
|
|
51
|
+
|
|
52
|
+
// Register vtables
|
|
53
|
+
if (registrations.vtables) {
|
|
54
|
+
for (const vtable of registrations.vtables) {
|
|
55
|
+
db.registerVtabModule(vtable.name, vtable.module, vtable.auxData);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Register functions, collations, types similarly...
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## API
|
|
63
|
+
|
|
64
|
+
See the [Plugin System Documentation](https://github.com/gotchoices/quereus/blob/main/packages/quereus/docs/plugins.md) for complete details.
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
MIT
|
|
69
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration loader for Quoomb
|
|
3
|
+
* Handles loading, parsing, and interpolating quoomb.config.json files
|
|
4
|
+
*/
|
|
5
|
+
import type { Database } from '@quereus/quereus';
|
|
6
|
+
/**
|
|
7
|
+
* Plugin configuration from config file
|
|
8
|
+
*/
|
|
9
|
+
export interface PluginConfig {
|
|
10
|
+
source: string;
|
|
11
|
+
config?: Record<string, any>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Quoomb configuration file format
|
|
15
|
+
*/
|
|
16
|
+
export interface QuoombConfig {
|
|
17
|
+
$schema?: string;
|
|
18
|
+
plugins?: PluginConfig[];
|
|
19
|
+
autoload?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Interpolate environment variables in a value
|
|
23
|
+
* Supports ${VAR_NAME} and ${VAR_NAME:-default} syntax
|
|
24
|
+
*/
|
|
25
|
+
export declare function interpolateEnvVars(value: any, env?: Record<string, string>): any;
|
|
26
|
+
/**
|
|
27
|
+
* Interpolate environment variables in a config object
|
|
28
|
+
*/
|
|
29
|
+
export declare function interpolateConfigEnvVars(config: QuoombConfig, env?: Record<string, string>): QuoombConfig;
|
|
30
|
+
/**
|
|
31
|
+
* Load plugins from a config object
|
|
32
|
+
*/
|
|
33
|
+
export declare function loadPluginsFromConfig(db: Database, config: QuoombConfig, options?: {
|
|
34
|
+
allowCdn?: boolean;
|
|
35
|
+
env?: 'auto' | 'browser' | 'node';
|
|
36
|
+
}): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Validate a config object
|
|
39
|
+
*/
|
|
40
|
+
export declare function validateConfig(config: any): config is QuoombConfig;
|
|
41
|
+
//# sourceMappingURL=config-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/config-loader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAY,MAAM,kBAAkB,CAAC;AAG3D;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,GAAG,CAkBpF;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY,CAazG;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAA;CAAE,GAClE,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,GAAG,GAAG,MAAM,IAAI,YAAY,CA2BlE"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration loader for Quoomb
|
|
3
|
+
* Handles loading, parsing, and interpolating quoomb.config.json files
|
|
4
|
+
*/
|
|
5
|
+
import { loadPlugin } from './plugin-loader.js';
|
|
6
|
+
/**
|
|
7
|
+
* Interpolate environment variables in a value
|
|
8
|
+
* Supports ${VAR_NAME} and ${VAR_NAME:-default} syntax
|
|
9
|
+
*/
|
|
10
|
+
export function interpolateEnvVars(value, env = {}) {
|
|
11
|
+
if (typeof value === 'string') {
|
|
12
|
+
return value.replace(/\$\{([^}]+)\}/g, (match, varSpec) => {
|
|
13
|
+
const [varName, defaultValue] = varSpec.split(':-');
|
|
14
|
+
return env[varName.trim()] ?? defaultValue ?? match;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
if (typeof value === 'object' && value !== null) {
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
return value.map(v => interpolateEnvVars(v, env));
|
|
20
|
+
}
|
|
21
|
+
const result = {};
|
|
22
|
+
for (const [key, val] of Object.entries(value)) {
|
|
23
|
+
result[key] = interpolateEnvVars(val, env);
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Interpolate environment variables in a config object
|
|
31
|
+
*/
|
|
32
|
+
export function interpolateConfigEnvVars(config, env) {
|
|
33
|
+
let envVars = {};
|
|
34
|
+
if (env) {
|
|
35
|
+
envVars = env;
|
|
36
|
+
}
|
|
37
|
+
else if (typeof process !== 'undefined' && process.env) {
|
|
38
|
+
// Filter out undefined values from process.env
|
|
39
|
+
envVars = Object.fromEntries(Object.entries(process.env).filter(([, v]) => v !== undefined));
|
|
40
|
+
}
|
|
41
|
+
return interpolateEnvVars(config, envVars);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Load plugins from a config object
|
|
45
|
+
*/
|
|
46
|
+
export async function loadPluginsFromConfig(db, config, options) {
|
|
47
|
+
if (!config.plugins || config.plugins.length === 0) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
for (const pluginConfig of config.plugins) {
|
|
51
|
+
try {
|
|
52
|
+
const configObj = pluginConfig.config ?? {};
|
|
53
|
+
// Convert config values to SqlValue type
|
|
54
|
+
const sqlConfig = {};
|
|
55
|
+
for (const [key, value] of Object.entries(configObj)) {
|
|
56
|
+
if (value === null || value === undefined) {
|
|
57
|
+
sqlConfig[key] = null;
|
|
58
|
+
}
|
|
59
|
+
else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
60
|
+
sqlConfig[key] = value;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// For complex types, convert to JSON string
|
|
64
|
+
sqlConfig[key] = JSON.stringify(value);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
await loadPlugin(pluginConfig.source, db, sqlConfig, options);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
console.warn(`Warning: Failed to load plugin from ${pluginConfig.source}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Validate a config object
|
|
76
|
+
*/
|
|
77
|
+
export function validateConfig(config) {
|
|
78
|
+
if (typeof config !== 'object' || config === null) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
if (config.plugins !== undefined) {
|
|
82
|
+
if (!Array.isArray(config.plugins)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
for (const plugin of config.plugins) {
|
|
86
|
+
if (typeof plugin !== 'object' || plugin === null) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
if (typeof plugin.source !== 'string') {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
if (plugin.config !== undefined && typeof plugin.config !== 'object') {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (config.autoload !== undefined && typeof config.autoload !== 'boolean') {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=config-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../../src/config-loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @quereus/plugin-loader
|
|
3
|
+
*
|
|
4
|
+
* Plugin loading system for Quereus with dynamic import() support.
|
|
5
|
+
*
|
|
6
|
+
* WARNING: This package uses dynamic import() which is NOT compatible with React Native.
|
|
7
|
+
* For React Native environments, use static imports and manual plugin registration instead.
|
|
8
|
+
*/
|
|
9
|
+
export { dynamicLoadModule, validatePluginUrl, loadPlugin } from './plugin-loader.js';
|
|
10
|
+
export type { PluginModule, LoadPluginOptions } from './plugin-loader.js';
|
|
11
|
+
export type { PluginManifest, PluginRecord, PluginSetting, VTablePluginInfo, FunctionPluginInfo, CollationPluginInfo, TypePluginInfo, PluginRegistrations } from './manifest.js';
|
|
12
|
+
export { interpolateEnvVars, interpolateConfigEnvVars, loadPluginsFromConfig, validateConfig } from './config-loader.js';
|
|
13
|
+
export type { PluginConfig, QuoombConfig } from './config-loader.js';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACtF,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAG1E,YAAY,EACX,cAAc,EACd,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACN,kBAAkB,EAClB,wBAAwB,EACxB,qBAAqB,EACrB,cAAc,EACd,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @quereus/plugin-loader
|
|
3
|
+
*
|
|
4
|
+
* Plugin loading system for Quereus with dynamic import() support.
|
|
5
|
+
*
|
|
6
|
+
* WARNING: This package uses dynamic import() which is NOT compatible with React Native.
|
|
7
|
+
* For React Native environments, use static imports and manual plugin registration instead.
|
|
8
|
+
*/
|
|
9
|
+
// Re-export plugin loader functions
|
|
10
|
+
export { dynamicLoadModule, validatePluginUrl, loadPlugin } from './plugin-loader.js';
|
|
11
|
+
// Re-export config loader
|
|
12
|
+
export { interpolateEnvVars, interpolateConfigEnvVars, loadPluginsFromConfig, validateConfig } from './config-loader.js';
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,oCAAoC;AACpC,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAetF,0BAA0B;AAC1B,OAAO,EACN,kBAAkB,EAClB,wBAAwB,EACxB,qBAAqB,EACrB,cAAc,EACd,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { SqlValue, FunctionSchema, CollationFunction, TypePluginInfo, VirtualTableModule, VirtualTable } from '@quereus/quereus';
|
|
2
|
+
export type { TypePluginInfo };
|
|
3
|
+
/**
|
|
4
|
+
* Configuration setting definition for a plugin
|
|
5
|
+
*/
|
|
6
|
+
export interface PluginSetting {
|
|
7
|
+
key: string;
|
|
8
|
+
label: string;
|
|
9
|
+
type: 'string' | 'number' | 'boolean' | 'select';
|
|
10
|
+
default?: SqlValue;
|
|
11
|
+
options?: SqlValue[];
|
|
12
|
+
help?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Virtual table module registration info
|
|
16
|
+
*/
|
|
17
|
+
export interface VTablePluginInfo {
|
|
18
|
+
name: string;
|
|
19
|
+
module: VirtualTableModule<VirtualTable, any>;
|
|
20
|
+
auxData?: unknown;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Function registration info
|
|
24
|
+
*/
|
|
25
|
+
export interface FunctionPluginInfo {
|
|
26
|
+
schema: FunctionSchema;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Collation registration info
|
|
30
|
+
*/
|
|
31
|
+
export interface CollationPluginInfo {
|
|
32
|
+
name: string;
|
|
33
|
+
func: CollationFunction;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Plugin registration items - what the plugin wants to register
|
|
37
|
+
*/
|
|
38
|
+
export interface PluginRegistrations {
|
|
39
|
+
vtables?: VTablePluginInfo[];
|
|
40
|
+
functions?: FunctionPluginInfo[];
|
|
41
|
+
collations?: CollationPluginInfo[];
|
|
42
|
+
types?: TypePluginInfo[];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Plugin manifest that describes the plugin's metadata and configuration options
|
|
46
|
+
*/
|
|
47
|
+
export interface PluginManifest {
|
|
48
|
+
name: string;
|
|
49
|
+
version: string;
|
|
50
|
+
author?: string;
|
|
51
|
+
description?: string;
|
|
52
|
+
pragmaPrefix?: string;
|
|
53
|
+
settings?: PluginSetting[];
|
|
54
|
+
capabilities?: string[];
|
|
55
|
+
provides?: {
|
|
56
|
+
vtables?: string[];
|
|
57
|
+
functions?: string[];
|
|
58
|
+
collations?: string[];
|
|
59
|
+
types?: string[];
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Plugin record used for persistence across sessions
|
|
64
|
+
*/
|
|
65
|
+
export interface PluginRecord {
|
|
66
|
+
id: string;
|
|
67
|
+
url: string;
|
|
68
|
+
enabled: boolean;
|
|
69
|
+
manifest?: PluginManifest;
|
|
70
|
+
config: Record<string, SqlValue>;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,iBAAiB,EAAE,cAAc,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGtI,YAAY,EAAE,cAAc,EAAE,CAAC;AAE/B;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IACjD,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,kBAAkB,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;IAC9C,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,MAAM,EAAE,cAAc,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,iBAAiB,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC7B,SAAS,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACjC,UAAU,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACnC,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAGxB,QAAQ,CAAC,EAAE;QACV,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC;CACF;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;CACjC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/manifest.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Database, SqlValue } from '@quereus/quereus';
|
|
2
|
+
import type { PluginManifest, PluginRegistrations } from './manifest.js';
|
|
3
|
+
/**
|
|
4
|
+
* Plugin module interface - what we expect from a plugin module
|
|
5
|
+
*/
|
|
6
|
+
export interface PluginModule {
|
|
7
|
+
/** Default export - the plugin registration function */
|
|
8
|
+
default: (db: Database, config?: Record<string, SqlValue>) => Promise<PluginRegistrations> | PluginRegistrations;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Dynamically loads and registers a plugin module
|
|
12
|
+
*
|
|
13
|
+
* @param url The URL to the ES module (can be https:// or file:// URL)
|
|
14
|
+
* @param db The Database instance to register the module with
|
|
15
|
+
* @param config Configuration values to pass to the module
|
|
16
|
+
* @returns The plugin's manifest if available
|
|
17
|
+
*/
|
|
18
|
+
export declare function dynamicLoadModule(url: string, db: Database, config?: Record<string, SqlValue>): Promise<PluginManifest | undefined>;
|
|
19
|
+
/**
|
|
20
|
+
* Validates that a URL is likely to be a valid plugin module
|
|
21
|
+
*
|
|
22
|
+
* @param url The URL to validate
|
|
23
|
+
* @returns true if the URL appears valid
|
|
24
|
+
*/
|
|
25
|
+
export declare function validatePluginUrl(url: string): boolean;
|
|
26
|
+
/** Loader options for loadPlugin */
|
|
27
|
+
export interface LoadPluginOptions {
|
|
28
|
+
/**
|
|
29
|
+
* Environment hint. Defaults to auto-detection.
|
|
30
|
+
* 'browser' enables optional CDN resolution when allowCdn is true.
|
|
31
|
+
*/
|
|
32
|
+
env?: 'auto' | 'browser' | 'node';
|
|
33
|
+
/**
|
|
34
|
+
* Allow resolving npm: specs to a public CDN in browser contexts.
|
|
35
|
+
* Disabled by default (opt-in).
|
|
36
|
+
*/
|
|
37
|
+
allowCdn?: boolean;
|
|
38
|
+
/** Which CDN to use when allowCdn is true. Defaults to 'jsdelivr'. */
|
|
39
|
+
cdn?: 'jsdelivr' | 'unpkg' | 'esm.sh';
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* High-level plugin loader that accepts npm specs or direct URLs.
|
|
43
|
+
*
|
|
44
|
+
* Examples:
|
|
45
|
+
* - npm:@scope/quereus-plugin-foo@^1
|
|
46
|
+
* - @scope/quereus-plugin-foo (npm package name)
|
|
47
|
+
* - https://raw.githubusercontent.com/user/repo/main/plugin.js
|
|
48
|
+
* - file:///path/to/plugin.js (Node only)
|
|
49
|
+
*/
|
|
50
|
+
export declare function loadPlugin(spec: string, db: Database, config?: Record<string, SqlValue>, options?: LoadPluginOptions): Promise<PluginManifest | undefined>;
|
|
51
|
+
//# sourceMappingURL=plugin-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-loader.d.ts","sourceRoot":"","sources":["../../src/plugin-loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE3D,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEzE;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,wDAAwD;IACxD,OAAO,EAAE,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,OAAO,CAAC,mBAAmB,CAAC,GAAG,mBAAmB,CAAC;CACjH;AAqBD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACtC,GAAG,EAAE,MAAM,EACX,EAAE,EAAE,QAAQ,EACZ,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAM,GACnC,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAyCrC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAkBtD;AAGD,oCAAoC;AACpC,MAAM,WAAW,iBAAiB;IAC9B;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,GAAG,CAAC,EAAE,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;CACzC;AAED;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAC5B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,QAAQ,EACZ,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAM,EACrC,OAAO,GAAE,iBAAsB,GAChC,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAoErC"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { registerPlugin } from '@quereus/quereus';
|
|
2
|
+
/**
|
|
3
|
+
* Extracts plugin manifest from package.json metadata
|
|
4
|
+
* Looks for metadata in package.json root fields and quereus.provides/settings
|
|
5
|
+
*/
|
|
6
|
+
function extractManifestFromPackageJson(pkg) {
|
|
7
|
+
const quereus = pkg.quereus || {};
|
|
8
|
+
return {
|
|
9
|
+
name: pkg.name || 'Unknown Plugin',
|
|
10
|
+
version: pkg.version || '0.0.0',
|
|
11
|
+
author: pkg.author,
|
|
12
|
+
description: pkg.description,
|
|
13
|
+
pragmaPrefix: quereus.pragmaPrefix,
|
|
14
|
+
settings: quereus.settings,
|
|
15
|
+
provides: quereus.provides,
|
|
16
|
+
capabilities: quereus.capabilities
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Dynamically loads and registers a plugin module
|
|
21
|
+
*
|
|
22
|
+
* @param url The URL to the ES module (can be https:// or file:// URL)
|
|
23
|
+
* @param db The Database instance to register the module with
|
|
24
|
+
* @param config Configuration values to pass to the module
|
|
25
|
+
* @returns The plugin's manifest if available
|
|
26
|
+
*/
|
|
27
|
+
export async function dynamicLoadModule(url, db, config = {}) {
|
|
28
|
+
try {
|
|
29
|
+
// Add cache-busting timestamp for development
|
|
30
|
+
const moduleUrl = new URL(url);
|
|
31
|
+
if (moduleUrl.protocol === 'file:' || moduleUrl.hostname === 'localhost') {
|
|
32
|
+
moduleUrl.searchParams.set('t', Date.now().toString());
|
|
33
|
+
}
|
|
34
|
+
// Dynamic import with Vite ignore comment for bundler compatibility
|
|
35
|
+
const mod = await import(/* @vite-ignore */ moduleUrl.toString());
|
|
36
|
+
// Validate module structure
|
|
37
|
+
if (typeof mod.default !== 'function') {
|
|
38
|
+
throw new Error(`Module at ${url} has no default export function`);
|
|
39
|
+
}
|
|
40
|
+
// Use the core registerPlugin function from @quereus/quereus
|
|
41
|
+
// This reuses the same registration logic as static plugin loading
|
|
42
|
+
await registerPlugin(db, mod.default, config);
|
|
43
|
+
console.log(`Successfully loaded plugin from ${url}`);
|
|
44
|
+
// Try to extract manifest from package.json
|
|
45
|
+
let manifest;
|
|
46
|
+
try {
|
|
47
|
+
const packageJsonUrl = new URL('package.json', moduleUrl);
|
|
48
|
+
const packageJsonResponse = await fetch(packageJsonUrl.toString());
|
|
49
|
+
if (packageJsonResponse.ok) {
|
|
50
|
+
const pkg = await packageJsonResponse.json();
|
|
51
|
+
manifest = extractManifestFromPackageJson(pkg);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// package.json not found or not accessible - that's okay
|
|
56
|
+
console.log(`Could not load package.json for plugin at ${url}`);
|
|
57
|
+
}
|
|
58
|
+
return manifest;
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
62
|
+
throw new Error(`Failed to load plugin from ${url}: ${message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Validates that a URL is likely to be a valid plugin module
|
|
67
|
+
*
|
|
68
|
+
* @param url The URL to validate
|
|
69
|
+
* @returns true if the URL appears valid
|
|
70
|
+
*/
|
|
71
|
+
export function validatePluginUrl(url) {
|
|
72
|
+
try {
|
|
73
|
+
const parsed = new URL(url);
|
|
74
|
+
// Allow https:// and file:// protocols
|
|
75
|
+
if (!['https:', 'file:'].includes(parsed.protocol)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
// Must end with .js or .mjs
|
|
79
|
+
if (!/\.(m?js)$/i.test(parsed.pathname)) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* High-level plugin loader that accepts npm specs or direct URLs.
|
|
90
|
+
*
|
|
91
|
+
* Examples:
|
|
92
|
+
* - npm:@scope/quereus-plugin-foo@^1
|
|
93
|
+
* - @scope/quereus-plugin-foo (npm package name)
|
|
94
|
+
* - https://raw.githubusercontent.com/user/repo/main/plugin.js
|
|
95
|
+
* - file:///path/to/plugin.js (Node only)
|
|
96
|
+
*/
|
|
97
|
+
export async function loadPlugin(spec, db, config = {}, options = {}) {
|
|
98
|
+
const env = options.env && options.env !== 'auto' ? options.env : (isBrowserEnv() ? 'browser' : 'node');
|
|
99
|
+
// Direct URL or file path via dynamicLoadModule
|
|
100
|
+
if (isUrlLike(spec)) {
|
|
101
|
+
return await dynamicLoadModule(spec, db, config);
|
|
102
|
+
}
|
|
103
|
+
// Interpret as npm spec or bare package name
|
|
104
|
+
const npm = parseNpmSpec(spec);
|
|
105
|
+
if (!npm) {
|
|
106
|
+
throw new Error(`Invalid plugin spec: ${spec}. Use a URL, file://, or npm package (e.g., npm:@scope/name@version).`);
|
|
107
|
+
}
|
|
108
|
+
if (env === 'node') {
|
|
109
|
+
// Resolve using Node ESM resolution. Prefer exported subpath './plugin'.
|
|
110
|
+
const subpathImport = `${npm.name}/plugin${npm.subpath ?? ''}`;
|
|
111
|
+
const candidates = [subpathImport, `${npm.name}${npm.subpath ?? ''}`];
|
|
112
|
+
let mod;
|
|
113
|
+
let lastErr = undefined;
|
|
114
|
+
for (const target of candidates) {
|
|
115
|
+
try {
|
|
116
|
+
mod = await import(/* @vite-ignore */ target);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
lastErr = e;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (!mod) {
|
|
124
|
+
throw new Error(`Failed to resolve plugin package '${npm.name}'. Ensure it exports './plugin' or a default module. Last error: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
|
|
125
|
+
}
|
|
126
|
+
if (typeof mod.default !== 'function') {
|
|
127
|
+
throw new Error(`Resolved module for '${npm.name}' has no default export function`);
|
|
128
|
+
}
|
|
129
|
+
// Use the core registerPlugin function from @quereus/quereus
|
|
130
|
+
await registerPlugin(db, mod.default, config);
|
|
131
|
+
console.log(`Successfully loaded plugin from package ${npm.name}`);
|
|
132
|
+
// Try to extract manifest from package.json
|
|
133
|
+
let manifest;
|
|
134
|
+
try {
|
|
135
|
+
// Try to import package.json directly
|
|
136
|
+
const pkg = await import(`${npm.name}/package.json`, { assert: { type: 'json' } });
|
|
137
|
+
manifest = extractManifestFromPackageJson(pkg.default);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// package.json not found - that's okay
|
|
141
|
+
console.log(`Could not load package.json for plugin ${npm.name}`);
|
|
142
|
+
}
|
|
143
|
+
return manifest;
|
|
144
|
+
}
|
|
145
|
+
// Browser path: npm spec requires CDN; only if explicitly allowed
|
|
146
|
+
if (!options.allowCdn) {
|
|
147
|
+
throw new Error(`Loading npm packages in the browser requires allowCdn=true. Received spec '${spec}'. ` +
|
|
148
|
+
`Either provide a direct https:// URL to the ESM plugin or enable CDN resolution.`);
|
|
149
|
+
}
|
|
150
|
+
const cdnUrl = toCdnUrl(npm, options.cdn ?? 'jsdelivr');
|
|
151
|
+
return await dynamicLoadModule(cdnUrl, db, config);
|
|
152
|
+
}
|
|
153
|
+
function isBrowserEnv() {
|
|
154
|
+
// Heuristic: presence of document on globalThis implies browser
|
|
155
|
+
return typeof globalThis !== 'undefined' && typeof globalThis.document !== 'undefined';
|
|
156
|
+
}
|
|
157
|
+
function isUrlLike(s) {
|
|
158
|
+
try {
|
|
159
|
+
const u = new URL(s);
|
|
160
|
+
return u.protocol === 'https:' || u.protocol === 'http:' || u.protocol === 'file:';
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function parseNpmSpec(input) {
|
|
167
|
+
// Remove optional npm: prefix
|
|
168
|
+
const raw = input.startsWith('npm:') ? input.slice(4) : input;
|
|
169
|
+
// Quick reject if contains spaces or empty
|
|
170
|
+
if (!raw || /\s/.test(raw))
|
|
171
|
+
return null;
|
|
172
|
+
// Support patterns like:
|
|
173
|
+
// @scope/name@1.2.3/path name@^1 name name/path
|
|
174
|
+
// Split off subpath (first '/' that is not part of scope)
|
|
175
|
+
let nameAndVersion = raw;
|
|
176
|
+
let subpath;
|
|
177
|
+
if (raw.startsWith('@')) {
|
|
178
|
+
// Scoped: look for second '/'
|
|
179
|
+
const secondSlash = raw.indexOf('/', raw.indexOf('/') + 1);
|
|
180
|
+
if (secondSlash !== -1) {
|
|
181
|
+
nameAndVersion = raw.slice(0, secondSlash);
|
|
182
|
+
subpath = raw.slice(secondSlash);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
const firstSlash = raw.indexOf('/');
|
|
187
|
+
if (firstSlash !== -1) {
|
|
188
|
+
nameAndVersion = raw.slice(0, firstSlash);
|
|
189
|
+
subpath = raw.slice(firstSlash);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Now split name@version
|
|
193
|
+
const atIndex = nameAndVersion.lastIndexOf('@');
|
|
194
|
+
if (nameAndVersion.startsWith('@')) {
|
|
195
|
+
// Scoped: the first '@' is part of the scope
|
|
196
|
+
if (atIndex > 0) {
|
|
197
|
+
const name = nameAndVersion.slice(0, atIndex);
|
|
198
|
+
const version = nameAndVersion.slice(atIndex + 1) || undefined;
|
|
199
|
+
return { name, version, subpath };
|
|
200
|
+
}
|
|
201
|
+
return { name: nameAndVersion, subpath };
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
if (atIndex > 0) {
|
|
205
|
+
const name = nameAndVersion.slice(0, atIndex);
|
|
206
|
+
const version = nameAndVersion.slice(atIndex + 1) || undefined;
|
|
207
|
+
return { name, version, subpath };
|
|
208
|
+
}
|
|
209
|
+
return { name: nameAndVersion, subpath };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function toCdnUrl(spec, cdn) {
|
|
213
|
+
const versionSegment = spec.version ? `@${spec.version}` : '';
|
|
214
|
+
const subpath = spec.subpath ? spec.subpath.replace(/^\//, '') : 'plugin';
|
|
215
|
+
switch (cdn) {
|
|
216
|
+
case 'unpkg':
|
|
217
|
+
return `https://unpkg.com/${spec.name}${versionSegment}/${subpath}`;
|
|
218
|
+
case 'esm.sh':
|
|
219
|
+
// esm.sh expects ?path=/subpath or direct subpath after package
|
|
220
|
+
// Use direct subpath; esm.sh will transform to ESM
|
|
221
|
+
return `https://esm.sh/${spec.name}${versionSegment}/${subpath}`;
|
|
222
|
+
case 'jsdelivr':
|
|
223
|
+
default:
|
|
224
|
+
return `https://cdn.jsdelivr.net/npm/${spec.name}${versionSegment}/${subpath}`;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=plugin-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-loader.js","sourceRoot":"","sources":["../../src/plugin-loader.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quereus/plugin-loader",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Plugin loading system for Quereus - supports dynamic imports for Node.js and browsers",
|
|
6
|
+
"publisher": "Got Choices Foundation",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/gotchoices/quereus.git",
|
|
10
|
+
"directory": "packages/plugin-loader"
|
|
11
|
+
},
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"main": "dist/src/index.js",
|
|
14
|
+
"types": "./dist/src/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"dist",
|
|
18
|
+
"!**/*.tsbuildinfo"
|
|
19
|
+
],
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/src/index.d.ts",
|
|
23
|
+
"import": "./dist/src/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"clean": "rm -rf dist",
|
|
28
|
+
"build": "tsc",
|
|
29
|
+
"typecheck": "tsc --noEmit"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@quereus/quereus": "*"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22.15.29",
|
|
36
|
+
"typescript": "^5.8.3"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration loader for Quoomb
|
|
3
|
+
* Handles loading, parsing, and interpolating quoomb.config.json files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Database, SqlValue } from '@quereus/quereus';
|
|
7
|
+
import { loadPlugin } from './plugin-loader.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Plugin configuration from config file
|
|
11
|
+
*/
|
|
12
|
+
export interface PluginConfig {
|
|
13
|
+
source: string;
|
|
14
|
+
config?: Record<string, any>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Quoomb configuration file format
|
|
19
|
+
*/
|
|
20
|
+
export interface QuoombConfig {
|
|
21
|
+
$schema?: string;
|
|
22
|
+
plugins?: PluginConfig[];
|
|
23
|
+
autoload?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Interpolate environment variables in a value
|
|
28
|
+
* Supports ${VAR_NAME} and ${VAR_NAME:-default} syntax
|
|
29
|
+
*/
|
|
30
|
+
export function interpolateEnvVars(value: any, env: Record<string, string> = {}): any {
|
|
31
|
+
if (typeof value === 'string') {
|
|
32
|
+
return value.replace(/\$\{([^}]+)\}/g, (match, varSpec) => {
|
|
33
|
+
const [varName, defaultValue] = varSpec.split(':-');
|
|
34
|
+
return env[varName.trim()] ?? defaultValue ?? match;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (typeof value === 'object' && value !== null) {
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
return value.map(v => interpolateEnvVars(v, env));
|
|
40
|
+
}
|
|
41
|
+
const result: Record<string, any> = {};
|
|
42
|
+
for (const [key, val] of Object.entries(value)) {
|
|
43
|
+
result[key] = interpolateEnvVars(val, env);
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Interpolate environment variables in a config object
|
|
52
|
+
*/
|
|
53
|
+
export function interpolateConfigEnvVars(config: QuoombConfig, env?: Record<string, string>): QuoombConfig {
|
|
54
|
+
let envVars: Record<string, string> = {};
|
|
55
|
+
|
|
56
|
+
if (env) {
|
|
57
|
+
envVars = env;
|
|
58
|
+
} else if (typeof process !== 'undefined' && process.env) {
|
|
59
|
+
// Filter out undefined values from process.env
|
|
60
|
+
envVars = Object.fromEntries(
|
|
61
|
+
Object.entries(process.env).filter(([, v]) => v !== undefined) as Array<[string, string]>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return interpolateEnvVars(config, envVars);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Load plugins from a config object
|
|
70
|
+
*/
|
|
71
|
+
export async function loadPluginsFromConfig(
|
|
72
|
+
db: Database,
|
|
73
|
+
config: QuoombConfig,
|
|
74
|
+
options?: { allowCdn?: boolean; env?: 'auto' | 'browser' | 'node' }
|
|
75
|
+
): Promise<void> {
|
|
76
|
+
if (!config.plugins || config.plugins.length === 0) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const pluginConfig of config.plugins) {
|
|
81
|
+
try {
|
|
82
|
+
const configObj = pluginConfig.config ?? {};
|
|
83
|
+
|
|
84
|
+
// Convert config values to SqlValue type
|
|
85
|
+
const sqlConfig: Record<string, SqlValue> = {};
|
|
86
|
+
for (const [key, value] of Object.entries(configObj)) {
|
|
87
|
+
if (value === null || value === undefined) {
|
|
88
|
+
sqlConfig[key] = null;
|
|
89
|
+
} else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
90
|
+
sqlConfig[key] = value;
|
|
91
|
+
} else {
|
|
92
|
+
// For complex types, convert to JSON string
|
|
93
|
+
sqlConfig[key] = JSON.stringify(value);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await loadPlugin(pluginConfig.source, db, sqlConfig, options);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.warn(
|
|
100
|
+
`Warning: Failed to load plugin from ${pluginConfig.source}: ${
|
|
101
|
+
error instanceof Error ? error.message : 'Unknown error'
|
|
102
|
+
}`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Validate a config object
|
|
110
|
+
*/
|
|
111
|
+
export function validateConfig(config: any): config is QuoombConfig {
|
|
112
|
+
if (typeof config !== 'object' || config === null) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (config.plugins !== undefined) {
|
|
117
|
+
if (!Array.isArray(config.plugins)) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
for (const plugin of config.plugins) {
|
|
121
|
+
if (typeof plugin !== 'object' || plugin === null) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
if (typeof plugin.source !== 'string') {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
if (plugin.config !== undefined && typeof plugin.config !== 'object') {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (config.autoload !== undefined && typeof config.autoload !== 'boolean') {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @quereus/plugin-loader
|
|
3
|
+
*
|
|
4
|
+
* Plugin loading system for Quereus with dynamic import() support.
|
|
5
|
+
*
|
|
6
|
+
* WARNING: This package uses dynamic import() which is NOT compatible with React Native.
|
|
7
|
+
* For React Native environments, use static imports and manual plugin registration instead.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Re-export plugin loader functions
|
|
11
|
+
export { dynamicLoadModule, validatePluginUrl, loadPlugin } from './plugin-loader.js';
|
|
12
|
+
export type { PluginModule, LoadPluginOptions } from './plugin-loader.js';
|
|
13
|
+
|
|
14
|
+
// Re-export manifest types
|
|
15
|
+
export type {
|
|
16
|
+
PluginManifest,
|
|
17
|
+
PluginRecord,
|
|
18
|
+
PluginSetting,
|
|
19
|
+
VTablePluginInfo,
|
|
20
|
+
FunctionPluginInfo,
|
|
21
|
+
CollationPluginInfo,
|
|
22
|
+
TypePluginInfo,
|
|
23
|
+
PluginRegistrations
|
|
24
|
+
} from './manifest.js';
|
|
25
|
+
|
|
26
|
+
// Re-export config loader
|
|
27
|
+
export {
|
|
28
|
+
interpolateEnvVars,
|
|
29
|
+
interpolateConfigEnvVars,
|
|
30
|
+
loadPluginsFromConfig,
|
|
31
|
+
validateConfig
|
|
32
|
+
} from './config-loader.js';
|
|
33
|
+
export type { PluginConfig, QuoombConfig } from './config-loader.js';
|
|
34
|
+
|
package/src/manifest.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { SqlValue, FunctionSchema, CollationFunction, TypePluginInfo, VirtualTableModule, VirtualTable } from '@quereus/quereus';
|
|
2
|
+
|
|
3
|
+
// Re-export TypePluginInfo so it can be imported from this module
|
|
4
|
+
export type { TypePluginInfo };
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configuration setting definition for a plugin
|
|
8
|
+
*/
|
|
9
|
+
export interface PluginSetting {
|
|
10
|
+
key: string; // "path"
|
|
11
|
+
label: string; // "JSON Path"
|
|
12
|
+
type: 'string' | 'number' | 'boolean' | 'select';
|
|
13
|
+
default?: SqlValue;
|
|
14
|
+
options?: SqlValue[]; // for select type
|
|
15
|
+
help?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Virtual table module registration info
|
|
20
|
+
*/
|
|
21
|
+
export interface VTablePluginInfo {
|
|
22
|
+
name: string; // module name for registration
|
|
23
|
+
module: VirtualTableModule<VirtualTable, any>; // the VirtualTableModule implementation
|
|
24
|
+
auxData?: unknown; // optional auxiliary data
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Function registration info
|
|
29
|
+
*/
|
|
30
|
+
export interface FunctionPluginInfo {
|
|
31
|
+
schema: FunctionSchema; // complete function schema
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Collation registration info
|
|
36
|
+
*/
|
|
37
|
+
export interface CollationPluginInfo {
|
|
38
|
+
name: string; // collation name
|
|
39
|
+
func: CollationFunction; // comparison function
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Plugin registration items - what the plugin wants to register
|
|
44
|
+
*/
|
|
45
|
+
export interface PluginRegistrations {
|
|
46
|
+
vtables?: VTablePluginInfo[];
|
|
47
|
+
functions?: FunctionPluginInfo[];
|
|
48
|
+
collations?: CollationPluginInfo[];
|
|
49
|
+
types?: TypePluginInfo[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Plugin manifest that describes the plugin's metadata and configuration options
|
|
54
|
+
*/
|
|
55
|
+
export interface PluginManifest {
|
|
56
|
+
name: string; // "JSON_TABLE"
|
|
57
|
+
version: string; // "1.0.0"
|
|
58
|
+
author?: string;
|
|
59
|
+
description?: string;
|
|
60
|
+
pragmaPrefix?: string; // default = name, used for PRAGMA commands
|
|
61
|
+
settings?: PluginSetting[]; // configuration options
|
|
62
|
+
capabilities?: string[]; // e.g. ['scan', 'index', 'write']
|
|
63
|
+
|
|
64
|
+
// Plugin type indicators (for UI display)
|
|
65
|
+
provides?: {
|
|
66
|
+
vtables?: string[]; // names of vtable modules provided
|
|
67
|
+
functions?: string[]; // names of functions provided
|
|
68
|
+
collations?: string[]; // names of collations provided
|
|
69
|
+
types?: string[]; // names of types provided
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Plugin record used for persistence across sessions
|
|
75
|
+
*/
|
|
76
|
+
export interface PluginRecord {
|
|
77
|
+
id: string; // UUID for this installation
|
|
78
|
+
url: string; // Full URL to the ES module
|
|
79
|
+
enabled: boolean; // Whether to load at startup
|
|
80
|
+
manifest?: PluginManifest; // Cached after first successful load
|
|
81
|
+
config: Record<string, SqlValue>; // User-configured values
|
|
82
|
+
}
|
|
83
|
+
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import type { Database, SqlValue } from '@quereus/quereus';
|
|
2
|
+
import { registerPlugin } from '@quereus/quereus';
|
|
3
|
+
import type { PluginManifest, PluginRegistrations } from './manifest.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Plugin module interface - what we expect from a plugin module
|
|
7
|
+
*/
|
|
8
|
+
export interface PluginModule {
|
|
9
|
+
/** Default export - the plugin registration function */
|
|
10
|
+
default: (db: Database, config?: Record<string, SqlValue>) => Promise<PluginRegistrations> | PluginRegistrations;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extracts plugin manifest from package.json metadata
|
|
15
|
+
* Looks for metadata in package.json root fields and quereus.provides/settings
|
|
16
|
+
*/
|
|
17
|
+
function extractManifestFromPackageJson(pkg: any): PluginManifest {
|
|
18
|
+
const quereus = pkg.quereus || {};
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
name: pkg.name || 'Unknown Plugin',
|
|
22
|
+
version: pkg.version || '0.0.0',
|
|
23
|
+
author: pkg.author,
|
|
24
|
+
description: pkg.description,
|
|
25
|
+
pragmaPrefix: quereus.pragmaPrefix,
|
|
26
|
+
settings: quereus.settings,
|
|
27
|
+
provides: quereus.provides,
|
|
28
|
+
capabilities: quereus.capabilities
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Dynamically loads and registers a plugin module
|
|
34
|
+
*
|
|
35
|
+
* @param url The URL to the ES module (can be https:// or file:// URL)
|
|
36
|
+
* @param db The Database instance to register the module with
|
|
37
|
+
* @param config Configuration values to pass to the module
|
|
38
|
+
* @returns The plugin's manifest if available
|
|
39
|
+
*/
|
|
40
|
+
export async function dynamicLoadModule(
|
|
41
|
+
url: string,
|
|
42
|
+
db: Database,
|
|
43
|
+
config: Record<string, SqlValue> = {}
|
|
44
|
+
): Promise<PluginManifest | undefined> {
|
|
45
|
+
try {
|
|
46
|
+
// Add cache-busting timestamp for development
|
|
47
|
+
const moduleUrl = new URL(url);
|
|
48
|
+
if (moduleUrl.protocol === 'file:' || moduleUrl.hostname === 'localhost') {
|
|
49
|
+
moduleUrl.searchParams.set('t', Date.now().toString());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Dynamic import with Vite ignore comment for bundler compatibility
|
|
53
|
+
const mod = await import(/* @vite-ignore */ moduleUrl.toString()) as PluginModule;
|
|
54
|
+
|
|
55
|
+
// Validate module structure
|
|
56
|
+
if (typeof mod.default !== 'function') {
|
|
57
|
+
throw new Error(`Module at ${url} has no default export function`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Use the core registerPlugin function from @quereus/quereus
|
|
61
|
+
// This reuses the same registration logic as static plugin loading
|
|
62
|
+
await registerPlugin(db, mod.default, config);
|
|
63
|
+
|
|
64
|
+
console.log(`Successfully loaded plugin from ${url}`);
|
|
65
|
+
|
|
66
|
+
// Try to extract manifest from package.json
|
|
67
|
+
let manifest: PluginManifest | undefined;
|
|
68
|
+
try {
|
|
69
|
+
const packageJsonUrl = new URL('package.json', moduleUrl);
|
|
70
|
+
const packageJsonResponse = await fetch(packageJsonUrl.toString());
|
|
71
|
+
if (packageJsonResponse.ok) {
|
|
72
|
+
const pkg = await packageJsonResponse.json();
|
|
73
|
+
manifest = extractManifestFromPackageJson(pkg);
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
// package.json not found or not accessible - that's okay
|
|
77
|
+
console.log(`Could not load package.json for plugin at ${url}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return manifest;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
83
|
+
throw new Error(`Failed to load plugin from ${url}: ${message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Validates that a URL is likely to be a valid plugin module
|
|
89
|
+
*
|
|
90
|
+
* @param url The URL to validate
|
|
91
|
+
* @returns true if the URL appears valid
|
|
92
|
+
*/
|
|
93
|
+
export function validatePluginUrl(url: string): boolean {
|
|
94
|
+
try {
|
|
95
|
+
const parsed = new URL(url);
|
|
96
|
+
|
|
97
|
+
// Allow https:// and file:// protocols
|
|
98
|
+
if (!['https:', 'file:'].includes(parsed.protocol)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Must end with .js or .mjs
|
|
103
|
+
if (!/\.(m?js)$/i.test(parsed.pathname)) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return true;
|
|
108
|
+
} catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
/** Loader options for loadPlugin */
|
|
115
|
+
export interface LoadPluginOptions {
|
|
116
|
+
/**
|
|
117
|
+
* Environment hint. Defaults to auto-detection.
|
|
118
|
+
* 'browser' enables optional CDN resolution when allowCdn is true.
|
|
119
|
+
*/
|
|
120
|
+
env?: 'auto' | 'browser' | 'node';
|
|
121
|
+
/**
|
|
122
|
+
* Allow resolving npm: specs to a public CDN in browser contexts.
|
|
123
|
+
* Disabled by default (opt-in).
|
|
124
|
+
*/
|
|
125
|
+
allowCdn?: boolean;
|
|
126
|
+
/** Which CDN to use when allowCdn is true. Defaults to 'jsdelivr'. */
|
|
127
|
+
cdn?: 'jsdelivr' | 'unpkg' | 'esm.sh';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* High-level plugin loader that accepts npm specs or direct URLs.
|
|
132
|
+
*
|
|
133
|
+
* Examples:
|
|
134
|
+
* - npm:@scope/quereus-plugin-foo@^1
|
|
135
|
+
* - @scope/quereus-plugin-foo (npm package name)
|
|
136
|
+
* - https://raw.githubusercontent.com/user/repo/main/plugin.js
|
|
137
|
+
* - file:///path/to/plugin.js (Node only)
|
|
138
|
+
*/
|
|
139
|
+
export async function loadPlugin(
|
|
140
|
+
spec: string,
|
|
141
|
+
db: Database,
|
|
142
|
+
config: Record<string, SqlValue> = {},
|
|
143
|
+
options: LoadPluginOptions = {}
|
|
144
|
+
): Promise<PluginManifest | undefined> {
|
|
145
|
+
const env = options.env && options.env !== 'auto' ? options.env : (isBrowserEnv() ? 'browser' : 'node');
|
|
146
|
+
|
|
147
|
+
// Direct URL or file path via dynamicLoadModule
|
|
148
|
+
if (isUrlLike(spec)) {
|
|
149
|
+
return await dynamicLoadModule(spec, db, config);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Interpret as npm spec or bare package name
|
|
153
|
+
const npm = parseNpmSpec(spec);
|
|
154
|
+
if (!npm) {
|
|
155
|
+
throw new Error(`Invalid plugin spec: ${spec}. Use a URL, file://, or npm package (e.g., npm:@scope/name@version).`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (env === 'node') {
|
|
159
|
+
// Resolve using Node ESM resolution. Prefer exported subpath './plugin'.
|
|
160
|
+
const subpathImport = `${npm.name}/plugin${npm.subpath ?? ''}`;
|
|
161
|
+
const candidates = [subpathImport, `${npm.name}${npm.subpath ?? ''}`];
|
|
162
|
+
|
|
163
|
+
let mod: PluginModule | undefined;
|
|
164
|
+
let lastErr: unknown = undefined;
|
|
165
|
+
for (const target of candidates) {
|
|
166
|
+
try {
|
|
167
|
+
mod = await import(/* @vite-ignore */ target) as PluginModule;
|
|
168
|
+
break;
|
|
169
|
+
} catch (e) {
|
|
170
|
+
lastErr = e;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!mod) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`Failed to resolve plugin package '${npm.name}'. Ensure it exports './plugin' or a default module. Last error: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (typeof mod.default !== 'function') {
|
|
181
|
+
throw new Error(`Resolved module for '${npm.name}' has no default export function`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Use the core registerPlugin function from @quereus/quereus
|
|
185
|
+
await registerPlugin(db, mod.default, config);
|
|
186
|
+
console.log(`Successfully loaded plugin from package ${npm.name}`);
|
|
187
|
+
|
|
188
|
+
// Try to extract manifest from package.json
|
|
189
|
+
let manifest: PluginManifest | undefined;
|
|
190
|
+
try {
|
|
191
|
+
// Try to import package.json directly
|
|
192
|
+
const pkg = await import(`${npm.name}/package.json`, { assert: { type: 'json' } });
|
|
193
|
+
manifest = extractManifestFromPackageJson(pkg.default);
|
|
194
|
+
} catch {
|
|
195
|
+
// package.json not found - that's okay
|
|
196
|
+
console.log(`Could not load package.json for plugin ${npm.name}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return manifest;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Browser path: npm spec requires CDN; only if explicitly allowed
|
|
203
|
+
if (!options.allowCdn) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`Loading npm packages in the browser requires allowCdn=true. Received spec '${spec}'. ` +
|
|
206
|
+
`Either provide a direct https:// URL to the ESM plugin or enable CDN resolution.`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const cdnUrl = toCdnUrl(npm, options.cdn ?? 'jsdelivr');
|
|
211
|
+
return await dynamicLoadModule(cdnUrl, db, config);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function isBrowserEnv(): boolean {
|
|
215
|
+
// Heuristic: presence of document on globalThis implies browser
|
|
216
|
+
return typeof globalThis !== 'undefined' && typeof (globalThis as unknown as { document?: unknown }).document !== 'undefined';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function isUrlLike(s: string): boolean {
|
|
220
|
+
try {
|
|
221
|
+
const u = new URL(s);
|
|
222
|
+
return u.protocol === 'https:' || u.protocol === 'http:' || u.protocol === 'file:';
|
|
223
|
+
} catch {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
interface NpmSpec {
|
|
229
|
+
name: string; // @scope/pkg or pkg
|
|
230
|
+
version?: string; // optional semver or tag
|
|
231
|
+
subpath?: string; // optional subpath after package root (rarely used)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function parseNpmSpec(input: string): NpmSpec | null {
|
|
235
|
+
// Remove optional npm: prefix
|
|
236
|
+
const raw = input.startsWith('npm:') ? input.slice(4) : input;
|
|
237
|
+
|
|
238
|
+
// Quick reject if contains spaces or empty
|
|
239
|
+
if (!raw || /\s/.test(raw)) return null;
|
|
240
|
+
|
|
241
|
+
// Support patterns like:
|
|
242
|
+
// @scope/name@1.2.3/path name@^1 name name/path
|
|
243
|
+
// Split off subpath (first '/' that is not part of scope)
|
|
244
|
+
let nameAndVersion = raw;
|
|
245
|
+
let subpath: string | undefined;
|
|
246
|
+
if (raw.startsWith('@')) {
|
|
247
|
+
// Scoped: look for second '/'
|
|
248
|
+
const secondSlash = raw.indexOf('/', raw.indexOf('/') + 1);
|
|
249
|
+
if (secondSlash !== -1) {
|
|
250
|
+
nameAndVersion = raw.slice(0, secondSlash);
|
|
251
|
+
subpath = raw.slice(secondSlash);
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
const firstSlash = raw.indexOf('/');
|
|
255
|
+
if (firstSlash !== -1) {
|
|
256
|
+
nameAndVersion = raw.slice(0, firstSlash);
|
|
257
|
+
subpath = raw.slice(firstSlash);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Now split name@version
|
|
262
|
+
const atIndex = nameAndVersion.lastIndexOf('@');
|
|
263
|
+
if (nameAndVersion.startsWith('@')) {
|
|
264
|
+
// Scoped: the first '@' is part of the scope
|
|
265
|
+
if (atIndex > 0) {
|
|
266
|
+
const name = nameAndVersion.slice(0, atIndex);
|
|
267
|
+
const version = nameAndVersion.slice(atIndex + 1) || undefined;
|
|
268
|
+
return { name, version, subpath };
|
|
269
|
+
}
|
|
270
|
+
return { name: nameAndVersion, subpath };
|
|
271
|
+
} else {
|
|
272
|
+
if (atIndex > 0) {
|
|
273
|
+
const name = nameAndVersion.slice(0, atIndex);
|
|
274
|
+
const version = nameAndVersion.slice(atIndex + 1) || undefined;
|
|
275
|
+
return { name, version, subpath };
|
|
276
|
+
}
|
|
277
|
+
return { name: nameAndVersion, subpath };
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function toCdnUrl(spec: NpmSpec, cdn: 'jsdelivr' | 'unpkg' | 'esm.sh'): string {
|
|
282
|
+
const versionSegment = spec.version ? `@${spec.version}` : '';
|
|
283
|
+
const subpath = spec.subpath ? spec.subpath.replace(/^\//, '') : 'plugin';
|
|
284
|
+
switch (cdn) {
|
|
285
|
+
case 'unpkg':
|
|
286
|
+
return `https://unpkg.com/${spec.name}${versionSegment}/${subpath}`;
|
|
287
|
+
case 'esm.sh':
|
|
288
|
+
// esm.sh expects ?path=/subpath or direct subpath after package
|
|
289
|
+
// Use direct subpath; esm.sh will transform to ESM
|
|
290
|
+
return `https://esm.sh/${spec.name}${versionSegment}/${subpath}`;
|
|
291
|
+
case 'jsdelivr':
|
|
292
|
+
default:
|
|
293
|
+
return `https://cdn.jsdelivr.net/npm/${spec.name}${versionSegment}/${subpath}`;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|