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}
})();`});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 snippet definitions from global context, get the correct snippet
// then eval the JS. This will error if the snippet doesn't exist, but
// that's intended.
// Snippet definitions are injected to the isolate global scope before
// this bundle is loaded, so we can access it from there.
// https://esbuild.github.io/content-types/#direct-eval for info on why
// eval is being called this way.
// @ts-ignore
// 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))
},
}

View File

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

View File

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

View File

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

View File

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

View File

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