Fetch snippets from app doc when creating a new isolate

This commit is contained in:
Andrew Kingston 2024-03-12 15:39:26 +00:00
parent b13d2d3803
commit 10c581c3be
7 changed files with 52 additions and 26 deletions

View File

@ -1,3 +1,3 @@
"use strict";var snippets=(()=>{var u=Object.create;var i=Object.defineProperty;var c=Object.getOwnPropertyDescriptor;var d=Object.getOwnPropertyNames;var m=Object.getPrototypeOf,x=Object.prototype.hasOwnProperty;var l=(e,n)=>()=>(n||e((n={exports:{}}).exports,n),n.exports),W=(e,n)=>{for(var r in n)i(e,r,{get:n[r],enumerable:!0})},o=(e,n,r,p)=>{if(n&&typeof n=="object"||typeof n=="function")for(let t of d(n))!x.call(e,t)&&t!==r&&i(e,t,{get:()=>n[t],enumerable:!(p=c(n,t))||p.enumerable});return e};var g=(e,n,r)=>(r=e!=null?u(m(e)):{},o(n||!e||!e.__esModule?i(r,"default",{value:e,enumerable:!0}):r,e)),v=e=>o(i({},"__esModule",{value:!0}),e);var a=l((_,f)=>{f.exports.iifeWrapper=e=>`(function(){ "use strict";var snippets=(()=>{var u=Object.create;var p=Object.defineProperty;var c=Object.getOwnPropertyDescriptor;var d=Object.getOwnPropertyNames;var m=Object.getPrototypeOf,x=Object.prototype.hasOwnProperty;var l=(e,n)=>()=>(n||e((n={exports:{}}).exports,n),n.exports),W=(e,n)=>{for(var i in n)p(e,i,{get:n[i],enumerable:!0})},o=(e,n,i,t)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of d(n))!x.call(e,r)&&r!==i&&p(e,r,{get:()=>n[r],enumerable:!(t=c(n,r))||t.enumerable});return e};var g=(e,n,i)=>(i=e!=null?u(m(e)):{},o(n||!e||!e.__esModule?p(i,"default",{value:e,enumerable:!0}):i,e)),v=e=>o(p({},"__esModule",{value:!0}),e);var a=l((P,f)=>{f.exports.iifeWrapper=e=>`(function(){
${e} ${e}
})();`});var y={};W(y,{default:()=>w});var s=g(a()),w=new Proxy({},{get:function(e,n){let r=($("snippets")||[]).find(p=>p.name===n);return[eval][0]((0,s.iifeWrapper)(r.code))}});return v(y);})(); })();`});var y={};W(y,{default:()=>w});var s=g(a()),w=new Proxy({},{get:function(e,n){let i=(snippetDefinitions||[]).find(t=>t.name===n);return[eval][0]((0,s.iifeWrapper)(i.code))}});return v(y);})();

View File

