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":";;;;;AACO,IAAM,SAAA,GAAN,cAAwB,KAAA,CAAM;AAAA,EACnC,WAAA,CAAY,SAAiB,OAAA,EAA+B;AAC1D,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,OAAO,GAAA,CAAA,MAAA,CAAW,IAAA;AACvB,IAAA,IAAI,OAAA,EAAS,KAAA,KAAU,MAAA,EAAW,IAAA,CAAK,QAAQ,OAAA,CAAQ,KAAA;AAEvD,IAAA,MAAA,CAAO,cAAA,CAAe,IAAA,EAAM,GAAA,CAAA,MAAA,CAAW,SAAS,CAAA;AAAA,EAClD;AACF,CAAA;AAGO,IAAM,gBAAA,GAAN,cAA+B,SAAA,CAAU;AAAA,EACrC,IAAA;AAAA,EACA,MAAA;AAAA,EAET,WAAA,CAAY,OAAA,EAAiB,QAAA,EAA+C,KAAA,EAAiB;AAC3F,IAAA,KAAA,CAAM,OAAA,EAAS,EAAE,KAAA,EAAO,CAAA;AACxB,IAAA,IAAA,CAAK,OAAO,QAAA,EAAU,IAAA;AACtB,IAAA,IAAA,CAAK,SAAS,QAAA,EAAU,MAAA;AAAA,EAC1B;AACF;AAqEO,SAAS,gBAAA,CACd,WACA,SAAA,EACoB;AACpB,EAAA,MAAM,KAAA,GAAQ,UAAU,WAAA,EAAY;AAGpC,EAAA,MAAM,SAAA,GAAY,UAAU,IAAA,CAAK,CAAC,QAAQ,GAAA,CAAI,WAAA,OAAkB,KAAK,CAAA;AACrE,EAAA,IAAI,WAAW,OAAO,SAAA;AAEtB,EAAA,MAAM,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,SAAA,CAAU,MAAA,GAAS,CAAC,CAAC,CAAC,CAAA;AAC3E,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI,eAAe,SAAA,GAAY,CAAA;AAE/B,EAAA,KAAA,MAAW,OAAO,SAAA,EAAW;AAE3B,IAAA,IAAI,KAAK,GAAA,CAAI,GAAA,CAAI,SAAS,SAAA,CAAU,MAAM,IAAI,SAAA,EAAW;AACzD,IAAA,MAAM,WAAW,YAAA,CAAa,KAAA,EAAO,GAAA,CAAI,WAAA,IAAe,YAAY,CAAA;AACpE,IAAA,IAAI,WAAW,YAAA,EAAc;AAC3B,MAAA,YAAA,GAAe,QAAA;AACf,MAAA,IAAA,GAAO,GAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,OAAO,YAAA,IAAgB,YAAY,IAAA,GAAO,MAAA;AAC5C;AAMA,SAAS,YAAA,CAAa,CAAA,EAAW,CAAA,EAAW,KAAA,EAAuB;AACjE,EAAA,IAAI,CAAA,KAAM,GAAG,OAAO,CAAA;AACpB,EAAA,IAAI,QAAA,GAAW,IAAI,KAAA,CAAc,CAAA,CAAE,SAAS,CAAC,CAAA;AAC7C,EAAA,IAAI,OAAA,GAAU,IAAI,KAAA,CAAc,CAAA,CAAE,SAAS,CAAC,CAAA;AAE5C,EAAA,KAAA,IAAS,CAAA,GAAI,GAAG,CAAA,IAAK,CAAA,CAAE,QAAQ,CAAA,EAAA,EAAK,QAAA,CAAS,CAAC,CAAA,GAAI,CAAA;AAElD,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,IAAK,CAAA,CAAE,QAAQ,CAAA,EAAA,EAAK;AAClC,IAAA,OAAA,CAAQ,CAAC,CAAA,GAAI,CAAA;AACb,IAAA,IAAI,MAAA,GAAS,CAAA;AACb,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,IAAK,CAAA,CAAE,QAAQ,CAAA,EAAA,EAAK;AAClC,MAAA,MAAM,IAAA,GAAO,CAAA,CAAE,UAAA,CAAW,CAAA,GAAI,CAAC,CAAA,KAAM,CAAA,CAAE,UAAA,CAAW,CAAA,GAAI,CAAC,CAAA,GAAI,CAAA,GAAI,CAAA;AAC/D,MAAA,MAAM,QAAQ,IAAA,CAAK,GAAA;AAAA,QAChB,OAAA,CAAQ,CAAA,GAAI,CAAC,CAAA,GAAe,CAAA;AAAA,QAC5B,QAAA,CAAS,CAAC,CAAA,GAAe,CAAA;AAAA,QACzB,QAAA,CAAS,CAAA,GAAI,CAAC,CAAA,GAAe;AAAA,OAChC;AACA,MAAA,OAAA,CAAQ,CAAC,CAAA,GAAI,KAAA;AACb,MAAA,IAAI,KAAA,GAAQ,QAAQ,MAAA,GAAS,KAAA;AAAA,IAC/B;AACA,IAAA,IAAI,MAAA,GAAS,KAAA,EAAO,OAAO,KAAA,GAAQ,CAAA;AACnC,IAAA,MAAM,IAAA,GAAO,QAAA;AACb,IAAA,QAAA,GAAW,OAAA;AACX,IAAA,OAAA,GAAU,IAAA;AAAA,EACZ;AAEA,EAAA,OAAO,QAAA,CAAS,EAAE,MAAM,CAAA;AAC1B;;;AC7GO,IAAM,uBAAA,GAAsD;AAAA,EACjE,QAAA,EAAU,cAAA;AAAA,EACV,UAAA,EAAY,IAAA;AAAA,EACZ,UAAA,EAAY,WAAA;AAAA,EACZ,eAAA,EAAiB;AACnB,CAAA;AAGA,IAAM,gBAAA,GAAmB,2DAAA;AAGzB,IAAM,cAAA,GAAiB,yBAAA;AAShB,SAAS,eAAe,MAAA,EAAyB;AACtD,EAAA,OAAO,iBAAiB,IAAA,CAAK,MAAM,CAAA,IAAK,cAAA,CAAe,KAAK,MAAM,CAAA;AACpE;AAmBO,SAAS,cAAc,MAAA,EAAyB;AACrD,EAAA,MAAM,eAAA,GAAkB,OACrB,OAAA,CAAQ,mBAAA,EAAqB,GAAG,CAAA,CAChC,OAAA,CAAQ,oBAAoB,GAAG,CAAA;AAClC,EAAA,OAAO,eAAA,CAAgB,MAAK,KAAM,EAAA;AACpC;AAGO,SAAS,eAAe,OAAA,EAAqC;AAClE,EAAA,OAAO;AAAA,IACL,UAAA,EAAY,CAAC,KAAA,EAAO,YAAA,EAAc,SAAS,CAAA;AAAA,IAC3C,YAAY,OAAA,CAAQ,UAAA;AAAA,IACpB,iBAAiB,OAAA,CAAQ,eAAA;AAAA,IACzB,YAAY,OAAA,CAAQ,UAAA;AAAA,IACpB,UAAU,OAAA,CAAQ,QAAA;AAAA;AAAA;AAAA;AAAA,IAIlB,qBAAA,EAAuB;AAAA,GACzB;AACF;AAMO,SAAS,YAAA,CACd,WAAA,EACA,MAAA,EACA,OAAA,EACiB;AACjB,EAAA,MAAM,IAAA,GAAO,eAAe,OAAO,CAAA;AAEnC,EAAA,IAAI,CAAC,cAAA,CAAe,MAAM,KAAK,CAAC,aAAA,CAAc,MAAM,CAAA,EAAG;AAIrD,IAAA,IAAI;AACF,MAAA,OAAO;AAAA,QACL,MAAM,WAAA,CAAY,CAAA;AAAA,EAAqB,MAAM;AAAA,CAAA,CAAA,EAAO,IAAI,CAAA,CAAE,IAAA;AAAA,QAC1D,gBAAA,EAAkB,CAAA;AAAA,QAClB,UAAA,EAAY;AAAA,OACd;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,IAAA,EAAM,WAAA,CAAY,MAAA,EAAQ,IAAI,EAAE,IAAA,EAAM,gBAAA,EAAkB,CAAA,EAAG,UAAA,EAAY,KAAA,EAAM;AACxF;;;AC1HO,IAAM,kBAAA,GAAqB;AAAA,EAChC,OAAA;AAAA,EACA,mBAAA;AAAA,EACA;AACF,CAAA;;;ACDA,IAAM,QAAA,GAAW,kEAAA;AAqMV,SAAS,mBAAmB,SAAA,EAA4B;AAC7D,EAAA,OAAO,QAAA,CAAS,KAAK,SAAS,CAAA;AAChC;AAQO,SAAS,gBAAA,CACd,WACA,IAAA,EACoB;AACpB,EAAA,IAAI,IAAA,CAAK,QAAA,CAAS,SAAS,CAAA,EAAG,OAAO,SAAA;AAErC,EAAA,IAAI,IAAA;AACJ,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,IAAI,CAAC,IAAI,QAAA,CAAS,GAAG,KAAK,CAAC,SAAA,CAAU,UAAA,CAAW,GAAG,CAAA,EAAG;AACtD,IAAA,IAAI,SAAS,MAAA,IAAa,GAAA,CAAI,MAAA,GAAS,IAAA,CAAK,QAAQ,IAAA,GAAO,GAAA;AAAA,EAC7D;AACA,EAAA,OAAO,IAAA;AACT;AAuFO,SAAS,aAAa,IAAA,EAA2B;AACtD,EAAA,MAAM,KAAA,uBAAY,GAAA,EAAY;AAC9B,EAAA,MAAM,EAAA,GAAK,uDAAA;AACX,EAAA,IAAI,KAAA;AACJ,EAAA,OAAA,CAAQ,KAAA,GAAQ,EAAA,CAAG,IAAA,CAAK,IAAI,OAAO,IAAA,EAAM;AACvC,IAAA,MAAM,SAAA,GAAY,MAAM,CAAC,CAAA;AACzB,IAAA,IAAI,SAAA,EAAW,KAAA,CAAM,GAAA,CAAI,SAAS,CAAA;AAAA,EACpC;AACA,EAAA,OAAO,KAAA;AACT;;;ACnPO,SAAS,eAAA,CACd,MAAA,EACA,OAAA,GAA2B,EAAC,EACV;AAClB,EAAA,MAAM;AAAA,IACJ,OAAA;AAAA,IACA,cAAA;AAAA,IACA,kBAAA;AAAA,IACA,mBAAA;AAAA,IACA,cAAA;AAAA,IACA,GAAG;AAAA,GACL,GAAI,OAAA;AACJ,EAAA,MAAM,QAAA,GAAW,EAAE,GAAG,uBAAA,EAAyB,GAAG,gBAAA,EAAiB;AAEnE,EAAA,MAAM,YAAA,GAAe;AAAA,IACnB,GAAG,kBAAA;AAAA,IACH,GAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,GAAK,UAAuB,MAAA,CAAO,IAAA,CAAK,OAAA,IAAW,EAAE;AAAA,GAChF;AAEA,EAAA,IAAI,mBAAmB,MAAA,EAAW;AAChC,IAAA,MAAM,QAAQ,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,MAAM,CAAA,CAAE,MAAA;AAC/C,IAAA,IAAI,QAAQ,cAAA,EAAgB;AAC1B,MAAA,OAAO;AAAA,QACL,EAAA,EAAI,KAAA;AAAA,QACJ,MAAA,EAAQ;AAAA,UACN;AAAA,YACE,IAAA,EAAM,kBAAA;AAAA,YACN,OAAA,EAAS,CAAA,WAAA,EAAc,KAAK,CAAA,+BAAA,EAAkC,cAAc,CAAA,CAAA;AAAA;AAC9E,SACF;AAAA,QACA,SAAS;AAAC,OACZ;AAAA,IACF;AAAA,EACF;AAEA,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI;AACF,IAAA,IAAA,GAAO,YAAA,CAAa,SAAA,EAAW,MAAA,EAAQ,QAAQ,CAAA,CAAE,IAAA;AAAA,EACnD,SAAS,KAAA,EAAO;AACd,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,CAAC,WAAA,CAAY,KAAK,CAAC,CAAA,EAAG,OAAA,EAAS,EAAC,EAAE;AAAA,EAChE;AAEA,EAAA,MAAM,UAAU,CAAC,GAAG,aAAa,IAAI,CAAC,EAAE,IAAA,EAAK;AAC7C,EAAA,MAAM,SAA4B,EAAC;AAEnC,EAAA,KAAA,MAAW,aAAa,OAAA,EAAS;AAC/B,IAAA,MAAM,WAAA,GAAc,gBAAgB,SAAA,EAAW;AAAA,MAC7C,kBAAA;AAAA,MACA,mBAAA;AAAA,MACA;AAAA,KACD,CAAA;AACD,IAAA,IAAI,WAAA,EAAa;AACf,MAAA,MAAA,CAAO,KAAK,WAAW,CAAA;AACvB,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,kBAAA,CAAmB,SAAS,CAAA,EAAG;AACnC,IAAA,IAAI,gBAAA,CAAiB,SAAA,EAAW,YAAY,CAAA,EAAG;AAE/C,IAAA,MAAM,UAAA,GAAa,gBAAA,CAAiB,SAAA,EAAW,YAAY,CAAA;AAC3D,IAAA,MAAA,CAAO,IAAA,CAAK;AAAA,MACV,IAAA,EAAM,mBAAA;AAAA,MACN,SAAA;AAAA,MACA,SACE,CAAA,QAAA,EAAW,SAAS,0BACnB,UAAA,GAAa,CAAA,eAAA,EAAkB,UAAU,CAAA,EAAA,CAAA,GAAO,EAAA,CAAA;AAAA,MACnD,GAAI,UAAA,GAAa,EAAE,UAAA,KAAe;AAAC,KACpC,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,EAAE,EAAA,EAAI,MAAA,CAAO,MAAA,KAAW,CAAA,EAAG,QAAQ,OAAA,EAAQ;AACpD;AAEA,SAAS,eAAA,CACP,WACA,OAAA,EAKwB;AACxB,EAAA,IAAI,OAAA,CAAQ,kBAAA,IAAsB,SAAA,CAAU,UAAA,CAAW,OAAO,CAAA,EAAG;AAC/D,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,kBAAA;AAAA,MACN,SAAA;AAAA,MACA,OAAA,EAAS,WAAW,SAAS,CAAA,6DAAA;AAAA,KAC/B;AAAA,EACF;AAEA,EAAA,IACE,OAAA,CAAQ,wBACP,cAAA,CAAe,IAAA,CAAK,SAAS,CAAA,IAAK,SAAA,CAAU,UAAA,CAAW,IAAI,CAAA,CAAA,EAC5D;AACA,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,kBAAA;AAAA,MACN,SAAA;AAAA,MACA,OAAA,EAAS,WAAW,SAAS,CAAA,6DAAA;AAAA,KAC/B;AAAA,EACF;AAEA,EAAA,IAAI,OAAA,CAAQ,gBAAgB,MAAA,EAAQ;AAClC,IAAA,MAAM,MAAA,GAAS,gBAAA,CAAiB,SAAA,EAAW,OAAA,CAAQ,cAAc,CAAA;AACjE,IAAA,IAAI,WAAW,MAAA,EAAW;AACxB,MAAA,OAAO;AAAA,QACL,IAAA,EAAM,kBAAA;AAAA,QACN,SAAA;AAAA,QACA,OAAA,EAAS,CAAA,QAAA,EAAW,SAAS,CAAA,6CAAA,EAAgD,MAAM,CAAA,GAAA;AAAA,OACrF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT;AAWO,SAAS,gBAAA,CACd,QAAA,EACA,OAAA,GAA2B,EAAC,EACqB;AACjD,EAAA,MAAM,WAA4D,EAAC;AACnE,EAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC9B,IAAA,MAAM,MAAA,GAAS,eAAA,CAAgB,OAAA,CAAQ,MAAA,EAAQ;AAAA,MAC7C,QAAA,EAAU,CAAA,EAAG,OAAA,CAAQ,EAAE,CAAA,IAAA,CAAA;AAAA,MACvB,GAAG;AAAA,KACJ,CAAA;AACD,IAAA,IAAI,CAAC,MAAA,CAAO,EAAA,EAAI,QAAA,CAAS,IAAA,CAAK,EAAE,EAAA,EAAI,OAAA,CAAQ,EAAA,EAAI,MAAA,EAAQ,CAAA;AAAA,EAC1D;AACA,EAAA,OAAO,QAAA;AACT;AAEA,SAAS,YAAY,KAAA,EAAiC;AACpD,EAAA,MAAM,UAAU,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AACrE,EAAA,MAAM,KAAA,GAAQ,qBAAA,CAAsB,IAAA,CAAK,OAAO,CAAA;AAChD,EAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAE,IAAA,EAAM,UAAU,OAAA,EAAQ;AAE7C,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,QAAA;AAAA,IACN,SAAS,OAAA,CAAQ,KAAA,CAAM,GAAG,KAAA,CAAM,KAAK,EAAE,IAAA,EAAK;AAAA,IAC5C,IAAA,EAAM,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,IACrB,MAAA,EAAQ,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC;AAAA,GACzB;AACF;;;AC5MO,SAAS,UAAA,CAAW,MAAA,EAAgB,OAAA,GAA4B,EAAC,EAAqB;AAC3F,EAAA,MAAM,QAAA,GAAW,EAAE,GAAG,uBAAA,EAAyB,GAAG,OAAA,EAAQ;AAE1D,EAAA,IAAI;AAGF,IAAA,MAAM,MAAA,GAAS,YAAA,CAAaA,SAAAA,EAAW,MAAA,EAAQ,QAAQ,CAAA;AACvD,IAAA,OAAO,EAAE,GAAG,MAAA,EAAQ,MAAM,MAAA,CAAO,MAAA,EAAQ,QAAQ,CAAA,EAAE;AAAA,EACrD,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,UAAU,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AACrE,IAAA,MAAM,KAAA,GAAQ,qBAAA,CAAsB,IAAA,CAAK,OAAO,CAAA;AAChD,IAAA,MAAM,QACF,IAAI,gBAAA;AAAA,MACF,QAAQ,KAAA,CAAM,CAAA,EAAG,KAAA,CAAM,KAAK,EAAE,IAAA,EAAK;AAAA,MACnC,EAAE,IAAA,EAAM,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA,EAAG,MAAA,EAAQ,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA,EAAE;AAAA,MACnD;AAAA,KACF,GACA,IAAI,gBAAA,CAAiB,OAAA,EAAS,QAAW,KAAK,CAAA;AAAA,EACpD;AACF;AAOA,SAAS,MAAA,CAAO,QAAgB,OAAA,EAA6C;AAC3E,EAAA,MAAM,KAAA,GAAQ,CAAA,EAAG,OAAA,CAAQ,UAAU,CAAA,CAAA,EAAI,OAAA,CAAQ,eAAe,CAAA,CAAA,EAAI,OAAA,CAAQ,UAAU,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA;AAC9F,EAAA,IAAI,IAAA,GAAO,UAAA;AACX,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,IAAA,IAAQ,KAAA,CAAM,WAAW,CAAC,CAAA;AAC1B,IAAA,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,IAAA,EAAM,QAAU,CAAA;AAAA,EACnC;AACA,EAAA,OAAA,CAAQ,IAAA,KAAS,CAAA,EAAG,QAAA,CAAS,EAAE,CAAA;AACjC","file":"server.js","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"]}
package/dist/shared.js ADDED
@@ -0,0 +1,30 @@
1
+ "use client";
2
+ import { createContext } from 'react';
3
+
4
+ // src/context/LiveContext.tsx
5
+ var CONTEXT_KEY = /* @__PURE__ */ Symbol.for("next-live.LiveContext");
6
+ function createLiveContext() {
7
+ const store = globalThis;
8
+ const existing = store[CONTEXT_KEY];
9
+ if (existing) return existing;
10
+ const context = createContext(null);
11
+ context.displayName = "LiveContext";
12
+ store[CONTEXT_KEY] = context;
13
+ return context;
14
+ }
15
+ var LiveContext = createLiveContext();
16
+
17
+ // src/core/positions.ts
18
+ function errorPosition(error) {
19
+ if (error === null || error === void 0) return null;
20
+ const positioned = error;
21
+ if (positioned.line === void 0 || positioned.line < 1) return null;
22
+ return {
23
+ line: positioned.line,
24
+ ...positioned.column !== void 0 ? { column: positioned.column } : {}
25
+ };
26
+ }
27
+
28
+ export { LiveContext, errorPosition };
29
+ //# sourceMappingURL=shared.js.map
30
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/context/LiveContext.tsx","../src/core/positions.ts"],"names":[],"mappings":";;;AAsBA,IAAM,WAAA,mBAAc,MAAA,CAAO,GAAA,CAAI,uBAAuB,CAAA;AAMtD,SAAS,iBAAA,GAAsD;AAC7D,EAAA,MAAM,KAAA,GAAQ,UAAA;AACd,EAAA,MAAM,QAAA,GAAW,MAAM,WAAW,CAAA;AAClC,EAAA,IAAI,UAAU,OAAO,QAAA;AAMrB,EAAA,MAAM,OAAA,GAAU,cAAuC,IAAI,CAAA;AAC3D,EAAA,OAAA,CAAQ,WAAA,GAAc,aAAA;AACtB,EAAA,KAAA,CAAM,WAAW,CAAA,GAAI,OAAA;AACrB,EAAA,OAAO,OAAA;AACT;AAEO,IAAM,cAAc,iBAAA;;;AC7BpB,SAAS,cAAc,KAAA,EAA2E;AACvG,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,EAAW,OAAO,IAAA;AAClD,EAAA,MAAM,UAAA,GAAa,KAAA;AACnB,EAAA,IAAI,WAAW,IAAA,KAAS,MAAA,IAAa,UAAA,CAAW,IAAA,GAAO,GAAG,OAAO,IAAA;AACjE,EAAA,OAAO;AAAA,IACL,MAAM,UAAA,CAAW,IAAA;AAAA,IACjB,GAAI,WAAW,MAAA,KAAW,MAAA,GAAY,EAAE,MAAA,EAAQ,UAAA,CAAW,MAAA,EAAO,GAAI;AAAC,GACzE;AACF","file":"shared.js","sourcesContent":["'use client';\n\nimport { createContext } from 'react';\nimport type { Context } from 'react';\nimport type { LiveContextValue } from '../core/types';\n\n/**\n * One context, however many copies of this module exist.\n *\n * The context is cached on a global symbol rather than being a plain\n * module-level constant, because a plain one is only unique per module\n * instance, and there are realistic ways to end up with more than one:\n *\n * - The CommonJS build cannot code-split, so `index.cjs` and `editor.cjs` each\n * inline their own copy. Without this, `<LiveEditor>` from `next-live/editor`\n * would read a different context than `<LiveProvider>` provides and throw\n * \"useLiveContext must be called inside a <LiveProvider>\".\n * - npm can install two versions of the package side by side.\n *\n * Keying on a symbol means every copy resolves to whichever created it first,\n * so provider and consumer always meet.\n */\nconst CONTEXT_KEY = Symbol.for('next-live.LiveContext');\n\ntype GlobalWithContext = typeof globalThis & {\n [CONTEXT_KEY]?: Context<LiveContextValue | null>;\n};\n\nfunction createLiveContext(): Context<LiveContextValue | null> {\n const store = globalThis as GlobalWithContext;\n const existing = store[CONTEXT_KEY];\n if (existing) return existing;\n\n /**\n * Null rather than a default value, so `useLiveContext` can tell \"outside a\n * provider\" apart from \"inside a provider that has not compiled yet\".\n */\n const context = createContext<LiveContextValue | null>(null);\n context.displayName = 'LiveContext';\n store[CONTEXT_KEY] = context;\n return context;\n}\n\nexport const LiveContext = createLiveContext();\n","export interface PositionedError extends Error {\n line?: number;\n column?: number;\n}\n\n/**\n * Reads a 1-based line/column from a compile or runtime error, if present.\n *\n * Accepts a nullish value and returns null for it. The state this is meant to\n * be fed from - `LiveRunnerState.error` and `LiveContextValue.error` - is\n * `Error | null`, so `errorPosition(error)` is the natural thing to write in a\n * custom editor. TypeScript rejects that, but a JavaScript host would have hit\n * a TypeError instead of the \"no position\" answer the name promises.\n */\nexport function errorPosition(error: Error | null | undefined): { line: number; column?: number } | null {\n if (error === null || error === undefined) return null;\n const positioned = error as PositionedError;\n if (positioned.line === undefined || positioned.line < 1) return null;\n return {\n line: positioned.line,\n ...(positioned.column !== undefined ? { column: positioned.column } : {}),\n };\n}\n"]}
@@ -0,0 +1,244 @@
1
+ # Getting started
2
+
3
+ [Docs index](./README.md) · [Module registry →](./02-module-registry.md)
4
+
5
+ By the end of this page you will have a page that takes a string of TSX and
6
+ renders it as a live React component.
7
+
8
+ **Requirements:** React 19+ and Node 20.9+.
9
+
10
+ Next.js is **not** required. The library imports only `react`,
11
+ `react/jsx-runtime`, `react/jsx-dev-runtime`, `prism-react-renderer` and
12
+ `sucrase`, and Next is not even a peer dependency. This guide is written
13
+ against the Next 16 App Router because that is what it is tuned for (SSR
14
+ safety, the CSP notes), but everything works in Vite, Remix, or anywhere React
15
+ runs.
16
+
17
+ ## Step 1: Install
18
+
19
+ ```bash
20
+ npm install next-live
21
+ ```
22
+
23
+ This guide uses the built-in `<LiveEditor>`, which needs one **optional peer
24
+ dependency**. npm does not install it for you, so add it too:
25
+
26
+ ```bash
27
+ npm install prism-react-renderer
28
+ ```
29
+
30
+ If you only ever *run* stored snippets and never edit them, skip it - a
31
+ preview-only page never imports `next-live/editor`, and that is exactly why the
32
+ highlighter lives on a separate entry.
33
+
34
+ ## Step 2: Create the runner
35
+
36
+ `next-live` compiles in the browser, so the component that uses it must be a
37
+ Client Component. Create `app/apps/Runner.tsx`:
38
+
39
+ ```tsx
40
+ 'use client';
41
+
42
+ import { LiveProvider, LivePreview, LiveError } from 'next-live';
43
+ import { LiveEditor } from 'next-live/editor';
44
+
45
+ export function Runner({ source }: { source: string }) {
46
+ return (
47
+ <LiveProvider code={source}>
48
+ <LiveEditor />
49
+ <LivePreview />
50
+ <LiveError />
51
+ </LiveProvider>
52
+ );
53
+ }
54
+ ```
55
+
56
+ That is already a working playground - `react` is registered for you, so
57
+ snippets can use hooks immediately.
58
+
59
+ ## Step 3: Render it from a page
60
+
61
+ `app/apps/page.tsx`, a normal Server Component:
62
+
63
+ ```tsx
64
+ import { Runner } from './Runner';
65
+
66
+ const EXAMPLE = `import { useState } from 'react';
67
+
68
+ export default function App() {
69
+ const [n, setN] = useState(0);
70
+ return <button onClick={() => setN(n + 1)}>clicked {n} times</button>;
71
+ }
72
+ `;
73
+
74
+ export default function AppsPage() {
75
+ return <Runner source={EXAMPLE} />;
76
+ }
77
+ ```
78
+
79
+ Load the page and the button works. Edit the code in the editor and the preview
80
+ updates as you type.
81
+
82
+ > You do **not** need `next/dynamic(..., { ssr: false })`. Reaching for it is the
83
+ > reflex, but `next-live` is already SSR-safe - see
84
+ > [SSR and hydration](#ssr-and-hydration) below.
85
+
86
+ ## Step 4: Give snippets access to your app
87
+
88
+ By default a snippet can only import `react`. To let it import your own code,
89
+ pass a `modules` registry:
90
+
91
+ ```tsx
92
+ 'use client';
93
+
94
+ import { LiveProvider, LivePreview, LiveError, defineLoader } from 'next-live';
95
+ import { LiveEditor } from 'next-live/editor';
96
+
97
+ export function Runner({ source, user }: { source: string; user: User }) {
98
+ return (
99
+ <LiveProvider
100
+ code={source}
101
+ modules={{
102
+ '@app/store': defineLoader(() => import('@/lib/store')),
103
+ '@app/ui': defineLoader(() => import('@/components/ui')),
104
+ }}
105
+ props={{ user }}
106
+ >
107
+ <LiveEditor />
108
+ <LivePreview />
109
+ <LiveError />
110
+ </LiveProvider>
111
+ );
112
+ }
113
+ ```
114
+
115
+ Snippet authors then write ordinary code:
116
+
117
+ ```tsx
118
+ import { useCart } from '@app/store';
119
+ import { Button } from '@app/ui';
120
+
121
+ export default function App({ user }) {
122
+ const cart = useCart();
123
+ return <Button>{user.name}: {cart.length} items</Button>;
124
+ }
125
+ ```
126
+
127
+ Read [Module registry](./02-module-registry.md) next - it is the core concept.
128
+
129
+ ## Step 5: Before you deploy
130
+
131
+ `next-live` evaluates code with `new Function`, which requires `'unsafe-eval'`
132
+ in your Content Security Policy. It should be scoped to the routes that run
133
+ snippets, not your whole app.
134
+
135
+ **Do not skip this** - read [Security](./05-security.md) before going to
136
+ production. It takes about ten minutes and covers the CSP plus the one rule that
137
+ actually protects you.
138
+
139
+ ## What each piece does
140
+
141
+ | Component | Purpose |
142
+ |---|---|
143
+ | `<LiveProvider>` | Compiles `code` and provides the result. Everything else must be inside it. |
144
+ | `<LiveEditor>` | A textarea with syntax highlighting. Optional, omit it for a read-only runner. |
145
+ | `<LivePreview>` | Renders the compiled component, wrapped in an error boundary. |
146
+ | `<LiveError>` | Shows the current compile or runtime error; renders nothing when healthy. |
147
+
148
+ You can also skip the components entirely and drive the engine yourself with
149
+ [`useLiveRunner`](./06-api-reference.md#useliverunner).
150
+
151
+ ## What a snippet may look like
152
+
153
+ All of these work:
154
+
155
+ ```tsx
156
+ export default function App() { return <div/> } // a module (recommended)
157
+ function App() { return <div/> } // bare declaration
158
+ <div>hello</div> // bare expression
159
+ () => <div/> // bare expression
160
+ render(<App prop="x"/>) // explicit render call
161
+ ```
162
+
163
+ `export default` is the supported, unambiguous form. The others are recovered
164
+ heuristically for legacy snippets - prefer `export default` in
165
+ anything you store.
166
+
167
+ TypeScript works: types, interfaces, and generics are all stripped. Note they
168
+ are **not checked**, see [Troubleshooting](./07-troubleshooting.md#my-typescript-errors-are-not-reported).
169
+
170
+ ## SSR and hydration
171
+
172
+ Nothing is compiled during the server pass. `<LivePreview>` renders its
173
+ `fallback` on the server *and* on the client's first render, so the two are
174
+ identical and hydration cannot mismatch. Compilation starts afterwards, in an
175
+ effect.
176
+
177
+ Give it a `fallback` sized like your content to avoid layout shift:
178
+
179
+ ```tsx
180
+ <LivePreview fallback={<div style={{ height: 320 }} />} />
181
+ ```
182
+
183
+ ## Loading snippets from an API
184
+
185
+ The real use case is code stored elsewhere. `code` is controlled - change it and
186
+ the preview follows:
187
+
188
+ ```tsx
189
+ 'use client';
190
+
191
+ import { useEffect, useState } from 'react';
192
+ import { LiveProvider, LivePreview, LiveError } from 'next-live';
193
+
194
+ export function RemoteApp({ id }: { id: string }) {
195
+ const [source, setSource] = useState<string | null>(null);
196
+
197
+ useEffect(() => {
198
+ const controller = new AbortController();
199
+ fetch(`/api/apps/${id}`, { signal: controller.signal })
200
+ .then((r) => r.json())
201
+ .then((data: { source: string }) => setSource(data.source))
202
+ .catch(() => {});
203
+ return () => controller.abort();
204
+ }, [id]);
205
+
206
+ if (source === null) return <p>Loading…</p>;
207
+
208
+ return (
209
+ <LiveProvider code={source}>
210
+ <LivePreview />
211
+ <LiveError />
212
+ </LiveProvider>
213
+ );
214
+ }
215
+ ```
216
+
217
+ **Only ever fetch snippet source from your own authenticated API.** Never from a
218
+ query parameter, hash fragment, or `localStorage` - see
219
+ [Security](./05-security.md).
220
+
221
+ ## Try the live demos
222
+
223
+ This repository's playground includes two routes:
224
+
225
+ - **`/apps`** - production-shaped shell (sidebar tabs, API-fetched snippets,
226
+ shadcn UI via `@app/ui`, hooks demos)
227
+ - **`/playground`**, developer lab with editor and experiments
228
+
229
+ ```bash
230
+ npm install && npm run dev
231
+ ```
232
+
233
+ ## Next steps
234
+
235
+ - [Module registry](./02-module-registry.md), how imports resolve
236
+ - [Sharing libraries with your app](./03-sharing-your-app-libraries.md), the
237
+ single-instance question
238
+ - [Scaling to many apps](./04-scaling.md), keeping the bundle small
239
+ - [Security](./05-security.md), read before deploying
240
+ - [Integration guide](./08-integration-guide.md), apps stored in a database
241
+
242
+ ---
243
+
244
+ [Docs index](./README.md) · [Module registry →](./02-module-registry.md)