@mindstudio-ai/agent 0.1.6 → 0.1.9
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.
- package/README.md +35 -0
- package/dist/cli.js +1960 -253
- package/dist/index.d.ts +1274 -100
- package/dist/index.js +1719 -24
- package/dist/index.js.map +1 -1
- package/dist/postinstall.js +1960 -253
- package/llms.txt +53 -23
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -37,7 +37,7 @@ var init_metadata = __esm({
|
|
|
37
37
|
stepType: "addSubtitlesToVideo",
|
|
38
38
|
description: "Automatically add subtitles to a video",
|
|
39
39
|
usageNotes: "- Can control style of text and animation",
|
|
40
|
-
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video" }, "language": { "type": "string", "description": "ISO language code for subtitle transcription" }, "fontName": { "type": "string", "description": "Google Font name for subtitle text" }, "fontSize": { "type": "number", "description": "Font size in pixels. Default: 100." }, "fontWeight": { "enum": ["normal", "bold", "black"], "type": "string", "description": "Font weight for subtitle text" }, "fontColor": { "enum": ["white", "black", "red", "green", "blue", "yellow", "orange", "purple", "pink", "brown", "gray", "cyan", "magenta"], "type": "string", "description": "Color of the subtitle text" }, "highlightColor": { "enum": ["white", "black", "red", "green", "blue", "yellow", "orange", "purple", "pink", "brown", "gray", "cyan", "magenta"], "type": "string", "description": "Color used to highlight the currently spoken word" }, "strokeWidth": { "type": "number", "description": "Width of the text stroke outline in pixels" }, "strokeColor": { "enum": ["black", "white", "red", "green", "blue", "yellow", "orange", "purple", "pink", "brown", "gray", "cyan", "magenta"], "type": "string", "description": "Color of the text stroke outline" }, "backgroundColor": { "enum": ["black", "white", "red", "green", "blue", "yellow", "orange", "purple", "pink", "brown", "gray", "cyan", "magenta", "none"], "type": "string", "description": "Background color behind subtitle text. Use 'none' for transparent." }, "backgroundOpacity": { "type": "number", "description": "Opacity of the subtitle background. 0.0 = fully transparent, 1.0 = fully opaque." }, "position": { "enum": ["top", "center", "bottom"], "type": "string", "description": "Vertical position of subtitle text on screen" }, "yOffset": { "type": "number", "description": "Vertical offset in pixels from the position. Positive moves down, negative moves up. Default: 75." }, "wordsPerSubtitle": { "type": "number", "description": "Maximum number of words per subtitle segment. Use 1 for single-word display, 2-3 for short phrases, or 8-12 for full sentences. Default: 3." }, "enableAnimation": { "type": "boolean", "description": "When true, enables bounce-style entrance animation for subtitles. Default: true." }, "
|
|
40
|
+
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video" }, "language": { "type": "string", "description": "ISO language code for subtitle transcription" }, "fontName": { "type": "string", "description": "Google Font name for subtitle text" }, "fontSize": { "type": "number", "description": "Font size in pixels. Default: 100." }, "fontWeight": { "enum": ["normal", "bold", "black"], "type": "string", "description": "Font weight for subtitle text" }, "fontColor": { "enum": ["white", "black", "red", "green", "blue", "yellow", "orange", "purple", "pink", "brown", "gray", "cyan", "magenta"], "type": "string", "description": "Color of the subtitle text" }, "highlightColor": { "enum": ["white", "black", "red", "green", "blue", "yellow", "orange", "purple", "pink", "brown", "gray", "cyan", "magenta"], "type": "string", "description": "Color used to highlight the currently spoken word" }, "strokeWidth": { "type": "number", "description": "Width of the text stroke outline in pixels" }, "strokeColor": { "enum": ["black", "white", "red", "green", "blue", "yellow", "orange", "purple", "pink", "brown", "gray", "cyan", "magenta"], "type": "string", "description": "Color of the text stroke outline" }, "backgroundColor": { "enum": ["black", "white", "red", "green", "blue", "yellow", "orange", "purple", "pink", "brown", "gray", "cyan", "magenta", "none"], "type": "string", "description": "Background color behind subtitle text. Use 'none' for transparent." }, "backgroundOpacity": { "type": "number", "description": "Opacity of the subtitle background. 0.0 = fully transparent, 1.0 = fully opaque." }, "position": { "enum": ["top", "center", "bottom"], "type": "string", "description": "Vertical position of subtitle text on screen" }, "yOffset": { "type": "number", "description": "Vertical offset in pixels from the position. Positive moves down, negative moves up. Default: 75." }, "wordsPerSubtitle": { "type": "number", "description": "Maximum number of words per subtitle segment. Use 1 for single-word display, 2-3 for short phrases, or 8-12 for full sentences. Default: 3." }, "enableAnimation": { "type": "boolean", "description": "When true, enables bounce-style entrance animation for subtitles. Default: true." }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["videoUrl", "language", "fontName", "fontSize", "fontWeight", "fontColor", "highlightColor", "strokeWidth", "strokeColor", "backgroundColor", "backgroundOpacity", "position", "yOffset", "wordsPerSubtitle", "enableAnimation"] },
|
|
41
41
|
outputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the video with subtitles added" } }, "required": ["videoUrl"] }
|
|
42
42
|
},
|
|
43
43
|
"airtableCreateUpdateRecord": {
|
|
@@ -89,6 +89,13 @@ var init_metadata = __esm({
|
|
|
89
89
|
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video to capture a frame from" }, "at": { "anyOf": [{ "type": "number" }, { "type": "string" }] } }, "required": ["videoUrl", "at"] },
|
|
90
90
|
outputSchema: { "type": "object", "properties": { "thumbnailUrl": { "type": "string", "description": "URL of the captured thumbnail image" } }, "required": ["thumbnailUrl"] }
|
|
91
91
|
},
|
|
92
|
+
"checkAppRole": {
|
|
93
|
+
stepType: "checkAppRole",
|
|
94
|
+
description: "Check whether the current user has a specific app role and branch accordingly.",
|
|
95
|
+
usageNotes: '- Checks if the current user has been assigned a specific role in this app.\n- If the user has the role, transitions to the "has role" path.\n- If the user does not have the role, transitions to the "no role" path, or errors if no path is configured.\n- Role names are defined by the app creator and assigned to users via the app roles system.\n- The roleName field supports {{variables}} for dynamic role checks.',
|
|
96
|
+
inputSchema: { "type": "object", "properties": { "roleName": { "type": "string", "description": "The role name to check (supports {{variables}})" }, "hasRoleStepId": { "type": "string", "description": "Step to transition to if the user has the role (same workflow)" }, "hasRoleWorkflowId": { "type": "string", "description": "Workflow to jump to if the user has the role (cross workflow)" }, "noRoleStepId": { "type": "string", "description": "Step to transition to if the user does not have the role (same workflow)" }, "noRoleWorkflowId": { "type": "string", "description": "Workflow to jump to if the user does not have the role (cross workflow)" } }, "required": ["roleName"], "description": "Configuration for the check app role step" },
|
|
97
|
+
outputSchema: { "type": "object", "properties": { "hasRole": { "type": "boolean", "description": "Whether the current user has the checked role" }, "userRoles": { "type": "array", "items": { "type": "string" }, "description": "All roles assigned to the current user for this app" } }, "required": ["hasRole", "userRoles"] }
|
|
98
|
+
},
|
|
92
99
|
"codaCreateUpdatePage": {
|
|
93
100
|
stepType: "codaCreateUpdatePage",
|
|
94
101
|
description: "Create a new page or update an existing page in a Coda document.",
|
|
@@ -340,7 +347,7 @@ var init_metadata = __esm({
|
|
|
340
347
|
stepType: "generatePdf",
|
|
341
348
|
description: "Generate an HTML asset and export it as a webpage, PDF, or image",
|
|
342
349
|
usageNotes: '- Agents can generate HTML documents and export as webpage, PDFs, images, or videos. They do this by using the "generatePdf" block, which defines an HTML page with variables, and then the generation process renders the page to create the output and save its URL at the specified variable.\n- The template for the HTML page is generated by a separate process, and it can only use variables that have already been defined in the workflow at the time of its execution. It has full access to handlebars to render the HTML template, including a handlebars helper to render a markdown variable string as HTML (which can be useful for creating templates that render long strings). The template can also create its own simple JavaScript to do things like format dates and strings.\n- If PDF or composited image generation are part of the workflow, assistant adds the block and leaves the "source" empty. In a separate step, assistant generates a detailed request for the developer who will write the HTML.\n- Can also auto-generate HTML from a prompt (like a generate text block to generate HTML). In these cases, create a prompt with variables in the dynamicPrompt variable describing, in detail, the document to generate\n- Can either display output directly to user (foreground mode) or save the URL of the asset to a variable (background mode)',
|
|
343
|
-
inputSchema: { "type": "object", "properties": { "source": { "type": "string", "description": "The HTML or Markdown source template for the asset" }, "sourceType": { "enum": ["html", "markdown", "spa", "raw", "dynamic", "customInterface"], "type": "string", "description": "Source type: html, markdown (auto-formatted), spa (single page app), raw (pre-generated HTML in a variable), dynamic (AI-generated from prompt), or customInterface" }, "outputFormat": { "enum": ["pdf", "png", "html", "mp4", "openGraph"], "type": "string", "description": "The output format for the generated asset" }, "pageSize": { "enum": ["full", "letter", "A4", "custom"], "type": "string", "description": "Page size for PDF, PNG, or MP4 output" }, "testData": { "type": "object", "description": "Test data used for previewing the template with sample variable values" }, "options": { "type": "object", "properties": { "pageWidthPx": { "type": "number", "description": "Custom page width in pixels (for custom pageSize)" }, "pageHeightPx": { "type": "number", "description": "Custom page height in pixels (for custom pageSize)" }, "pageOrientation": { "enum": ["portrait", "landscape"], "type": "string", "description": "Page orientation for the rendered output" }, "rehostMedia": { "type": "boolean", "description": "Whether to re-host third-party images on the MindStudio CDN" }, "videoDurationSeconds": { "type": "number", "description": "Duration in seconds for MP4 video output" } }, "description": "Additional rendering options" }, "spaSource": { "type": "object", "properties": { "source": { "type": "string", "description": "Source code of the SPA (legacy, use files instead)" }, "lastCompiledSource": { "type": "string", "description": "Last compiled source (cached)" }, "files": { "type": "object", "description": "Multi-file SPA source" }, "paths": { "type": "array", "items": { "type": "string" }, "description": "Available route paths in the SPA" }, "root": { "type": "string", "description": "Root URL of the SPA bundle" }, "zipUrl": { "type": "string", "description": "URL of the zipped SPA bundle" } }, "required": ["paths", "root", "zipUrl"], "description": "Single page app source configuration (advanced)" }, "rawSource": { "type": "string", "description": "Raw HTML source stored in a variable, using handlebars syntax (e.g. {{myHtmlVariable}})" }, "dynamicPrompt": { "type": "string", "description": 'Prompt to generate the HTML dynamically when sourceType is "dynamic"' }, "dynamicSourceModelOverride": { "type": "object", "properties": { "model": { "type": "string", "description": 'Model identifier (e.g. "gpt-4", "claude-3-opus")' }, "temperature": { "type": "number", "description": "Sampling temperature for the model (0-2)" }, "maxResponseTokens": { "type": "number", "description": "Maximum number of tokens in the model's response" }, "ignorePreamble": { "type": "boolean", "description": "Whether to skip the system preamble/instructions" }, "userMessagePreprocessor": { "type": "object", "properties": { "dataSource": { "type": "string", "description": "Data source identifier for the preprocessor" }, "messageTemplate": { "type": "string", "description": "Template string applied to user messages before sending to the model" }, "maxResults": { "type": "number", "description": "Maximum number of results to include from the data source" }, "enabled": { "type": "boolean", "description": "Whether the preprocessor is active" }, "shouldInherit": { "type": "boolean", "description": "Whether child steps should inherit this preprocessor configuration" } }, "description": "Preprocessor applied to user messages before sending to the model" }, "preamble": { "type": "string", "description": "System preamble/instructions for the model" }, "multiModelEnabled": { "type": "boolean", "description": "Whether multi-model candidate generation is enabled" }, "editResponseEnabled": { "type": "boolean", "description": "Whether the user can edit the model's response" }, "config": { "type": "object", "description": "Additional model-specific configuration" } }, "required": ["model", "temperature", "maxResponseTokens"], "description": "Model override for dynamic HTML generation. Leave undefined to use the default model" }, "transitionControl": { "enum": ["default", "native"], "type": "string", "description": "Controls how the step transitions after displaying in foreground mode" }, "shareControl": { "enum": ["default", "hidden"], "type": "string", "description": "Controls visibility of the share button on displayed assets" }, "shareImageUrl": { "type": "string", "description": "URL of a custom Open Graph share image" }, "
|
|
350
|
+
inputSchema: { "type": "object", "properties": { "source": { "type": "string", "description": "The HTML or Markdown source template for the asset" }, "sourceType": { "enum": ["html", "markdown", "spa", "raw", "dynamic", "customInterface"], "type": "string", "description": "Source type: html, markdown (auto-formatted), spa (single page app), raw (pre-generated HTML in a variable), dynamic (AI-generated from prompt), or customInterface" }, "outputFormat": { "enum": ["pdf", "png", "html", "mp4", "openGraph"], "type": "string", "description": "The output format for the generated asset" }, "pageSize": { "enum": ["full", "letter", "A4", "custom"], "type": "string", "description": "Page size for PDF, PNG, or MP4 output" }, "testData": { "type": "object", "description": "Test data used for previewing the template with sample variable values" }, "options": { "type": "object", "properties": { "pageWidthPx": { "type": "number", "description": "Custom page width in pixels (for custom pageSize)" }, "pageHeightPx": { "type": "number", "description": "Custom page height in pixels (for custom pageSize)" }, "pageOrientation": { "enum": ["portrait", "landscape"], "type": "string", "description": "Page orientation for the rendered output" }, "rehostMedia": { "type": "boolean", "description": "Whether to re-host third-party images on the MindStudio CDN" }, "videoDurationSeconds": { "type": "number", "description": "Duration in seconds for MP4 video output" } }, "description": "Additional rendering options" }, "spaSource": { "type": "object", "properties": { "source": { "type": "string", "description": "Source code of the SPA (legacy, use files instead)" }, "lastCompiledSource": { "type": "string", "description": "Last compiled source (cached)" }, "files": { "type": "object", "description": "Multi-file SPA source" }, "paths": { "type": "array", "items": { "type": "string" }, "description": "Available route paths in the SPA" }, "root": { "type": "string", "description": "Root URL of the SPA bundle" }, "zipUrl": { "type": "string", "description": "URL of the zipped SPA bundle" } }, "required": ["paths", "root", "zipUrl"], "description": "Single page app source configuration (advanced)" }, "rawSource": { "type": "string", "description": "Raw HTML source stored in a variable, using handlebars syntax (e.g. {{myHtmlVariable}})" }, "dynamicPrompt": { "type": "string", "description": 'Prompt to generate the HTML dynamically when sourceType is "dynamic"' }, "dynamicSourceModelOverride": { "type": "object", "properties": { "model": { "type": "string", "description": 'Model identifier (e.g. "gpt-4", "claude-3-opus")' }, "temperature": { "type": "number", "description": "Sampling temperature for the model (0-2)" }, "maxResponseTokens": { "type": "number", "description": "Maximum number of tokens in the model's response" }, "ignorePreamble": { "type": "boolean", "description": "Whether to skip the system preamble/instructions" }, "userMessagePreprocessor": { "type": "object", "properties": { "dataSource": { "type": "string", "description": "Data source identifier for the preprocessor" }, "messageTemplate": { "type": "string", "description": "Template string applied to user messages before sending to the model" }, "maxResults": { "type": "number", "description": "Maximum number of results to include from the data source" }, "enabled": { "type": "boolean", "description": "Whether the preprocessor is active" }, "shouldInherit": { "type": "boolean", "description": "Whether child steps should inherit this preprocessor configuration" } }, "description": "Preprocessor applied to user messages before sending to the model" }, "preamble": { "type": "string", "description": "System preamble/instructions for the model" }, "multiModelEnabled": { "type": "boolean", "description": "Whether multi-model candidate generation is enabled" }, "editResponseEnabled": { "type": "boolean", "description": "Whether the user can edit the model's response" }, "config": { "type": "object", "description": "Additional model-specific configuration" } }, "required": ["model", "temperature", "maxResponseTokens"], "description": "Model override for dynamic HTML generation. Leave undefined to use the default model" }, "transitionControl": { "enum": ["default", "native"], "type": "string", "description": "Controls how the step transitions after displaying in foreground mode" }, "shareControl": { "enum": ["default", "hidden"], "type": "string", "description": "Controls visibility of the share button on displayed assets" }, "shareImageUrl": { "type": "string", "description": "URL of a custom Open Graph share image" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["source", "sourceType", "outputFormat", "pageSize", "testData"] },
|
|
344
351
|
outputSchema: { "type": "object", "properties": { "url": { "type": "string", "description": "CDN URL of the generated asset (PDF, PNG, HTML, or MP4 depending on outputFormat)" } }, "required": ["url"] }
|
|
345
352
|
},
|
|
346
353
|
"generateChart": {
|
|
@@ -354,28 +361,28 @@ var init_metadata = __esm({
|
|
|
354
361
|
stepType: "generateImage",
|
|
355
362
|
description: "Generate an image from a text prompt using an AI model.",
|
|
356
363
|
usageNotes: "- Prompts should be descriptive but concise (roughly 3\u20136 sentences).\n- Images are automatically hosted on a CDN.\n- In foreground mode, the image is displayed to the user. In background mode, the URL is saved to a variable.\n- When generateVariants is true with numVariants > 1, multiple images are generated in parallel.\n- In direct execution, foreground mode behaves as background, and userSelect variant behavior behaves as saveAll.",
|
|
357
|
-
inputSchema: { "type": "object", "properties": { "prompt": { "type": "string", "description": "Text prompt describing the image to generate" }, "
|
|
364
|
+
inputSchema: { "type": "object", "properties": { "prompt": { "type": "string", "description": "Text prompt describing the image to generate" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" }, "imageModelOverride": { "type": "object", "properties": { "model": { "type": "string", "description": "Image generation model identifier" }, "config": { "type": "object", "description": "Additional model-specific configuration" } }, "required": ["model"], "description": "Optional model configuration override. Uses the workflow's default image model if not specified" }, "generateVariants": { "type": "boolean", "description": "Whether to generate multiple image variants in parallel" }, "numVariants": { "type": "number", "description": "Number of variants to generate (max 10)" }, "addWatermark": { "type": "boolean", "description": "Whether to add a MindStudio watermark to the generated image" } }, "required": ["prompt"] },
|
|
358
365
|
outputSchema: { "type": "object", "properties": { "imageUrl": { "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] } }, "required": ["imageUrl"] }
|
|
359
366
|
},
|
|
360
367
|
"generateLipsync": {
|
|
361
368
|
stepType: "generateLipsync",
|
|
362
369
|
description: "Generate a lip sync video from provided audio and image.",
|
|
363
370
|
usageNotes: "- In foreground mode, the video is displayed to the user. In background mode, the URL is saved to a variable.",
|
|
364
|
-
inputSchema: { "type": "object", "properties": { "
|
|
371
|
+
inputSchema: { "type": "object", "properties": { "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" }, "addWatermark": { "type": "boolean", "description": "Whether to add a MindStudio watermark to the generated video" }, "lipsyncModelOverride": { "type": "object", "properties": { "model": { "type": "string" }, "config": { "type": "object" } }, "required": ["model"], "description": "Optional model configuration override. Uses the workflow's default lipsync model if not specified" } } },
|
|
365
372
|
outputSchema: { "description": "This step does not produce output data." }
|
|
366
373
|
},
|
|
367
374
|
"generateMusic": {
|
|
368
375
|
stepType: "generateMusic",
|
|
369
376
|
description: "Generate an audio file from provided instructions (text) using a music model.",
|
|
370
377
|
usageNotes: "- The text field contains the instructions (prompt) for the music generation.\n- In foreground mode, the audio is displayed to the user. In background mode, the URL is saved to a variable.",
|
|
371
|
-
inputSchema: { "type": "object", "properties": { "text": { "type": "string", "description": "The instructions (prompt) for the music generation" }, "
|
|
378
|
+
inputSchema: { "type": "object", "properties": { "text": { "type": "string", "description": "The instructions (prompt) for the music generation" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" }, "musicModelOverride": { "type": "object", "properties": { "model": { "type": "string" }, "config": { "type": "object" } }, "required": ["model"], "description": "Optional model configuration override. Uses the workflow's default music model if not specified" } }, "required": ["text"] },
|
|
372
379
|
outputSchema: { "description": "This step does not produce output data." }
|
|
373
380
|
},
|
|
374
381
|
"generatePdf": {
|
|
375
382
|
stepType: "generatePdf",
|
|
376
383
|
description: "Generate an HTML asset and export it as a webpage, PDF, or image",
|
|
377
384
|
usageNotes: '- Agents can generate HTML documents and export as webpage, PDFs, images, or videos. They do this by using the "generatePdf" block, which defines an HTML page with variables, and then the generation process renders the page to create the output and save its URL at the specified variable.\n- The template for the HTML page is generated by a separate process, and it can only use variables that have already been defined in the workflow at the time of its execution. It has full access to handlebars to render the HTML template, including a handlebars helper to render a markdown variable string as HTML (which can be useful for creating templates that render long strings). The template can also create its own simple JavaScript to do things like format dates and strings.\n- If PDF or composited image generation are part of the workflow, assistant adds the block and leaves the "source" empty. In a separate step, assistant generates a detailed request for the developer who will write the HTML.\n- Can also auto-generate HTML from a prompt (like a generate text block to generate HTML). In these cases, create a prompt with variables in the dynamicPrompt variable describing, in detail, the document to generate\n- Can either display output directly to user (foreground mode) or save the URL of the asset to a variable (background mode)',
|
|
378
|
-
inputSchema: { "type": "object", "properties": { "source": { "type": "string", "description": "The HTML or Markdown source template for the asset" }, "sourceType": { "enum": ["html", "markdown", "spa", "raw", "dynamic", "customInterface"], "type": "string", "description": "Source type: html, markdown (auto-formatted), spa (single page app), raw (pre-generated HTML in a variable), dynamic (AI-generated from prompt), or customInterface" }, "outputFormat": { "enum": ["pdf", "png", "html", "mp4", "openGraph"], "type": "string", "description": "The output format for the generated asset" }, "pageSize": { "enum": ["full", "letter", "A4", "custom"], "type": "string", "description": "Page size for PDF, PNG, or MP4 output" }, "testData": { "type": "object", "description": "Test data used for previewing the template with sample variable values" }, "options": { "type": "object", "properties": { "pageWidthPx": { "type": "number", "description": "Custom page width in pixels (for custom pageSize)" }, "pageHeightPx": { "type": "number", "description": "Custom page height in pixels (for custom pageSize)" }, "pageOrientation": { "enum": ["portrait", "landscape"], "type": "string", "description": "Page orientation for the rendered output" }, "rehostMedia": { "type": "boolean", "description": "Whether to re-host third-party images on the MindStudio CDN" }, "videoDurationSeconds": { "type": "number", "description": "Duration in seconds for MP4 video output" } }, "description": "Additional rendering options" }, "spaSource": { "type": "object", "properties": { "source": { "type": "string", "description": "Source code of the SPA (legacy, use files instead)" }, "lastCompiledSource": { "type": "string", "description": "Last compiled source (cached)" }, "files": { "type": "object", "description": "Multi-file SPA source" }, "paths": { "type": "array", "items": { "type": "string" }, "description": "Available route paths in the SPA" }, "root": { "type": "string", "description": "Root URL of the SPA bundle" }, "zipUrl": { "type": "string", "description": "URL of the zipped SPA bundle" } }, "required": ["paths", "root", "zipUrl"], "description": "Single page app source configuration (advanced)" }, "rawSource": { "type": "string", "description": "Raw HTML source stored in a variable, using handlebars syntax (e.g. {{myHtmlVariable}})" }, "dynamicPrompt": { "type": "string", "description": 'Prompt to generate the HTML dynamically when sourceType is "dynamic"' }, "dynamicSourceModelOverride": { "type": "object", "properties": { "model": { "type": "string", "description": 'Model identifier (e.g. "gpt-4", "claude-3-opus")' }, "temperature": { "type": "number", "description": "Sampling temperature for the model (0-2)" }, "maxResponseTokens": { "type": "number", "description": "Maximum number of tokens in the model's response" }, "ignorePreamble": { "type": "boolean", "description": "Whether to skip the system preamble/instructions" }, "userMessagePreprocessor": { "type": "object", "properties": { "dataSource": { "type": "string", "description": "Data source identifier for the preprocessor" }, "messageTemplate": { "type": "string", "description": "Template string applied to user messages before sending to the model" }, "maxResults": { "type": "number", "description": "Maximum number of results to include from the data source" }, "enabled": { "type": "boolean", "description": "Whether the preprocessor is active" }, "shouldInherit": { "type": "boolean", "description": "Whether child steps should inherit this preprocessor configuration" } }, "description": "Preprocessor applied to user messages before sending to the model" }, "preamble": { "type": "string", "description": "System preamble/instructions for the model" }, "multiModelEnabled": { "type": "boolean", "description": "Whether multi-model candidate generation is enabled" }, "editResponseEnabled": { "type": "boolean", "description": "Whether the user can edit the model's response" }, "config": { "type": "object", "description": "Additional model-specific configuration" } }, "required": ["model", "temperature", "maxResponseTokens"], "description": "Model override for dynamic HTML generation. Leave undefined to use the default model" }, "transitionControl": { "enum": ["default", "native"], "type": "string", "description": "Controls how the step transitions after displaying in foreground mode" }, "shareControl": { "enum": ["default", "hidden"], "type": "string", "description": "Controls visibility of the share button on displayed assets" }, "shareImageUrl": { "type": "string", "description": "URL of a custom Open Graph share image" }, "
|
|
385
|
+
inputSchema: { "type": "object", "properties": { "source": { "type": "string", "description": "The HTML or Markdown source template for the asset" }, "sourceType": { "enum": ["html", "markdown", "spa", "raw", "dynamic", "customInterface"], "type": "string", "description": "Source type: html, markdown (auto-formatted), spa (single page app), raw (pre-generated HTML in a variable), dynamic (AI-generated from prompt), or customInterface" }, "outputFormat": { "enum": ["pdf", "png", "html", "mp4", "openGraph"], "type": "string", "description": "The output format for the generated asset" }, "pageSize": { "enum": ["full", "letter", "A4", "custom"], "type": "string", "description": "Page size for PDF, PNG, or MP4 output" }, "testData": { "type": "object", "description": "Test data used for previewing the template with sample variable values" }, "options": { "type": "object", "properties": { "pageWidthPx": { "type": "number", "description": "Custom page width in pixels (for custom pageSize)" }, "pageHeightPx": { "type": "number", "description": "Custom page height in pixels (for custom pageSize)" }, "pageOrientation": { "enum": ["portrait", "landscape"], "type": "string", "description": "Page orientation for the rendered output" }, "rehostMedia": { "type": "boolean", "description": "Whether to re-host third-party images on the MindStudio CDN" }, "videoDurationSeconds": { "type": "number", "description": "Duration in seconds for MP4 video output" } }, "description": "Additional rendering options" }, "spaSource": { "type": "object", "properties": { "source": { "type": "string", "description": "Source code of the SPA (legacy, use files instead)" }, "lastCompiledSource": { "type": "string", "description": "Last compiled source (cached)" }, "files": { "type": "object", "description": "Multi-file SPA source" }, "paths": { "type": "array", "items": { "type": "string" }, "description": "Available route paths in the SPA" }, "root": { "type": "string", "description": "Root URL of the SPA bundle" }, "zipUrl": { "type": "string", "description": "URL of the zipped SPA bundle" } }, "required": ["paths", "root", "zipUrl"], "description": "Single page app source configuration (advanced)" }, "rawSource": { "type": "string", "description": "Raw HTML source stored in a variable, using handlebars syntax (e.g. {{myHtmlVariable}})" }, "dynamicPrompt": { "type": "string", "description": 'Prompt to generate the HTML dynamically when sourceType is "dynamic"' }, "dynamicSourceModelOverride": { "type": "object", "properties": { "model": { "type": "string", "description": 'Model identifier (e.g. "gpt-4", "claude-3-opus")' }, "temperature": { "type": "number", "description": "Sampling temperature for the model (0-2)" }, "maxResponseTokens": { "type": "number", "description": "Maximum number of tokens in the model's response" }, "ignorePreamble": { "type": "boolean", "description": "Whether to skip the system preamble/instructions" }, "userMessagePreprocessor": { "type": "object", "properties": { "dataSource": { "type": "string", "description": "Data source identifier for the preprocessor" }, "messageTemplate": { "type": "string", "description": "Template string applied to user messages before sending to the model" }, "maxResults": { "type": "number", "description": "Maximum number of results to include from the data source" }, "enabled": { "type": "boolean", "description": "Whether the preprocessor is active" }, "shouldInherit": { "type": "boolean", "description": "Whether child steps should inherit this preprocessor configuration" } }, "description": "Preprocessor applied to user messages before sending to the model" }, "preamble": { "type": "string", "description": "System preamble/instructions for the model" }, "multiModelEnabled": { "type": "boolean", "description": "Whether multi-model candidate generation is enabled" }, "editResponseEnabled": { "type": "boolean", "description": "Whether the user can edit the model's response" }, "config": { "type": "object", "description": "Additional model-specific configuration" } }, "required": ["model", "temperature", "maxResponseTokens"], "description": "Model override for dynamic HTML generation. Leave undefined to use the default model" }, "transitionControl": { "enum": ["default", "native"], "type": "string", "description": "Controls how the step transitions after displaying in foreground mode" }, "shareControl": { "enum": ["default", "hidden"], "type": "string", "description": "Controls visibility of the share button on displayed assets" }, "shareImageUrl": { "type": "string", "description": "URL of a custom Open Graph share image" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["source", "sourceType", "outputFormat", "pageSize", "testData"] },
|
|
379
386
|
outputSchema: { "type": "object", "properties": { "url": { "type": "string", "description": "CDN URL of the generated asset (PDF, PNG, HTML, or MP4 depending on outputFormat)" } }, "required": ["url"] }
|
|
380
387
|
},
|
|
381
388
|
"generateStaticVideoFromImage": {
|
|
@@ -399,7 +406,7 @@ var init_metadata = __esm({
|
|
|
399
406
|
stepType: "generateVideo",
|
|
400
407
|
description: "Generate a video from a text prompt using an AI model.",
|
|
401
408
|
usageNotes: "- Prompts should be descriptive but concise (roughly 3\u20136 sentences).\n- Videos are automatically hosted on a CDN.\n- In foreground mode, the video is displayed to the user. In background mode, the URL is saved to a variable.\n- When generateVariants is true with numVariants > 1, multiple videos are generated in parallel.\n- In direct execution, foreground mode behaves as background, and userSelect variant behavior behaves as saveAll.",
|
|
402
|
-
inputSchema: { "type": "object", "properties": { "prompt": { "type": "string", "description": "Text prompt describing the video to generate" }, "
|
|
409
|
+
inputSchema: { "type": "object", "properties": { "prompt": { "type": "string", "description": "Text prompt describing the video to generate" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" }, "videoModelOverride": { "type": "object", "properties": { "model": { "type": "string", "description": "Video generation model identifier" }, "config": { "type": "object", "description": "Additional model-specific configuration" } }, "required": ["model"], "description": "Optional model configuration override. Uses the workflow's default video model if not specified" }, "generateVariants": { "type": "boolean", "description": "Whether to generate multiple video variants in parallel" }, "numVariants": { "type": "number", "description": "Number of variants to generate (max 10)" }, "addWatermark": { "type": "boolean", "description": "Whether to add a MindStudio watermark to the generated video" } }, "required": ["prompt"] },
|
|
403
410
|
outputSchema: { "type": "object", "properties": { "videoUrl": { "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] } }, "required": ["videoUrl"] }
|
|
404
411
|
},
|
|
405
412
|
"getGmailAttachments": {
|
|
@@ -539,14 +546,14 @@ var init_metadata = __esm({
|
|
|
539
546
|
stepType: "imageRemoveWatermark",
|
|
540
547
|
description: "Remove watermarks from an image using AI.",
|
|
541
548
|
usageNotes: "- Output is re-hosted on the CDN as a PNG.",
|
|
542
|
-
inputSchema: { "type": "object", "properties": { "imageUrl": { "type": "string", "description": "URL of the image to remove the watermark from" }, "engine": { "type": "string", "description": "Watermark removal engine to use" }, "
|
|
549
|
+
inputSchema: { "type": "object", "properties": { "imageUrl": { "type": "string", "description": "URL of the image to remove the watermark from" }, "engine": { "type": "string", "description": "Watermark removal engine to use" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["imageUrl", "engine"] },
|
|
543
550
|
outputSchema: { "type": "object", "properties": { "imageUrl": { "type": "string", "description": "CDN URL of the processed image with watermark removed (PNG)" } }, "required": ["imageUrl"] }
|
|
544
551
|
},
|
|
545
552
|
"insertVideoClips": {
|
|
546
553
|
stepType: "insertVideoClips",
|
|
547
554
|
description: "Insert b-roll clips into a base video at a timecode, optionally with an xfade transition.",
|
|
548
555
|
usageNotes: "",
|
|
549
|
-
inputSchema: { "type": "object", "properties": { "baseVideoUrl": { "type": "string", "description": "URL of the base video to insert clips into" }, "overlayVideos": { "type": "array", "items": { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the overlay video clip" }, "startTimeSec": { "type": "number", "description": "Timecode in seconds at which to insert this clip" } }, "required": ["videoUrl", "startTimeSec"] }, "description": "Array of overlay clips to insert at specified timecodes" }, "transition": { "type": "string", "description": "Optional xfade transition effect name between clips" }, "transitionDuration": { "type": "number", "description": "Duration of the transition in seconds" }, "useOverlayAudio": { "type": "boolean", "description": "When true, uses audio from the overlay clips instead of the base video audio during inserts" }, "
|
|
556
|
+
inputSchema: { "type": "object", "properties": { "baseVideoUrl": { "type": "string", "description": "URL of the base video to insert clips into" }, "overlayVideos": { "type": "array", "items": { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the overlay video clip" }, "startTimeSec": { "type": "number", "description": "Timecode in seconds at which to insert this clip" } }, "required": ["videoUrl", "startTimeSec"] }, "description": "Array of overlay clips to insert at specified timecodes" }, "transition": { "type": "string", "description": "Optional xfade transition effect name between clips" }, "transitionDuration": { "type": "number", "description": "Duration of the transition in seconds" }, "useOverlayAudio": { "type": "boolean", "description": "When true, uses audio from the overlay clips instead of the base video audio during inserts" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["baseVideoUrl", "overlayVideos"] },
|
|
550
557
|
outputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the video with clips inserted" } }, "required": ["videoUrl"] }
|
|
551
558
|
},
|
|
552
559
|
"listDataSources": {
|
|
@@ -613,28 +620,28 @@ var init_metadata = __esm({
|
|
|
613
620
|
stepType: "mergeAudio",
|
|
614
621
|
description: "Merge one or more clips into a single audio file.",
|
|
615
622
|
usageNotes: "",
|
|
616
|
-
inputSchema: { "type": "object", "properties": { "mp3Urls": { "type": "array", "items": { "type": "string" }, "description": "URLs of the MP3 audio clips to merge in order" }, "fileMetadata": { "type": "object", "description": "FFmpeg MP3 metadata key-value pairs to embed in the output file" }, "albumArtUrl": { "type": "string", "description": "URL of an image to embed as album art in the output file" }, "
|
|
623
|
+
inputSchema: { "type": "object", "properties": { "mp3Urls": { "type": "array", "items": { "type": "string" }, "description": "URLs of the MP3 audio clips to merge in order" }, "fileMetadata": { "type": "object", "description": "FFmpeg MP3 metadata key-value pairs to embed in the output file" }, "albumArtUrl": { "type": "string", "description": "URL of an image to embed as album art in the output file" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["mp3Urls"] },
|
|
617
624
|
outputSchema: { "type": "object", "properties": { "audioUrl": { "type": "string", "description": "URL of the merged audio file" } }, "required": ["audioUrl"] }
|
|
618
625
|
},
|
|
619
626
|
"mergeVideos": {
|
|
620
627
|
stepType: "mergeVideos",
|
|
621
628
|
description: "Merge one or more clips into a single video.",
|
|
622
629
|
usageNotes: "",
|
|
623
|
-
inputSchema: { "type": "object", "properties": { "videoUrls": { "type": "array", "items": { "type": "string" }, "description": "URLs of the video clips to merge in order" }, "transition": { "type": "string", "description": "Optional xfade transition effect name" }, "transitionDuration": { "type": "number", "description": "Duration of the transition in seconds" }, "
|
|
630
|
+
inputSchema: { "type": "object", "properties": { "videoUrls": { "type": "array", "items": { "type": "string" }, "description": "URLs of the video clips to merge in order" }, "transition": { "type": "string", "description": "Optional xfade transition effect name" }, "transitionDuration": { "type": "number", "description": "Duration of the transition in seconds" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["videoUrls"] },
|
|
624
631
|
outputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the merged video" } }, "required": ["videoUrl"] }
|
|
625
632
|
},
|
|
626
633
|
"mixAudioIntoVideo": {
|
|
627
634
|
stepType: "mixAudioIntoVideo",
|
|
628
635
|
description: "Mix an audio track into a video",
|
|
629
636
|
usageNotes: "",
|
|
630
|
-
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video" }, "audioUrl": { "type": "string", "description": "URL of the audio track to mix into the video" }, "options": { "type": "object", "properties": { "keepVideoAudio": { "type": "boolean", "description": "When true, preserves the original video audio alongside the new track. Defaults to false." }, "audioGainDb": { "type": "number", "description": "Volume adjustment for the new audio track in decibels. Defaults to 0." }, "videoGainDb": { "type": "number", "description": "Volume adjustment for the existing video audio in decibels. Defaults to 0." }, "loopAudio": { "type": "boolean", "description": "When true, loops the audio track to match the video duration. Defaults to false." } }, "description": "Audio mixing options" }, "
|
|
637
|
+
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video" }, "audioUrl": { "type": "string", "description": "URL of the audio track to mix into the video" }, "options": { "type": "object", "properties": { "keepVideoAudio": { "type": "boolean", "description": "When true, preserves the original video audio alongside the new track. Defaults to false." }, "audioGainDb": { "type": "number", "description": "Volume adjustment for the new audio track in decibels. Defaults to 0." }, "videoGainDb": { "type": "number", "description": "Volume adjustment for the existing video audio in decibels. Defaults to 0." }, "loopAudio": { "type": "boolean", "description": "When true, loops the audio track to match the video duration. Defaults to false." } }, "description": "Audio mixing options" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["videoUrl", "audioUrl", "options"] },
|
|
631
638
|
outputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the video with the mixed audio track" } }, "required": ["videoUrl"] }
|
|
632
639
|
},
|
|
633
640
|
"muteVideo": {
|
|
634
641
|
stepType: "muteVideo",
|
|
635
642
|
description: "Mute a video file",
|
|
636
643
|
usageNotes: "",
|
|
637
|
-
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video to mute" }, "
|
|
644
|
+
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video to mute" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["videoUrl"] },
|
|
638
645
|
outputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the muted video" } }, "required": ["videoUrl"] }
|
|
639
646
|
},
|
|
640
647
|
"n8nRunNode": {
|
|
@@ -668,8 +675,8 @@ var init_metadata = __esm({
|
|
|
668
675
|
"postToLinkedIn": {
|
|
669
676
|
stepType: "postToLinkedIn",
|
|
670
677
|
description: "Create a post on LinkedIn from the connected account.",
|
|
671
|
-
usageNotes: "- Requires a LinkedIn OAuth connection (connectionId).\n- Supports text posts, image posts, and
|
|
672
|
-
inputSchema: { "type": "object", "properties": { "message": { "type": "string", "description": "The text content of the LinkedIn post" }, "visibility": { "enum": ["PUBLIC", "CONNECTIONS"], "type": "string", "description": 'Who can see the post: "PUBLIC" or "CONNECTIONS"' }, "videoUrl": { "type": "string", "description": "URL of a video to attach to the post" }, "
|
|
678
|
+
usageNotes: "- Requires a LinkedIn OAuth connection (connectionId).\n- Supports text posts, image posts, video posts, document posts, and article posts.\n- Attach one media type per post: image, video, document, or article.\n- Documents support PDF, PPT, PPTX, DOC, DOCX (max 100MB, 300 pages). Displays as a slideshow carousel.\n- Articles create a link preview with optional custom title, description, and thumbnail.\n- Visibility controls who can see the post.",
|
|
679
|
+
inputSchema: { "type": "object", "properties": { "message": { "type": "string", "description": "The text content of the LinkedIn post" }, "visibility": { "enum": ["PUBLIC", "CONNECTIONS"], "type": "string", "description": 'Who can see the post: "PUBLIC" or "CONNECTIONS"' }, "imageUrl": { "type": "string", "description": "URL of an image to attach to the post" }, "videoUrl": { "type": "string", "description": "URL of a video to attach to the post" }, "documentUrl": { "type": "string", "description": "URL of a document (PDF, PPT, DOC) to attach to the post" }, "articleUrl": { "type": "string", "description": "URL to share as an article link preview" }, "titleText": { "type": "string", "description": "Title text for media or article attachments" }, "descriptionText": { "type": "string", "description": "Description text for article attachments" }, "connectionId": { "type": "string", "description": "LinkedIn OAuth connection ID" } }, "required": ["message", "visibility"] },
|
|
673
680
|
outputSchema: { "description": "This step does not produce output data." }
|
|
674
681
|
},
|
|
675
682
|
"postToSlackChannel": {
|
|
@@ -693,6 +700,13 @@ var init_metadata = __esm({
|
|
|
693
700
|
inputSchema: { "type": "object", "properties": { "webhookUrl": { "type": "string", "description": "Zapier webhook URL to send data to" }, "input": { "type": "object", "description": "Key-value pairs to send as the JSON POST body" } }, "required": ["webhookUrl", "input"] },
|
|
694
701
|
outputSchema: { "type": "object", "properties": { "data": { "description": "Parsed webhook response from Zapier (JSON object, array, or string)" } }, "required": ["data"] }
|
|
695
702
|
},
|
|
703
|
+
"queryAppDatabase": {
|
|
704
|
+
stepType: "queryAppDatabase",
|
|
705
|
+
description: "Execute a SQL query against the app managed database.",
|
|
706
|
+
usageNotes: '- Executes raw SQL against a SQLite database managed by the app.\n- For SELECT queries, returns rows as JSON.\n- For INSERT/UPDATE/DELETE, returns the number of affected rows.\n- Use {{variables}} directly in your SQL. By default they are automatically extracted\n and passed as safe parameterized values (preventing SQL injection).\n Example: INSERT INTO contacts (name, comment) VALUES ({{name}}, {{comment}})\n- Full MindStudio handlebars syntax is supported, including helpers like {{json myVar}},\n {{get myVar "$.path"}}, {{global.orgName}}, etc.\n- Set parameterize to false for raw/dynamic SQL where variables are interpolated directly\n into the query string. Use this when another step generates full or partial SQL, e.g.\n a bulk INSERT with a precomputed VALUES list. The user is responsible for sanitization\n when parameterize is false.',
|
|
707
|
+
inputSchema: { "type": "object", "properties": { "databaseId": { "type": "string", "description": "Name or ID of the app data database to query" }, "sql": { "type": "string", "description": "SQL query to execute. Use {{variables}} directly in the SQL \u2014 they are handled according to the `parameterize` setting.\n\nWhen parameterize is true (default): {{variables}} are extracted from the SQL, replaced with ? placeholders, resolved via the full MindStudio handlebars pipeline, and passed as safe parameterized values to SQLite. This prevents SQL injection. Example: INSERT INTO contacts (name, email) VALUES ({{name}}, {{email}})\n\nWhen parameterize is false: The entire SQL string is resolved via compileString (standard handlebars interpolation) and executed as-is. Use this for dynamic/generated SQL where another step builds the query. The user is responsible for safety. Example: {{generatedInsertQuery}}\n\nAsk the user for the database schema if they have not already provided it." }, "parameterize": { "type": "boolean", "description": "Whether to treat {{variables}} as parameterized query values (default: true).\n\n- true: {{vars}} are extracted, replaced with ?, and passed as bind params. Safe from SQL injection. Use for standard CRUD operations.\n- false: {{vars}} are interpolated directly into the SQL string via handlebars. Use when another step generates full or partial SQL (e.g. bulk inserts with precomputed VALUES). The user is responsible for sanitization." } }, "required": ["databaseId", "sql"] },
|
|
708
|
+
outputSchema: { "type": "object", "properties": { "rows": { "type": "array", "items": {}, "description": "Result rows for SELECT queries (empty array for write queries)" }, "changes": { "type": "number", "description": "Number of rows affected by INSERT, UPDATE, or DELETE queries (0 for SELECT)" } }, "required": ["rows", "changes"] }
|
|
709
|
+
},
|
|
696
710
|
"queryDataSource": {
|
|
697
711
|
stepType: "queryDataSource",
|
|
698
712
|
description: "Search a vector data source (RAG) and return relevant document chunks.",
|
|
@@ -732,7 +746,7 @@ var init_metadata = __esm({
|
|
|
732
746
|
stepType: "resizeVideo",
|
|
733
747
|
description: "Resize a video file",
|
|
734
748
|
usageNotes: "",
|
|
735
|
-
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video to resize" }, "mode": { "enum": ["fit", "exact"], "type": "string", "description": "Resize mode: 'fit' scales within max dimensions, 'exact' forces exact dimensions" }, "maxWidth": { "type": "number", "description": "Maximum width in pixels (used with 'fit' mode)" }, "maxHeight": { "type": "number", "description": "Maximum height in pixels (used with 'fit' mode)" }, "width": { "type": "number", "description": "Exact width in pixels (used with 'exact' mode)" }, "height": { "type": "number", "description": "Exact height in pixels (used with 'exact' mode)" }, "strategy": { "enum": ["pad", "crop"], "type": "string", "description": "Strategy for handling aspect ratio mismatch in 'exact' mode" }, "
|
|
749
|
+
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video to resize" }, "mode": { "enum": ["fit", "exact"], "type": "string", "description": "Resize mode: 'fit' scales within max dimensions, 'exact' forces exact dimensions" }, "maxWidth": { "type": "number", "description": "Maximum width in pixels (used with 'fit' mode)" }, "maxHeight": { "type": "number", "description": "Maximum height in pixels (used with 'fit' mode)" }, "width": { "type": "number", "description": "Exact width in pixels (used with 'exact' mode)" }, "height": { "type": "number", "description": "Exact height in pixels (used with 'exact' mode)" }, "strategy": { "enum": ["pad", "crop"], "type": "string", "description": "Strategy for handling aspect ratio mismatch in 'exact' mode" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["videoUrl", "mode"] },
|
|
736
750
|
outputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the resized video" } }, "required": ["videoUrl"] }
|
|
737
751
|
},
|
|
738
752
|
"runFromConnectorRegistry": {
|
|
@@ -1028,7 +1042,7 @@ var init_metadata = __esm({
|
|
|
1028
1042
|
stepType: "textToSpeech",
|
|
1029
1043
|
description: "Generate an audio file from provided text using a speech model.",
|
|
1030
1044
|
usageNotes: "- The text field contains the exact words to be spoken (not instructions).\n- In foreground mode, the audio is displayed to the user. In background mode, the URL is saved to a variable.",
|
|
1031
|
-
inputSchema: { "type": "object", "properties": { "text": { "type": "string", "description": "The text to convert to speech" }, "
|
|
1045
|
+
inputSchema: { "type": "object", "properties": { "text": { "type": "string", "description": "The text to convert to speech" }, "intermediateAsset": { "type": "boolean" }, "speechModelOverride": { "type": "object", "properties": { "model": { "type": "string", "description": "Speech synthesis model identifier" }, "config": { "type": "object", "description": "Additional model-specific configuration" } }, "required": ["model"], "description": "Optional model configuration override. Uses the workflow's default speech model if not specified" } }, "required": ["text"] },
|
|
1032
1046
|
outputSchema: { "type": "object", "properties": { "audioUrl": { "type": "string", "description": "URL of the generated audio file" } }, "required": ["audioUrl"] }
|
|
1033
1047
|
},
|
|
1034
1048
|
"transcribeAudio": {
|
|
@@ -1042,7 +1056,7 @@ var init_metadata = __esm({
|
|
|
1042
1056
|
stepType: "trimMedia",
|
|
1043
1057
|
description: "Trim an audio or video clip",
|
|
1044
1058
|
usageNotes: "",
|
|
1045
|
-
inputSchema: { "type": "object", "properties": { "inputUrl": { "type": "string", "description": "URL of the source audio or video file to trim" }, "start": { "type": ["number", "string"] }, "duration": { "type": ["string", "number"] }, "
|
|
1059
|
+
inputSchema: { "type": "object", "properties": { "inputUrl": { "type": "string", "description": "URL of the source audio or video file to trim" }, "start": { "type": ["number", "string"] }, "duration": { "type": ["string", "number"] }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["inputUrl"] },
|
|
1046
1060
|
outputSchema: { "type": "object", "properties": { "mediaUrl": { "type": "string", "description": "URL of the trimmed media file" } }, "required": ["mediaUrl"] }
|
|
1047
1061
|
},
|
|
1048
1062
|
"updateGmailLabels": {
|
|
@@ -1091,7 +1105,7 @@ var init_metadata = __esm({
|
|
|
1091
1105
|
stepType: "upscaleVideo",
|
|
1092
1106
|
description: "Upscale a video file",
|
|
1093
1107
|
usageNotes: "",
|
|
1094
|
-
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video to upscale" }, "targetResolution": { "enum": ["720p", "1080p", "2K", "4K"], "type": "string", "description": "Target output resolution for the upscaled video" }, "engine": { "enum": ["standard", "pro", "ultimate", "flashvsr", "seedance", "seedvr2", "runwayml/upscale-v1"], "type": "string", "description": "Upscaling engine to use. Higher tiers produce better quality at higher cost." }, "
|
|
1108
|
+
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video to upscale" }, "targetResolution": { "enum": ["720p", "1080p", "2K", "4K"], "type": "string", "description": "Target output resolution for the upscaled video" }, "engine": { "enum": ["standard", "pro", "ultimate", "flashvsr", "seedance", "seedvr2", "runwayml/upscale-v1"], "type": "string", "description": "Upscaling engine to use. Higher tiers produce better quality at higher cost." }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["videoUrl", "targetResolution", "engine"] },
|
|
1095
1109
|
outputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the upscaled video" } }, "required": ["videoUrl"] }
|
|
1096
1110
|
},
|
|
1097
1111
|
"userMessage": {
|
|
@@ -1108,35 +1122,35 @@ var init_metadata = __esm({
|
|
|
1108
1122
|
stepType: "videoFaceSwap",
|
|
1109
1123
|
description: "Swap faces in a video file",
|
|
1110
1124
|
usageNotes: "",
|
|
1111
|
-
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video containing faces to swap" }, "faceImageUrl": { "type": "string", "description": "URL of the image containing the replacement face" }, "targetIndex": { "type": "number", "description": "Zero-based index of the face to replace in the video" }, "engine": { "type": "string", "description": "Face swap engine to use" }, "
|
|
1125
|
+
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video containing faces to swap" }, "faceImageUrl": { "type": "string", "description": "URL of the image containing the replacement face" }, "targetIndex": { "type": "number", "description": "Zero-based index of the face to replace in the video" }, "engine": { "type": "string", "description": "Face swap engine to use" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["videoUrl", "faceImageUrl", "targetIndex", "engine"] },
|
|
1112
1126
|
outputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the face-swapped video" } }, "required": ["videoUrl"] }
|
|
1113
1127
|
},
|
|
1114
1128
|
"videoRemoveBackground": {
|
|
1115
1129
|
stepType: "videoRemoveBackground",
|
|
1116
1130
|
description: "Remove or replace background from a video",
|
|
1117
1131
|
usageNotes: "",
|
|
1118
|
-
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video" }, "newBackground": { "enum": ["transparent", "image"], "type": "string", "description": "Whether to make the background transparent or replace it with an image" }, "newBackgroundImageUrl": { "type": "string", "description": "URL of a replacement background image. Required when newBackground is 'image'." }, "engine": { "type": "string", "description": "Background removal engine to use" }, "
|
|
1132
|
+
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video" }, "newBackground": { "enum": ["transparent", "image"], "type": "string", "description": "Whether to make the background transparent or replace it with an image" }, "newBackgroundImageUrl": { "type": "string", "description": "URL of a replacement background image. Required when newBackground is 'image'." }, "engine": { "type": "string", "description": "Background removal engine to use" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["videoUrl", "newBackground", "engine"] },
|
|
1119
1133
|
outputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the video with background removed or replaced" } }, "required": ["videoUrl"] }
|
|
1120
1134
|
},
|
|
1121
1135
|
"videoRemoveWatermark": {
|
|
1122
1136
|
stepType: "videoRemoveWatermark",
|
|
1123
1137
|
description: "Remove a watermark from a video",
|
|
1124
1138
|
usageNotes: "",
|
|
1125
|
-
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video containing a watermark" }, "engine": { "type": "string", "description": "Watermark removal engine to use" }, "
|
|
1139
|
+
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video containing a watermark" }, "engine": { "type": "string", "description": "Watermark removal engine to use" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["videoUrl", "engine"] },
|
|
1126
1140
|
outputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the video with watermark removed" } }, "required": ["videoUrl"] }
|
|
1127
1141
|
},
|
|
1128
1142
|
"watermarkImage": {
|
|
1129
1143
|
stepType: "watermarkImage",
|
|
1130
1144
|
description: "Overlay a watermark image onto another image.",
|
|
1131
1145
|
usageNotes: "- The watermark is placed at the specified corner with configurable padding and width.",
|
|
1132
|
-
inputSchema: { "type": "object", "properties": { "imageUrl": { "type": "string", "description": "URL of the base image" }, "watermarkImageUrl": { "type": "string", "description": "URL of the watermark image to overlay" }, "corner": { "enum": ["top-left", "top-right", "bottom-left", "bottom-right"], "type": "string", "description": "Corner position for the watermark placement" }, "paddingPx": { "type": "number", "description": "Padding from the corner in pixels" }, "widthPx": { "type": "number", "description": "Width of the watermark overlay in pixels" }, "
|
|
1146
|
+
inputSchema: { "type": "object", "properties": { "imageUrl": { "type": "string", "description": "URL of the base image" }, "watermarkImageUrl": { "type": "string", "description": "URL of the watermark image to overlay" }, "corner": { "enum": ["top-left", "top-right", "bottom-left", "bottom-right"], "type": "string", "description": "Corner position for the watermark placement" }, "paddingPx": { "type": "number", "description": "Padding from the corner in pixels" }, "widthPx": { "type": "number", "description": "Width of the watermark overlay in pixels" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["imageUrl", "watermarkImageUrl", "corner", "paddingPx", "widthPx"] },
|
|
1133
1147
|
outputSchema: { "type": "object", "properties": { "imageUrl": { "type": "string", "description": "CDN URL of the watermarked image" } }, "required": ["imageUrl"] }
|
|
1134
1148
|
},
|
|
1135
1149
|
"watermarkVideo": {
|
|
1136
1150
|
stepType: "watermarkVideo",
|
|
1137
1151
|
description: "Add an image watermark to a video",
|
|
1138
1152
|
usageNotes: "",
|
|
1139
|
-
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video" }, "imageUrl": { "type": "string", "description": "URL of the watermark image to overlay" }, "corner": { "enum": ["top-left", "top-right", "bottom-left", "bottom-right"], "type": "string", "description": "Corner position for the watermark placement" }, "paddingPx": { "type": "number", "description": "Padding from the corner in pixels" }, "widthPx": { "type": "number", "description": "Width of the watermark overlay in pixels" }, "
|
|
1153
|
+
inputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the source video" }, "imageUrl": { "type": "string", "description": "URL of the watermark image to overlay" }, "corner": { "enum": ["top-left", "top-right", "bottom-left", "bottom-right"], "type": "string", "description": "Corner position for the watermark placement" }, "paddingPx": { "type": "number", "description": "Padding from the corner in pixels" }, "widthPx": { "type": "number", "description": "Width of the watermark overlay in pixels" }, "intermediateAsset": { "type": "boolean", "description": "When true, the asset is created but hidden from the user's gallery (tagged as intermediate)" } }, "required": ["videoUrl", "imageUrl", "corner", "paddingPx", "widthPx"] },
|
|
1140
1154
|
outputSchema: { "type": "object", "properties": { "videoUrl": { "type": "string", "description": "URL of the watermarked video" } }, "required": ["videoUrl"] }
|
|
1141
1155
|
}
|
|
1142
1156
|
};
|
|
@@ -1324,230 +1338,1606 @@ var init_config = __esm({
|
|
|
1324
1338
|
}
|
|
1325
1339
|
});
|
|
1326
1340
|
|
|
1327
|
-
// src/
|
|
1328
|
-
var
|
|
1329
|
-
|
|
1330
|
-
|
|
1341
|
+
// src/auth/index.ts
|
|
1342
|
+
var AuthContext, Roles;
|
|
1343
|
+
var init_auth = __esm({
|
|
1344
|
+
"src/auth/index.ts"() {
|
|
1345
|
+
"use strict";
|
|
1346
|
+
init_errors();
|
|
1347
|
+
AuthContext = class {
|
|
1348
|
+
/** The current user's ID. */
|
|
1349
|
+
userId;
|
|
1350
|
+
/** The current user's roles in this app. */
|
|
1351
|
+
roles;
|
|
1352
|
+
/** All role assignments for this app (all users, all roles). */
|
|
1353
|
+
_roleAssignments;
|
|
1354
|
+
constructor(ctx) {
|
|
1355
|
+
this.userId = ctx.userId;
|
|
1356
|
+
this._roleAssignments = ctx.roleAssignments;
|
|
1357
|
+
this.roles = ctx.roleAssignments.filter((a) => a.userId === ctx.userId).map((a) => a.roleName);
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Check if the current user has **any** of the given roles.
|
|
1361
|
+
* Returns true if at least one matches.
|
|
1362
|
+
*
|
|
1363
|
+
* @example
|
|
1364
|
+
* ```ts
|
|
1365
|
+
* if (auth.hasRole(Roles.admin, Roles.approver)) {
|
|
1366
|
+
* // user is an admin OR an approver
|
|
1367
|
+
* }
|
|
1368
|
+
* ```
|
|
1369
|
+
*/
|
|
1370
|
+
hasRole(...roles) {
|
|
1371
|
+
return roles.some((r) => this.roles.includes(r));
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Require the current user to have at least one of the given roles.
|
|
1375
|
+
* Throws a `MindStudioError` with code `'forbidden'` and status 403
|
|
1376
|
+
* if the user lacks all of the specified roles.
|
|
1377
|
+
*
|
|
1378
|
+
* Use this at the top of route handlers to gate access.
|
|
1379
|
+
*
|
|
1380
|
+
* @example
|
|
1381
|
+
* ```ts
|
|
1382
|
+
* auth.requireRole(Roles.admin);
|
|
1383
|
+
* // code below only runs if user is an admin
|
|
1384
|
+
* ```
|
|
1385
|
+
*/
|
|
1386
|
+
requireRole(...roles) {
|
|
1387
|
+
if (!this.hasRole(...roles)) {
|
|
1388
|
+
throw new MindStudioError(
|
|
1389
|
+
`User does not have required role: ${roles.join(", ")}`,
|
|
1390
|
+
"forbidden",
|
|
1391
|
+
403
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Get all user IDs that have the given role in this app.
|
|
1397
|
+
* Synchronous — scans the preloaded role assignments.
|
|
1398
|
+
*
|
|
1399
|
+
* @example
|
|
1400
|
+
* ```ts
|
|
1401
|
+
* const reviewers = auth.getUsersByRole(Roles.reviewer);
|
|
1402
|
+
* // ['user-id-1', 'user-id-2', ...]
|
|
1403
|
+
* ```
|
|
1404
|
+
*/
|
|
1405
|
+
getUsersByRole(role) {
|
|
1406
|
+
return this._roleAssignments.filter((a) => a.roleName === role).map((a) => a.userId);
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
Roles = new Proxy(
|
|
1410
|
+
{},
|
|
1411
|
+
{
|
|
1412
|
+
get(_, prop) {
|
|
1413
|
+
if (typeof prop === "string") return prop;
|
|
1414
|
+
return void 0;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1331
1419
|
});
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
};
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
};
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
return
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
return
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
return
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
return
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1420
|
+
|
|
1421
|
+
// src/db/sql.ts
|
|
1422
|
+
function escapeValue(val) {
|
|
1423
|
+
if (val === null || val === void 0) return "NULL";
|
|
1424
|
+
if (typeof val === "boolean") return val ? "1" : "0";
|
|
1425
|
+
if (typeof val === "number") return String(val);
|
|
1426
|
+
if (typeof val === "string") return `'${val.replace(/'/g, "''")}'`;
|
|
1427
|
+
const json = JSON.stringify(val);
|
|
1428
|
+
return `'${json.replace(/'/g, "''")}'`;
|
|
1429
|
+
}
|
|
1430
|
+
function serializeValue(val, columnName, columns) {
|
|
1431
|
+
const col = columns.find((c) => c.name === columnName);
|
|
1432
|
+
if (col?.type === "user" && typeof val === "string") {
|
|
1433
|
+
return escapeValue(`@@user@@${val}`);
|
|
1434
|
+
}
|
|
1435
|
+
return escapeValue(val);
|
|
1436
|
+
}
|
|
1437
|
+
function deserializeRow(row, columns) {
|
|
1438
|
+
const result = {};
|
|
1439
|
+
for (const [key, value] of Object.entries(row)) {
|
|
1440
|
+
const col = columns.find((c) => c.name === key);
|
|
1441
|
+
if (col?.type === "user" && typeof value === "string" && value.startsWith(USER_PREFIX)) {
|
|
1442
|
+
result[key] = value.slice(USER_PREFIX.length);
|
|
1443
|
+
} else if (col?.type === "json" && typeof value === "string") {
|
|
1444
|
+
try {
|
|
1445
|
+
result[key] = JSON.parse(value);
|
|
1446
|
+
} catch {
|
|
1447
|
+
result[key] = value;
|
|
1448
|
+
}
|
|
1449
|
+
} else {
|
|
1450
|
+
result[key] = value;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
return result;
|
|
1454
|
+
}
|
|
1455
|
+
function buildSelect(table, options = {}) {
|
|
1456
|
+
let sql = `SELECT * FROM ${table}`;
|
|
1457
|
+
if (options.where) sql += ` WHERE ${options.where}`;
|
|
1458
|
+
if (options.orderBy) sql += ` ORDER BY ${options.orderBy}${options.desc ? " DESC" : " ASC"}`;
|
|
1459
|
+
if (options.limit != null) sql += ` LIMIT ${options.limit}`;
|
|
1460
|
+
if (options.offset != null) sql += ` OFFSET ${options.offset}`;
|
|
1461
|
+
return sql;
|
|
1462
|
+
}
|
|
1463
|
+
function buildCount(table, where) {
|
|
1464
|
+
let sql = `SELECT COUNT(*) as count FROM ${table}`;
|
|
1465
|
+
if (where) sql += ` WHERE ${where}`;
|
|
1466
|
+
return sql;
|
|
1467
|
+
}
|
|
1468
|
+
function buildExists(table, where, negate) {
|
|
1469
|
+
const inner = where ? `SELECT 1 FROM ${table} WHERE ${where}` : `SELECT 1 FROM ${table}`;
|
|
1470
|
+
const fn = negate ? "NOT EXISTS" : "EXISTS";
|
|
1471
|
+
return `SELECT ${fn}(${inner}) as result`;
|
|
1472
|
+
}
|
|
1473
|
+
function buildInsert(table, data, columns) {
|
|
1474
|
+
const filtered = stripSystemColumns(data);
|
|
1475
|
+
const keys = Object.keys(filtered);
|
|
1476
|
+
const vals = keys.map((k) => serializeValue(filtered[k], k, columns));
|
|
1477
|
+
return `INSERT INTO ${table} (${keys.join(", ")}) VALUES (${vals.join(", ")})`;
|
|
1478
|
+
}
|
|
1479
|
+
function buildUpdate(table, id, data, columns) {
|
|
1480
|
+
const filtered = stripSystemColumns(data);
|
|
1481
|
+
const assignments = Object.entries(filtered).map(([k, v]) => `${k} = ${serializeValue(v, k, columns)}`).join(", ");
|
|
1482
|
+
return `UPDATE ${table} SET ${assignments} WHERE id = ${escapeValue(id)}`;
|
|
1483
|
+
}
|
|
1484
|
+
function buildDelete(table, where) {
|
|
1485
|
+
let sql = `DELETE FROM ${table}`;
|
|
1486
|
+
if (where) sql += ` WHERE ${where}`;
|
|
1487
|
+
return sql;
|
|
1488
|
+
}
|
|
1489
|
+
function stripSystemColumns(data) {
|
|
1490
|
+
const result = {};
|
|
1491
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1492
|
+
if (!SYSTEM_COLUMNS.has(key)) {
|
|
1493
|
+
result[key] = value;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
return result;
|
|
1497
|
+
}
|
|
1498
|
+
var USER_PREFIX, SYSTEM_COLUMNS;
|
|
1499
|
+
var init_sql = __esm({
|
|
1500
|
+
"src/db/sql.ts"() {
|
|
1501
|
+
"use strict";
|
|
1502
|
+
USER_PREFIX = "@@user@@";
|
|
1503
|
+
SYSTEM_COLUMNS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "lastUpdatedBy"]);
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
|
|
1507
|
+
// src/db/predicate.ts
|
|
1508
|
+
function compilePredicate(fn) {
|
|
1509
|
+
try {
|
|
1510
|
+
const source = fn.toString();
|
|
1511
|
+
const paramName = extractParamName(source);
|
|
1512
|
+
if (!paramName) return { type: "js", fn };
|
|
1513
|
+
const body = extractBody(source);
|
|
1514
|
+
if (!body) return { type: "js", fn };
|
|
1515
|
+
const tokens = tokenize(body);
|
|
1516
|
+
if (tokens.length === 0) return { type: "js", fn };
|
|
1517
|
+
const parser = new Parser(tokens, paramName, fn);
|
|
1518
|
+
const ast = parser.parseExpression();
|
|
1519
|
+
if (!ast) return { type: "js", fn };
|
|
1520
|
+
if (parser.pos < tokens.length) return { type: "js", fn };
|
|
1521
|
+
const where = compileNode(ast);
|
|
1522
|
+
if (!where) return { type: "js", fn };
|
|
1523
|
+
return { type: "sql", where };
|
|
1524
|
+
} catch {
|
|
1525
|
+
return { type: "js", fn };
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
function extractParamName(source) {
|
|
1529
|
+
const match = source.match(
|
|
1530
|
+
/^\s*(?:\(?\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?::[^)]*?)?\)?\s*=>)/
|
|
1531
|
+
);
|
|
1532
|
+
return match?.[1] ?? null;
|
|
1533
|
+
}
|
|
1534
|
+
function extractBody(source) {
|
|
1535
|
+
const arrowIdx = source.indexOf("=>");
|
|
1536
|
+
if (arrowIdx === -1) return null;
|
|
1537
|
+
let body = source.slice(arrowIdx + 2).trim();
|
|
1538
|
+
if (body.startsWith("{")) {
|
|
1539
|
+
const match = body.match(/^\{\s*return\s+([\s\S]+?)\s*;?\s*\}$/);
|
|
1540
|
+
if (!match) return null;
|
|
1541
|
+
body = match[1];
|
|
1542
|
+
}
|
|
1543
|
+
return body.trim() || null;
|
|
1544
|
+
}
|
|
1545
|
+
function tokenize(expr) {
|
|
1546
|
+
const tokens = [];
|
|
1547
|
+
let i = 0;
|
|
1548
|
+
while (i < expr.length) {
|
|
1549
|
+
const ch = expr[i];
|
|
1550
|
+
if (/\s/.test(ch)) {
|
|
1551
|
+
i++;
|
|
1552
|
+
continue;
|
|
1553
|
+
}
|
|
1554
|
+
if (ch === "'" || ch === '"') {
|
|
1555
|
+
const quote = ch;
|
|
1556
|
+
let str = "";
|
|
1557
|
+
i++;
|
|
1558
|
+
while (i < expr.length && expr[i] !== quote) {
|
|
1559
|
+
if (expr[i] === "\\") {
|
|
1560
|
+
i++;
|
|
1561
|
+
if (i < expr.length) str += expr[i];
|
|
1562
|
+
} else {
|
|
1563
|
+
str += expr[i];
|
|
1564
|
+
}
|
|
1565
|
+
i++;
|
|
1566
|
+
}
|
|
1567
|
+
if (i >= expr.length) return [];
|
|
1568
|
+
i++;
|
|
1569
|
+
tokens.push({ type: "string", value: str });
|
|
1570
|
+
continue;
|
|
1571
|
+
}
|
|
1572
|
+
if (ch === "`") return [];
|
|
1573
|
+
if (/[0-9]/.test(ch) || ch === "-" && i + 1 < expr.length && /[0-9]/.test(expr[i + 1])) {
|
|
1574
|
+
let num = ch;
|
|
1575
|
+
i++;
|
|
1576
|
+
while (i < expr.length && /[0-9.]/.test(expr[i])) {
|
|
1577
|
+
num += expr[i];
|
|
1578
|
+
i++;
|
|
1579
|
+
}
|
|
1580
|
+
tokens.push({ type: "number", value: num });
|
|
1581
|
+
continue;
|
|
1582
|
+
}
|
|
1583
|
+
if (expr.slice(i, i + 3) === "===" || expr.slice(i, i + 3) === "!==") {
|
|
1584
|
+
tokens.push({ type: "operator", value: expr.slice(i, i + 3) });
|
|
1585
|
+
i += 3;
|
|
1586
|
+
continue;
|
|
1587
|
+
}
|
|
1588
|
+
if (expr.slice(i, i + 2) === "==" || expr.slice(i, i + 2) === "!=" || expr.slice(i, i + 2) === "<=" || expr.slice(i, i + 2) === ">=" || expr.slice(i, i + 2) === "&&" || expr.slice(i, i + 2) === "||") {
|
|
1589
|
+
tokens.push({ type: "operator", value: expr.slice(i, i + 2) });
|
|
1590
|
+
i += 2;
|
|
1591
|
+
continue;
|
|
1592
|
+
}
|
|
1593
|
+
if (ch === "!" || ch === "<" || ch === ">") {
|
|
1594
|
+
tokens.push({ type: "operator", value: ch });
|
|
1595
|
+
i++;
|
|
1596
|
+
continue;
|
|
1597
|
+
}
|
|
1598
|
+
if (ch === ".") {
|
|
1599
|
+
tokens.push({ type: "dot", value: "." });
|
|
1600
|
+
i++;
|
|
1601
|
+
continue;
|
|
1602
|
+
}
|
|
1603
|
+
if (ch === "(") {
|
|
1604
|
+
tokens.push({ type: "lparen", value: "(" });
|
|
1605
|
+
i++;
|
|
1606
|
+
continue;
|
|
1607
|
+
}
|
|
1608
|
+
if (ch === ")") {
|
|
1609
|
+
tokens.push({ type: "rparen", value: ")" });
|
|
1610
|
+
i++;
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
if (ch === "[") {
|
|
1614
|
+
tokens.push({ type: "lbracket", value: "[" });
|
|
1615
|
+
i++;
|
|
1616
|
+
continue;
|
|
1617
|
+
}
|
|
1618
|
+
if (ch === "]") {
|
|
1619
|
+
tokens.push({ type: "rbracket", value: "]" });
|
|
1620
|
+
i++;
|
|
1621
|
+
continue;
|
|
1622
|
+
}
|
|
1623
|
+
if (ch === ",") {
|
|
1624
|
+
tokens.push({ type: "comma", value: "," });
|
|
1625
|
+
i++;
|
|
1626
|
+
continue;
|
|
1627
|
+
}
|
|
1628
|
+
if (/[a-zA-Z_$]/.test(ch)) {
|
|
1629
|
+
let ident = ch;
|
|
1630
|
+
i++;
|
|
1631
|
+
while (i < expr.length && /[a-zA-Z0-9_$]/.test(expr[i])) {
|
|
1632
|
+
ident += expr[i];
|
|
1633
|
+
i++;
|
|
1634
|
+
}
|
|
1635
|
+
tokens.push({ type: "identifier", value: ident });
|
|
1636
|
+
continue;
|
|
1637
|
+
}
|
|
1638
|
+
return [];
|
|
1639
|
+
}
|
|
1640
|
+
return tokens;
|
|
1641
|
+
}
|
|
1642
|
+
function compileNode(node) {
|
|
1643
|
+
switch (node.kind) {
|
|
1644
|
+
case "comparison":
|
|
1645
|
+
return `${node.field} ${node.operator} ${escapeValue(node.value)}`;
|
|
1646
|
+
case "nullCheck":
|
|
1647
|
+
return `${node.field} ${node.isNull ? "IS NULL" : "IS NOT NULL"}`;
|
|
1648
|
+
case "in": {
|
|
1649
|
+
if (node.values.length === 0) return "0";
|
|
1650
|
+
const vals = node.values.map(escapeValue).join(", ");
|
|
1651
|
+
return `${node.field} IN (${vals})`;
|
|
1652
|
+
}
|
|
1653
|
+
case "like":
|
|
1654
|
+
return `${node.field} LIKE ${escapeValue(node.pattern)}`;
|
|
1655
|
+
case "booleanField":
|
|
1656
|
+
return node.negated ? `${node.field} = 0` : `${node.field} = 1`;
|
|
1657
|
+
case "logical": {
|
|
1658
|
+
const left = compileNode(node.left);
|
|
1659
|
+
const right = compileNode(node.right);
|
|
1660
|
+
if (!left || !right) return null;
|
|
1661
|
+
return `(${left} ${node.operator} ${right})`;
|
|
1662
|
+
}
|
|
1663
|
+
case "not": {
|
|
1664
|
+
const inner = compileNode(node.operand);
|
|
1665
|
+
if (!inner) return null;
|
|
1666
|
+
return `NOT (${inner})`;
|
|
1667
|
+
}
|
|
1668
|
+
default:
|
|
1669
|
+
return null;
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
function isComparisonOp(value) {
|
|
1673
|
+
return value in JS_TO_SQL_OP;
|
|
1674
|
+
}
|
|
1675
|
+
var Parser, JS_TO_SQL_OP, PARSE_FAILED;
|
|
1676
|
+
var init_predicate = __esm({
|
|
1677
|
+
"src/db/predicate.ts"() {
|
|
1678
|
+
"use strict";
|
|
1679
|
+
init_sql();
|
|
1680
|
+
Parser = class {
|
|
1681
|
+
constructor(tokens, paramName, originalFn) {
|
|
1682
|
+
this.tokens = tokens;
|
|
1683
|
+
this.paramName = paramName;
|
|
1684
|
+
this.originalFn = originalFn;
|
|
1685
|
+
}
|
|
1686
|
+
pos = 0;
|
|
1687
|
+
/** Peek at the current token without consuming it. */
|
|
1688
|
+
peek() {
|
|
1689
|
+
return this.tokens[this.pos];
|
|
1690
|
+
}
|
|
1691
|
+
/** Consume the current token and advance. */
|
|
1692
|
+
advance() {
|
|
1693
|
+
return this.tokens[this.pos++];
|
|
1694
|
+
}
|
|
1695
|
+
/** Check if the current token matches an expected type and value. */
|
|
1696
|
+
match(type, value) {
|
|
1697
|
+
const t = this.peek();
|
|
1698
|
+
if (!t) return false;
|
|
1699
|
+
if (t.type !== type) return false;
|
|
1700
|
+
if (value !== void 0 && t.value !== value) return false;
|
|
1701
|
+
return true;
|
|
1702
|
+
}
|
|
1703
|
+
/** Consume a token if it matches, otherwise return false. */
|
|
1704
|
+
eat(type, value) {
|
|
1705
|
+
if (this.match(type, value)) {
|
|
1706
|
+
this.advance();
|
|
1707
|
+
return true;
|
|
1708
|
+
}
|
|
1709
|
+
return false;
|
|
1710
|
+
}
|
|
1711
|
+
// --- Grammar rules ---
|
|
1712
|
+
/** Entry point: parse a full expression. */
|
|
1713
|
+
parseExpression() {
|
|
1714
|
+
return this.parseOr();
|
|
1715
|
+
}
|
|
1716
|
+
/** or_expr → and_expr ( '||' and_expr )* */
|
|
1717
|
+
parseOr() {
|
|
1718
|
+
let left = this.parseAnd();
|
|
1719
|
+
if (!left) return null;
|
|
1720
|
+
while (this.match("operator", "||")) {
|
|
1721
|
+
this.advance();
|
|
1722
|
+
const right = this.parseAnd();
|
|
1723
|
+
if (!right) return null;
|
|
1724
|
+
left = { kind: "logical", operator: "OR", left, right };
|
|
1725
|
+
}
|
|
1726
|
+
return left;
|
|
1727
|
+
}
|
|
1728
|
+
/** and_expr → not_expr ( '&&' not_expr )* */
|
|
1729
|
+
parseAnd() {
|
|
1730
|
+
let left = this.parseNot();
|
|
1731
|
+
if (!left) return null;
|
|
1732
|
+
while (this.match("operator", "&&")) {
|
|
1733
|
+
this.advance();
|
|
1734
|
+
const right = this.parseNot();
|
|
1735
|
+
if (!right) return null;
|
|
1736
|
+
left = { kind: "logical", operator: "AND", left, right };
|
|
1737
|
+
}
|
|
1738
|
+
return left;
|
|
1739
|
+
}
|
|
1740
|
+
/** not_expr → '!' not_expr | primary */
|
|
1741
|
+
parseNot() {
|
|
1742
|
+
if (this.match("operator", "!")) {
|
|
1743
|
+
this.advance();
|
|
1744
|
+
if (this.match("lparen")) {
|
|
1745
|
+
this.advance();
|
|
1746
|
+
const inner2 = this.parseExpression();
|
|
1747
|
+
if (!inner2) return null;
|
|
1748
|
+
if (!this.eat("rparen")) return null;
|
|
1749
|
+
return { kind: "not", operand: inner2 };
|
|
1750
|
+
}
|
|
1751
|
+
const inner = this.parsePrimary();
|
|
1752
|
+
if (!inner) return null;
|
|
1753
|
+
if (inner.kind === "booleanField") {
|
|
1754
|
+
return { ...inner, negated: !inner.negated };
|
|
1755
|
+
}
|
|
1756
|
+
return { kind: "not", operand: inner };
|
|
1757
|
+
}
|
|
1758
|
+
return this.parsePrimary();
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* primary → field_comparison | null_check | includes_expr | paren_expr | boolean_field
|
|
1762
|
+
*
|
|
1763
|
+
* This is the workhorse — handles the different patterns that can appear
|
|
1764
|
+
* as atomic expressions within a larger &&/|| combination.
|
|
1765
|
+
*/
|
|
1766
|
+
parsePrimary() {
|
|
1767
|
+
if (this.match("lparen")) {
|
|
1768
|
+
this.advance();
|
|
1769
|
+
const inner = this.parseExpression();
|
|
1770
|
+
if (!inner) return null;
|
|
1771
|
+
if (!this.eat("rparen")) return null;
|
|
1772
|
+
return inner;
|
|
1773
|
+
}
|
|
1774
|
+
if (this.match("lbracket")) {
|
|
1775
|
+
return this.parseArrayIncludes();
|
|
1776
|
+
}
|
|
1777
|
+
if (this.match("identifier", this.paramName)) {
|
|
1778
|
+
return this.parseFieldExpression();
|
|
1779
|
+
}
|
|
1780
|
+
if (this.match("identifier")) {
|
|
1781
|
+
return this.parseNonParamExpression();
|
|
1782
|
+
}
|
|
1783
|
+
return null;
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Parse an expression that starts with the parameter name (e.g. `o.field`).
|
|
1787
|
+
*
|
|
1788
|
+
* Could be:
|
|
1789
|
+
* - `o.field === value` (comparison)
|
|
1790
|
+
* - `o.field != null` (null check)
|
|
1791
|
+
* - `o.field.includes('text')` (LIKE)
|
|
1792
|
+
* - `o.field` alone (boolean field check)
|
|
1793
|
+
*/
|
|
1794
|
+
parseFieldExpression() {
|
|
1795
|
+
this.advance();
|
|
1796
|
+
const field = this.parseFieldPath();
|
|
1797
|
+
if (!field) return null;
|
|
1798
|
+
const next = this.peek();
|
|
1799
|
+
if (next?.type === "dot" && this.lookAheadForIncludes()) {
|
|
1800
|
+
return this.parseFieldIncludes(field);
|
|
1801
|
+
}
|
|
1802
|
+
if (next?.type === "operator" && isComparisonOp(next.value)) {
|
|
1803
|
+
return this.parseComparison(field);
|
|
1804
|
+
}
|
|
1805
|
+
return { kind: "booleanField", field, negated: false };
|
|
1806
|
+
}
|
|
1807
|
+
/**
|
|
1808
|
+
* Parse a dot-separated field path after the parameter name.
|
|
1809
|
+
* `o.status` → `"status"`, `o.address.city` → `"json_extract(address, '$.city')"`.
|
|
1810
|
+
*/
|
|
1811
|
+
parseFieldPath() {
|
|
1812
|
+
if (!this.eat("dot")) return null;
|
|
1813
|
+
if (!this.match("identifier")) return null;
|
|
1814
|
+
const parts = [this.advance().value];
|
|
1815
|
+
while (this.match("dot") && this.tokens[this.pos + 1]?.type === "identifier") {
|
|
1816
|
+
this.advance();
|
|
1817
|
+
parts.push(this.advance().value);
|
|
1818
|
+
}
|
|
1819
|
+
if (parts.length === 1) {
|
|
1820
|
+
return parts[0];
|
|
1821
|
+
}
|
|
1822
|
+
const root = parts[0];
|
|
1823
|
+
const jsonPath = "$." + parts.slice(1).join(".");
|
|
1824
|
+
return `json_extract(${root}, '${jsonPath}')`;
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Parse a comparison: `field OP value`.
|
|
1828
|
+
* The field has already been parsed; we need the operator and right-hand value.
|
|
1829
|
+
*/
|
|
1830
|
+
parseComparison(field) {
|
|
1831
|
+
const opToken = this.advance();
|
|
1832
|
+
const jsOp = opToken.value;
|
|
1833
|
+
const value = this.parseValue();
|
|
1834
|
+
if (value === PARSE_FAILED) return null;
|
|
1835
|
+
if (value === null || value === void 0) {
|
|
1836
|
+
if (jsOp === "===" || jsOp === "==") {
|
|
1837
|
+
return { kind: "nullCheck", field, isNull: true };
|
|
1838
|
+
}
|
|
1839
|
+
if (jsOp === "!==" || jsOp === "!=") {
|
|
1840
|
+
return { kind: "nullCheck", field, isNull: false };
|
|
1841
|
+
}
|
|
1842
|
+
return null;
|
|
1843
|
+
}
|
|
1844
|
+
const sqlOp = JS_TO_SQL_OP[jsOp];
|
|
1845
|
+
if (!sqlOp) return null;
|
|
1846
|
+
return { kind: "comparison", field, operator: sqlOp, value };
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Parse `o.field.includes('text')` → LIKE expression.
|
|
1850
|
+
* The field name has already been parsed.
|
|
1851
|
+
*/
|
|
1852
|
+
parseFieldIncludes(field) {
|
|
1853
|
+
this.advance();
|
|
1854
|
+
this.advance();
|
|
1855
|
+
if (!this.eat("lparen")) return null;
|
|
1856
|
+
const value = this.parseValue();
|
|
1857
|
+
if (value === PARSE_FAILED || typeof value !== "string") return null;
|
|
1858
|
+
if (!this.eat("rparen")) return null;
|
|
1859
|
+
const escaped = value.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
1860
|
+
return { kind: "like", field, pattern: `%${escaped}%` };
|
|
1861
|
+
}
|
|
1862
|
+
/**
|
|
1863
|
+
* Parse `['a', 'b', 'c'].includes(o.field)` → IN expression.
|
|
1864
|
+
* The opening bracket has been peeked but not consumed.
|
|
1865
|
+
*/
|
|
1866
|
+
parseArrayIncludes() {
|
|
1867
|
+
this.advance();
|
|
1868
|
+
const values = [];
|
|
1869
|
+
while (!this.match("rbracket")) {
|
|
1870
|
+
if (values.length > 0) {
|
|
1871
|
+
if (!this.eat("comma")) return null;
|
|
1872
|
+
}
|
|
1873
|
+
const val = this.parseValue();
|
|
1874
|
+
if (val === PARSE_FAILED) return null;
|
|
1875
|
+
values.push(val);
|
|
1876
|
+
}
|
|
1877
|
+
this.advance();
|
|
1878
|
+
if (!this.eat("dot")) return null;
|
|
1879
|
+
if (!this.match("identifier", "includes")) return null;
|
|
1880
|
+
this.advance();
|
|
1881
|
+
if (!this.eat("lparen")) return null;
|
|
1882
|
+
if (!this.match("identifier", this.paramName)) return null;
|
|
1883
|
+
this.advance();
|
|
1884
|
+
const field = this.parseFieldPath();
|
|
1885
|
+
if (!field) return null;
|
|
1886
|
+
if (!this.eat("rparen")) return null;
|
|
1887
|
+
return { kind: "in", field, values };
|
|
1888
|
+
}
|
|
1889
|
+
/**
|
|
1890
|
+
* Parse an expression that starts with an identifier that is NOT the
|
|
1891
|
+
* parameter name. This could be:
|
|
1892
|
+
* - A keyword literal: `true`, `false`, `null`, `undefined`
|
|
1893
|
+
* - A closure variable used in a comparison (handled by backtracking)
|
|
1894
|
+
*/
|
|
1895
|
+
parseNonParamExpression() {
|
|
1896
|
+
const ident = this.peek().value;
|
|
1897
|
+
if (ident === "true" || ident === "false") return null;
|
|
1898
|
+
return null;
|
|
1899
|
+
}
|
|
1900
|
+
/**
|
|
1901
|
+
* Parse a literal value or closure variable reference.
|
|
1902
|
+
*
|
|
1903
|
+
* Returns the parsed value, or PARSE_FAILED if parsing fails.
|
|
1904
|
+
* Returns `null` or `undefined` for those keyword literals.
|
|
1905
|
+
*/
|
|
1906
|
+
parseValue() {
|
|
1907
|
+
const t = this.peek();
|
|
1908
|
+
if (!t) return PARSE_FAILED;
|
|
1909
|
+
if (t.type === "string") {
|
|
1910
|
+
this.advance();
|
|
1911
|
+
return t.value;
|
|
1912
|
+
}
|
|
1913
|
+
if (t.type === "number") {
|
|
1914
|
+
this.advance();
|
|
1915
|
+
return Number(t.value);
|
|
1916
|
+
}
|
|
1917
|
+
if (t.type === "identifier") {
|
|
1918
|
+
if (t.value === "true") {
|
|
1919
|
+
this.advance();
|
|
1920
|
+
return true;
|
|
1921
|
+
}
|
|
1922
|
+
if (t.value === "false") {
|
|
1923
|
+
this.advance();
|
|
1924
|
+
return false;
|
|
1925
|
+
}
|
|
1926
|
+
if (t.value === "null") {
|
|
1927
|
+
this.advance();
|
|
1928
|
+
return null;
|
|
1929
|
+
}
|
|
1930
|
+
if (t.value === "undefined") {
|
|
1931
|
+
this.advance();
|
|
1932
|
+
return void 0;
|
|
1933
|
+
}
|
|
1934
|
+
return this.resolveClosureVariable();
|
|
1935
|
+
}
|
|
1936
|
+
if (t.type === "operator" && t.value === "-") {
|
|
1937
|
+
this.advance();
|
|
1938
|
+
const next = this.peek();
|
|
1939
|
+
if (next?.type === "number") {
|
|
1940
|
+
this.advance();
|
|
1941
|
+
return -Number(next.value);
|
|
1942
|
+
}
|
|
1943
|
+
return PARSE_FAILED;
|
|
1944
|
+
}
|
|
1945
|
+
return PARSE_FAILED;
|
|
1946
|
+
}
|
|
1947
|
+
/**
|
|
1948
|
+
* Attempt to resolve a closure variable by invoking the original function
|
|
1949
|
+
* with a recording Proxy and inspecting what values it compares against.
|
|
1950
|
+
*
|
|
1951
|
+
* This handles the common pattern:
|
|
1952
|
+
* ```ts
|
|
1953
|
+
* const userId = auth.userId;
|
|
1954
|
+
* orders.filter(o => o.requestedBy === userId)
|
|
1955
|
+
* ```
|
|
1956
|
+
*
|
|
1957
|
+
* The Proxy captures property accesses on the parameter and we can then
|
|
1958
|
+
* extract the comparison value from the function's behavior. However,
|
|
1959
|
+
* this approach has limitations — if the function throws, has side effects,
|
|
1960
|
+
* or uses the variable in a non-comparison context, we fall back to JS.
|
|
1961
|
+
*/
|
|
1962
|
+
resolveClosureVariable() {
|
|
1963
|
+
const identToken = this.advance();
|
|
1964
|
+
let closureExpr = identToken.value;
|
|
1965
|
+
while (this.match("dot") && this.tokens[this.pos + 1]?.type === "identifier") {
|
|
1966
|
+
this.advance();
|
|
1967
|
+
closureExpr += "." + this.advance().value;
|
|
1968
|
+
}
|
|
1969
|
+
try {
|
|
1970
|
+
const MARKER = /* @__PURE__ */ Symbol("field_access_marker");
|
|
1971
|
+
const accessed = [];
|
|
1972
|
+
const proxy = new Proxy(
|
|
1973
|
+
{},
|
|
1974
|
+
{
|
|
1975
|
+
get(_, prop) {
|
|
1976
|
+
accessed.push(prop);
|
|
1977
|
+
return new Proxy(() => MARKER, {
|
|
1978
|
+
get(_2, nestedProp) {
|
|
1979
|
+
accessed.push(nestedProp);
|
|
1980
|
+
return MARKER;
|
|
1981
|
+
}
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
);
|
|
1986
|
+
void proxy;
|
|
1987
|
+
return PARSE_FAILED;
|
|
1988
|
+
} catch {
|
|
1989
|
+
return PARSE_FAILED;
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Look ahead to check if the next tokens form `.includes(`.
|
|
1994
|
+
* Used to disambiguate `o.field.includes(...)` from `o.field.nested`.
|
|
1995
|
+
*/
|
|
1996
|
+
lookAheadForIncludes() {
|
|
1997
|
+
return this.tokens[this.pos]?.type === "dot" && this.tokens[this.pos + 1]?.type === "identifier" && this.tokens[this.pos + 1]?.value === "includes" && this.tokens[this.pos + 2]?.type === "lparen";
|
|
1998
|
+
}
|
|
1999
|
+
};
|
|
2000
|
+
JS_TO_SQL_OP = {
|
|
2001
|
+
"===": "=",
|
|
2002
|
+
"==": "=",
|
|
2003
|
+
"!==": "!=",
|
|
2004
|
+
"!=": "!=",
|
|
2005
|
+
"<": "<",
|
|
2006
|
+
">": ">",
|
|
2007
|
+
"<=": "<=",
|
|
2008
|
+
">=": ">="
|
|
2009
|
+
};
|
|
2010
|
+
PARSE_FAILED = /* @__PURE__ */ Symbol("PARSE_FAILED");
|
|
2011
|
+
}
|
|
2012
|
+
});
|
|
2013
|
+
|
|
2014
|
+
// src/db/query.ts
|
|
2015
|
+
function extractFieldName(accessor) {
|
|
2016
|
+
const source = accessor.toString();
|
|
2017
|
+
const match = source.match(
|
|
2018
|
+
/^\s*\(?([a-zA-Z_$][a-zA-Z0-9_$]*)\)?\s*=>\s*\1\.([a-zA-Z_$][a-zA-Z0-9_$]*)\s*$/
|
|
2019
|
+
);
|
|
2020
|
+
return match?.[2] ?? null;
|
|
2021
|
+
}
|
|
2022
|
+
var Query;
|
|
2023
|
+
var init_query = __esm({
|
|
2024
|
+
"src/db/query.ts"() {
|
|
2025
|
+
"use strict";
|
|
2026
|
+
init_predicate();
|
|
2027
|
+
init_sql();
|
|
2028
|
+
Query = class _Query {
|
|
2029
|
+
/** @internal Accumulated predicate functions to filter by. */
|
|
2030
|
+
_predicates;
|
|
2031
|
+
/** @internal The field accessor for sorting, if set. */
|
|
2032
|
+
_sortAccessor;
|
|
2033
|
+
/** @internal Whether the sort order is reversed (DESC). */
|
|
2034
|
+
_reversed;
|
|
2035
|
+
/** @internal Maximum number of results (SQL LIMIT). */
|
|
2036
|
+
_limit;
|
|
2037
|
+
/** @internal Number of results to skip (SQL OFFSET). */
|
|
2038
|
+
_offset;
|
|
2039
|
+
/** @internal Binding to the database execution layer. */
|
|
2040
|
+
_config;
|
|
2041
|
+
constructor(config, options) {
|
|
2042
|
+
this._config = config;
|
|
2043
|
+
this._predicates = options?.predicates ?? [];
|
|
2044
|
+
this._sortAccessor = options?.sortAccessor;
|
|
2045
|
+
this._reversed = options?.reversed ?? false;
|
|
2046
|
+
this._limit = options?.limit;
|
|
2047
|
+
this._offset = options?.offset;
|
|
2048
|
+
}
|
|
2049
|
+
/**
|
|
2050
|
+
* Create a clone of this query with some options overridden.
|
|
2051
|
+
* Used internally by chain methods to maintain immutability.
|
|
2052
|
+
*/
|
|
2053
|
+
_clone(overrides) {
|
|
2054
|
+
return new _Query(this._config, {
|
|
2055
|
+
predicates: overrides.predicates ?? this._predicates,
|
|
2056
|
+
sortAccessor: overrides.sortAccessor ?? this._sortAccessor,
|
|
2057
|
+
reversed: overrides.reversed ?? this._reversed,
|
|
2058
|
+
limit: overrides.limit ?? this._limit,
|
|
2059
|
+
offset: overrides.offset ?? this._offset
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2062
|
+
// -------------------------------------------------------------------------
|
|
2063
|
+
// Chain methods — return new Query instances
|
|
2064
|
+
// -------------------------------------------------------------------------
|
|
2065
|
+
/**
|
|
2066
|
+
* Add a filter predicate. Multiple filters are ANDed together.
|
|
2067
|
+
*
|
|
2068
|
+
* @example
|
|
2069
|
+
* ```ts
|
|
2070
|
+
* const active = Orders.filter(o => o.status === 'active');
|
|
2071
|
+
* const expensive = active.filter(o => o.amount > 5000);
|
|
2072
|
+
* // WHERE status = 'active' AND amount > 5000
|
|
2073
|
+
* ```
|
|
2074
|
+
*/
|
|
2075
|
+
filter(predicate) {
|
|
2076
|
+
return this._clone({
|
|
2077
|
+
predicates: [...this._predicates, predicate]
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
/**
|
|
2081
|
+
* Sort results by a field (ascending by default).
|
|
2082
|
+
* Use `.reverse()` after `.sortBy()` for descending order.
|
|
2083
|
+
*
|
|
2084
|
+
* @example
|
|
2085
|
+
* ```ts
|
|
2086
|
+
* const newest = Orders.sortBy(o => o.createdAt).reverse();
|
|
2087
|
+
* ```
|
|
2088
|
+
*/
|
|
2089
|
+
sortBy(accessor) {
|
|
2090
|
+
return this._clone({ sortAccessor: accessor });
|
|
2091
|
+
}
|
|
2092
|
+
/**
|
|
2093
|
+
* Reverse the current sort order. If no sort is set, this has no effect.
|
|
2094
|
+
*/
|
|
2095
|
+
reverse() {
|
|
2096
|
+
return this._clone({ reversed: !this._reversed });
|
|
2097
|
+
}
|
|
2098
|
+
/**
|
|
2099
|
+
* Limit the number of results returned.
|
|
2100
|
+
*
|
|
2101
|
+
* @example
|
|
2102
|
+
* ```ts
|
|
2103
|
+
* const top10 = Orders.sortBy(o => o.amount).reverse().take(10);
|
|
2104
|
+
* ```
|
|
2105
|
+
*/
|
|
2106
|
+
take(n) {
|
|
2107
|
+
return this._clone({ limit: n });
|
|
2108
|
+
}
|
|
2109
|
+
/**
|
|
2110
|
+
* Skip the first n results. Use with `.take()` for pagination.
|
|
2111
|
+
*
|
|
2112
|
+
* @example
|
|
2113
|
+
* ```ts
|
|
2114
|
+
* const page2 = Orders.sortBy(o => o.createdAt).skip(50).take(50);
|
|
2115
|
+
* ```
|
|
2116
|
+
*/
|
|
2117
|
+
skip(n) {
|
|
2118
|
+
return this._clone({ offset: n });
|
|
2119
|
+
}
|
|
2120
|
+
// -------------------------------------------------------------------------
|
|
2121
|
+
// Terminal methods — execute the query and return results
|
|
2122
|
+
// -------------------------------------------------------------------------
|
|
2123
|
+
/**
|
|
2124
|
+
* Return the first matching row, or null if no rows match.
|
|
2125
|
+
* Applies the current sort order before taking the first result.
|
|
2126
|
+
*/
|
|
2127
|
+
async first() {
|
|
2128
|
+
const rows = await this._clone({ limit: 1 })._execute();
|
|
2129
|
+
return rows[0] ?? null;
|
|
2130
|
+
}
|
|
2131
|
+
/**
|
|
2132
|
+
* Return the last matching row (per current sort), or null.
|
|
2133
|
+
* Flips the sort direction and takes 1 row.
|
|
2134
|
+
*/
|
|
2135
|
+
async last() {
|
|
2136
|
+
const rows = await this._clone({ limit: 1, reversed: !this._reversed })._execute();
|
|
2137
|
+
return rows[0] ?? null;
|
|
2138
|
+
}
|
|
2139
|
+
/**
|
|
2140
|
+
* Count matching rows. Returns a number, not the rows themselves.
|
|
2141
|
+
* Executes as `SELECT COUNT(*)` when predicates compile to SQL.
|
|
2142
|
+
*/
|
|
2143
|
+
async count() {
|
|
2144
|
+
const compiled = this._compilePredicates();
|
|
2145
|
+
if (compiled.allSql) {
|
|
2146
|
+
const where = compiled.sqlWhere || void 0;
|
|
2147
|
+
const sql = buildCount(this._config.tableName, where);
|
|
2148
|
+
const result = await this._config.executeQuery(sql);
|
|
2149
|
+
const row = result.rows[0];
|
|
2150
|
+
return row?.count ?? 0;
|
|
2151
|
+
}
|
|
2152
|
+
const rows = await this._fetchAndFilterInJs(compiled);
|
|
2153
|
+
return rows.length;
|
|
2154
|
+
}
|
|
2155
|
+
/**
|
|
2156
|
+
* Check if any row matches the current filters. Short-circuits —
|
|
2157
|
+
* doesn't load all rows when using SQL.
|
|
2158
|
+
*/
|
|
2159
|
+
async some() {
|
|
2160
|
+
const compiled = this._compilePredicates();
|
|
2161
|
+
if (compiled.allSql) {
|
|
2162
|
+
const where = compiled.sqlWhere || void 0;
|
|
2163
|
+
const sql = buildExists(this._config.tableName, where);
|
|
2164
|
+
const result = await this._config.executeQuery(sql);
|
|
2165
|
+
const row = result.rows[0];
|
|
2166
|
+
return row?.result === 1;
|
|
2167
|
+
}
|
|
2168
|
+
const rows = await this._fetchAndFilterInJs(compiled);
|
|
2169
|
+
return rows.length > 0;
|
|
2170
|
+
}
|
|
2171
|
+
/**
|
|
2172
|
+
* Check if all rows match the current filters. Short-circuits on false.
|
|
2173
|
+
*
|
|
2174
|
+
* Implemented as NOT EXISTS(... WHERE NOT predicate) — returns true
|
|
2175
|
+
* if no rows fail the predicate.
|
|
2176
|
+
*/
|
|
2177
|
+
async every() {
|
|
2178
|
+
const compiled = this._compilePredicates();
|
|
2179
|
+
if (compiled.allSql && compiled.sqlWhere) {
|
|
2180
|
+
const sql = buildExists(this._config.tableName, `NOT (${compiled.sqlWhere})`, true);
|
|
2181
|
+
const result = await this._config.executeQuery(sql);
|
|
2182
|
+
const row = result.rows[0];
|
|
2183
|
+
return row?.result === 1;
|
|
2184
|
+
}
|
|
2185
|
+
if (this._predicates.length === 0) return true;
|
|
2186
|
+
const allRows = await this._fetchAllRows();
|
|
2187
|
+
return allRows.every(
|
|
2188
|
+
(row) => this._predicates.every((pred) => pred(row))
|
|
2189
|
+
);
|
|
2190
|
+
}
|
|
2191
|
+
/**
|
|
2192
|
+
* Return the row with the minimum value for the given field.
|
|
2193
|
+
* Executes as `ORDER BY field ASC LIMIT 1` in SQL.
|
|
2194
|
+
*/
|
|
2195
|
+
async min(accessor) {
|
|
2196
|
+
return this.sortBy(accessor).first();
|
|
2197
|
+
}
|
|
2198
|
+
/**
|
|
2199
|
+
* Return the row with the maximum value for the given field.
|
|
2200
|
+
* Executes as `ORDER BY field DESC LIMIT 1` in SQL.
|
|
2201
|
+
*/
|
|
2202
|
+
async max(accessor) {
|
|
2203
|
+
return this.sortBy(accessor).reverse().first();
|
|
2204
|
+
}
|
|
2205
|
+
/**
|
|
2206
|
+
* Group rows by a field value. Returns a Map.
|
|
2207
|
+
* Always executes in JS (no SQL equivalent for grouping into a Map).
|
|
2208
|
+
*/
|
|
2209
|
+
async groupBy(accessor) {
|
|
2210
|
+
const rows = await this._execute();
|
|
2211
|
+
const map = /* @__PURE__ */ new Map();
|
|
2212
|
+
for (const row of rows) {
|
|
2213
|
+
const key = accessor(row);
|
|
2214
|
+
const group = map.get(key);
|
|
2215
|
+
if (group) {
|
|
2216
|
+
group.push(row);
|
|
2217
|
+
} else {
|
|
2218
|
+
map.set(key, [row]);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
return map;
|
|
2222
|
+
}
|
|
2223
|
+
// -------------------------------------------------------------------------
|
|
2224
|
+
// PromiseLike implementation — makes `await query` work
|
|
2225
|
+
// -------------------------------------------------------------------------
|
|
2226
|
+
/**
|
|
2227
|
+
* PromiseLike.then() — executes the query and pipes the result.
|
|
2228
|
+
* This is what makes `const rows = await query` work.
|
|
2229
|
+
*/
|
|
2230
|
+
then(onfulfilled, onrejected) {
|
|
2231
|
+
return this._execute().then(onfulfilled, onrejected);
|
|
2232
|
+
}
|
|
2233
|
+
// -------------------------------------------------------------------------
|
|
2234
|
+
// Execution internals
|
|
2235
|
+
// -------------------------------------------------------------------------
|
|
2236
|
+
/**
|
|
2237
|
+
* Execute the query and return typed result rows.
|
|
2238
|
+
*
|
|
2239
|
+
* This is the core execution method. It:
|
|
2240
|
+
* 1. Tries to compile all predicates to SQL
|
|
2241
|
+
* 2. If all compile → builds and executes a single SQL query
|
|
2242
|
+
* 3. If any fail → fetches all rows and processes in JS
|
|
2243
|
+
* 4. Deserializes rows (user prefix stripping, JSON parsing)
|
|
2244
|
+
*/
|
|
2245
|
+
async _execute() {
|
|
2246
|
+
const compiled = this._compilePredicates();
|
|
2247
|
+
if (compiled.allSql) {
|
|
2248
|
+
const sortField = this._sortAccessor ? extractFieldName(this._sortAccessor) : void 0;
|
|
2249
|
+
const sql = buildSelect(this._config.tableName, {
|
|
2250
|
+
where: compiled.sqlWhere || void 0,
|
|
2251
|
+
orderBy: sortField ?? void 0,
|
|
2252
|
+
desc: this._reversed,
|
|
2253
|
+
limit: this._limit,
|
|
2254
|
+
offset: this._offset
|
|
2255
|
+
});
|
|
2256
|
+
const result = await this._config.executeQuery(sql);
|
|
2257
|
+
return result.rows.map(
|
|
2258
|
+
(row) => deserializeRow(
|
|
2259
|
+
row,
|
|
2260
|
+
this._config.columns
|
|
2261
|
+
)
|
|
2262
|
+
);
|
|
2263
|
+
}
|
|
2264
|
+
let rows = await this._fetchAndFilterInJs(compiled);
|
|
2265
|
+
if (this._sortAccessor) {
|
|
2266
|
+
const accessor = this._sortAccessor;
|
|
2267
|
+
rows.sort((a, b) => {
|
|
2268
|
+
const aVal = accessor(a);
|
|
2269
|
+
const bVal = accessor(b);
|
|
2270
|
+
if (aVal < bVal) return this._reversed ? 1 : -1;
|
|
2271
|
+
if (aVal > bVal) return this._reversed ? -1 : 1;
|
|
2272
|
+
return 0;
|
|
2273
|
+
});
|
|
2274
|
+
}
|
|
2275
|
+
if (this._offset != null || this._limit != null) {
|
|
2276
|
+
const start = this._offset ?? 0;
|
|
2277
|
+
const end = this._limit != null ? start + this._limit : void 0;
|
|
2278
|
+
rows = rows.slice(start, end);
|
|
2279
|
+
}
|
|
2280
|
+
return rows;
|
|
2281
|
+
}
|
|
2282
|
+
/**
|
|
2283
|
+
* Compile all accumulated predicates and determine the execution strategy.
|
|
2284
|
+
*
|
|
2285
|
+
* Returns an object with:
|
|
2286
|
+
* - `allSql`: whether all predicates compiled to SQL
|
|
2287
|
+
* - `sqlWhere`: combined WHERE clause (ANDed) if all compiled
|
|
2288
|
+
* - `compiled`: individual compilation results
|
|
2289
|
+
*/
|
|
2290
|
+
_compilePredicates() {
|
|
2291
|
+
if (this._predicates.length === 0) {
|
|
2292
|
+
return { allSql: true, sqlWhere: "", compiled: [] };
|
|
2293
|
+
}
|
|
2294
|
+
const compiled = this._predicates.map((pred) => compilePredicate(pred));
|
|
2295
|
+
const allSql = compiled.every((c) => c.type === "sql");
|
|
2296
|
+
let sqlWhere = "";
|
|
2297
|
+
if (allSql) {
|
|
2298
|
+
sqlWhere = compiled.map((c) => c.where).join(" AND ");
|
|
2299
|
+
}
|
|
2300
|
+
return { allSql, sqlWhere, compiled };
|
|
2301
|
+
}
|
|
2302
|
+
/**
|
|
2303
|
+
* Fetch all rows from the table and apply JS predicates.
|
|
2304
|
+
* This is the fallback path when SQL compilation fails.
|
|
2305
|
+
*
|
|
2306
|
+
* Logs a warning to stderr so developers know they're on the slow path.
|
|
2307
|
+
*/
|
|
2308
|
+
async _fetchAndFilterInJs(compiled) {
|
|
2309
|
+
const allRows = await this._fetchAllRows();
|
|
2310
|
+
if (compiled.compiled.some((c) => c.type === "js")) {
|
|
2311
|
+
console.warn(
|
|
2312
|
+
`[mindstudio] Filter on ${this._config.tableName} could not be compiled to SQL \u2014 scanning ${allRows.length} rows in JS`
|
|
2313
|
+
);
|
|
2314
|
+
}
|
|
2315
|
+
return allRows.filter(
|
|
2316
|
+
(row) => this._predicates.every((pred) => pred(row))
|
|
2317
|
+
);
|
|
2318
|
+
}
|
|
2319
|
+
/**
|
|
2320
|
+
* Fetch all rows from the table (SELECT * with no WHERE).
|
|
2321
|
+
* Used by the JS fallback path.
|
|
2322
|
+
*/
|
|
2323
|
+
async _fetchAllRows() {
|
|
2324
|
+
const sql = buildSelect(this._config.tableName);
|
|
2325
|
+
const result = await this._config.executeQuery(sql);
|
|
2326
|
+
return result.rows.map(
|
|
2327
|
+
(row) => deserializeRow(row, this._config.columns)
|
|
2328
|
+
);
|
|
2329
|
+
}
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
});
|
|
2333
|
+
|
|
2334
|
+
// src/db/table.ts
|
|
2335
|
+
var Table;
|
|
2336
|
+
var init_table = __esm({
|
|
2337
|
+
"src/db/table.ts"() {
|
|
2338
|
+
"use strict";
|
|
2339
|
+
init_query();
|
|
2340
|
+
init_predicate();
|
|
2341
|
+
init_sql();
|
|
2342
|
+
Table = class {
|
|
2343
|
+
/** @internal Runtime config binding this table to the execution layer. */
|
|
2344
|
+
_config;
|
|
2345
|
+
constructor(config) {
|
|
2346
|
+
this._config = config;
|
|
2347
|
+
}
|
|
2348
|
+
// -------------------------------------------------------------------------
|
|
2349
|
+
// Reads — direct (return Promises)
|
|
2350
|
+
// -------------------------------------------------------------------------
|
|
2351
|
+
/**
|
|
2352
|
+
* Get a single row by ID. Returns null if not found.
|
|
2353
|
+
*
|
|
2354
|
+
* @example
|
|
2355
|
+
* ```ts
|
|
2356
|
+
* const order = await Orders.get('abc-123');
|
|
2357
|
+
* if (order) console.log(order.status);
|
|
2358
|
+
* ```
|
|
2359
|
+
*/
|
|
2360
|
+
async get(id) {
|
|
2361
|
+
const sql = buildSelect(this._config.tableName, {
|
|
2362
|
+
where: `id = ${escapeValue(id)}`,
|
|
2363
|
+
limit: 1
|
|
2364
|
+
});
|
|
2365
|
+
const result = await this._config.executeQuery(sql);
|
|
2366
|
+
if (result.rows.length === 0) return null;
|
|
2367
|
+
return deserializeRow(
|
|
2368
|
+
result.rows[0],
|
|
2369
|
+
this._config.columns
|
|
2370
|
+
);
|
|
2371
|
+
}
|
|
2372
|
+
/**
|
|
2373
|
+
* Find the first row matching a predicate. Returns null if none match.
|
|
2374
|
+
*
|
|
2375
|
+
* @example
|
|
2376
|
+
* ```ts
|
|
2377
|
+
* const activeOrder = await Orders.findOne(o => o.status === 'active');
|
|
2378
|
+
* ```
|
|
2379
|
+
*/
|
|
2380
|
+
async findOne(predicate) {
|
|
2381
|
+
return this.filter(predicate).first();
|
|
2382
|
+
}
|
|
2383
|
+
/**
|
|
2384
|
+
* Count rows, optionally filtered by a predicate.
|
|
2385
|
+
*
|
|
2386
|
+
* @example
|
|
2387
|
+
* ```ts
|
|
2388
|
+
* const total = await Orders.count();
|
|
2389
|
+
* const pending = await Orders.count(o => o.status === 'pending');
|
|
2390
|
+
* ```
|
|
2391
|
+
*/
|
|
2392
|
+
async count(predicate) {
|
|
2393
|
+
if (predicate) {
|
|
2394
|
+
return this.filter(predicate).count();
|
|
2395
|
+
}
|
|
2396
|
+
const sql = buildCount(this._config.tableName);
|
|
2397
|
+
const result = await this._config.executeQuery(sql);
|
|
2398
|
+
const row = result.rows[0];
|
|
2399
|
+
return row?.count ?? 0;
|
|
2400
|
+
}
|
|
2401
|
+
/**
|
|
2402
|
+
* Check if any row matches a predicate. Short-circuits.
|
|
2403
|
+
*
|
|
2404
|
+
* @example
|
|
2405
|
+
* ```ts
|
|
2406
|
+
* const hasActive = await Orders.some(o => o.status === 'active');
|
|
2407
|
+
* ```
|
|
2408
|
+
*/
|
|
2409
|
+
async some(predicate) {
|
|
2410
|
+
return this.filter(predicate).some();
|
|
2411
|
+
}
|
|
2412
|
+
/**
|
|
2413
|
+
* Check if all rows match a predicate.
|
|
2414
|
+
*
|
|
2415
|
+
* @example
|
|
2416
|
+
* ```ts
|
|
2417
|
+
* const allComplete = await Orders.every(o => o.status === 'completed');
|
|
2418
|
+
* ```
|
|
2419
|
+
*/
|
|
2420
|
+
async every(predicate) {
|
|
2421
|
+
return this.filter(predicate).every();
|
|
2422
|
+
}
|
|
2423
|
+
/**
|
|
2424
|
+
* Check if the table has zero rows.
|
|
2425
|
+
*
|
|
2426
|
+
* @example
|
|
2427
|
+
* ```ts
|
|
2428
|
+
* if (await Orders.isEmpty()) console.log('No orders yet');
|
|
2429
|
+
* ```
|
|
2430
|
+
*/
|
|
2431
|
+
async isEmpty() {
|
|
2432
|
+
const sql = buildExists(this._config.tableName, void 0, true);
|
|
2433
|
+
const result = await this._config.executeQuery(sql);
|
|
2434
|
+
const row = result.rows[0];
|
|
2435
|
+
return row?.result === 1;
|
|
2436
|
+
}
|
|
2437
|
+
/**
|
|
2438
|
+
* Return the row with the minimum value for a field.
|
|
2439
|
+
* Executes as `ORDER BY field ASC LIMIT 1`.
|
|
2440
|
+
*
|
|
2441
|
+
* @example
|
|
2442
|
+
* ```ts
|
|
2443
|
+
* const cheapest = await Orders.min(o => o.amount);
|
|
2444
|
+
* ```
|
|
2445
|
+
*/
|
|
2446
|
+
async min(accessor) {
|
|
2447
|
+
return this.sortBy(accessor).first();
|
|
2448
|
+
}
|
|
2449
|
+
/**
|
|
2450
|
+
* Return the row with the maximum value for a field.
|
|
2451
|
+
* Executes as `ORDER BY field DESC LIMIT 1`.
|
|
2452
|
+
*
|
|
2453
|
+
* @example
|
|
2454
|
+
* ```ts
|
|
2455
|
+
* const mostExpensive = await Orders.max(o => o.amount);
|
|
2456
|
+
* ```
|
|
2457
|
+
*/
|
|
2458
|
+
async max(accessor) {
|
|
2459
|
+
return this.sortBy(accessor).reverse().first();
|
|
2460
|
+
}
|
|
2461
|
+
/**
|
|
2462
|
+
* Group all rows by a field value. Returns a Map.
|
|
2463
|
+
*
|
|
2464
|
+
* @example
|
|
2465
|
+
* ```ts
|
|
2466
|
+
* const byStatus = await Orders.groupBy(o => o.status);
|
|
2467
|
+
* // Map { 'pending' => [...], 'approved' => [...] }
|
|
2468
|
+
* ```
|
|
2469
|
+
*/
|
|
2470
|
+
async groupBy(accessor) {
|
|
2471
|
+
return new Query(this._config).groupBy(accessor);
|
|
2472
|
+
}
|
|
2473
|
+
// -------------------------------------------------------------------------
|
|
2474
|
+
// Reads — chainable (return Query<T>)
|
|
2475
|
+
// -------------------------------------------------------------------------
|
|
2476
|
+
/**
|
|
2477
|
+
* Filter rows by a predicate. Returns a chainable Query.
|
|
2478
|
+
*
|
|
2479
|
+
* The predicate is compiled to SQL when possible. If compilation fails,
|
|
2480
|
+
* the query falls back to fetching all rows and filtering in JS.
|
|
2481
|
+
*
|
|
2482
|
+
* @example
|
|
2483
|
+
* ```ts
|
|
2484
|
+
* const active = await Orders.filter(o => o.status === 'active');
|
|
2485
|
+
* const recentActive = await Orders
|
|
2486
|
+
* .filter(o => o.status === 'active')
|
|
2487
|
+
* .sortBy(o => o.createdAt)
|
|
2488
|
+
* .reverse()
|
|
2489
|
+
* .take(10);
|
|
2490
|
+
* ```
|
|
2491
|
+
*/
|
|
2492
|
+
filter(predicate) {
|
|
2493
|
+
return new Query(this._config).filter(predicate);
|
|
2494
|
+
}
|
|
2495
|
+
/**
|
|
2496
|
+
* Sort all rows by a field. Returns a chainable Query.
|
|
2497
|
+
*
|
|
2498
|
+
* @example
|
|
2499
|
+
* ```ts
|
|
2500
|
+
* const newest = await Orders.sortBy(o => o.createdAt).reverse().take(5);
|
|
2501
|
+
* ```
|
|
2502
|
+
*/
|
|
2503
|
+
sortBy(accessor) {
|
|
2504
|
+
return new Query(this._config).sortBy(accessor);
|
|
2505
|
+
}
|
|
2506
|
+
async push(data) {
|
|
2507
|
+
const isArray = Array.isArray(data);
|
|
2508
|
+
const items = isArray ? data : [data];
|
|
2509
|
+
const results = [];
|
|
2510
|
+
for (const item of items) {
|
|
2511
|
+
const insertSql = buildInsert(
|
|
2512
|
+
this._config.tableName,
|
|
2513
|
+
item,
|
|
2514
|
+
this._config.columns
|
|
2515
|
+
);
|
|
2516
|
+
await this._config.executeQuery(insertSql);
|
|
2517
|
+
const fetchSql = `SELECT * FROM ${this._config.tableName} WHERE rowid = last_insert_rowid()`;
|
|
2518
|
+
const fetchResult = await this._config.executeQuery(fetchSql);
|
|
2519
|
+
if (fetchResult.rows.length > 0) {
|
|
2520
|
+
results.push(
|
|
2521
|
+
deserializeRow(
|
|
2522
|
+
fetchResult.rows[0],
|
|
2523
|
+
this._config.columns
|
|
2524
|
+
)
|
|
2525
|
+
);
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
return isArray ? results : results[0];
|
|
2529
|
+
}
|
|
2530
|
+
/**
|
|
2531
|
+
* Update a row by ID. Only the provided fields are changed.
|
|
2532
|
+
* Returns the updated row.
|
|
2533
|
+
*
|
|
2534
|
+
* System columns cannot be updated — they're stripped automatically.
|
|
2535
|
+
* `updatedAt` and `lastUpdatedBy` are set by the platform.
|
|
2536
|
+
*
|
|
2537
|
+
* @example
|
|
2538
|
+
* ```ts
|
|
2539
|
+
* const updated = await Orders.update(order.id, { status: 'approved' });
|
|
2540
|
+
* console.log(updated.updatedAt); // freshly updated
|
|
2541
|
+
* ```
|
|
2542
|
+
*/
|
|
2543
|
+
async update(id, data) {
|
|
2544
|
+
const updateSql = buildUpdate(
|
|
2545
|
+
this._config.tableName,
|
|
2546
|
+
id,
|
|
2547
|
+
data,
|
|
2548
|
+
this._config.columns
|
|
2549
|
+
);
|
|
2550
|
+
await this._config.executeQuery(updateSql);
|
|
2551
|
+
const fetchSql = buildSelect(this._config.tableName, {
|
|
2552
|
+
where: `id = ${escapeValue(id)}`,
|
|
2553
|
+
limit: 1
|
|
2554
|
+
});
|
|
2555
|
+
const result = await this._config.executeQuery(fetchSql);
|
|
2556
|
+
return deserializeRow(
|
|
2557
|
+
result.rows[0],
|
|
2558
|
+
this._config.columns
|
|
2559
|
+
);
|
|
2560
|
+
}
|
|
2561
|
+
/**
|
|
2562
|
+
* Remove a row by ID.
|
|
2563
|
+
*
|
|
2564
|
+
* @example
|
|
2565
|
+
* ```ts
|
|
2566
|
+
* await Orders.remove('abc-123');
|
|
2567
|
+
* ```
|
|
2568
|
+
*/
|
|
2569
|
+
async remove(id) {
|
|
2570
|
+
const sql = buildDelete(
|
|
2571
|
+
this._config.tableName,
|
|
2572
|
+
`id = ${escapeValue(id)}`
|
|
2573
|
+
);
|
|
2574
|
+
await this._config.executeQuery(sql);
|
|
2575
|
+
}
|
|
2576
|
+
/**
|
|
2577
|
+
* Remove all rows matching a predicate. Returns the count removed.
|
|
2578
|
+
*
|
|
2579
|
+
* The predicate is compiled to SQL when possible. If compilation fails,
|
|
2580
|
+
* the function fetches all matching rows, collects their IDs, and
|
|
2581
|
+
* deletes them individually.
|
|
2582
|
+
*
|
|
2583
|
+
* @example
|
|
2584
|
+
* ```ts
|
|
2585
|
+
* const removed = await Orders.removeAll(o => o.status === 'rejected');
|
|
2586
|
+
* console.log(`Removed ${removed} orders`);
|
|
2587
|
+
* ```
|
|
2588
|
+
*/
|
|
2589
|
+
async removeAll(predicate) {
|
|
2590
|
+
const compiled = compilePredicate(predicate);
|
|
2591
|
+
if (compiled.type === "sql") {
|
|
2592
|
+
const sql = buildDelete(this._config.tableName, compiled.where);
|
|
2593
|
+
const result = await this._config.executeQuery(sql);
|
|
2594
|
+
return result.changes;
|
|
2595
|
+
}
|
|
2596
|
+
console.warn(
|
|
2597
|
+
`[mindstudio] removeAll predicate on ${this._config.tableName} could not be compiled to SQL \u2014 fetching all rows first`
|
|
2598
|
+
);
|
|
2599
|
+
const allSql = buildSelect(this._config.tableName);
|
|
2600
|
+
const allResult = await this._config.executeQuery(allSql);
|
|
2601
|
+
const allRows = allResult.rows.map(
|
|
2602
|
+
(r) => deserializeRow(
|
|
2603
|
+
r,
|
|
2604
|
+
this._config.columns
|
|
2605
|
+
)
|
|
2606
|
+
);
|
|
2607
|
+
const matching = allRows.filter((row) => predicate(row));
|
|
2608
|
+
let count = 0;
|
|
2609
|
+
for (const row of matching) {
|
|
2610
|
+
const id = row.id;
|
|
2611
|
+
if (id) {
|
|
2612
|
+
const sql = buildDelete(this._config.tableName, `id = ${escapeValue(id)}`);
|
|
2613
|
+
await this._config.executeQuery(sql);
|
|
2614
|
+
count++;
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
return count;
|
|
2618
|
+
}
|
|
2619
|
+
/**
|
|
2620
|
+
* Remove all rows from the table.
|
|
2621
|
+
*
|
|
2622
|
+
* @example
|
|
2623
|
+
* ```ts
|
|
2624
|
+
* await Orders.clear();
|
|
2625
|
+
* ```
|
|
2626
|
+
*/
|
|
2627
|
+
async clear() {
|
|
2628
|
+
const sql = buildDelete(this._config.tableName);
|
|
2629
|
+
await this._config.executeQuery(sql);
|
|
2630
|
+
}
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2633
|
+
});
|
|
2634
|
+
|
|
2635
|
+
// src/db/index.ts
|
|
2636
|
+
function createDb(databases, executeQuery) {
|
|
2637
|
+
return {
|
|
2638
|
+
defineTable(name, options) {
|
|
2639
|
+
const resolved = resolveTable(databases, name, options?.database);
|
|
2640
|
+
const config = {
|
|
2641
|
+
databaseId: resolved.databaseId,
|
|
2642
|
+
tableName: name,
|
|
2643
|
+
columns: resolved.columns,
|
|
2644
|
+
executeQuery: (sql) => executeQuery(resolved.databaseId, sql)
|
|
2645
|
+
};
|
|
2646
|
+
return new Table(config);
|
|
2647
|
+
},
|
|
2648
|
+
// --- Time helpers ---
|
|
2649
|
+
// Pure JS, no platform dependency. All timestamps are unix ms.
|
|
2650
|
+
now: () => Date.now(),
|
|
2651
|
+
days: (n) => n * 864e5,
|
|
2652
|
+
hours: (n) => n * 36e5,
|
|
2653
|
+
minutes: (n) => n * 6e4,
|
|
2654
|
+
ago: (ms) => Date.now() - ms,
|
|
2655
|
+
fromNow: (ms) => Date.now() + ms
|
|
2656
|
+
};
|
|
2657
|
+
}
|
|
2658
|
+
function resolveTable(databases, tableName, databaseHint) {
|
|
2659
|
+
if (databases.length === 0) {
|
|
2660
|
+
throw new MindStudioError(
|
|
2661
|
+
`No databases found in app context. Make sure the app has at least one database configured.`,
|
|
2662
|
+
"no_databases",
|
|
2663
|
+
400
|
|
2664
|
+
);
|
|
2665
|
+
}
|
|
2666
|
+
if (databaseHint) {
|
|
2667
|
+
const targetDb = databases.find(
|
|
2668
|
+
(db) => db.id === databaseHint || db.name === databaseHint
|
|
2669
|
+
);
|
|
2670
|
+
if (!targetDb) {
|
|
2671
|
+
const available = databases.map((db) => db.name || db.id).join(", ");
|
|
2672
|
+
throw new MindStudioError(
|
|
2673
|
+
`Database "${databaseHint}" not found. Available databases: ${available}`,
|
|
2674
|
+
"database_not_found",
|
|
2675
|
+
400
|
|
2676
|
+
);
|
|
2677
|
+
}
|
|
2678
|
+
const table = targetDb.tables.find((t) => t.name === tableName);
|
|
2679
|
+
if (!table) {
|
|
2680
|
+
const available = targetDb.tables.map((t) => t.name).join(", ");
|
|
2681
|
+
throw new MindStudioError(
|
|
2682
|
+
`Table "${tableName}" not found in database "${databaseHint}". Available tables: ${available || "(none)"}`,
|
|
2683
|
+
"table_not_found",
|
|
2684
|
+
400
|
|
2685
|
+
);
|
|
2686
|
+
}
|
|
2687
|
+
return { databaseId: targetDb.id, columns: table.schema };
|
|
2688
|
+
}
|
|
2689
|
+
for (const db of databases) {
|
|
2690
|
+
const table = db.tables.find((t) => t.name === tableName);
|
|
2691
|
+
if (table) {
|
|
2692
|
+
return {
|
|
2693
|
+
databaseId: db.id,
|
|
2694
|
+
columns: table.schema
|
|
2695
|
+
};
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
const availableTables = databases.flatMap((db) => db.tables.map((t) => t.name)).join(", ");
|
|
2699
|
+
throw new MindStudioError(
|
|
2700
|
+
`Table "${tableName}" not found in app databases. Available tables: ${availableTables || "(none)"}`,
|
|
2701
|
+
"table_not_found",
|
|
2702
|
+
400
|
|
2703
|
+
);
|
|
2704
|
+
}
|
|
2705
|
+
var init_db = __esm({
|
|
2706
|
+
"src/db/index.ts"() {
|
|
2707
|
+
"use strict";
|
|
2708
|
+
init_errors();
|
|
2709
|
+
init_table();
|
|
2710
|
+
init_table();
|
|
2711
|
+
}
|
|
2712
|
+
});
|
|
2713
|
+
|
|
2714
|
+
// src/generated/steps.ts
|
|
2715
|
+
var steps_exports = {};
|
|
2716
|
+
__export(steps_exports, {
|
|
2717
|
+
applyStepMethods: () => applyStepMethods
|
|
2718
|
+
});
|
|
2719
|
+
function applyStepMethods(AgentClass) {
|
|
2720
|
+
const proto = AgentClass.prototype;
|
|
2721
|
+
proto.activeCampaignAddNote = function(step, options) {
|
|
2722
|
+
return this.executeStep("activeCampaignAddNote", step, options);
|
|
2723
|
+
};
|
|
2724
|
+
proto.activeCampaignCreateContact = function(step, options) {
|
|
2725
|
+
return this.executeStep("activeCampaignCreateContact", step, options);
|
|
2726
|
+
};
|
|
2727
|
+
proto.addSubtitlesToVideo = function(step, options) {
|
|
2728
|
+
return this.executeStep("addSubtitlesToVideo", step, options);
|
|
2729
|
+
};
|
|
2730
|
+
proto.airtableCreateUpdateRecord = function(step, options) {
|
|
2731
|
+
return this.executeStep("airtableCreateUpdateRecord", step, options);
|
|
2732
|
+
};
|
|
2733
|
+
proto.airtableDeleteRecord = function(step, options) {
|
|
2734
|
+
return this.executeStep("airtableDeleteRecord", step, options);
|
|
2735
|
+
};
|
|
2736
|
+
proto.airtableGetRecord = function(step, options) {
|
|
2737
|
+
return this.executeStep("airtableGetRecord", step, options);
|
|
2738
|
+
};
|
|
2739
|
+
proto.airtableGetTableRecords = function(step, options) {
|
|
2740
|
+
return this.executeStep("airtableGetTableRecords", step, options);
|
|
2741
|
+
};
|
|
2742
|
+
proto.analyzeImage = function(step, options) {
|
|
2743
|
+
return this.executeStep("analyzeImage", step, options);
|
|
2744
|
+
};
|
|
2745
|
+
proto.analyzeVideo = function(step, options) {
|
|
2746
|
+
return this.executeStep("analyzeVideo", step, options);
|
|
2747
|
+
};
|
|
2748
|
+
proto.captureThumbnail = function(step, options) {
|
|
2749
|
+
return this.executeStep("captureThumbnail", step, options);
|
|
2750
|
+
};
|
|
2751
|
+
proto.checkAppRole = function(step, options) {
|
|
2752
|
+
return this.executeStep("checkAppRole", step, options);
|
|
2753
|
+
};
|
|
2754
|
+
proto.codaCreateUpdatePage = function(step, options) {
|
|
2755
|
+
return this.executeStep("codaCreateUpdatePage", step, options);
|
|
2756
|
+
};
|
|
2757
|
+
proto.codaCreateUpdateRow = function(step, options) {
|
|
2758
|
+
return this.executeStep("codaCreateUpdateRow", step, options);
|
|
2759
|
+
};
|
|
2760
|
+
proto.codaFindRow = function(step, options) {
|
|
2761
|
+
return this.executeStep("codaFindRow", step, options);
|
|
2762
|
+
};
|
|
2763
|
+
proto.codaGetPage = function(step, options) {
|
|
2764
|
+
return this.executeStep("codaGetPage", step, options);
|
|
2765
|
+
};
|
|
2766
|
+
proto.codaGetTableRows = function(step, options) {
|
|
2767
|
+
return this.executeStep("codaGetTableRows", step, options);
|
|
2768
|
+
};
|
|
2769
|
+
proto.convertPdfToImages = function(step, options) {
|
|
2770
|
+
return this.executeStep("convertPdfToImages", step, options);
|
|
2771
|
+
};
|
|
2772
|
+
proto.createDataSource = function(step, options) {
|
|
2773
|
+
return this.executeStep("createDataSource", step, options);
|
|
2774
|
+
};
|
|
2775
|
+
proto.createGmailDraft = function(step, options) {
|
|
2776
|
+
return this.executeStep("createGmailDraft", step, options);
|
|
2777
|
+
};
|
|
2778
|
+
proto.createGoogleCalendarEvent = function(step, options) {
|
|
2779
|
+
return this.executeStep("createGoogleCalendarEvent", step, options);
|
|
2780
|
+
};
|
|
2781
|
+
proto.createGoogleDoc = function(step, options) {
|
|
2782
|
+
return this.executeStep("createGoogleDoc", step, options);
|
|
2783
|
+
};
|
|
2784
|
+
proto.createGoogleSheet = function(step, options) {
|
|
2785
|
+
return this.executeStep("createGoogleSheet", step, options);
|
|
2786
|
+
};
|
|
2787
|
+
proto.deleteDataSource = function(step, options) {
|
|
2788
|
+
return this.executeStep("deleteDataSource", step, options);
|
|
2789
|
+
};
|
|
2790
|
+
proto.deleteDataSourceDocument = function(step, options) {
|
|
2791
|
+
return this.executeStep("deleteDataSourceDocument", step, options);
|
|
2792
|
+
};
|
|
2793
|
+
proto.deleteGmailEmail = function(step, options) {
|
|
2794
|
+
return this.executeStep("deleteGmailEmail", step, options);
|
|
2795
|
+
};
|
|
2796
|
+
proto.deleteGoogleCalendarEvent = function(step, options) {
|
|
2797
|
+
return this.executeStep("deleteGoogleCalendarEvent", step, options);
|
|
2798
|
+
};
|
|
2799
|
+
proto.deleteGoogleSheetRows = function(step, options) {
|
|
2800
|
+
return this.executeStep("deleteGoogleSheetRows", step, options);
|
|
2801
|
+
};
|
|
2802
|
+
proto.detectChanges = function(step, options) {
|
|
2803
|
+
return this.executeStep("detectChanges", step, options);
|
|
2804
|
+
};
|
|
2805
|
+
proto.detectPII = function(step, options) {
|
|
2806
|
+
return this.executeStep("detectPII", step, options);
|
|
2807
|
+
};
|
|
2808
|
+
proto.discordEditMessage = function(step, options) {
|
|
2809
|
+
return this.executeStep("discordEditMessage", step, options);
|
|
2810
|
+
};
|
|
2811
|
+
proto.discordSendFollowUp = function(step, options) {
|
|
2812
|
+
return this.executeStep("discordSendFollowUp", step, options);
|
|
2813
|
+
};
|
|
2814
|
+
proto.discordSendMessage = function(step, options) {
|
|
2815
|
+
return this.executeStep("discordSendMessage", step, options);
|
|
2816
|
+
};
|
|
2817
|
+
proto.downloadVideo = function(step, options) {
|
|
2818
|
+
return this.executeStep("downloadVideo", step, options);
|
|
2819
|
+
};
|
|
2820
|
+
proto.enhanceImageGenerationPrompt = function(step, options) {
|
|
2821
|
+
return this.executeStep("enhanceImageGenerationPrompt", step, options);
|
|
2822
|
+
};
|
|
2823
|
+
proto.enhanceVideoGenerationPrompt = function(step, options) {
|
|
2824
|
+
return this.executeStep("enhanceVideoGenerationPrompt", step, options);
|
|
2825
|
+
};
|
|
2826
|
+
proto.enrichPerson = function(step, options) {
|
|
2827
|
+
return this.executeStep("enrichPerson", step, options);
|
|
2828
|
+
};
|
|
2829
|
+
proto.extractAudioFromVideo = function(step, options) {
|
|
2830
|
+
return this.executeStep("extractAudioFromVideo", step, options);
|
|
2831
|
+
};
|
|
2832
|
+
proto.extractText = function(step, options) {
|
|
2833
|
+
return this.executeStep("extractText", step, options);
|
|
2834
|
+
};
|
|
2835
|
+
proto.fetchDataSourceDocument = function(step, options) {
|
|
2836
|
+
return this.executeStep("fetchDataSourceDocument", step, options);
|
|
2837
|
+
};
|
|
2838
|
+
proto.fetchGoogleDoc = function(step, options) {
|
|
2839
|
+
return this.executeStep("fetchGoogleDoc", step, options);
|
|
2840
|
+
};
|
|
2841
|
+
proto.fetchGoogleSheet = function(step, options) {
|
|
2842
|
+
return this.executeStep("fetchGoogleSheet", step, options);
|
|
2843
|
+
};
|
|
2844
|
+
proto.fetchSlackChannelHistory = function(step, options) {
|
|
2845
|
+
return this.executeStep("fetchSlackChannelHistory", step, options);
|
|
2846
|
+
};
|
|
2847
|
+
proto.fetchYoutubeCaptions = function(step, options) {
|
|
2848
|
+
return this.executeStep("fetchYoutubeCaptions", step, options);
|
|
2849
|
+
};
|
|
2850
|
+
proto.fetchYoutubeChannel = function(step, options) {
|
|
2851
|
+
return this.executeStep("fetchYoutubeChannel", step, options);
|
|
2852
|
+
};
|
|
2853
|
+
proto.fetchYoutubeComments = function(step, options) {
|
|
2854
|
+
return this.executeStep("fetchYoutubeComments", step, options);
|
|
2855
|
+
};
|
|
2856
|
+
proto.fetchYoutubeVideo = function(step, options) {
|
|
2857
|
+
return this.executeStep("fetchYoutubeVideo", step, options);
|
|
2858
|
+
};
|
|
2859
|
+
proto.generateChart = function(step, options) {
|
|
2860
|
+
return this.executeStep("generateChart", step, options);
|
|
2861
|
+
};
|
|
2862
|
+
proto.generateImage = function(step, options) {
|
|
2863
|
+
return this.executeStep("generateImage", step, options);
|
|
2864
|
+
};
|
|
2865
|
+
proto.generateLipsync = function(step, options) {
|
|
2866
|
+
return this.executeStep("generateLipsync", step, options);
|
|
2867
|
+
};
|
|
2868
|
+
proto.generateMusic = function(step, options) {
|
|
2869
|
+
return this.executeStep("generateMusic", step, options);
|
|
2870
|
+
};
|
|
2871
|
+
proto.generateAsset = function(step, options) {
|
|
2872
|
+
return this.executeStep("generatePdf", step, options);
|
|
2873
|
+
};
|
|
2874
|
+
proto.generateStaticVideoFromImage = function(step, options) {
|
|
2875
|
+
return this.executeStep("generateStaticVideoFromImage", step, options);
|
|
2876
|
+
};
|
|
2877
|
+
proto.generateVideo = function(step, options) {
|
|
2878
|
+
return this.executeStep("generateVideo", step, options);
|
|
2879
|
+
};
|
|
2880
|
+
proto.getGmailAttachments = function(step, options) {
|
|
2881
|
+
return this.executeStep("getGmailAttachments", step, options);
|
|
2882
|
+
};
|
|
2883
|
+
proto.getGmailDraft = function(step, options) {
|
|
2884
|
+
return this.executeStep("getGmailDraft", step, options);
|
|
2885
|
+
};
|
|
2886
|
+
proto.getGmailEmail = function(step, options) {
|
|
2887
|
+
return this.executeStep("getGmailEmail", step, options);
|
|
2888
|
+
};
|
|
2889
|
+
proto.getGmailUnreadCount = function(step, options) {
|
|
2890
|
+
return this.executeStep("getGmailUnreadCount", step, options);
|
|
2891
|
+
};
|
|
2892
|
+
proto.getGoogleCalendarEvent = function(step, options) {
|
|
2893
|
+
return this.executeStep("getGoogleCalendarEvent", step, options);
|
|
2894
|
+
};
|
|
2895
|
+
proto.getGoogleDriveFile = function(step, options) {
|
|
2896
|
+
return this.executeStep("getGoogleDriveFile", step, options);
|
|
2897
|
+
};
|
|
2898
|
+
proto.getGoogleSheetInfo = function(step, options) {
|
|
2899
|
+
return this.executeStep("getGoogleSheetInfo", step, options);
|
|
2900
|
+
};
|
|
2901
|
+
proto.getMediaMetadata = function(step, options) {
|
|
2902
|
+
return this.executeStep("getMediaMetadata", step, options);
|
|
2903
|
+
};
|
|
2904
|
+
proto.httpRequest = function(step, options) {
|
|
2905
|
+
return this.executeStep("httpRequest", step, options);
|
|
2906
|
+
};
|
|
2907
|
+
proto.hubspotCreateCompany = function(step, options) {
|
|
2908
|
+
return this.executeStep("hubspotCreateCompany", step, options);
|
|
2909
|
+
};
|
|
2910
|
+
proto.hubspotCreateContact = function(step, options) {
|
|
2911
|
+
return this.executeStep("hubspotCreateContact", step, options);
|
|
2912
|
+
};
|
|
2913
|
+
proto.hubspotGetCompany = function(step, options) {
|
|
2914
|
+
return this.executeStep("hubspotGetCompany", step, options);
|
|
2915
|
+
};
|
|
2916
|
+
proto.hubspotGetContact = function(step, options) {
|
|
2917
|
+
return this.executeStep("hubspotGetContact", step, options);
|
|
2918
|
+
};
|
|
2919
|
+
proto.hunterApiCompanyEnrichment = function(step, options) {
|
|
2920
|
+
return this.executeStep("hunterApiCompanyEnrichment", step, options);
|
|
2921
|
+
};
|
|
2922
|
+
proto.hunterApiDomainSearch = function(step, options) {
|
|
2923
|
+
return this.executeStep("hunterApiDomainSearch", step, options);
|
|
2924
|
+
};
|
|
2925
|
+
proto.hunterApiEmailFinder = function(step, options) {
|
|
2926
|
+
return this.executeStep("hunterApiEmailFinder", step, options);
|
|
2927
|
+
};
|
|
2928
|
+
proto.hunterApiEmailVerification = function(step, options) {
|
|
2929
|
+
return this.executeStep("hunterApiEmailVerification", step, options);
|
|
2930
|
+
};
|
|
2931
|
+
proto.hunterApiPersonEnrichment = function(step, options) {
|
|
2932
|
+
return this.executeStep("hunterApiPersonEnrichment", step, options);
|
|
2933
|
+
};
|
|
2934
|
+
proto.imageFaceSwap = function(step, options) {
|
|
2935
|
+
return this.executeStep("imageFaceSwap", step, options);
|
|
2936
|
+
};
|
|
2937
|
+
proto.imageRemoveWatermark = function(step, options) {
|
|
2938
|
+
return this.executeStep("imageRemoveWatermark", step, options);
|
|
2939
|
+
};
|
|
2940
|
+
proto.insertVideoClips = function(step, options) {
|
|
1551
2941
|
return this.executeStep("insertVideoClips", step, options);
|
|
1552
2942
|
};
|
|
1553
2943
|
proto.listDataSources = function(step, options) {
|
|
@@ -1610,6 +3000,9 @@ function applyStepMethods(AgentClass) {
|
|
|
1610
3000
|
proto.postToZapier = function(step, options) {
|
|
1611
3001
|
return this.executeStep("postToZapier", step, options);
|
|
1612
3002
|
};
|
|
3003
|
+
proto.queryAppDatabase = function(step, options) {
|
|
3004
|
+
return this.executeStep("queryAppDatabase", step, options);
|
|
3005
|
+
};
|
|
1613
3006
|
proto.queryDataSource = function(step, options) {
|
|
1614
3007
|
return this.executeStep("queryDataSource", step, options);
|
|
1615
3008
|
};
|
|
@@ -1836,6 +3229,8 @@ var init_client = __esm({
|
|
|
1836
3229
|
init_errors();
|
|
1837
3230
|
init_rate_limit();
|
|
1838
3231
|
init_config();
|
|
3232
|
+
init_auth();
|
|
3233
|
+
init_db();
|
|
1839
3234
|
init_steps();
|
|
1840
3235
|
DEFAULT_BASE_URL = "https://v1.mindstudio-api.com";
|
|
1841
3236
|
DEFAULT_MAX_RETRIES = 3;
|
|
@@ -1846,17 +3241,46 @@ var init_client = __esm({
|
|
|
1846
3241
|
_reuseThreadId;
|
|
1847
3242
|
/** @internal */
|
|
1848
3243
|
_threadId;
|
|
3244
|
+
// ---- App context (db + auth) ----
|
|
3245
|
+
/**
|
|
3246
|
+
* @internal App ID for context resolution. Resolved from:
|
|
3247
|
+
* constructor appId → MINDSTUDIO_APP_ID env → sandbox globals →
|
|
3248
|
+
* auto-detected from first executeStep response header.
|
|
3249
|
+
*/
|
|
3250
|
+
_appId;
|
|
3251
|
+
/**
|
|
3252
|
+
* @internal Cached app context (auth + databases). Populated by
|
|
3253
|
+
* ensureContext() and cached for the lifetime of the instance.
|
|
3254
|
+
*/
|
|
3255
|
+
_context;
|
|
3256
|
+
/**
|
|
3257
|
+
* @internal Deduplication promise for ensureContext(). Ensures only one
|
|
3258
|
+
* context fetch is in-flight at a time, even if multiple db/auth
|
|
3259
|
+
* operations trigger it concurrently.
|
|
3260
|
+
*/
|
|
3261
|
+
_contextPromise;
|
|
3262
|
+
/** @internal Cached AuthContext instance, created during context hydration. */
|
|
3263
|
+
_auth;
|
|
3264
|
+
/** @internal Cached Db namespace instance, created during context hydration. */
|
|
3265
|
+
_db;
|
|
3266
|
+
/** @internal Auth type — 'internal' for CALLBACK_TOKEN (managed mode), 'apiKey' otherwise. */
|
|
3267
|
+
_authType;
|
|
1849
3268
|
constructor(options = {}) {
|
|
1850
3269
|
const config = loadConfig();
|
|
1851
3270
|
const { token, authType } = resolveToken(options.apiKey, config);
|
|
1852
3271
|
const baseUrl = options.baseUrl ?? process.env.MINDSTUDIO_BASE_URL ?? process.env.REMOTE_HOSTNAME ?? config.baseUrl ?? DEFAULT_BASE_URL;
|
|
1853
3272
|
this._reuseThreadId = options.reuseThreadId ?? /^(true|1)$/i.test(process.env.MINDSTUDIO_REUSE_THREAD_ID ?? "");
|
|
3273
|
+
this._appId = options.appId ?? process.env.MINDSTUDIO_APP_ID ?? void 0;
|
|
3274
|
+
this._authType = authType;
|
|
1854
3275
|
this._httpConfig = {
|
|
1855
3276
|
baseUrl,
|
|
1856
3277
|
token,
|
|
1857
3278
|
rateLimiter: new RateLimiter(authType),
|
|
1858
3279
|
maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES
|
|
1859
3280
|
};
|
|
3281
|
+
if (authType === "internal") {
|
|
3282
|
+
this._trySandboxHydration();
|
|
3283
|
+
}
|
|
1860
3284
|
}
|
|
1861
3285
|
/**
|
|
1862
3286
|
* Execute any step by its type name. This is the low-level method that all
|
|
@@ -1895,6 +3319,10 @@ var init_client = __esm({
|
|
|
1895
3319
|
if (this._reuseThreadId && returnedThreadId) {
|
|
1896
3320
|
this._threadId = returnedThreadId;
|
|
1897
3321
|
}
|
|
3322
|
+
const returnedAppId = headers.get("x-mindstudio-app-id");
|
|
3323
|
+
if (!this._appId && returnedAppId) {
|
|
3324
|
+
this._appId = returnedAppId;
|
|
3325
|
+
}
|
|
1898
3326
|
const remaining = headers.get("x-ratelimit-remaining");
|
|
1899
3327
|
const billingCost = headers.get("x-mindstudio-billing-cost");
|
|
1900
3328
|
const billingEvents = headers.get("x-mindstudio-billing-events");
|
|
@@ -2161,6 +3589,278 @@ var init_client = __esm({
|
|
|
2161
3589
|
return data;
|
|
2162
3590
|
}
|
|
2163
3591
|
// -------------------------------------------------------------------------
|
|
3592
|
+
// db + auth namespaces
|
|
3593
|
+
// -------------------------------------------------------------------------
|
|
3594
|
+
/**
|
|
3595
|
+
* The `auth` namespace — synchronous role-based access control.
|
|
3596
|
+
*
|
|
3597
|
+
* Provides the current user's identity and roles. All methods are
|
|
3598
|
+
* synchronous since the role map is preloaded during context hydration.
|
|
3599
|
+
*
|
|
3600
|
+
* **Important**: Context must be hydrated before accessing `auth`.
|
|
3601
|
+
* - Inside the MindStudio sandbox: automatic (populated from globals)
|
|
3602
|
+
* - Outside the sandbox: call `await agent.ensureContext()` first,
|
|
3603
|
+
* or access `auth` after any `db` operation (which auto-hydrates)
|
|
3604
|
+
*
|
|
3605
|
+
* @throws {MindStudioError} if context has not been hydrated yet
|
|
3606
|
+
*
|
|
3607
|
+
* @example
|
|
3608
|
+
* ```ts
|
|
3609
|
+
* await agent.ensureContext();
|
|
3610
|
+
* agent.auth.requireRole(Roles.admin);
|
|
3611
|
+
* const admins = agent.auth.getUsersByRole(Roles.admin);
|
|
3612
|
+
* ```
|
|
3613
|
+
*/
|
|
3614
|
+
get auth() {
|
|
3615
|
+
if (!this._auth) {
|
|
3616
|
+
throw new MindStudioError(
|
|
3617
|
+
"Auth context not yet loaded. Call `await agent.ensureContext()` or perform any db operation first (which auto-hydrates context). Inside the MindStudio sandbox, context is loaded automatically.",
|
|
3618
|
+
"context_not_loaded",
|
|
3619
|
+
400
|
|
3620
|
+
);
|
|
3621
|
+
}
|
|
3622
|
+
return this._auth;
|
|
3623
|
+
}
|
|
3624
|
+
/**
|
|
3625
|
+
* The `db` namespace — chainable collection API over managed databases.
|
|
3626
|
+
*
|
|
3627
|
+
* Use `db.defineTable<T>(name)` to get a typed Table<T>, then call
|
|
3628
|
+
* collection methods (filter, sortBy, push, update, etc.) on it.
|
|
3629
|
+
*
|
|
3630
|
+
* Context is auto-hydrated on first query execution — you can safely
|
|
3631
|
+
* call `defineTable()` at module scope without triggering any HTTP.
|
|
3632
|
+
*
|
|
3633
|
+
* @example
|
|
3634
|
+
* ```ts
|
|
3635
|
+
* const Orders = agent.db.defineTable<Order>('orders');
|
|
3636
|
+
* const active = await Orders.filter(o => o.status === 'active').take(10);
|
|
3637
|
+
* ```
|
|
3638
|
+
*/
|
|
3639
|
+
get db() {
|
|
3640
|
+
if (this._db) return this._db;
|
|
3641
|
+
return this._createLazyDb();
|
|
3642
|
+
}
|
|
3643
|
+
/**
|
|
3644
|
+
* Hydrate the app context (auth + database metadata). This must be
|
|
3645
|
+
* called before using `auth` synchronously. For `db`, hydration happens
|
|
3646
|
+
* automatically on first query.
|
|
3647
|
+
*
|
|
3648
|
+
* Context is fetched once and cached for the instance's lifetime.
|
|
3649
|
+
* Calling `ensureContext()` multiple times is safe (no-op after first).
|
|
3650
|
+
*
|
|
3651
|
+
* Context sources (checked in order):
|
|
3652
|
+
* 1. Sandbox globals (`globalThis.ai.auth`, `globalThis.ai.databases`)
|
|
3653
|
+
* 2. HTTP: `GET /developer/v2/helpers/app-context?appId={appId}`
|
|
3654
|
+
*
|
|
3655
|
+
* @throws {MindStudioError} if no `appId` is available
|
|
3656
|
+
*
|
|
3657
|
+
* @example
|
|
3658
|
+
* ```ts
|
|
3659
|
+
* await agent.ensureContext();
|
|
3660
|
+
* // auth is now available synchronously
|
|
3661
|
+
* agent.auth.requireRole(Roles.admin);
|
|
3662
|
+
* ```
|
|
3663
|
+
*/
|
|
3664
|
+
async ensureContext() {
|
|
3665
|
+
if (this._context) return;
|
|
3666
|
+
if (!this._contextPromise) {
|
|
3667
|
+
this._contextPromise = this._hydrateContext();
|
|
3668
|
+
}
|
|
3669
|
+
await this._contextPromise;
|
|
3670
|
+
}
|
|
3671
|
+
/**
|
|
3672
|
+
* @internal Fetch and cache app context, then create auth + db instances.
|
|
3673
|
+
*
|
|
3674
|
+
* In managed mode (CALLBACK_TOKEN), the platform resolves the app from
|
|
3675
|
+
* the token — no appId needed. With an API key, appId is required.
|
|
3676
|
+
*/
|
|
3677
|
+
async _hydrateContext() {
|
|
3678
|
+
if (!this._appId && this._authType !== "internal") {
|
|
3679
|
+
throw new MindStudioError(
|
|
3680
|
+
"No app ID available for context resolution. Pass `appId` to the constructor, set the MINDSTUDIO_APP_ID environment variable, or make a step execution call first (which auto-detects the app ID).",
|
|
3681
|
+
"missing_app_id",
|
|
3682
|
+
400
|
|
3683
|
+
);
|
|
3684
|
+
}
|
|
3685
|
+
const context = await this.getAppContext(this._appId);
|
|
3686
|
+
this._applyContext(context);
|
|
3687
|
+
}
|
|
3688
|
+
/**
|
|
3689
|
+
* @internal Apply a resolved context object — creates AuthContext and Db.
|
|
3690
|
+
* Used by both the HTTP path and sandbox hydration.
|
|
3691
|
+
*/
|
|
3692
|
+
_applyContext(context) {
|
|
3693
|
+
this._context = context;
|
|
3694
|
+
this._auth = new AuthContext(context.auth);
|
|
3695
|
+
this._db = createDb(
|
|
3696
|
+
context.databases,
|
|
3697
|
+
this._executeDbQuery.bind(this)
|
|
3698
|
+
);
|
|
3699
|
+
}
|
|
3700
|
+
/**
|
|
3701
|
+
* @internal Try to hydrate context synchronously from sandbox globals.
|
|
3702
|
+
* Called in the constructor when CALLBACK_TOKEN auth is detected.
|
|
3703
|
+
*
|
|
3704
|
+
* The MindStudio sandbox pre-populates `globalThis.ai` with:
|
|
3705
|
+
* - `ai.auth`: { userId, roleAssignments[] }
|
|
3706
|
+
* - `ai.databases`: [{ id, name, tables[] }]
|
|
3707
|
+
*/
|
|
3708
|
+
_trySandboxHydration() {
|
|
3709
|
+
const ai = globalThis.ai;
|
|
3710
|
+
if (ai?.auth && ai?.databases) {
|
|
3711
|
+
this._applyContext({
|
|
3712
|
+
auth: ai.auth,
|
|
3713
|
+
databases: ai.databases
|
|
3714
|
+
});
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
/**
|
|
3718
|
+
* @internal Execute a SQL query against a managed database.
|
|
3719
|
+
* Used as the `executeQuery` callback for Table instances.
|
|
3720
|
+
*
|
|
3721
|
+
* Calls the `queryAppDatabase` step with `parameterize: false`
|
|
3722
|
+
* (the SDK builds fully-formed SQL with escaped inline values).
|
|
3723
|
+
*/
|
|
3724
|
+
async _executeDbQuery(databaseId, sql) {
|
|
3725
|
+
const result = await this.executeStep("queryAppDatabase", {
|
|
3726
|
+
databaseId,
|
|
3727
|
+
sql,
|
|
3728
|
+
parameterize: false
|
|
3729
|
+
});
|
|
3730
|
+
return { rows: result.rows ?? [], changes: result.changes ?? 0 };
|
|
3731
|
+
}
|
|
3732
|
+
/**
|
|
3733
|
+
* @internal Create a lazy Db proxy that auto-hydrates context.
|
|
3734
|
+
*
|
|
3735
|
+
* defineTable() returns Table instances immediately (no async needed).
|
|
3736
|
+
* But the Table's executeQuery callback is wrapped to call ensureContext()
|
|
3737
|
+
* before the first query, so context is fetched lazily.
|
|
3738
|
+
*/
|
|
3739
|
+
_createLazyDb() {
|
|
3740
|
+
const agent = this;
|
|
3741
|
+
return {
|
|
3742
|
+
defineTable(name, options) {
|
|
3743
|
+
const databaseHint = options?.database;
|
|
3744
|
+
return new Table({
|
|
3745
|
+
databaseId: "",
|
|
3746
|
+
tableName: name,
|
|
3747
|
+
columns: [],
|
|
3748
|
+
executeQuery: async (sql) => {
|
|
3749
|
+
await agent.ensureContext();
|
|
3750
|
+
const databases = agent._context.databases;
|
|
3751
|
+
let targetDb;
|
|
3752
|
+
if (databaseHint) {
|
|
3753
|
+
targetDb = databases.find(
|
|
3754
|
+
(d) => d.id === databaseHint || d.name === databaseHint
|
|
3755
|
+
);
|
|
3756
|
+
} else {
|
|
3757
|
+
targetDb = databases.find(
|
|
3758
|
+
(d) => d.tables.some((t) => t.name === name)
|
|
3759
|
+
);
|
|
3760
|
+
}
|
|
3761
|
+
const databaseId = targetDb?.id ?? databases[0]?.id ?? "";
|
|
3762
|
+
return agent._executeDbQuery(databaseId, sql);
|
|
3763
|
+
}
|
|
3764
|
+
});
|
|
3765
|
+
},
|
|
3766
|
+
// Time helpers work without context
|
|
3767
|
+
now: () => Date.now(),
|
|
3768
|
+
days: (n) => n * 864e5,
|
|
3769
|
+
hours: (n) => n * 36e5,
|
|
3770
|
+
minutes: (n) => n * 6e4,
|
|
3771
|
+
ago: (ms) => Date.now() - ms,
|
|
3772
|
+
fromNow: (ms) => Date.now() + ms
|
|
3773
|
+
};
|
|
3774
|
+
}
|
|
3775
|
+
// -------------------------------------------------------------------------
|
|
3776
|
+
// Helper methods — user resolution
|
|
3777
|
+
// -------------------------------------------------------------------------
|
|
3778
|
+
/**
|
|
3779
|
+
* Resolve a single user ID to display info (name, email, profile picture).
|
|
3780
|
+
*
|
|
3781
|
+
* Use this when you have a `User`-typed field value and need the person's
|
|
3782
|
+
* display name, email, or avatar. Returns null if the user ID is not found.
|
|
3783
|
+
*
|
|
3784
|
+
* Also available as a top-level import:
|
|
3785
|
+
* ```ts
|
|
3786
|
+
* import { resolveUser } from '@mindstudio-ai/agent';
|
|
3787
|
+
* ```
|
|
3788
|
+
*
|
|
3789
|
+
* @param userId - The user ID to resolve (a `User` branded string or plain UUID)
|
|
3790
|
+
* @returns Resolved user info, or null if not found
|
|
3791
|
+
*
|
|
3792
|
+
* @example
|
|
3793
|
+
* ```ts
|
|
3794
|
+
* const user = await agent.resolveUser(order.requestedBy);
|
|
3795
|
+
* if (user) {
|
|
3796
|
+
* console.log(user.name); // "Jane Smith"
|
|
3797
|
+
* console.log(user.email); // "jane@example.com"
|
|
3798
|
+
* console.log(user.profilePictureUrl); // "https://..." or null
|
|
3799
|
+
* }
|
|
3800
|
+
* ```
|
|
3801
|
+
*/
|
|
3802
|
+
async resolveUser(userId) {
|
|
3803
|
+
const { users } = await this.resolveUsers([userId]);
|
|
3804
|
+
return users[0] ?? null;
|
|
3805
|
+
}
|
|
3806
|
+
/**
|
|
3807
|
+
* Resolve multiple user IDs to display info in a single request.
|
|
3808
|
+
* Maximum 100 user IDs per request.
|
|
3809
|
+
*
|
|
3810
|
+
* Use this for batch resolution when you have multiple user references
|
|
3811
|
+
* to display (e.g. all approvers on a purchase order, all team members).
|
|
3812
|
+
*
|
|
3813
|
+
* @param userIds - Array of user IDs to resolve (max 100)
|
|
3814
|
+
* @returns Object with `users` array of resolved user info
|
|
3815
|
+
*
|
|
3816
|
+
* @example
|
|
3817
|
+
* ```ts
|
|
3818
|
+
* // Resolve all approvers at once
|
|
3819
|
+
* const approverIds = approvals.map(a => a.assignedTo);
|
|
3820
|
+
* const { users } = await agent.resolveUsers(approverIds);
|
|
3821
|
+
*
|
|
3822
|
+
* for (const u of users) {
|
|
3823
|
+
* console.log(`${u.name} (${u.email})`);
|
|
3824
|
+
* }
|
|
3825
|
+
* ```
|
|
3826
|
+
*/
|
|
3827
|
+
async resolveUsers(userIds) {
|
|
3828
|
+
const { data } = await request(
|
|
3829
|
+
this._httpConfig,
|
|
3830
|
+
"POST",
|
|
3831
|
+
"/helpers/resolve-users",
|
|
3832
|
+
{ userIds }
|
|
3833
|
+
);
|
|
3834
|
+
return data;
|
|
3835
|
+
}
|
|
3836
|
+
// -------------------------------------------------------------------------
|
|
3837
|
+
// App context
|
|
3838
|
+
// -------------------------------------------------------------------------
|
|
3839
|
+
/**
|
|
3840
|
+
* Get auth and database context for an app.
|
|
3841
|
+
*
|
|
3842
|
+
* Returns role assignments and managed database schemas. Useful for
|
|
3843
|
+
* hydrating `auth` and `db` namespaces when running outside the sandbox.
|
|
3844
|
+
*
|
|
3845
|
+
* When called with a CALLBACK_TOKEN (managed mode), `appId` is optional —
|
|
3846
|
+
* the platform resolves the app from the token. With an API key, `appId`
|
|
3847
|
+
* is required.
|
|
3848
|
+
*
|
|
3849
|
+
* ```ts
|
|
3850
|
+
* const ctx = await agent.getAppContext('your-app-id');
|
|
3851
|
+
* console.log(ctx.auth.roleAssignments, ctx.databases);
|
|
3852
|
+
* ```
|
|
3853
|
+
*/
|
|
3854
|
+
async getAppContext(appId) {
|
|
3855
|
+
const query = appId ? `?appId=${encodeURIComponent(appId)}` : "";
|
|
3856
|
+
const { data } = await request(
|
|
3857
|
+
this._httpConfig,
|
|
3858
|
+
"GET",
|
|
3859
|
+
`/helpers/app-context${query}`
|
|
3860
|
+
);
|
|
3861
|
+
return data;
|
|
3862
|
+
}
|
|
3863
|
+
// -------------------------------------------------------------------------
|
|
2164
3864
|
// Account methods
|
|
2165
3865
|
// -------------------------------------------------------------------------
|
|
2166
3866
|
/** Update the display name of the authenticated user/agent. */
|
|
@@ -2274,7 +3974,7 @@ async function startMcpServer(options) {
|
|
|
2274
3974
|
capabilities: { tools: {} },
|
|
2275
3975
|
serverInfo: {
|
|
2276
3976
|
name: "mindstudio-agent",
|
|
2277
|
-
version: "0.1.
|
|
3977
|
+
version: "0.1.9"
|
|
2278
3978
|
},
|
|
2279
3979
|
instructions: "Welcome to MindStudio \u2014 a platform with 200+ AI models, 850+ third-party integrations, and pre-built agents.\n\nGetting started:\n1. Call `listAgents` to verify your connection and see available agents.\n2. Call `changeName` to set your display name \u2014 use your name or whatever your user calls you. This is how you'll appear in MindStudio request logs.\n3. If you have a profile picture or icon, call `uploadFile` to upload it, then `changeProfilePicture` with the returned URL. This helps users identify your requests in their logs.\n4. Call `listActions` to discover all available actions.\n\nThen use the tools to generate text, images, video, audio, search the web, work with data sources, run agents, and more.\n\nImportant:\n- AI-powered actions (text generation, image generation, video, audio, etc.) cost money. Before running these, call `estimateActionCost` and confirm with the user before proceeding \u2014 unless they've explicitly told you to go ahead.\n- Not all agents from `listAgents` are configured for API use. Do not try to run an agent just because it appears in the list \u2014 it will likely fail. Only run agents the user specifically asks you to run."
|
|
2280
3980
|
});
|
|
@@ -2327,8 +4027,11 @@ async function startMcpServer(options) {
|
|
|
2327
4027
|
} else if (toolName === "listConnections") {
|
|
2328
4028
|
result = await getAgent().listConnections();
|
|
2329
4029
|
} else if (toolName === "estimateActionCost") {
|
|
4030
|
+
const meta = await getMetadata();
|
|
4031
|
+
const rawType = args.stepType;
|
|
4032
|
+
const resolved = meta[rawType]?.stepType ?? rawType;
|
|
2330
4033
|
result = await getAgent().estimateStepCost(
|
|
2331
|
-
|
|
4034
|
+
resolved,
|
|
2332
4035
|
args.step,
|
|
2333
4036
|
{
|
|
2334
4037
|
appId: args.appId,
|
|
@@ -3179,7 +4882,7 @@ function isNewerVersion(current, latest) {
|
|
|
3179
4882
|
return false;
|
|
3180
4883
|
}
|
|
3181
4884
|
async function checkForUpdate() {
|
|
3182
|
-
const currentVersion = "0.1.
|
|
4885
|
+
const currentVersion = "0.1.9";
|
|
3183
4886
|
if (!currentVersion) return null;
|
|
3184
4887
|
try {
|
|
3185
4888
|
const { loadConfig: loadConfig2, saveConfig: saveConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
@@ -3208,7 +4911,7 @@ async function checkForUpdate() {
|
|
|
3208
4911
|
}
|
|
3209
4912
|
}
|
|
3210
4913
|
function printUpdateNotice(latestVersion) {
|
|
3211
|
-
const currentVersion = "0.1.
|
|
4914
|
+
const currentVersion = "0.1.9";
|
|
3212
4915
|
process.stderr.write(
|
|
3213
4916
|
`
|
|
3214
4917
|
${ansi.cyanBright("Update available")} ${ansi.gray(currentVersion + " \u2192")} ${ansi.cyanBold(latestVersion)}
|
|
@@ -3283,7 +4986,7 @@ async function cmdLogin(options) {
|
|
|
3283
4986
|
process.stderr.write("\n");
|
|
3284
4987
|
printLogo();
|
|
3285
4988
|
process.stderr.write("\n");
|
|
3286
|
-
const ver = "0.1.
|
|
4989
|
+
const ver = "0.1.9";
|
|
3287
4990
|
process.stderr.write(
|
|
3288
4991
|
` ${ansi.bold("MindStudio Agent")} ${ver ? " " + ansi.gray("v" + ver) : ""}
|
|
3289
4992
|
`
|
|
@@ -3823,6 +5526,10 @@ async function main() {
|
|
|
3823
5526
|
"",
|
|
3824
5527
|
'Tip: run "mindstudio list-actions" to see available actions.'
|
|
3825
5528
|
]);
|
|
5529
|
+
const allKeys2 = await getAllMethodKeys();
|
|
5530
|
+
const resolvedMethod = resolveMethodOrFail(stepMethod, allKeys2);
|
|
5531
|
+
const { stepMetadata: stepMetadata2 } = await Promise.resolve().then(() => (init_metadata(), metadata_exports));
|
|
5532
|
+
const meta = stepMetadata2[resolvedMethod];
|
|
3826
5533
|
const costArgv = positionals.slice(2);
|
|
3827
5534
|
let costInput;
|
|
3828
5535
|
const firstArg = costArgv[0];
|
|
@@ -3835,7 +5542,7 @@ async function main() {
|
|
|
3835
5542
|
} else {
|
|
3836
5543
|
costInput = parseStepFlags(costArgv);
|
|
3837
5544
|
}
|
|
3838
|
-
await cmdEstimateStepCost(
|
|
5545
|
+
await cmdEstimateStepCost(meta.stepType, costInput, {
|
|
3839
5546
|
apiKey: values["api-key"],
|
|
3840
5547
|
baseUrl: values["base-url"]
|
|
3841
5548
|
});
|