next-live 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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/errors.ts","../src/core/transpile.ts","../src/core/builtin-specifiers.ts","../src/core/resolver.ts","../src/validate.ts","../src/server.ts"],"names":["transform"],"mappings":"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","file":"server.cjs","sourcesContent":["/** Base class for every error next-live raises. */\nexport class LiveError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message);\n this.name = new.target.name;\n if (options?.cause !== undefined) this.cause = options.cause;\n // Restores the prototype chain when compiled down to ES5 by a consumer.\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** The snippet could not be parsed or transpiled. */\nexport class LiveCompileError extends LiveError {\n readonly line: number | undefined;\n readonly column: number | undefined;\n\n constructor(message: string, position?: { line?: number; column?: number }, cause?: unknown) {\n super(message, { cause });\n this.line = position?.line;\n this.column = position?.column;\n }\n}\n\n/** The snippet threw while being evaluated or rendered. */\nexport class LiveRuntimeError extends LiveError {}\n\n/** A render loop was detected and stopped. */\nexport class RenderLoopError extends LiveRuntimeError {}\n\n/** `import` referenced a specifier that is not in the registry. */\nexport class ModuleNotFoundError extends LiveError {\n constructor(\n readonly specifier: string,\n readonly available: readonly string[],\n ) {\n super(buildModuleNotFoundMessage(specifier, available));\n }\n}\n\n/** The snippet compiled and ran but produced nothing renderable. */\nexport class NoComponentError extends LiveError {}\n\n/** Sucrase could not be loaded (usually a chunk-load failure). */\nexport class TranspilerLoadError extends LiveError {\n constructor(cause: unknown) {\n super(\n 'next-live could not load its transpiler (sucrase). This is usually a ' +\n 'network or code-splitting failure - check that the chunk is reachable.',\n { cause },\n );\n }\n}\n\nconst MAX_LISTED = 12;\n\nfunction buildModuleNotFoundMessage(specifier: string, available: readonly string[]): string {\n const lines = [`Module '${specifier}' is not registered in the next-live scope.`];\n\n const suggestion = nearestSpecifier(specifier, available);\n if (suggestion) lines.push('', `Did you mean '${suggestion}'?`);\n\n if (available.length > 0) {\n const sorted = [...available].sort();\n const shown = sorted.slice(0, MAX_LISTED);\n const rest = sorted.length - shown.length;\n lines.push(\n '',\n `Registered modules (${sorted.length}): ${shown.join(', ')}` +\n (rest > 0 ? `, …and ${rest} more` : ''),\n );\n } else {\n lines.push('', 'No modules are registered.');\n }\n\n // Bare specifiers are overwhelmingly the \"I expected npm to work\" case.\n if (!specifier.startsWith('.') && !specifier.startsWith('/')) {\n lines.push(\n '',\n 'next-live does not bundle npm packages - pass them in explicitly:',\n ` <LiveProvider modules={{ '${specifier}': theModule }} />`,\n );\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Closest registered specifier by edit distance, or undefined if nothing is\n * close enough to be worth suggesting.\n */\nexport function nearestSpecifier(\n specifier: string,\n available: readonly string[],\n): string | undefined {\n const lower = specifier.toLowerCase();\n\n // A pure casing slip is always the intended match.\n const caseMatch = available.find((key) => key.toLowerCase() === lower);\n if (caseMatch) return caseMatch;\n\n const threshold = Math.max(1, Math.min(3, Math.floor(specifier.length / 3)));\n let best: string | undefined;\n let bestDistance = threshold + 1;\n\n for (const key of available) {\n // Length alone can rule a candidate out before doing any real work.\n if (Math.abs(key.length - specifier.length) > threshold) continue;\n const distance = editDistance(lower, key.toLowerCase(), bestDistance);\n if (distance < bestDistance) {\n bestDistance = distance;\n best = key;\n }\n }\n\n return bestDistance <= threshold ? best : undefined;\n}\n\n/**\n * Levenshtein distance, abandoning the walk as soon as every cell in a row\n * exceeds `limit` - the common case is \"nothing is close\", and that exits fast.\n */\nfunction editDistance(a: string, b: string, limit: number): number {\n if (a === b) return 0;\n let previous = new Array<number>(b.length + 1);\n let current = new Array<number>(b.length + 1);\n\n for (let j = 0; j <= b.length; j++) previous[j] = j;\n\n for (let i = 1; i <= a.length; i++) {\n current[0] = i;\n let rowMin = i;\n for (let j = 1; j <= b.length; j++) {\n const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;\n const value = Math.min(\n (current[j - 1] as number) + 1,\n (previous[j] as number) + 1,\n (previous[j - 1] as number) + cost,\n );\n current[j] = value;\n if (value < rowMin) rowMin = value;\n }\n if (rowMin > limit) return limit + 1;\n const swap = previous;\n previous = current;\n current = swap;\n }\n\n return previous[b.length] as number;\n}\n","import { LiveCompileError, TranspilerLoadError } from './errors';\nimport type { TransformResult, TranspileOptions } from './types';\n\ntype SucraseModule = typeof import('sucrase');\n\nlet transpilerPromise: Promise<SucraseModule> | null = null;\n\n/**\n * Loads Sucrase on first use.\n *\n * The *promise* is cached rather than the resolved module, so concurrent\n * callers share one chunk fetch; a rejected load clears the cache so a\n * transient network failure can be retried.\n */\nexport function loadTranspiler(): Promise<SucraseModule> {\n if (transpilerPromise === null) {\n transpilerPromise = import('sucrase').catch((cause: unknown) => {\n transpilerPromise = null;\n throw new TranspilerLoadError(cause);\n });\n }\n return transpilerPromise;\n}\n\n/**\n * Starts fetching the transpiler chunk ahead of time. Worth calling on idle or\n * on route prefetch so the first compile is not gated on a network round trip.\n */\nexport function preloadTranspiler(): void {\n void loadTranspiler().catch(() => {\n // Preloading is best-effort; the real compile reports the failure.\n });\n}\n\n/** Replaces the loaded transpiler. Intended for tests and custom backends. */\nexport function setTranspiler(module: SucraseModule | null): void {\n transpilerPromise = module === null ? null : Promise.resolve(module);\n}\n\nexport const defaultTranspileOptions: Required<TranspileOptions> = {\n filePath: 'LiveCode.tsx',\n production: true,\n jsxRuntime: 'automatic',\n jsxImportSource: 'react',\n};\n\n/** Top-level `import`/`export`, ignoring matches that are not line-initial. */\nconst MODULE_SYNTAX_RE = /^[ \\t]*(?:export\\b|import\\s*[({'\"*]|import\\s+[A-Za-z_$])/m;\n\n/** A call to the injected `render()` helper (react-live's \"noInline\" style). */\nconst RENDER_CALL_RE = /(^|[^.\\w$])render\\s*\\(/m;\n\n/**\n * Whether a snippet is a module body rather than a bare expression.\n *\n * Imports and exports are unambiguous; a `render(...)` call is a statement, so\n * it is one too. Anything else may be a lone expression like `<div/>`, which is\n * not a valid module body and has to be wrapped.\n */\nexport function isModuleSource(source: string): boolean {\n return MODULE_SYNTAX_RE.test(source) || RENDER_CALL_RE.test(source);\n}\n\n/**\n * Whether a snippet carries no code at all - empty, whitespace, or only\n * comments.\n *\n * Such a snippet must not go down the bare-expression path: wrapping it\n * produces `export default ( )`, which Sucrase happily emits because it is a\n * token-based transform rather than a validating parser. The invalid code then\n * survives all the way to `new Function`, where the author sees a bare\n * `Unexpected token ')'` instead of being told the snippet is empty. Routing it\n * through module mode yields an empty module, and the normal \"did not produce a\n * component\" message.\n *\n * Only line-initial `//` is treated as a comment, so a URL inside a string on a\n * line of real code cannot make that line look blank. The check errs towards\n * \"has content\", which is the safe direction: it only ever restores the\n * previous behaviour.\n */\nexport function isBlankSource(source: string): boolean {\n const withoutComments = source\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, ' ')\n .replace(/^[ \\t]*\\/\\/.*$/gm, ' ');\n return withoutComments.trim() === '';\n}\n\n/** The Sucrase options every entry point uses, so they cannot drift apart. */\nexport function sucraseOptions(options: Required<TranspileOptions>) {\n return {\n transforms: ['jsx', 'typescript', 'imports'] as Array<'jsx' | 'typescript' | 'imports'>,\n jsxRuntime: options.jsxRuntime,\n jsxImportSource: options.jsxImportSource,\n production: options.production,\n filePath: options.filePath,\n // Leaving native `import()` intact would resolve against the *page* URL\n // and 404 on './utils'; routing it through our shim is the only sane\n // behaviour inside an evaluated snippet.\n preserveDynamicImport: false,\n };\n}\n\n/**\n * Applies the transpile pass, wrapping a bare expression so it becomes a valid\n * module body. Shared by the async browser path and the sync server path.\n */\nexport function runTranspile(\n transformFn: (input: string, opts: ReturnType<typeof sucraseOptions>) => { code: string },\n source: string,\n options: Required<TranspileOptions>,\n): TransformResult {\n const opts = sucraseOptions(options);\n\n if (!isModuleSource(source) && !isBlankSource(source)) {\n // The newlines matter: they shift the user's code down exactly one line,\n // which `linePrefixOffset` corrects, whereas inlining would destroy line\n // mapping for the whole snippet.\n try {\n return {\n code: transformFn(`export default (\\n${source}\\n)`, opts).code,\n linePrefixOffset: 1,\n expression: true,\n };\n } catch {\n // Multi-statement code with no exports - fall through to module mode.\n }\n }\n\n return { code: transformFn(source, opts).code, linePrefixOffset: 0, expression: false };\n}\n\n/**\n * Transpiles a snippet to CommonJS that `new Function` can evaluate.\n *\n * Three authoring styles are supported, resolved without ever asking the user\n * which one they used:\n *\n * 1. A real module - `export default function App() {}`.\n * 2. A bare expression, `<div/>` or `() => <div/>`.\n * 3. Bare statements with no export, `function App() {}`, or `render(<App/>)`.\n *\n * Styles 1 and 3 are compiled as-is; the component is recovered after\n * evaluation (see `evaluate.ts`). Style 2 is not a valid module body on its\n * own, so it is wrapped in `export default (...)`.\n */\nexport async function transpile(\n source: string,\n options: TranspileOptions = {},\n transform?: import('./types').TransformFn,\n): Promise<TransformResult> {\n const resolved = { ...defaultTranspileOptions, ...options };\n\n if (transform) return transform(source, resolved);\n\n const { transform: sucraseTransform } = await loadTranspiler();\n\n try {\n return runTranspile(sucraseTransform, source, resolved);\n } catch (cause) {\n throw toCompileError(cause);\n }\n}\n\n/** Sucrase parse errors carry a `(line:column)` suffix worth surfacing. */\nfunction toCompileError(cause: unknown): LiveCompileError {\n const message = cause instanceof Error ? cause.message : String(cause);\n const match = /\\((\\d+):(\\d+)\\)\\s*$/.exec(message);\n if (!match) return new LiveCompileError(message, undefined, cause);\n\n return new LiveCompileError(\n message.slice(0, match.index).trim(),\n { line: Number(match[1]), column: Number(match[2]) },\n cause,\n );\n}\n\n/**\n * Names declared at the top level of compiled output, used to recover a\n * component from a snippet that never exported one.\n *\n * Matched at the start of a line *or* just after a `;`. Column zero alone is\n * not enough: Sucrase prepends its own preamble - `\"use strict\";var _jsxruntime\n * = require(...)` - to line 1, so a component declared on the snippet's first\n * line no longer sits at column zero and was silently skipped. The snippet\n * below then exported `y`, and the author was told the default export was a\n * number:\n *\n * ```js\n * const App = () => <b/>; // invisible: shares line 1 with the preamble\n * const y = 2; // found, and wrongly chosen\n * ```\n *\n * Matching after `;` also admits declarations nested inside a function body,\n * which is harmless by the same reasoning as any other false positive: the\n * generated epilogue guards every name with `typeof`, and a block-scoped name\n * is `undefined` at module level, so it is skipped.\n */\nexport function scanTopLevelDeclarations(code: string): string[] {\n const names: string[] = [];\n const patterns = [\n /(?:^|;)\\s*(?:async\\s+)?function\\s*\\*?\\s*([A-Za-z_$][\\w$]*)/gm,\n /(?:^|;)\\s*class\\s+([A-Za-z_$][\\w$]*)/gm,\n /(?:^|;)\\s*(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*=/gm,\n ];\n\n for (const pattern of patterns) {\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(code)) !== null) {\n const name = match[1];\n if (name !== undefined && !names.includes(name)) names.push(name);\n }\n }\n return names;\n}\n\n/**\n * Wraps an already-compiled result as a `transform` function, so the client\n * skips loading Sucrase entirely:\n *\n * ```tsx\n * <LiveProvider code={source} transform={precompiledTransform(compiled)} />\n * ```\n *\n * Lives here rather than next-live/server on purpose. It is a pure closure over\n * a value and needs no transpiler - but importing it from the server entry\n * would pull Sucrase statically into the page bundle, which is the exact cost\n * precompiling exists to avoid.\n */\nexport function precompiledTransform(result: TransformResult): () => TransformResult {\n return () => result;\n}\n","/**\n * The specifiers `next-live` registers for every snippet.\n *\n * Kept apart from `builtins.ts` because that module imports React, and the\n * validator must be usable in a CI script or a Route Handler where pulling in\n * React would be pointless. Names only, no values.\n */\nexport const BUILTIN_SPECIFIERS = [\n 'react',\n 'react/jsx-runtime',\n 'react/jsx-dev-runtime',\n] as const;\n","import { ModuleNotFoundError } from './errors';\nimport type { ModuleLoader, ModuleRegistry, ModuleValue, NormalizedModule } from './types';\n\n/** Marks a record that has already been through {@link normalizeModule}. */\nconst NORMALIZED = Symbol.for('next-live.normalized');\n\n/** Marks a function as a lazy loader rather than the module value itself. */\nconst LOADER = Symbol.for('next-live.loader');\n\n/** Assets a snippet may import for side effects only; resolved to empty modules. */\nconst ASSET_RE = /\\.(css|scss|sass|less|styl|svg|png|jpe?g|gif|webp|avif|woff2?)$/i;\n\n/**\n * Wraps a function so the registry treats it as a lazy loader instead of as\n * the module value. Without this a registered component function would be\n * indistinguishable from a loader.\n *\n * ```ts\n * { 'heavy-chart': defineLoader(() => import('heavy-chart')) }\n * ```\n */\nexport function defineLoader(load: ModuleLoader): ModuleLoader {\n return Object.defineProperty(load, LOADER, { value: true }) as ModuleLoader;\n}\n\nfunction isLoader(value: unknown): value is ModuleLoader {\n return typeof value === 'function' && (value as { [LOADER]?: boolean })[LOADER] === true;\n}\n\n/**\n * Builds an explicit module record, for the shape that cannot be inferred.\n *\n * {@link normalizeModule} unwraps any registered object that has its own\n * `default` key, because that is almost always a module wrapper. When it is\n * not - a config object that happens to contain the word `default`, say so:\n *\n * ```ts\n * { './theme': defineModule({ default: { default: 'dark', light: '#fff' } }) }\n * // import theme from './theme' → the whole object\n * ```\n *\n * `default` and `exports.default` address the same slot, because ESM makes no\n * distinction between a default export and a named export called `default`.\n * A top-level `default` wins if both are given.\n */\nexport function defineModule(shape: {\n default?: unknown;\n exports?: Record<string, unknown>;\n}): NormalizedModule {\n const record = Object.create(null) as Record<string | symbol, unknown>;\n Object.defineProperty(record, '__esModule', { value: true });\n Object.defineProperty(record, NORMALIZED, { value: true });\n\n const defaultExport = 'default' in shape ? shape.default : shape.exports?.['default'];\n Object.defineProperty(record, 'default', { value: defaultExport, enumerable: true });\n\n for (const [key, value] of Object.entries(shape.exports ?? {})) {\n if (key === 'default') continue;\n Object.defineProperty(record, key, { value, enumerable: true });\n }\n return record as unknown as NormalizedModule;\n}\n\n/**\n * Converts a registered value into a record Sucrase's interop helpers accept.\n *\n * The trick that makes this total: both helpers Sucrase emits are identity\n * functions when the required value carries `__esModule === true` -\n * `_interopRequireDefault` is literally `obj && obj.__esModule ? obj : {default: obj}`.\n * By always returning such a record we neutralise both helpers, so this\n * function becomes the single source of truth for what `default` and each\n * named import resolve to.\n *\n * Named exports are exposed as *getters* over the original value rather than\n * copied. That preserves ES module live bindings, and - more importantly in\n * practice, avoids eagerly invoking the lazy namespace getters that packages\n * like icon sets and large UI barrels use, which a naive spread would trigger\n * on every single compile.\n */\nexport function normalizeModule(value: ModuleValue): NormalizedModule {\n if (isNormalized(value)) return value;\n\n if (value === null || value === undefined) {\n // A registered `undefined` is almost always a broken import on the host\n // side; surfacing it as an empty module beats a confusing downstream crash.\n return defineModule({ default: value });\n }\n\n if (typeof value !== 'object' && typeof value !== 'function') {\n return defineModule({ default: value });\n }\n\n const source = value as Record<string, unknown>;\n const record = Object.create(null) as Record<string | symbol, unknown>;\n Object.defineProperty(record, '__esModule', { value: true });\n Object.defineProperty(record, NORMALIZED, { value: true });\n\n // `in` rather than hasOwnProperty: bundler namespace objects can expose\n // `default` further up the chain. When a value carries its own `default`\n // it is module-shaped, so unwrap it; otherwise the value *is* the default.\n const defaultExport = 'default' in source ? source['default'] : source;\n Object.defineProperty(record, 'default', {\n get: () => defaultExport,\n enumerable: true,\n configurable: true,\n });\n\n for (const key of ownEnumerableKeys(source)) {\n if (key === 'default' || key === '__esModule') continue;\n Object.defineProperty(record, key, {\n get: () => source[key],\n enumerable: true,\n configurable: true,\n });\n }\n\n return record as unknown as NormalizedModule;\n}\n\nfunction isNormalized(value: unknown): value is NormalizedModule {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { [NORMALIZED]?: boolean })[NORMALIZED] === true\n );\n}\n\n/**\n * Enumerable string keys, without invoking any getters. `Object.keys` alone\n * misses inherited enumerables on some bundlers' namespace objects.\n */\nfunction ownEnumerableKeys(source: object): string[] {\n const keys = new Set<string>();\n for (const key of Object.keys(source)) keys.add(key);\n for (const key in source) keys.add(key);\n return [...keys];\n}\n\n/** The resolved, synchronously-readable module table handed to a snippet. */\nexport interface ResolvedModules {\n get(specifier: string): NormalizedModule | undefined;\n readonly keys: readonly string[];\n}\n\nexport interface ResolveOptions {\n registry: ModuleRegistry;\n /** Specifiers found in the compiled output. */\n specifiers: Iterable<string>;\n /**\n * Resolve `@scope/pkg/Sub` against a registered `@scope/pkg` by walking the\n * remaining segments as property accesses. Correct for barrel-shaped\n * packages, wrong for those whose subpaths are not re-exported - so it is\n * opt-in rather than a silent guess. Default false.\n */\n resolveSubpaths?: boolean;\n signal?: AbortSignal;\n}\n\n/**\n * Resolves every specifier a snippet needs *before* evaluation, because\n * Sucrase emits synchronous `require()` calls that cannot await anything.\n */\nexport async function resolveModules(options: ResolveOptions): Promise<ResolvedModules> {\n const { registry, specifiers, resolveSubpaths = false, signal } = options;\n const resolved = new Map<string, NormalizedModule>();\n\n const pending: Array<Promise<void>> = [];\n\n for (const specifier of specifiers) {\n if (resolved.has(specifier)) continue;\n\n const found = lookup(registry, specifier, resolveSubpaths);\n if (found === MISSING) {\n // Side-effect asset imports are a no-op rather than an error, so a\n // snippet copied out of a real file with `import './styles.css'` runs.\n if (ASSET_RE.test(specifier)) resolved.set(specifier, defineModule({}));\n // Anything else stays unresolved. We deliberately do not throw here:\n // the specifier scan is deliberately over-inclusive, so only an actual\n // require() at runtime is proof the snippet really needs it.\n continue;\n }\n\n if (isLoader(found)) {\n pending.push(\n Promise.resolve(found(specifier)).then((value) => {\n resolved.set(specifier, normalizeModule(value));\n }),\n );\n } else {\n resolved.set(specifier, normalizeModule(found));\n }\n }\n\n if (pending.length > 0) await Promise.all(pending);\n signal?.throwIfAborted();\n\n const keys = Object.keys(registry);\n return {\n get: (specifier) => resolved.get(specifier),\n keys,\n };\n}\n\n/** Sentinel distinguishing \"not registered\" from a registered `undefined`. */\nconst MISSING = Symbol('missing');\n\n/** Specifiers resolved to an empty module rather than an error. */\nexport function isIgnoredSpecifier(specifier: string): boolean {\n return ASSET_RE.test(specifier);\n}\n\n/**\n * The registry key that would serve a specifier, or undefined if none would.\n *\n * Key matching only - no values, no loaders, nothing executed. That is what\n * lets a snippet be checked on a server, or in CI, without running it.\n */\nexport function matchRegistryKey(\n specifier: string,\n keys: readonly string[],\n): string | undefined {\n if (keys.includes(specifier)) return specifier;\n\n let best: string | undefined;\n for (const key of keys) {\n if (!key.endsWith('/') || !specifier.startsWith(key)) continue;\n if (best === undefined || key.length > best.length) best = key;\n }\n return best;\n}\n\nfunction lookup(\n registry: ModuleRegistry,\n specifier: string,\n resolveSubpaths: boolean,\n): ModuleValue | ModuleLoader | typeof MISSING {\n if (Object.prototype.hasOwnProperty.call(registry, specifier)) {\n return registry[specifier];\n }\n\n // Prefix entries: a key ending in '/' claims the whole subtree and receives\n // the full specifier, letting a host route a whole package subtree through\n // one loader.\n let bestPrefix: string | undefined;\n for (const key of Object.keys(registry)) {\n if (!key.endsWith('/')) continue;\n if (!specifier.startsWith(key)) continue;\n if (bestPrefix === undefined || key.length > bestPrefix.length) bestPrefix = key;\n }\n if (bestPrefix !== undefined) {\n // No wrapping needed: the caller invokes the loader with the full\n // specifier, which is exactly what a prefix entry wants.\n return registry[bestPrefix];\n }\n\n if (resolveSubpaths) {\n const walked = walkSubpath(registry, specifier);\n if (walked !== MISSING) return walked;\n }\n\n return MISSING;\n}\n\n/**\n * `pkg/Sub` against a registered `pkg` namespace, by reading\n * the remaining path segments as properties.\n */\nfunction walkSubpath(\n registry: ModuleRegistry,\n specifier: string,\n): ModuleValue | typeof MISSING {\n let base: string | undefined;\n for (const key of Object.keys(registry)) {\n if (!specifier.startsWith(key + '/')) continue;\n if (base === undefined || key.length > base.length) base = key;\n }\n if (base === undefined) return MISSING;\n\n let current = registry[base];\n if (isLoader(current)) return MISSING; // cannot walk without awaiting\n\n for (const segment of specifier.slice(base.length + 1).split('/')) {\n if (current === null || current === undefined) return MISSING;\n if (typeof current !== 'object' && typeof current !== 'function') return MISSING;\n const next = (current as Record<string, unknown>)[segment];\n if (next === undefined) return MISSING;\n current = next;\n }\n return current;\n}\n\n/**\n * The synchronous `require` shim handed to compiled snippets. Sucrase emits\n * `var _react = require('react')` at module top level, so this must never\n * return a promise.\n */\nexport function createRequire(\n resolved: ResolvedModules,\n): (specifier: string) => NormalizedModule {\n return function require(specifier: string): NormalizedModule {\n const found = resolved.get(specifier);\n if (found === undefined) {\n throw new ModuleNotFoundError(specifier, resolved.keys);\n }\n return found;\n };\n}\n\n/**\n * Every `require()` target in compiled output.\n *\n * Deliberately over-inclusive: a match inside a string literal costs one\n * needless registry lookup, whereas a miss would mean a loader never runs and\n * evaluation fails. Unresolvable matches are dropped silently by\n * {@link resolveModules}.\n */\nexport function scanRequires(code: string): Set<string> {\n const found = new Set<string>();\n const re = /\\brequire\\s*\\(\\s*(['\"])((?:(?!\\1)[^\\\\]|\\\\.)*)\\1\\s*\\)/g;\n let match: RegExpExecArray | null;\n while ((match = re.exec(code)) !== null) {\n const specifier = match[2];\n if (specifier) found.add(specifier);\n }\n return found;\n}\n","/**\n * Static validation of stored snippets.\n *\n * The problem this solves: with snippets stored in a database, renaming\n * something in your SDK breaks them silently, the failure surfaces for\n * whoever opens that app next, not for the person who made the change. Run\n * this over every stored snippet in CI and the rename fails the build instead.\n *\n * Deliberately **static**: it transpiles and checks specifiers, but never\n * evaluates. That means it is safe to run over untrusted content in CI, needs\n * no DOM, and cannot be tripped up by a snippet's side effects. The trade-off\n * is that runtime errors are not caught - only syntax errors and imports that\n * would fail to resolve.\n *\n * Exported from the server entry only, `next-live/server`, because it\n * imports Sucrase statically. Pulling it into the client entry would defeat\n * the code-splitting that keeps the transpiler out of your page bundle.\n */\nimport { transform } from 'sucrase';\nimport { LiveCompileError } from './core/errors';\nimport { nearestSpecifier } from './core/errors';\nimport { BUILTIN_SPECIFIERS } from './core/builtin-specifiers';\nimport { isIgnoredSpecifier, matchRegistryKey, scanRequires } from './core/resolver';\nimport { defaultTranspileOptions, runTranspile } from './core/transpile';\nimport type { ModuleRegistry, TranspileOptions } from './core/types';\n\nexport type ValidationIssueKind =\n | 'syntax'\n | 'unresolved-import'\n | 'source-too-large'\n | 'forbidden-import';\n\nexport interface ValidationIssue {\n kind: ValidationIssueKind;\n message: string;\n /** The import specifier, for import-related issues. */\n specifier?: string;\n /** The closest registered specifier, when one is close enough to suggest. */\n suggestion?: string;\n line?: number;\n column?: number;\n}\n\nexport interface ValidationResult {\n ok: boolean;\n issues: ValidationIssue[];\n /** Every specifier the snippet imports, resolvable or not. */\n imports: string[];\n}\n\nexport interface ValidateOptions extends TranspileOptions {\n /**\n * The registry a snippet will run against - either the registry object, or\n * just its keys. Keys alone are usually easier to share with a CI script,\n * since the real registry is full of bundler-specific dynamic imports.\n */\n modules?: ModuleRegistry | readonly string[];\n /** Reject snippets larger than this many UTF-8 bytes. Checked before transpile. */\n maxSourceBytes?: number;\n /** Treat `node:*` imports as forbidden rather than unresolved. */\n forbidNodeBuiltins?: boolean;\n /** Treat remote URL imports as forbidden. */\n forbidRemoteImports?: boolean;\n /**\n * Deny these specifiers even when registered. Prefix keys ending in `/`\n * deny a whole subtree, matching registry prefix semantics.\n */\n denySpecifiers?: readonly string[];\n}\n\n/**\n * Checks that a snippet compiles and that every module it imports is\n * registered.\n *\n * ```ts\n * const result = validateSnippet(app.source, { modules: Object.keys(liveModules) });\n * if (!result.ok) {\n * console.error(app.id, result.issues);\n * process.exitCode = 1;\n * }\n * ```\n */\nexport function validateSnippet(\n source: string,\n options: ValidateOptions = {},\n): ValidationResult {\n const {\n modules,\n maxSourceBytes,\n forbidNodeBuiltins,\n forbidRemoteImports,\n denySpecifiers,\n ...transpileOptions\n } = options;\n const resolved = { ...defaultTranspileOptions, ...transpileOptions };\n\n const registryKeys = [\n ...BUILTIN_SPECIFIERS,\n ...(Array.isArray(modules) ? (modules as string[]) : Object.keys(modules ?? {})),\n ];\n\n if (maxSourceBytes !== undefined) {\n const bytes = new TextEncoder().encode(source).length;\n if (bytes > maxSourceBytes) {\n return {\n ok: false,\n issues: [\n {\n kind: 'source-too-large',\n message: `Snippet is ${bytes} bytes, exceeding the limit of ${maxSourceBytes}.`,\n },\n ],\n imports: [],\n };\n }\n }\n\n let code: string;\n try {\n code = runTranspile(transform, source, resolved).code;\n } catch (cause) {\n return { ok: false, issues: [syntaxIssue(cause)], imports: [] };\n }\n\n const imports = [...scanRequires(code)].sort();\n const issues: ValidationIssue[] = [];\n\n for (const specifier of imports) {\n const policyIssue = policyViolation(specifier, {\n forbidNodeBuiltins,\n forbidRemoteImports,\n denySpecifiers,\n });\n if (policyIssue) {\n issues.push(policyIssue);\n continue;\n }\n\n if (isIgnoredSpecifier(specifier)) continue;\n if (matchRegistryKey(specifier, registryKeys)) continue;\n\n const suggestion = nearestSpecifier(specifier, registryKeys);\n issues.push({\n kind: 'unresolved-import',\n specifier,\n message:\n `Module '${specifier}' is not registered.` +\n (suggestion ? ` Did you mean '${suggestion}'?` : ''),\n ...(suggestion ? { suggestion } : {}),\n });\n }\n\n return { ok: issues.length === 0, issues, imports };\n}\n\nfunction policyViolation(\n specifier: string,\n options: {\n forbidNodeBuiltins?: boolean;\n forbidRemoteImports?: boolean;\n denySpecifiers?: readonly string[];\n },\n): ValidationIssue | null {\n if (options.forbidNodeBuiltins && specifier.startsWith('node:')) {\n return {\n kind: 'forbidden-import',\n specifier,\n message: `Import '${specifier}' is forbidden: Node built-ins are not available to snippets.`,\n };\n }\n\n if (\n options.forbidRemoteImports &&\n (/^https?:\\/\\//.test(specifier) || specifier.startsWith('//'))\n ) {\n return {\n kind: 'forbidden-import',\n specifier,\n message: `Import '${specifier}' is forbidden: remote modules are not available to snippets.`,\n };\n }\n\n if (options.denySpecifiers?.length) {\n const denied = matchRegistryKey(specifier, options.denySpecifiers);\n if (denied !== undefined) {\n return {\n kind: 'forbidden-import',\n specifier,\n message: `Import '${specifier}' is forbidden by policy (matched deny rule '${denied}').`,\n };\n }\n }\n\n return null;\n}\n\n/**\n * Validates many snippets at once, returning only the ones with problems.\n *\n * ```ts\n * const broken = validateSnippets(apps.map((a) => ({ id: a.id, source: a.source })), {\n * modules: Object.keys(liveModules),\n * });\n * ```\n */\nexport function validateSnippets<T extends { id: string; source: string }>(\n snippets: readonly T[],\n options: ValidateOptions = {},\n): Array<{ id: string; result: ValidationResult }> {\n const failures: Array<{ id: string; result: ValidationResult }> = [];\n for (const snippet of snippets) {\n const result = validateSnippet(snippet.source, {\n filePath: `${snippet.id}.tsx`,\n ...options,\n });\n if (!result.ok) failures.push({ id: snippet.id, result });\n }\n return failures;\n}\n\nfunction syntaxIssue(cause: unknown): ValidationIssue {\n const message = cause instanceof Error ? cause.message : String(cause);\n const match = /\\((\\d+):(\\d+)\\)\\s*$/.exec(message);\n if (!match) return { kind: 'syntax', message };\n\n return {\n kind: 'syntax',\n message: message.slice(0, match.index).trim(),\n line: Number(match[1]),\n column: Number(match[2]),\n };\n}\n\nexport { LiveCompileError };\n","/**\n * Server-side precompilation.\n *\n * Sucrase runs just as happily in Node, so a host serving many stored snippets\n * can transpile once, cache by content hash, and ship ready JavaScript to the\n * browser, the client then never downloads the transpiler at all.\n *\n * This entry deliberately carries no `'use client'` directive and pulls in no\n * React, so it is safe to import from a Route Handler or a Server Component.\n */\nimport { transform } from 'sucrase';\nimport { defaultTranspileOptions, runTranspile } from './core/transpile';\nimport { LiveCompileError } from './core/errors';\nimport type { TransformResult, TranspileOptions } from './core/types';\n\nexport interface PrecompileResult extends TransformResult {\n /** Stable hash of source + options. Use it as a cache key or ETag. */\n hash: string;\n}\n\n/**\n * Transpiles a snippet to the same CommonJS the browser path produces.\n *\n * Only the `export default` / bare-statement forms are handled here; a bare\n * expression snippet (`<div/>`) is wrapped exactly as the client wraps it, so\n * the two paths stay interchangeable.\n */\nexport function precompile(source: string, options: TranspileOptions = {}): PrecompileResult {\n const resolved = { ...defaultTranspileOptions, ...options };\n\n try {\n // The exact same pass the browser runs, so a precompiled result and a\n // client-compiled one are interchangeable.\n const result = runTranspile(transform, source, resolved);\n return { ...result, hash: hashOf(source, resolved) };\n } catch (cause) {\n const message = cause instanceof Error ? cause.message : String(cause);\n const match = /\\((\\d+):(\\d+)\\)\\s*$/.exec(message);\n throw match\n ? new LiveCompileError(\n message.slice(0, match.index).trim(),\n { line: Number(match[1]), column: Number(match[2]) },\n cause,\n )\n : new LiveCompileError(message, undefined, cause);\n }\n}\n\n/**\n * FNV-1a over the source and the options that affect output. Not a\n * cryptographic hash - it only needs to be fast and collision-resistant enough\n * to key a cache.\n */\nfunction hashOf(source: string, options: Required<TranspileOptions>): string {\n const input = `${options.jsxRuntime}|${options.jsxImportSource}|${options.production}|${source}`;\n let hash = 0x811c9dc5;\n for (let i = 0; i < input.length; i++) {\n hash ^= input.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n return (hash >>> 0).toString(36);\n}\n\nexport { LiveCompileError } from './core/errors';\nexport { validateSnippet, validateSnippets } from './validate';\nexport type {\n ValidateOptions,\n ValidationIssue,\n ValidationIssueKind,\n ValidationResult,\n} from './validate';\nexport type { TransformResult, TranspileOptions } from './core/types';\n"]}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * A value registered under an import specifier.
3
+ *
4
+ * The idiomatic value is a real module namespace - `import * as ui from
5
+ * 'my-ui-kit'` - but any object, function, or primitive works. See
6
+ * {@link normalizeModule} for how each shape maps onto `default` and named
7
+ * imports.
8
+ */
9
+ type ModuleValue = unknown;
10
+ /**
11
+ * A lazily-loaded module, awaited before user code is evaluated.
12
+ *
13
+ * Receives the specifier that was imported. Exact-match entries can ignore it;
14
+ * prefix entries (a key ending in `/`) need it, since one loader serves the
15
+ * whole subtree.
16
+ */
17
+ type ModuleLoader = (specifier: string) => ModuleValue | Promise<ModuleValue>;
18
+ /**
19
+ * Maps import specifiers to values:
20
+ *
21
+ * ```ts
22
+ * { 'react': React, '@app/ui': ui, '@app/store': { useAppStore } }
23
+ * ```
24
+ */
25
+ interface ModuleRegistry {
26
+ [specifier: string]: ModuleValue | ModuleLoader;
27
+ }
28
+ interface TranspileOptions {
29
+ /**
30
+ * Logical filename. Drives the `sourceURL` so stack frames and DevTools
31
+ * agree on one name. Use a stable per-app identity, e.g. `app-42.tsx`.
32
+ */
33
+ filePath?: string;
34
+ /** `false` selects `react/jsx-dev-runtime`, giving richer component stacks. */
35
+ production?: boolean;
36
+ jsxRuntime?: 'automatic' | 'classic';
37
+ jsxImportSource?: string;
38
+ }
39
+ interface TransformResult {
40
+ code: string;
41
+ /** Lines inserted above the user's line 1. Added back during error mapping. */
42
+ linePrefixOffset: number;
43
+ /** True when the snippet was compiled as a bare expression. */
44
+ expression: boolean;
45
+ }
46
+
47
+ /** Base class for every error next-live raises. */
48
+ declare class LiveError extends Error {
49
+ constructor(message: string, options?: {
50
+ cause?: unknown;
51
+ });
52
+ }
53
+ /** The snippet could not be parsed or transpiled. */
54
+ declare class LiveCompileError extends LiveError {
55
+ readonly line: number | undefined;
56
+ readonly column: number | undefined;
57
+ constructor(message: string, position?: {
58
+ line?: number;
59
+ column?: number;
60
+ }, cause?: unknown);
61
+ }
62
+
63
+ type ValidationIssueKind = 'syntax' | 'unresolved-import' | 'source-too-large' | 'forbidden-import';
64
+ interface ValidationIssue {
65
+ kind: ValidationIssueKind;
66
+ message: string;
67
+ /** The import specifier, for import-related issues. */
68
+ specifier?: string;
69
+ /** The closest registered specifier, when one is close enough to suggest. */
70
+ suggestion?: string;
71
+ line?: number;
72
+ column?: number;
73
+ }
74
+ interface ValidationResult {
75
+ ok: boolean;
76
+ issues: ValidationIssue[];
77
+ /** Every specifier the snippet imports, resolvable or not. */
78
+ imports: string[];
79
+ }
80
+ interface ValidateOptions extends TranspileOptions {
81
+ /**
82
+ * The registry a snippet will run against - either the registry object, or
83
+ * just its keys. Keys alone are usually easier to share with a CI script,
84
+ * since the real registry is full of bundler-specific dynamic imports.
85
+ */
86
+ modules?: ModuleRegistry | readonly string[];
87
+ /** Reject snippets larger than this many UTF-8 bytes. Checked before transpile. */
88
+ maxSourceBytes?: number;
89
+ /** Treat `node:*` imports as forbidden rather than unresolved. */
90
+ forbidNodeBuiltins?: boolean;
91
+ /** Treat remote URL imports as forbidden. */
92
+ forbidRemoteImports?: boolean;
93
+ /**
94
+ * Deny these specifiers even when registered. Prefix keys ending in `/`
95
+ * deny a whole subtree, matching registry prefix semantics.
96
+ */
97
+ denySpecifiers?: readonly string[];
98
+ }
99
+ /**
100
+ * Checks that a snippet compiles and that every module it imports is
101
+ * registered.
102
+ *
103
+ * ```ts
104
+ * const result = validateSnippet(app.source, { modules: Object.keys(liveModules) });
105
+ * if (!result.ok) {
106
+ * console.error(app.id, result.issues);
107
+ * process.exitCode = 1;
108
+ * }
109
+ * ```
110
+ */
111
+ declare function validateSnippet(source: string, options?: ValidateOptions): ValidationResult;
112
+ /**
113
+ * Validates many snippets at once, returning only the ones with problems.
114
+ *
115
+ * ```ts
116
+ * const broken = validateSnippets(apps.map((a) => ({ id: a.id, source: a.source })), {
117
+ * modules: Object.keys(liveModules),
118
+ * });
119
+ * ```
120
+ */
121
+ declare function validateSnippets<T extends {
122
+ id: string;
123
+ source: string;
124
+ }>(snippets: readonly T[], options?: ValidateOptions): Array<{
125
+ id: string;
126
+ result: ValidationResult;
127
+ }>;
128
+
129
+ interface PrecompileResult extends TransformResult {
130
+ /** Stable hash of source + options. Use it as a cache key or ETag. */
131
+ hash: string;
132
+ }
133
+ /**
134
+ * Transpiles a snippet to the same CommonJS the browser path produces.
135
+ *
136
+ * Only the `export default` / bare-statement forms are handled here; a bare
137
+ * expression snippet (`<div/>`) is wrapped exactly as the client wraps it, so
138
+ * the two paths stay interchangeable.
139
+ */
140
+ declare function precompile(source: string, options?: TranspileOptions): PrecompileResult;
141
+
142
+ export { LiveCompileError, type PrecompileResult, type TransformResult, type TranspileOptions, type ValidateOptions, type ValidationIssue, type ValidationIssueKind, type ValidationResult, precompile, validateSnippet, validateSnippets };
@@ -0,0 +1,142 @@
1
+ /**
2
+ * A value registered under an import specifier.
3
+ *
4
+ * The idiomatic value is a real module namespace - `import * as ui from
5
+ * 'my-ui-kit'` - but any object, function, or primitive works. See
6
+ * {@link normalizeModule} for how each shape maps onto `default` and named
7
+ * imports.
8
+ */
9
+ type ModuleValue = unknown;
10
+ /**
11
+ * A lazily-loaded module, awaited before user code is evaluated.
12
+ *
13
+ * Receives the specifier that was imported. Exact-match entries can ignore it;
14
+ * prefix entries (a key ending in `/`) need it, since one loader serves the
15
+ * whole subtree.
16
+ */
17
+ type ModuleLoader = (specifier: string) => ModuleValue | Promise<ModuleValue>;
18
+ /**
19
+ * Maps import specifiers to values:
20
+ *
21
+ * ```ts
22
+ * { 'react': React, '@app/ui': ui, '@app/store': { useAppStore } }
23
+ * ```
24
+ */
25
+ interface ModuleRegistry {
26
+ [specifier: string]: ModuleValue | ModuleLoader;
27
+ }
28
+ interface TranspileOptions {
29
+ /**
30
+ * Logical filename. Drives the `sourceURL` so stack frames and DevTools
31
+ * agree on one name. Use a stable per-app identity, e.g. `app-42.tsx`.
32
+ */
33
+ filePath?: string;
34
+ /** `false` selects `react/jsx-dev-runtime`, giving richer component stacks. */
35
+ production?: boolean;
36
+ jsxRuntime?: 'automatic' | 'classic';
37
+ jsxImportSource?: string;
38
+ }
39
+ interface TransformResult {
40
+ code: string;
41
+ /** Lines inserted above the user's line 1. Added back during error mapping. */
42
+ linePrefixOffset: number;
43
+ /** True when the snippet was compiled as a bare expression. */
44
+ expression: boolean;
45
+ }
46
+
47
+ /** Base class for every error next-live raises. */
48
+ declare class LiveError extends Error {
49
+ constructor(message: string, options?: {
50
+ cause?: unknown;
51
+ });
52
+ }
53
+ /** The snippet could not be parsed or transpiled. */
54
+ declare class LiveCompileError extends LiveError {
55
+ readonly line: number | undefined;
56
+ readonly column: number | undefined;
57
+ constructor(message: string, position?: {
58
+ line?: number;
59
+ column?: number;
60
+ }, cause?: unknown);
61
+ }
62
+
63
+ type ValidationIssueKind = 'syntax' | 'unresolved-import' | 'source-too-large' | 'forbidden-import';
64
+ interface ValidationIssue {
65
+ kind: ValidationIssueKind;
66
+ message: string;
67
+ /** The import specifier, for import-related issues. */
68
+ specifier?: string;
69
+ /** The closest registered specifier, when one is close enough to suggest. */
70
+ suggestion?: string;
71
+ line?: number;
72
+ column?: number;
73
+ }
74
+ interface ValidationResult {
75
+ ok: boolean;
76
+ issues: ValidationIssue[];
77
+ /** Every specifier the snippet imports, resolvable or not. */
78
+ imports: string[];
79
+ }
80
+ interface ValidateOptions extends TranspileOptions {
81
+ /**
82
+ * The registry a snippet will run against - either the registry object, or
83
+ * just its keys. Keys alone are usually easier to share with a CI script,
84
+ * since the real registry is full of bundler-specific dynamic imports.
85
+ */
86
+ modules?: ModuleRegistry | readonly string[];
87
+ /** Reject snippets larger than this many UTF-8 bytes. Checked before transpile. */
88
+ maxSourceBytes?: number;
89
+ /** Treat `node:*` imports as forbidden rather than unresolved. */
90
+ forbidNodeBuiltins?: boolean;
91
+ /** Treat remote URL imports as forbidden. */
92
+ forbidRemoteImports?: boolean;
93
+ /**
94
+ * Deny these specifiers even when registered. Prefix keys ending in `/`
95
+ * deny a whole subtree, matching registry prefix semantics.
96
+ */
97
+ denySpecifiers?: readonly string[];
98
+ }
99
+ /**
100
+ * Checks that a snippet compiles and that every module it imports is
101
+ * registered.
102
+ *
103
+ * ```ts
104
+ * const result = validateSnippet(app.source, { modules: Object.keys(liveModules) });
105
+ * if (!result.ok) {
106
+ * console.error(app.id, result.issues);
107
+ * process.exitCode = 1;
108
+ * }
109
+ * ```
110
+ */
111
+ declare function validateSnippet(source: string, options?: ValidateOptions): ValidationResult;
112
+ /**
113
+ * Validates many snippets at once, returning only the ones with problems.
114
+ *
115
+ * ```ts
116
+ * const broken = validateSnippets(apps.map((a) => ({ id: a.id, source: a.source })), {
117
+ * modules: Object.keys(liveModules),
118
+ * });
119
+ * ```
120
+ */
121
+ declare function validateSnippets<T extends {
122
+ id: string;
123
+ source: string;
124
+ }>(snippets: readonly T[], options?: ValidateOptions): Array<{
125
+ id: string;
126
+ result: ValidationResult;
127
+ }>;
128
+
129
+ interface PrecompileResult extends TransformResult {
130
+ /** Stable hash of source + options. Use it as a cache key or ETag. */
131
+ hash: string;
132
+ }
133
+ /**
134
+ * Transpiles a snippet to the same CommonJS the browser path produces.
135
+ *
136
+ * Only the `export default` / bare-statement forms are handled here; a bare
137
+ * expression snippet (`<div/>`) is wrapped exactly as the client wraps it, so
138
+ * the two paths stay interchangeable.
139
+ */
140
+ declare function precompile(source: string, options?: TranspileOptions): PrecompileResult;
141
+
142
+ export { LiveCompileError, type PrecompileResult, type TransformResult, type TranspileOptions, type ValidateOptions, type ValidationIssue, type ValidationIssueKind, type ValidationResult, precompile, validateSnippet, validateSnippets };
package/dist/server.js ADDED
@@ -0,0 +1,282 @@
1
+ import { transform } from 'sucrase';
2
+
3
+ // src/server.ts
4
+
5
+ // src/core/errors.ts
6
+ var LiveError = class extends Error {
7
+ constructor(message, options) {
8
+ super(message);
9
+ this.name = new.target.name;
10
+ if (options?.cause !== void 0) this.cause = options.cause;
11
+ Object.setPrototypeOf(this, new.target.prototype);
12
+ }
13
+ };
14
+ var LiveCompileError = class extends LiveError {
15
+ line;
16
+ column;
17
+ constructor(message, position, cause) {
18
+ super(message, { cause });
19
+ this.line = position?.line;
20
+ this.column = position?.column;
21
+ }
22
+ };
23
+ function nearestSpecifier(specifier, available) {
24
+ const lower = specifier.toLowerCase();
25
+ const caseMatch = available.find((key) => key.toLowerCase() === lower);
26
+ if (caseMatch) return caseMatch;
27
+ const threshold = Math.max(1, Math.min(3, Math.floor(specifier.length / 3)));
28
+ let best;
29
+ let bestDistance = threshold + 1;
30
+ for (const key of available) {
31
+ if (Math.abs(key.length - specifier.length) > threshold) continue;
32
+ const distance = editDistance(lower, key.toLowerCase(), bestDistance);
33
+ if (distance < bestDistance) {
34
+ bestDistance = distance;
35
+ best = key;
36
+ }
37
+ }
38
+ return bestDistance <= threshold ? best : void 0;
39
+ }
40
+ function editDistance(a, b, limit) {
41
+ if (a === b) return 0;
42
+ let previous = new Array(b.length + 1);
43
+ let current = new Array(b.length + 1);
44
+ for (let j = 0; j <= b.length; j++) previous[j] = j;
45
+ for (let i = 1; i <= a.length; i++) {
46
+ current[0] = i;
47
+ let rowMin = i;
48
+ for (let j = 1; j <= b.length; j++) {
49
+ const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
50
+ const value = Math.min(
51
+ current[j - 1] + 1,
52
+ previous[j] + 1,
53
+ previous[j - 1] + cost
54
+ );
55
+ current[j] = value;
56
+ if (value < rowMin) rowMin = value;
57
+ }
58
+ if (rowMin > limit) return limit + 1;
59
+ const swap = previous;
60
+ previous = current;
61
+ current = swap;
62
+ }
63
+ return previous[b.length];
64
+ }
65
+
66
+ // src/core/transpile.ts
67
+ var defaultTranspileOptions = {
68
+ filePath: "LiveCode.tsx",
69
+ production: true,
70
+ jsxRuntime: "automatic",
71
+ jsxImportSource: "react"
72
+ };
73
+ var MODULE_SYNTAX_RE = /^[ \t]*(?:export\b|import\s*[({'"*]|import\s+[A-Za-z_$])/m;
74
+ var RENDER_CALL_RE = /(^|[^.\w$])render\s*\(/m;
75
+ function isModuleSource(source) {
76
+ return MODULE_SYNTAX_RE.test(source) || RENDER_CALL_RE.test(source);
77
+ }
78
+ function isBlankSource(source) {
79
+ const withoutComments = source.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/^[ \t]*\/\/.*$/gm, " ");
80
+ return withoutComments.trim() === "";
81
+ }
82
+ function sucraseOptions(options) {
83
+ return {
84
+ transforms: ["jsx", "typescript", "imports"],
85
+ jsxRuntime: options.jsxRuntime,
86
+ jsxImportSource: options.jsxImportSource,
87
+ production: options.production,
88
+ filePath: options.filePath,
89
+ // Leaving native `import()` intact would resolve against the *page* URL
90
+ // and 404 on './utils'; routing it through our shim is the only sane
91
+ // behaviour inside an evaluated snippet.
92
+ preserveDynamicImport: false
93
+ };
94
+ }
95
+ function runTranspile(transformFn, source, options) {
96
+ const opts = sucraseOptions(options);
97
+ if (!isModuleSource(source) && !isBlankSource(source)) {
98
+ try {
99
+ return {
100
+ code: transformFn(`export default (
101
+ ${source}
102
+ )`, opts).code,
103
+ linePrefixOffset: 1,
104
+ expression: true
105
+ };
106
+ } catch {
107
+ }
108
+ }
109
+ return { code: transformFn(source, opts).code, linePrefixOffset: 0, expression: false };
110
+ }
111
+
112
+ // src/core/builtin-specifiers.ts
113
+ var BUILTIN_SPECIFIERS = [
114
+ "react",
115
+ "react/jsx-runtime",
116
+ "react/jsx-dev-runtime"
117
+ ];
118
+
119
+ // src/core/resolver.ts
120
+ var ASSET_RE = /\.(css|scss|sass|less|styl|svg|png|jpe?g|gif|webp|avif|woff2?)$/i;
121
+ function isIgnoredSpecifier(specifier) {
122
+ return ASSET_RE.test(specifier);
123
+ }
124
+ function matchRegistryKey(specifier, keys) {
125
+ if (keys.includes(specifier)) return specifier;
126
+ let best;
127
+ for (const key of keys) {
128
+ if (!key.endsWith("/") || !specifier.startsWith(key)) continue;
129
+ if (best === void 0 || key.length > best.length) best = key;
130
+ }
131
+ return best;
132
+ }
133
+ function scanRequires(code) {
134
+ const found = /* @__PURE__ */ new Set();
135
+ const re = /\brequire\s*\(\s*(['"])((?:(?!\1)[^\\]|\\.)*)\1\s*\)/g;
136
+ let match;
137
+ while ((match = re.exec(code)) !== null) {
138
+ const specifier = match[2];
139
+ if (specifier) found.add(specifier);
140
+ }
141
+ return found;
142
+ }
143
+
144
+ // src/validate.ts
145
+ function validateSnippet(source, options = {}) {
146
+ const {
147
+ modules,
148
+ maxSourceBytes,
149
+ forbidNodeBuiltins,
150
+ forbidRemoteImports,
151
+ denySpecifiers,
152
+ ...transpileOptions
153
+ } = options;
154
+ const resolved = { ...defaultTranspileOptions, ...transpileOptions };
155
+ const registryKeys = [
156
+ ...BUILTIN_SPECIFIERS,
157
+ ...Array.isArray(modules) ? modules : Object.keys(modules ?? {})
158
+ ];
159
+ if (maxSourceBytes !== void 0) {
160
+ const bytes = new TextEncoder().encode(source).length;
161
+ if (bytes > maxSourceBytes) {
162
+ return {
163
+ ok: false,
164
+ issues: [
165
+ {
166
+ kind: "source-too-large",
167
+ message: `Snippet is ${bytes} bytes, exceeding the limit of ${maxSourceBytes}.`
168
+ }
169
+ ],
170
+ imports: []
171
+ };
172
+ }
173
+ }
174
+ let code;
175
+ try {
176
+ code = runTranspile(transform, source, resolved).code;
177
+ } catch (cause) {
178
+ return { ok: false, issues: [syntaxIssue(cause)], imports: [] };
179
+ }
180
+ const imports = [...scanRequires(code)].sort();
181
+ const issues = [];
182
+ for (const specifier of imports) {
183
+ const policyIssue = policyViolation(specifier, {
184
+ forbidNodeBuiltins,
185
+ forbidRemoteImports,
186
+ denySpecifiers
187
+ });
188
+ if (policyIssue) {
189
+ issues.push(policyIssue);
190
+ continue;
191
+ }
192
+ if (isIgnoredSpecifier(specifier)) continue;
193
+ if (matchRegistryKey(specifier, registryKeys)) continue;
194
+ const suggestion = nearestSpecifier(specifier, registryKeys);
195
+ issues.push({
196
+ kind: "unresolved-import",
197
+ specifier,
198
+ message: `Module '${specifier}' is not registered.` + (suggestion ? ` Did you mean '${suggestion}'?` : ""),
199
+ ...suggestion ? { suggestion } : {}
200
+ });
201
+ }
202
+ return { ok: issues.length === 0, issues, imports };
203
+ }
204
+ function policyViolation(specifier, options) {
205
+ if (options.forbidNodeBuiltins && specifier.startsWith("node:")) {
206
+ return {
207
+ kind: "forbidden-import",
208
+ specifier,
209
+ message: `Import '${specifier}' is forbidden: Node built-ins are not available to snippets.`
210
+ };
211
+ }
212
+ if (options.forbidRemoteImports && (/^https?:\/\//.test(specifier) || specifier.startsWith("//"))) {
213
+ return {
214
+ kind: "forbidden-import",
215
+ specifier,
216
+ message: `Import '${specifier}' is forbidden: remote modules are not available to snippets.`
217
+ };
218
+ }
219
+ if (options.denySpecifiers?.length) {
220
+ const denied = matchRegistryKey(specifier, options.denySpecifiers);
221
+ if (denied !== void 0) {
222
+ return {
223
+ kind: "forbidden-import",
224
+ specifier,
225
+ message: `Import '${specifier}' is forbidden by policy (matched deny rule '${denied}').`
226
+ };
227
+ }
228
+ }
229
+ return null;
230
+ }
231
+ function validateSnippets(snippets, options = {}) {
232
+ const failures = [];
233
+ for (const snippet of snippets) {
234
+ const result = validateSnippet(snippet.source, {
235
+ filePath: `${snippet.id}.tsx`,
236
+ ...options
237
+ });
238
+ if (!result.ok) failures.push({ id: snippet.id, result });
239
+ }
240
+ return failures;
241
+ }
242
+ function syntaxIssue(cause) {
243
+ const message = cause instanceof Error ? cause.message : String(cause);
244
+ const match = /\((\d+):(\d+)\)\s*$/.exec(message);
245
+ if (!match) return { kind: "syntax", message };
246
+ return {
247
+ kind: "syntax",
248
+ message: message.slice(0, match.index).trim(),
249
+ line: Number(match[1]),
250
+ column: Number(match[2])
251
+ };
252
+ }
253
+
254
+ // src/server.ts
255
+ function precompile(source, options = {}) {
256
+ const resolved = { ...defaultTranspileOptions, ...options };
257
+ try {
258
+ const result = runTranspile(transform, source, resolved);
259
+ return { ...result, hash: hashOf(source, resolved) };
260
+ } catch (cause) {
261
+ const message = cause instanceof Error ? cause.message : String(cause);
262
+ const match = /\((\d+):(\d+)\)\s*$/.exec(message);
263
+ throw match ? new LiveCompileError(
264
+ message.slice(0, match.index).trim(),
265
+ { line: Number(match[1]), column: Number(match[2]) },
266
+ cause
267
+ ) : new LiveCompileError(message, void 0, cause);
268
+ }
269
+ }
270
+ function hashOf(source, options) {
271
+ const input = `${options.jsxRuntime}|${options.jsxImportSource}|${options.production}|${source}`;
272
+ let hash = 2166136261;
273
+ for (let i = 0; i < input.length; i++) {
274
+ hash ^= input.charCodeAt(i);
275
+ hash = Math.imul(hash, 16777619);
276
+ }
277
+ return (hash >>> 0).toString(36);
278
+ }
279
+
280
+ export { LiveCompileError, precompile, validateSnippet, validateSnippets };
281
+ //# sourceMappingURL=server.js.map
282
+ //# sourceMappingURL=server.js.map