@ -6,14 +6,13 @@ export default new Proxy(
{}, {},
{ {
get: function (_, name) { get: function (_, name) {
// Get snippet definitions from global context, get the correct snippet // Snippet definitions are injected to the isolate global scope before
// then eval the JS. This will error if the snippet doesn't exist, but // this bundle is loaded, so we can access it from there.
// that's intended.
// https://esbuild.github.io/content-types/#direct-eval for info on why // https://esbuild.github.io/content-types/#direct-eval for info on why
// eval is being called this way. // eval is being called this way.
// @ts-ignore // @ts-ignore
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
const snippet = ($("snippets") || []).find(x => x.name === name) const snippet = (snippetDefinitions || []).find(x => x.name === name)
return [eval][0](iifeWrapper(snippet.code)) return [eval][0](iifeWrapper(snippet.code))
}, },
} }

View File

@ -8,29 +8,48 @@ import {
import { context, logging } from "@budibase/backend-core" import { context, logging } from "@budibase/backend-core"
import tracer from "dd-trace" import tracer from "dd-trace"
import { IsolatedVM } from "./vm" import { IsolatedVM } from "./vm"
import { App, DocumentType, Snippet, VM } from "@budibase/types"
export function init() { async function getIsolate(ctx: any): Promise<VM> {
setJSRunner((js: string, ctx: Record<string, any>) => { // Reuse the existing isolate if one exists
return tracer.trace("runJS", {}, span => { if (ctx?.vm) {
try { return ctx.vm
const bbCtx = context.getCurrentContext() }
const vm = bbCtx?.vm // Get snippets to build into new isolate, if inside app context
? bbCtx.vm let snippets: Snippet[] | undefined
: new IsolatedVM({ const db = context.getAppDB()
if (db) {
console.log("READ APP METADATA")
const app = await db.get<App>(DocumentType.APP_METADATA)
snippets = app.snippets
}
// Build a new isolate
return new IsolatedVM({
memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, memoryLimit: env.JS_RUNNER_MEMORY_LIMIT,
invocationTimeout: env.JS_PER_INVOCATION_TIMEOUT_MS, invocationTimeout: env.JS_PER_INVOCATION_TIMEOUT_MS,
isolateAccumulatedTimeout: env.JS_PER_REQUEST_TIMEOUT_MS, isolateAccumulatedTimeout: env.JS_PER_REQUEST_TIMEOUT_MS,
}) })
.withHelpers() .withHelpers()
.withSnippets() .withSnippets(snippets)
}
export function init() {
setJSRunner((js: string, ctx: Record<string, any>) => {
return tracer.trace("runJS", {}, async span => {
try {
// Reuse an existing isolate from context, or make a new one
const bbCtx = context.getCurrentContext()
const vm = await getIsolate(bbCtx)
if (bbCtx) { if (bbCtx) {
// If we have a context, we want to persist it to reuse the isolate
bbCtx.vm = vm bbCtx.vm = vm
} }
// Strip helpers (an array) and snippets (a proxy isntance) as these
// will not survive the isolated-vm barrier
const { helpers, snippets, ...rest } = ctx const { helpers, snippets, ...rest } = ctx
return vm.withContext(rest, () => vm.execute(js)) return vm.withContext(rest, () => vm!.execute(js))
} catch (error: any) { } catch (error: any) {
if (error.message === "Script execution timed out.") { if (error.message === "Script execution timed out.") {
throw new JsErrorTimeout() throw new JsErrorTimeout()

View File

@ -6,7 +6,7 @@ import crypto from "crypto"
import querystring from "querystring" import querystring from "querystring"
import { BundleType, loadBundle } from "../bundles" import { BundleType, loadBundle } from "../bundles"
import { VM } from "@budibase/types" import { Snippet, VM } from "@budibase/types"
import { iifeWrapper } from "@budibase/string-templates" import { iifeWrapper } from "@budibase/string-templates"
import environment from "../../environment" import environment from "../../environment"
@ -98,11 +98,13 @@ export class IsolatedVM implements VM {
return this return this
} }
withSnippets() { withSnippets(snippets?: Snippet[]) {
const snippetsSource = loadBundle(BundleType.SNIPPETS) const snippetsSource = loadBundle(BundleType.SNIPPETS)
const script = this.isolate.compileScriptSync( const script = this.isolate.compileScriptSync(`
`${snippetsSource};snippets=snippets.default;` const snippetDefinitions = ${JSON.stringify(snippets || [])};
) ${snippetsSource};
snippets = snippets.default;
`)
script.runSync(this.vm, { timeout: this.invocationTimeout, release: false }) script.runSync(this.vm, { timeout: this.invocationTimeout, release: false })
new Promise(() => { new Promise(() => {
script.release() script.release()

View File

@ -1,4 +1,4 @@
import { User, Document, Plugin } from "../" import { User, Document, Plugin, Snippet } from "../"
import { SocketSession } from "../../sdk" import { SocketSession } from "../../sdk"
export type AppMetadataErrors = { [key: string]: string[] } export type AppMetadataErrors = { [key: string]: string[] }
@ -26,6 +26,7 @@ export interface App extends Document {
automations?: AutomationSettings automations?: AutomationSettings
usedPlugins?: Plugin[] usedPlugins?: Plugin[]
upgradableVersion?: string upgradableVersion?: string
snippets?: Snippet[]
} }
export interface AppInstance { export interface AppInstance {

View File

@ -14,3 +14,4 @@ export * from "./backup"
export * from "./webhook" export * from "./webhook"
export * from "./links" export * from "./links"
export * from "./component" export * from "./component"
export * from "./snippet"

View File

@ -0,0 +1,4 @@
export interface Snippet {
name: string
code: string
}