create-web-ai-service 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import('../dist/index.js');
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Create Web AI Service CLI
3
+ *
4
+ * Main entry point for the scaffolder.
5
+ */
6
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Create Web AI Service CLI
3
+ *
4
+ * Main entry point for the scaffolder.
5
+ */
6
+ import chalk from 'chalk';
7
+ import { execSync } from 'child_process';
8
+ import { promptForOptions } from './prompts.js';
9
+ import { generateProject } from './templates.js';
10
+ /**
11
+ * Print banner
12
+ */
13
+ function printBanner() {
14
+ console.log('');
15
+ console.log(chalk.bold.cyan('╔════════════════════════════════════════════╗'));
16
+ console.log(chalk.bold.cyan('║') + chalk.bold.white(' 🚀 Create Web AI Service ') + chalk.bold.cyan('║'));
17
+ console.log(chalk.bold.cyan('║') + chalk.gray(' Build AI-powered APIs with YAML ') + chalk.bold.cyan('║'));
18
+ console.log(chalk.bold.cyan('╚════════════════════════════════════════════╝'));
19
+ console.log('');
20
+ }
21
+ /**
22
+ * Parse command line arguments
23
+ */
24
+ function parseArgs() {
25
+ const args = process.argv.slice(2);
26
+ const result = {};
27
+ // First non-flag argument is the project name
28
+ for (let i = 0; i < args.length; i++) {
29
+ const arg = args[i];
30
+ if (arg === '--plugins' && args[i + 1]) {
31
+ result.plugins = args[i + 1].split(',').map(s => s.trim());
32
+ i++;
33
+ }
34
+ else if (!arg.startsWith('-') && !result.projectName) {
35
+ result.projectName = arg;
36
+ }
37
+ }
38
+ return result;
39
+ }
40
+ /**
41
+ * Install dependencies using npm
42
+ */
43
+ function installDependencies(projectPath) {
44
+ console.log('');
45
+ console.log(chalk.cyan('📦 Installing dependencies...'));
46
+ console.log('');
47
+ try {
48
+ execSync('npm install', {
49
+ cwd: projectPath,
50
+ stdio: 'inherit',
51
+ });
52
+ }
53
+ catch (error) {
54
+ console.log(chalk.yellow('⚠️ Failed to install dependencies. Please run npm install manually.'));
55
+ }
56
+ }
57
+ /**
58
+ * Print success message with next steps
59
+ */
60
+ function printSuccess(projectName) {
61
+ console.log('');
62
+ console.log(chalk.green('✅ Project created successfully!'));
63
+ console.log('');
64
+ console.log(chalk.bold('Next steps:'));
65
+ console.log('');
66
+ console.log(chalk.cyan(` cd ${projectName}`));
67
+ console.log(chalk.cyan(' cp .env.example .env'));
68
+ console.log(chalk.gray(' # Edit .env with your API keys'));
69
+ console.log(chalk.cyan(' npm run dev'));
70
+ console.log('');
71
+ console.log(chalk.gray('Then visit: ') + chalk.underline('http://localhost:3000/hello'));
72
+ console.log('');
73
+ console.log(chalk.bold('Documentation:'));
74
+ console.log(chalk.gray(' • Getting Started: ') + chalk.underline('https://github.com/yourrepo/web-ai-service#readme'));
75
+ console.log(chalk.gray(' • Creating Endpoints: see docs/creating-endpoints.md'));
76
+ console.log('');
77
+ }
78
+ /**
79
+ * Main function
80
+ */
81
+ async function main() {
82
+ printBanner();
83
+ const cliArgs = parseArgs();
84
+ // Get scaffold options (from prompts or CLI args)
85
+ const options = await promptForOptions(cliArgs.projectName);
86
+ // Override plugins if provided via CLI
87
+ if (cliArgs.plugins && cliArgs.plugins.length > 0) {
88
+ options.plugins = cliArgs.plugins;
89
+ }
90
+ console.log('');
91
+ console.log(chalk.cyan('🔧 Creating project...'));
92
+ // Generate project
93
+ await generateProject(options);
94
+ // Install dependencies
95
+ installDependencies(options.projectPath);
96
+ // Print success
97
+ printSuccess(options.projectName);
98
+ }
99
+ // Run
100
+ main().catch((error) => {
101
+ console.error(chalk.red('Error:'), error.message);
102
+ process.exit(1);
103
+ });
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Plugin Registry
3
+ *
4
+ * Defines available plugins for the scaffolder.
5
+ * Add new plugins here to make them available in the CLI.
6
+ */
7
+ export interface PluginEnvVar {
8
+ name: string;
9
+ description: string;
10
+ example: string;
11
+ }
12
+ export interface PluginConfig {
13
+ name: string;
14
+ description: string;
15
+ dependencies: Record<string, string>;
16
+ envVars: PluginEnvVar[];
17
+ files: {
18
+ source: string;
19
+ destination: string;
20
+ }[];
21
+ }
22
+ /**
23
+ * Available plugins registry
24
+ * Add new plugins to this object to make them available in the CLI
25
+ */
26
+ export declare const plugins: Record<string, PluginConfig>;
27
+ /**
28
+ * Get list of available plugin names
29
+ */
30
+ export declare function getAvailablePlugins(): string[];
31
+ /**
32
+ * Get plugin config by name
33
+ */
34
+ export declare function getPluginConfig(name: string): PluginConfig | undefined;
35
+ /**
36
+ * Get choices for inquirer checkbox
37
+ */
38
+ export declare function getPluginChoices(): {
39
+ name: string;
40
+ value: string;
41
+ checked: boolean;
42
+ }[];
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Plugin Registry
3
+ *
4
+ * Defines available plugins for the scaffolder.
5
+ * Add new plugins here to make them available in the CLI.
6
+ */
7
+ /**
8
+ * Available plugins registry
9
+ * Add new plugins to this object to make them available in the CLI
10
+ */
11
+ export const plugins = {
12
+ supabase: {
13
+ name: 'Supabase',
14
+ description: 'Database & Auth integration with Supabase',
15
+ dependencies: {
16
+ '@supabase/supabase-js': '^2.90.1',
17
+ },
18
+ envVars: [
19
+ {
20
+ name: 'SUPABASE_URL',
21
+ description: 'Your Supabase project URL',
22
+ example: 'https://your-project.supabase.co',
23
+ },
24
+ {
25
+ name: 'SUPABASE_ANON_KEY',
26
+ description: 'Your Supabase anon/public key',
27
+ example: 'your-supabase-anon-key',
28
+ },
29
+ {
30
+ name: 'SUPABASE_SERVICE_KEY',
31
+ description: 'Your Supabase service role key (optional, for admin operations)',
32
+ example: 'your-supabase-service-key',
33
+ },
34
+ ],
35
+ files: [
36
+ {
37
+ source: 'plugins/supabase/src/plugins/supabase.ts',
38
+ destination: 'src/plugins/supabase.ts',
39
+ },
40
+ ],
41
+ },
42
+ };
43
+ /**
44
+ * Get list of available plugin names
45
+ */
46
+ export function getAvailablePlugins() {
47
+ return Object.keys(plugins);
48
+ }
49
+ /**
50
+ * Get plugin config by name
51
+ */
52
+ export function getPluginConfig(name) {
53
+ return plugins[name];
54
+ }
55
+ /**
56
+ * Get choices for inquirer checkbox
57
+ */
58
+ export function getPluginChoices() {
59
+ return Object.entries(plugins).map(([key, plugin]) => ({
60
+ name: `${plugin.name} - ${plugin.description}`,
61
+ value: key,
62
+ checked: false,
63
+ }));
64
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * CLI Prompts Module
3
+ *
4
+ * Defines interactive prompts for the scaffolder CLI.
5
+ */
6
+ export interface ScaffoldOptions {
7
+ projectName: string;
8
+ projectPath: string;
9
+ plugins: string[];
10
+ }
11
+ /**
12
+ * Run interactive prompts to gather scaffold options
13
+ */
14
+ export declare function promptForOptions(initialName?: string): Promise<ScaffoldOptions>;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * CLI Prompts Module
3
+ *
4
+ * Defines interactive prompts for the scaffolder CLI.
5
+ */
6
+ import inquirer from 'inquirer';
7
+ import { getPluginChoices } from './plugins.js';
8
+ import path from 'path';
9
+ import fs from 'fs-extra';
10
+ /**
11
+ * Validate project name
12
+ */
13
+ function validateProjectName(input) {
14
+ if (!input || input.trim().length === 0) {
15
+ return 'Project name is required';
16
+ }
17
+ // Check for valid npm package name format
18
+ const validPattern = /^[a-z0-9]([a-z0-9-_.]*[a-z0-9])?$/;
19
+ if (!validPattern.test(input.toLowerCase())) {
20
+ return 'Project name must be lowercase and can only contain letters, numbers, hyphens, dots, and underscores';
21
+ }
22
+ return true;
23
+ }
24
+ /**
25
+ * Check if directory exists
26
+ */
27
+ async function checkDirectoryExists(projectPath) {
28
+ try {
29
+ await fs.access(projectPath);
30
+ return true;
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ }
36
+ /**
37
+ * Run interactive prompts to gather scaffold options
38
+ */
39
+ export async function promptForOptions(initialName) {
40
+ const answers = await inquirer.prompt([
41
+ {
42
+ type: 'input',
43
+ name: 'projectName',
44
+ message: 'What is your project name?',
45
+ default: initialName || 'my-api',
46
+ validate: validateProjectName,
47
+ filter: (input) => input.trim().toLowerCase(),
48
+ },
49
+ {
50
+ type: 'checkbox',
51
+ name: 'plugins',
52
+ message: 'Which plugins would you like to install?',
53
+ choices: getPluginChoices(),
54
+ },
55
+ ]);
56
+ const projectPath = path.resolve(process.cwd(), answers.projectName);
57
+ // Check if directory already exists
58
+ if (await checkDirectoryExists(projectPath)) {
59
+ const { overwrite } = await inquirer.prompt([
60
+ {
61
+ type: 'confirm',
62
+ name: 'overwrite',
63
+ message: `Directory ${answers.projectName} already exists. Overwrite?`,
64
+ default: false,
65
+ },
66
+ ]);
67
+ if (!overwrite) {
68
+ console.log('Aborted. Please choose a different project name.');
69
+ process.exit(1);
70
+ }
71
+ }
72
+ return {
73
+ projectName: answers.projectName,
74
+ projectPath,
75
+ plugins: answers.plugins,
76
+ };
77
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Template Generation Module
3
+ *
4
+ * Handles copying and rendering template files.
5
+ */
6
+ import { ScaffoldOptions } from './prompts.js';
7
+ /**
8
+ * Generate project from templates
9
+ */
10
+ export declare function generateProject(options: ScaffoldOptions): Promise<void>;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Template Generation Module
3
+ *
4
+ * Handles copying and rendering template files.
5
+ */
6
+ import fs from 'fs-extra';
7
+ import path from 'path';
8
+ import ejs from 'ejs';
9
+ import { fileURLToPath } from 'url';
10
+ import { getPluginConfig } from './plugins.js';
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+ // Templates are in the parent directory relative to dist/
14
+ const TEMPLATES_DIR = path.resolve(__dirname, '..', 'templates');
15
+ /**
16
+ * Build template data from scaffold options
17
+ */
18
+ function buildTemplateData(options) {
19
+ const dependencies = {
20
+ 'web-ai-service': '^1.0.0',
21
+ };
22
+ const devDependencies = {
23
+ '@types/node': '^20.11.5',
24
+ 'typescript': '^5.3.3',
25
+ 'tsx': '^4.7.0',
26
+ };
27
+ const envVars = [];
28
+ // Add plugin dependencies and env vars
29
+ for (const pluginName of options.plugins) {
30
+ const plugin = getPluginConfig(pluginName);
31
+ if (plugin) {
32
+ Object.assign(dependencies, plugin.dependencies);
33
+ envVars.push(...plugin.envVars);
34
+ }
35
+ }
36
+ return {
37
+ projectName: options.projectName,
38
+ plugins: options.plugins,
39
+ dependencies,
40
+ devDependencies,
41
+ envVars,
42
+ };
43
+ }
44
+ /**
45
+ * Copy and render base template files
46
+ */
47
+ async function copyBaseTemplate(options, data) {
48
+ const baseDir = path.join(TEMPLATES_DIR, 'base');
49
+ // Get all files in base template
50
+ const files = await fs.readdir(baseDir, { recursive: true });
51
+ for (const file of files) {
52
+ const sourcePath = path.join(baseDir, file.toString());
53
+ const stat = await fs.stat(sourcePath);
54
+ if (stat.isDirectory()) {
55
+ continue;
56
+ }
57
+ // Determine destination path (remove .ejs extension if present)
58
+ let destFile = file.toString();
59
+ const isEjsTemplate = destFile.endsWith('.ejs');
60
+ if (isEjsTemplate) {
61
+ destFile = destFile.slice(0, -4); // Remove .ejs
62
+ }
63
+ const destPath = path.join(options.projectPath, destFile);
64
+ // Ensure destination directory exists
65
+ await fs.ensureDir(path.dirname(destPath));
66
+ if (isEjsTemplate) {
67
+ // Render EJS template
68
+ const content = await fs.readFile(sourcePath, 'utf-8');
69
+ const rendered = ejs.render(content, data);
70
+ await fs.writeFile(destPath, rendered);
71
+ }
72
+ else {
73
+ // Copy file as-is
74
+ await fs.copy(sourcePath, destPath);
75
+ }
76
+ }
77
+ }
78
+ /**
79
+ * Copy plugin files
80
+ */
81
+ async function copyPluginFiles(options) {
82
+ for (const pluginName of options.plugins) {
83
+ const plugin = getPluginConfig(pluginName);
84
+ if (!plugin)
85
+ continue;
86
+ for (const fileMapping of plugin.files) {
87
+ const sourcePath = path.join(TEMPLATES_DIR, fileMapping.source);
88
+ const destPath = path.join(options.projectPath, fileMapping.destination);
89
+ // Ensure destination directory exists
90
+ await fs.ensureDir(path.dirname(destPath));
91
+ // Copy file
92
+ await fs.copy(sourcePath, destPath);
93
+ }
94
+ }
95
+ }
96
+ /**
97
+ * Generate project from templates
98
+ */
99
+ export async function generateProject(options) {
100
+ const data = buildTemplateData(options);
101
+ // Ensure project directory exists (clean if exists)
102
+ await fs.ensureDir(options.projectPath);
103
+ // Copy base template
104
+ await copyBaseTemplate(options, data);
105
+ // Copy plugin files
106
+ await copyPluginFiles(options);
107
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "create-web-ai-service",
3
+ "version": "1.0.0",
4
+ "description": "CLI scaffolder for creating new web-ai-service projects",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "create-web-ai-service": "./bin/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "bin",
13
+ "templates"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "dev": "tsc --watch",
18
+ "prepublishOnly": "npm run build"
19
+ },
20
+ "keywords": [
21
+ "cli",
22
+ "scaffolder",
23
+ "web-ai-service",
24
+ "api",
25
+ "workflow",
26
+ "llm"
27
+ ],
28
+ "author": "",
29
+ "license": "ISC",
30
+ "dependencies": {
31
+ "chalk": "^5.3.0",
32
+ "ejs": "^3.1.10",
33
+ "inquirer": "^9.2.12",
34
+ "fs-extra": "^11.2.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/ejs": "^3.1.5",
38
+ "@types/fs-extra": "^11.0.4",
39
+ "@types/inquirer": "^9.0.7",
40
+ "@types/node": "^20.11.5",
41
+ "typescript": "^5.3.3"
42
+ }
43
+ }
@@ -0,0 +1,22 @@
1
+ # Server Configuration
2
+ PORT=3000
3
+
4
+ # LLM Provider API Keys (add keys for providers you want to use)
5
+ GEMINI_API_KEY=your-gemini-api-key-here
6
+ GROK_API_KEY=your-grok-api-key-here
7
+ OPENAI_API_KEY=your-openai-api-key-here
8
+ ANTHROPIC_API_KEY=your-anthropic-api-key-here
9
+
10
+ # LLM Configuration
11
+ LLM_TIMEOUT_MS=30000
12
+
13
+ # Logging
14
+ LOG_LEVEL=info
15
+ <% if (envVars.length > 0) { %>
16
+
17
+ # Plugin Configuration
18
+ <% for (const envVar of envVars) { %>
19
+ # <%= envVar.description %>
20
+ <%= envVar.name %>=<%= envVar.example %>
21
+ <% } %>
22
+ <% } %>
@@ -0,0 +1,46 @@
1
+ # <%= projectName %>
2
+
3
+ An AI-powered API service built with [web-ai-service](https://github.com/yourrepo/web-ai-service).
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Install dependencies
9
+ npm install
10
+
11
+ # Configure environment
12
+ cp .env.example .env
13
+ # Edit .env with your API keys
14
+
15
+ # Start development server
16
+ npm run dev
17
+ ```
18
+
19
+ Server starts at `http://localhost:3000`.
20
+
21
+ ## Project Structure
22
+
23
+ ```
24
+ <%= projectName %>/
25
+ ├── src/
26
+ │ ├── endpoints/ # API endpoint definitions
27
+ │ │ └── hello/ # Example endpoint → GET /hello
28
+ │ │ ├── GET.yaml # Workflow definition
29
+ │ │ ├── codes/ # TypeScript code nodes
30
+ │ │ └── prompts/ # LLM system prompts
31
+ │ └── plugins/ # Shared code modules
32
+ ├── .env # Environment configuration
33
+ └── package.json
34
+ ```
35
+
36
+ ## Creating Endpoints
37
+
38
+ Each folder in `src/endpoints/` becomes an API route. Create a YAML file named after the HTTP method (e.g.,
39
+ `POST.yaml`, `GET.yaml`).
40
+
41
+ See the `hello` endpoint for an example.
42
+
43
+ ## Learn More
44
+
45
+ - [Web AI Service Documentation](https://github.com/yourrepo/web-ai-service)
46
+ - [Creating Endpoints Guide](./docs/creating-endpoints.md)
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "<%= projectName %>",
3
+ "version": "1.0.0",
4
+ "description": "An AI-powered API service built with web-ai-service",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "web-ai-service",
8
+ "build": "tsc",
9
+ "start": "node dist/engine/cli.js",
10
+ "validate": "web-ai-service validate"
11
+ },
12
+ "keywords": [
13
+ "api",
14
+ "ai",
15
+ "workflow"
16
+ ],
17
+ "author": "",
18
+ "license": "ISC",
19
+ "dependencies": {
20
+ <%- Object.entries(dependencies).map(([name, version]) => ` "${name}": "${version}"`).join(',\n') %>
21
+ },
22
+ "devDependencies": {
23
+ <%- Object.entries(devDependencies).map(([name, version]) => ` "${name}": "${version}"`).join(',\n') %>
24
+ }
25
+ }
@@ -0,0 +1,23 @@
1
+ version: "1.0"
2
+
3
+ stages:
4
+ - name: process
5
+ nodes:
6
+ # Code node to transform and validate the request
7
+ format_greeting:
8
+ type: code
9
+ input: $input
10
+ file: format-greeting.ts
11
+
12
+ - name: respond
13
+ nodes:
14
+ # LLM node to generate a personalized greeting
15
+ greeting:
16
+ type: llm
17
+ input: process.format_greeting
18
+ provider: gemini
19
+ model: gemini-2.0-flash-lite
20
+ temperature: 0.7
21
+ maxTokens: 256
22
+ systemMessages:
23
+ - file: greeting-system.txt
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Format Greeting Code Node
3
+ *
4
+ * This code node demonstrates input processing before passing to an LLM.
5
+ * It extracts and validates the name parameter, then formats the request
6
+ * for the greeting LLM node.
7
+ */
8
+
9
+ import type { NodeOutput } from '@workflow/types';
10
+
11
+ interface HelloInput {
12
+ name?: string;
13
+ }
14
+
15
+ interface FormattedRequest {
16
+ name: string;
17
+ timestamp: string;
18
+ greeting_type: 'morning' | 'afternoon' | 'evening';
19
+ }
20
+
21
+ /**
22
+ * Determine greeting type based on current time
23
+ */
24
+ function getGreetingType(): 'morning' | 'afternoon' | 'evening' {
25
+ const hour = new Date().getHours();
26
+
27
+ if (hour < 12) {
28
+ return 'morning';
29
+ } else if (hour < 17) {
30
+ return 'afternoon';
31
+ } else {
32
+ return 'evening';
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Main code node function
38
+ *
39
+ * @param input - The raw request input
40
+ * @returns Formatted request for the LLM node
41
+ */
42
+ export default async function (input: unknown): Promise<NodeOutput> {
43
+ const data = input as HelloInput;
44
+
45
+ // Extract and validate name (default to 'World')
46
+ const name = data.name?.trim() || 'World';
47
+
48
+ // Validate name length
49
+ if (name.length > 100) {
50
+ throw new Error('Name must be 100 characters or less');
51
+ }
52
+
53
+ // Build formatted request
54
+ const formatted: FormattedRequest = {
55
+ name,
56
+ timestamp: new Date().toISOString(),
57
+ greeting_type: getGreetingType(),
58
+ };
59
+
60
+ // Return as JSON output for the next node
61
+ return {
62
+ type: 'json',
63
+ value: formatted,
64
+ };
65
+ }
@@ -0,0 +1,18 @@
1
+ You are a friendly greeting assistant. Generate warm, personalized greetings.
2
+
3
+ You will receive a JSON object with:
4
+ - name: The person's name to greet
5
+ - timestamp: Current ISO timestamp
6
+ - greeting_type: Either 'morning', 'afternoon', or 'evening'
7
+
8
+ Generate a friendly, contextual greeting that:
9
+ 1. Uses the appropriate time-of-day salutation (Good morning/afternoon/evening)
10
+ 2. Addresses the person by name
11
+ 3. Adds a brief, positive message appropriate to the time of day
12
+ 4. Keeps the total response under 2 sentences
13
+
14
+ Example input:
15
+ {"name": "Alice", "timestamp": "2024-01-15T09:30:00Z", "greeting_type": "morning"}
16
+
17
+ Example output:
18
+ Good morning, Alice! Hope you have a wonderful and productive day ahead.
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "paths": {
13
+ "@workflow/types": [
14
+ "./node_modules/web-ai-service/dist/engine/types/index.js"
15
+ ],
16
+ "@code-plugins/*": [
17
+ "./src/plugins/*"
18
+ ]
19
+ }
20
+ },
21
+ "include": [
22
+ "src/**/*"
23
+ ],
24
+ "exclude": [
25
+ "node_modules",
26
+ "dist"
27
+ ]
28
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Supabase Plugin
3
+ *
4
+ * Provides a configured Supabase client for use in code nodes.
5
+ *
6
+ * Usage in code nodes:
7
+ * ```typescript
8
+ * import { supabase, getSupabaseClient } from '@code-plugins/supabase.js';
9
+ *
10
+ * // Use the default client (reads from env vars)
11
+ * const { data, error } = await supabase.from('table').select('*');
12
+ *
13
+ * // Or create a custom client
14
+ * const client = getSupabaseClient('https://custom.supabase.co', 'custom-key');
15
+ * ```
16
+ *
17
+ * Required environment variables:
18
+ * - SUPABASE_URL: Your Supabase project URL
19
+ * - SUPABASE_ANON_KEY: Your Supabase anon/public key
20
+ * - SUPABASE_SERVICE_KEY: (Optional) Service role key for admin operations
21
+ */
22
+
23
+ import { createClient, SupabaseClient } from '@supabase/supabase-js';
24
+
25
+ // Default client instance (lazy initialized)
26
+ let defaultClient: SupabaseClient | null = null;
27
+
28
+ /**
29
+ * Get the Supabase URL from environment
30
+ */
31
+ function getSupabaseUrl(): string {
32
+ const url = process.env.SUPABASE_URL;
33
+ if (!url) {
34
+ throw new Error('SUPABASE_URL environment variable is required');
35
+ }
36
+ return url;
37
+ }
38
+
39
+ /**
40
+ * Get the Supabase key from environment
41
+ * Prefers service key for server-side operations, falls back to anon key
42
+ */
43
+ function getSupabaseKey(): string {
44
+ const serviceKey = process.env.SUPABASE_SERVICE_KEY;
45
+ const anonKey = process.env.SUPABASE_ANON_KEY;
46
+
47
+ if (serviceKey) {
48
+ return serviceKey;
49
+ }
50
+ if (anonKey) {
51
+ return anonKey;
52
+ }
53
+ throw new Error('SUPABASE_SERVICE_KEY or SUPABASE_ANON_KEY environment variable is required');
54
+ }
55
+
56
+ /**
57
+ * Create a new Supabase client with custom URL and key
58
+ */
59
+ export function getSupabaseClient(url?: string, key?: string): SupabaseClient {
60
+ const supabaseUrl = url || getSupabaseUrl();
61
+ const supabaseKey = key || getSupabaseKey();
62
+
63
+ return createClient(supabaseUrl, supabaseKey, {
64
+ auth: {
65
+ autoRefreshToken: false,
66
+ persistSession: false,
67
+ },
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Get the default Supabase client (singleton)
73
+ * Uses environment variables for configuration
74
+ */
75
+ export function getDefaultClient(): SupabaseClient {
76
+ if (!defaultClient) {
77
+ defaultClient = getSupabaseClient();
78
+ }
79
+ return defaultClient;
80
+ }
81
+
82
+ /**
83
+ * Default Supabase client instance
84
+ * Lazy-loaded on first access
85
+ */
86
+ export const supabase: SupabaseClient = new Proxy({} as SupabaseClient, {
87
+ get(_target, prop) {
88
+ return Reflect.get(getDefaultClient(), prop);
89
+ },
90
+ });
91
+
92
+ // Re-export useful types
93
+ export type { SupabaseClient } from '@supabase/supabase-js';