@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.
Files changed (146) hide show
  1. package/README.md +20 -2
  2. package/dist/src/common/logger.d.ts +59 -0
  3. package/dist/src/common/logger.d.ts.map +1 -1
  4. package/dist/src/common/logger.js +68 -0
  5. package/dist/src/common/logger.js.map +1 -1
  6. package/dist/src/func/builtins/datetime.d.ts.map +1 -1
  7. package/dist/src/func/builtins/datetime.js +10 -5
  8. package/dist/src/func/builtins/datetime.js.map +1 -1
  9. package/dist/src/index.d.ts +5 -6
  10. package/dist/src/index.d.ts.map +1 -1
  11. package/dist/src/index.js +4 -4
  12. package/dist/src/index.js.map +1 -1
  13. package/dist/src/planner/building/constraint-builder.d.ts.map +1 -1
  14. package/dist/src/planner/building/constraint-builder.js +4 -0
  15. package/dist/src/planner/building/constraint-builder.js.map +1 -1
  16. package/dist/src/planner/building/delete.d.ts.map +1 -1
  17. package/dist/src/planner/building/delete.js +2 -1
  18. package/dist/src/planner/building/delete.js.map +1 -1
  19. package/dist/src/planner/building/insert.d.ts.map +1 -1
  20. package/dist/src/planner/building/insert.js +4 -1
  21. package/dist/src/planner/building/insert.js.map +1 -1
  22. package/dist/src/planner/building/update.d.ts.map +1 -1
  23. package/dist/src/planner/building/update.js +4 -2
  24. package/dist/src/planner/building/update.js.map +1 -1
  25. package/dist/src/planner/nodes/dml-executor-node.d.ts +8 -2
  26. package/dist/src/planner/nodes/dml-executor-node.d.ts.map +1 -1
  27. package/dist/src/planner/nodes/dml-executor-node.js +11 -2
  28. package/dist/src/planner/nodes/dml-executor-node.js.map +1 -1
  29. package/dist/src/planner/validation/determinism-validator.d.ts +29 -0
  30. package/dist/src/planner/validation/determinism-validator.d.ts.map +1 -0
  31. package/dist/src/planner/validation/determinism-validator.js +47 -0
  32. package/dist/src/planner/validation/determinism-validator.js.map +1 -0
  33. package/dist/src/runtime/emit/add-constraint.d.ts.map +1 -1
  34. package/dist/src/runtime/emit/add-constraint.js +3 -0
  35. package/dist/src/runtime/emit/add-constraint.js.map +1 -1
  36. package/dist/src/runtime/emit/dml-executor.d.ts.map +1 -1
  37. package/dist/src/runtime/emit/dml-executor.js +84 -8
  38. package/dist/src/runtime/emit/dml-executor.js.map +1 -1
  39. package/dist/src/runtime/types.d.ts +1 -0
  40. package/dist/src/runtime/types.d.ts.map +1 -1
  41. package/dist/src/runtime/types.js.map +1 -1
  42. package/dist/src/schema/manager.d.ts.map +1 -1
  43. package/dist/src/schema/manager.js +41 -0
  44. package/dist/src/schema/manager.js.map +1 -1
  45. package/dist/src/util/ast-stringify.d.ts.map +1 -1
  46. package/dist/src/util/ast-stringify.js +14 -1
  47. package/dist/src/util/ast-stringify.js.map +1 -1
  48. package/dist/src/util/mutation-statement.d.ts +16 -0
  49. package/dist/src/util/mutation-statement.d.ts.map +1 -0
  50. package/dist/src/util/mutation-statement.js +92 -0
  51. package/dist/src/util/mutation-statement.js.map +1 -0
  52. package/dist/src/util/plugin-helper.d.ts +45 -0
  53. package/dist/src/util/plugin-helper.d.ts.map +1 -0
  54. package/dist/src/util/plugin-helper.js +85 -0
  55. package/dist/src/util/plugin-helper.js.map +1 -0
  56. package/dist/src/util/sql-literal.d.ts +11 -0
  57. package/dist/src/util/sql-literal.d.ts.map +1 -0
  58. package/dist/src/util/sql-literal.js +18 -0
  59. package/dist/src/util/sql-literal.js.map +1 -0
  60. package/dist/src/vtab/memory/table.d.ts +1 -2
  61. package/dist/src/vtab/memory/table.d.ts.map +1 -1
  62. package/dist/src/vtab/memory/table.js +3 -3
  63. package/dist/src/vtab/memory/table.js.map +1 -1
  64. package/dist/src/vtab/table.d.ts +23 -5
  65. package/dist/src/vtab/table.d.ts.map +1 -1
  66. package/dist/src/vtab/table.js +6 -0
  67. package/dist/src/vtab/table.js.map +1 -1
  68. package/package.json +4 -2
  69. package/src/common/logger.ts +75 -1
  70. package/src/func/builtins/datetime.ts +10 -5
  71. package/src/index.ts +13 -16
  72. package/src/planner/building/constraint-builder.ts +5 -0
  73. package/src/planner/building/delete.ts +5 -1
  74. package/src/planner/building/insert.ts +8 -1
  75. package/src/planner/building/update.ts +10 -2
  76. package/src/planner/nodes/dml-executor-node.ts +8 -2
  77. package/src/planner/validation/determinism-validator.ts +76 -0
  78. package/src/runtime/emit/add-constraint.ts +3 -0
  79. package/src/runtime/emit/dml-executor.ts +105 -9
  80. package/src/runtime/types.ts +3 -0
  81. package/src/schema/manager.ts +45 -0
  82. package/src/util/ast-stringify.ts +24 -1
  83. package/src/util/hash.ts +90 -90
  84. package/src/util/mutation-statement.ts +129 -0
  85. package/src/util/plugin-helper.ts +110 -0
  86. package/src/util/sql-literal.ts +22 -0
  87. package/src/vtab/memory/table.ts +3 -8
  88. package/src/vtab/table.ts +25 -10
  89. package/dist/src/config/loader.d.ts +0 -41
  90. package/dist/src/config/loader.d.ts.map +0 -1
  91. package/dist/src/config/loader.js +0 -102
  92. package/dist/src/config/loader.js.map +0 -1
  93. package/dist/src/planner/nodes/physical-access-nodes.d.ts +0 -83
  94. package/dist/src/planner/nodes/physical-access-nodes.d.ts.map +0 -1
  95. package/dist/src/planner/nodes/physical-access-nodes.js +0 -226
  96. package/dist/src/planner/nodes/physical-access-nodes.js.map +0 -1
  97. package/dist/src/planner/nodes/scan.d.ts +0 -27
  98. package/dist/src/planner/nodes/scan.d.ts.map +0 -1
  99. package/dist/src/planner/nodes/scan.js +0 -78
  100. package/dist/src/planner/nodes/scan.js.map +0 -1
  101. package/dist/src/planner/nodes/update-executor-node.d.ts +0 -24
  102. package/dist/src/planner/nodes/update-executor-node.d.ts.map +0 -1
  103. package/dist/src/planner/nodes/update-executor-node.js +0 -57
  104. package/dist/src/planner/nodes/update-executor-node.js.map +0 -1
  105. package/dist/src/planner/physical-utils.d.ts +0 -36
  106. package/dist/src/planner/physical-utils.d.ts.map +0 -1
  107. package/dist/src/planner/physical-utils.js +0 -122
  108. package/dist/src/planner/physical-utils.js.map +0 -1
  109. package/dist/src/planner/rules/physical/rule-filter-optimization.d.ts +0 -11
  110. package/dist/src/planner/rules/physical/rule-filter-optimization.d.ts.map +0 -1
  111. package/dist/src/planner/rules/physical/rule-filter-optimization.js +0 -49
  112. package/dist/src/planner/rules/physical/rule-filter-optimization.js.map +0 -1
  113. package/dist/src/planner/rules/physical/rule-mark-physical.d.ts +0 -11
  114. package/dist/src/planner/rules/physical/rule-mark-physical.d.ts.map +0 -1
  115. package/dist/src/planner/rules/physical/rule-mark-physical.js +0 -29
  116. package/dist/src/planner/rules/physical/rule-mark-physical.js.map +0 -1
  117. package/dist/src/planner/rules/physical/rule-project-optimization.d.ts +0 -11
  118. package/dist/src/planner/rules/physical/rule-project-optimization.d.ts.map +0 -1
  119. package/dist/src/planner/rules/physical/rule-project-optimization.js +0 -44
  120. package/dist/src/planner/rules/physical/rule-project-optimization.js.map +0 -1
  121. package/dist/src/planner/rules/physical/rule-sort-optimization.d.ts +0 -11
  122. package/dist/src/planner/rules/physical/rule-sort-optimization.d.ts.map +0 -1
  123. package/dist/src/planner/rules/physical/rule-sort-optimization.js +0 -53
  124. package/dist/src/planner/rules/physical/rule-sort-optimization.js.map +0 -1
  125. package/dist/src/planner/rules/rewrite/rule-constant-folding.d.ts +0 -11
  126. package/dist/src/planner/rules/rewrite/rule-constant-folding.d.ts.map +0 -1
  127. package/dist/src/planner/rules/rewrite/rule-constant-folding.js +0 -59
  128. package/dist/src/planner/rules/rewrite/rule-constant-folding.js.map +0 -1
  129. package/dist/src/planner/util/deferred-constraint.d.ts +0 -14
  130. package/dist/src/planner/util/deferred-constraint.d.ts.map +0 -1
  131. package/dist/src/planner/util/deferred-constraint.js +0 -85
  132. package/dist/src/planner/util/deferred-constraint.js.map +0 -1
  133. package/dist/src/runtime/emit/table-reference.d.ts +0 -5
  134. package/dist/src/runtime/emit/table-reference.d.ts.map +0 -1
  135. package/dist/src/runtime/emit/table-reference.js +0 -67
  136. package/dist/src/runtime/emit/table-reference.js.map +0 -1
  137. package/dist/src/runtime/emit/update-executor.d.ts +0 -5
  138. package/dist/src/runtime/emit/update-executor.d.ts.map +0 -1
  139. package/dist/src/runtime/emit/update-executor.js +0 -54
  140. package/dist/src/runtime/emit/update-executor.js.map +0 -1
  141. package/dist/src/util/plugin-loader.d.ts +0 -52
  142. package/dist/src/util/plugin-loader.d.ts.map +0 -1
  143. package/dist/src/util/plugin-loader.js +0 -307
  144. package/dist/src/util/plugin-loader.js.map +0 -1
  145. package/src/config/loader.ts +0 -140
  146. 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
-