@flight-framework/core 0.5.0 → 0.6.1

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.
@@ -1,3 +1,5 @@
1
+ import { Plugin } from 'vite';
2
+
1
3
  /**
2
4
  * @flight-framework/core - Action Middleware
3
5
  *
@@ -633,6 +635,61 @@ declare function createQueueManager(options: {
633
635
  retryFailed: () => Promise<void>;
634
636
  };
635
637
 
638
+ /**
639
+ * @flight-framework/core - Server Actions Vite Plugin
640
+ *
641
+ * Transforms files with 'use server' directive.
642
+ * Similar to React 19 / Next.js server actions compilation.
643
+ */
644
+
645
+ interface ServerActionsPluginOptions {
646
+ /** Include patterns */
647
+ include?: string[];
648
+ /** Exclude patterns */
649
+ exclude?: string[];
650
+ }
651
+ /**
652
+ * Vite plugin for server actions transformation
653
+ *
654
+ * @example
655
+ * ```typescript
656
+ * // vite.config.ts
657
+ * import { serverActionsPlugin } from '@flight-framework/core/actions';
658
+ *
659
+ * export default {
660
+ * plugins: [serverActionsPlugin()],
661
+ * };
662
+ * ```
663
+ */
664
+ declare function serverActionsPlugin(options?: ServerActionsPluginOptions): Plugin;
665
+ /**
666
+ * Create a client-side action caller
667
+ *
668
+ * @example
669
+ * ```typescript
670
+ * // In client component
671
+ * import { createActionCaller } from '@flight-framework/core/actions';
672
+ *
673
+ * const submitForm = createActionCaller('action_abc123');
674
+ *
675
+ * // Usage
676
+ * const result = await submitForm({ name: 'John' });
677
+ * ```
678
+ */
679
+ declare function createActionCaller<TArgs extends unknown[], TResult>(actionId: string): (...args: TArgs) => Promise<TResult>;
680
+ /**
681
+ * Create a form action for HTML forms
682
+ *
683
+ * @example
684
+ * ```tsx
685
+ * <form action={formAction('action_abc123')}>
686
+ * <input name="email" />
687
+ * <button type="submit">Submit</button>
688
+ * </form>
689
+ * ```
690
+ */
691
+ declare function formAction(actionId: string): string;
692
+
636
693
  /**
637
694
  * @flight-framework/core - Server Actions
638
695
  *
@@ -740,4 +797,4 @@ declare function createActionReference(actionId: string): string;
740
797
  */
741
798
  declare function handleActionRequest(request: Request): Promise<Response>;
742
799
 
743
- export { type Action, type ActionContext, type ActionMiddleware, type ActionMiddlewareHandler, type ActionNext, type ActionResult, type CookieOptions, type CreateMiddlewareOptions, type FormActionData, type QueueStatus, type QueueStorageAdapter, type QueuedAction, type QueuedActionEntry, type QueuedActionOptions, type QueuedActionResult, RedirectError, type RetryStrategy, type ServerAction, type StreamController, type StreamItem, type StreamProgress, type StreamResult, type StreamableAction, type StreamableActionOptions, type StreamableGenerator, clearActions, collectStream, cookies, createActionContext, createActionMiddleware, createActionReference, createLoggingMiddleware, createMemoryAdapter, createProgressReporter, createQueueManager, createQueuedAction, createRetryMiddleware, createStreamableAction, createTimingMiddleware, defaultRetryStrategy, executeAction, executeFormAction, executeWithMiddleware, exponentialBackoff, fixedDelay, generateActionId, getAction, getAllActions, handleActionRequest, isRedirectError, parseFormData, pipeline, redirect, registerAction, responseToStream, streamToResponse };
800
+ export { type Action, type ActionContext, type ActionMiddleware, type ActionMiddlewareHandler, type ActionNext, type ActionResult, type CookieOptions, type CreateMiddlewareOptions, type FormActionData, type QueueStatus, type QueueStorageAdapter, type QueuedAction, type QueuedActionEntry, type QueuedActionOptions, type QueuedActionResult, RedirectError, type RetryStrategy, type ServerAction, type ServerActionsPluginOptions, type StreamController, type StreamItem, type StreamProgress, type StreamResult, type StreamableAction, type StreamableActionOptions, type StreamableGenerator, clearActions, collectStream, cookies, createActionCaller, createActionContext, createActionMiddleware, createActionReference, createLoggingMiddleware, createMemoryAdapter, createProgressReporter, createQueueManager, createQueuedAction, createRetryMiddleware, createStreamableAction, createTimingMiddleware, defaultRetryStrategy, executeAction, executeFormAction, executeWithMiddleware, exponentialBackoff, fixedDelay, formAction, generateActionId, getAction, getAllActions, handleActionRequest, isRedirectError, parseFormData, pipeline, redirect, registerAction, responseToStream, serverActionsPlugin, streamToResponse };
@@ -1,3 +1,3 @@
1
- export { RedirectError, clearActions, collectStream, cookies, createActionContext, createActionMiddleware, createActionReference, createLoggingMiddleware, createMemoryAdapter, createProgressReporter, createQueueManager, createQueuedAction, createRetryMiddleware, createStreamableAction, createTimingMiddleware, defaultRetryStrategy, executeAction, executeFormAction, executeWithMiddleware, exponentialBackoff, fixedDelay, generateActionId, getAction, getAllActions, handleActionRequest, isRedirectError, parseFormData, pipeline, redirect, registerAction, responseToStream, streamToResponse } from '../chunk-3QP3E7HS.js';
1
+ export { RedirectError, clearActions, collectStream, cookies, createActionCaller, createActionContext, createActionMiddleware, createActionReference, createLoggingMiddleware, createMemoryAdapter, createProgressReporter, createQueueManager, createQueuedAction, createRetryMiddleware, createStreamableAction, createTimingMiddleware, defaultRetryStrategy, executeAction, executeFormAction, executeWithMiddleware, exponentialBackoff, fixedDelay, formAction, generateActionId, getAction, getAllActions, handleActionRequest, isRedirectError, parseFormData, pipeline, redirect, registerAction, responseToStream, serverActionsPlugin, streamToResponse } from '../chunk-XGP3C2ZE.js';
2
2
  //# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,93 @@
1
+ // src/plugins/server-boundary-plugin.ts
2
+ function serverBoundaryPlugin(options = {}) {
3
+ const {
4
+ serverFilePatterns = [".server.ts", ".server.tsx", ".server.js", ".server.jsx"],
5
+ serverDirPatterns = ["/server/", "/api/"],
6
+ violationBehavior = "error",
7
+ customErrorMessage,
8
+ exclude = ["**/*.test.*", "**/*.spec.*", "**/node_modules/**"]
9
+ } = options;
10
+ const filePatterns = serverFilePatterns.map(
11
+ (p) => new RegExp(p.replace(/\./g, "\\.") + "$", "i")
12
+ );
13
+ const dirPatterns = serverDirPatterns.map(
14
+ (p) => new RegExp(p.replace(/\//g, "[\\\\/]"), "i")
15
+ );
16
+ function isServerOnlyModule(id) {
17
+ for (const pattern of filePatterns) {
18
+ if (pattern.test(id)) return true;
19
+ }
20
+ for (const pattern of dirPatterns) {
21
+ if (pattern.test(id)) return true;
22
+ }
23
+ return false;
24
+ }
25
+ function isExcluded(id) {
26
+ for (const pattern of exclude) {
27
+ const regex = new RegExp(
28
+ pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/\\\\]*").replace(/\./g, "\\.")
29
+ );
30
+ if (regex.test(id)) return true;
31
+ }
32
+ return false;
33
+ }
34
+ function createErrorMessage(module, importer) {
35
+ if (customErrorMessage) {
36
+ return customErrorMessage(module, importer);
37
+ }
38
+ return `[Flight] Cannot import server-only module in client code.
39
+
40
+ Module: ${module}
41
+ Imported by: ${importer}
42
+
43
+ Solutions:
44
+ 1. Move the import to a 'use server' file
45
+ 2. Use an API route instead
46
+ 3. Use dynamic import with typeof window check
47
+ 4. Rename the importing file to *.server.ts
48
+
49
+ Learn more: https://flight-framework.dev/docs/server-client-boundaries`;
50
+ }
51
+ return {
52
+ name: "flight:server-boundary",
53
+ enforce: "pre",
54
+ resolveId(source, importer, options2) {
55
+ if (options2?.ssr) return null;
56
+ if (!importer) return null;
57
+ if (isExcluded(importer)) return null;
58
+ if (isServerOnlyModule(importer)) return null;
59
+ if (isServerOnlyModule(source)) {
60
+ const message = createErrorMessage(source, importer);
61
+ if (violationBehavior === "error") {
62
+ this.error(message);
63
+ } else {
64
+ this.warn(message);
65
+ }
66
+ }
67
+ return null;
68
+ },
69
+ transform(code, id, options2) {
70
+ if (options2?.ssr) return null;
71
+ if (isExcluded(id)) return null;
72
+ if (isServerOnlyModule(id)) return null;
73
+ const trimmed = code.trim();
74
+ if (trimmed.startsWith("'use server'") || trimmed.startsWith('"use server"')) {
75
+ const message = `[Flight] 'use server' file imported in client bundle.
76
+
77
+ File: ${id}
78
+
79
+ This file should only run on the server. Check your import graph to find where this is imported.`;
80
+ if (violationBehavior === "error") {
81
+ this.error(message);
82
+ } else {
83
+ this.warn(message);
84
+ }
85
+ }
86
+ return null;
87
+ }
88
+ };
89
+ }
90
+
91
+ export { serverBoundaryPlugin };
92
+ //# sourceMappingURL=chunk-56ZZNOJD.js.map
93
+ //# sourceMappingURL=chunk-56ZZNOJD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugins/server-boundary-plugin.ts"],"names":["options"],"mappings":"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","file":"chunk-56ZZNOJD.js","sourcesContent":["/**\r\n * @flight-framework/core - Server Boundary Plugin\r\n * \r\n * Vite plugin to detect server-only code imported in client bundles.\r\n * OPTIONAL - use if you want build-time protection.\r\n * \r\n * Features:\r\n * - Detects *.server.ts files imported in client code\r\n * - Detects /server/ and /api/ directory imports\r\n * - Configurable behavior (warn, error)\r\n * - Clear error messages with solutions\r\n * \r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { serverBoundaryPlugin } from '@flight-framework/core/plugins';\r\n * \r\n * export default {\r\n * plugins: [serverBoundaryPlugin()],\r\n * };\r\n * ```\r\n */\r\n\r\nimport type { Plugin } from 'vite';\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\nexport interface ServerBoundaryPluginOptions {\r\n /**\r\n * File patterns that are server-only.\r\n * @default ['.server.ts', '.server.tsx', '.server.js', '.server.jsx']\r\n */\r\n serverFilePatterns?: string[];\r\n\r\n /**\r\n * Directory patterns that are server-only.\r\n * @default ['/server/', '/api/']\r\n */\r\n serverDirPatterns?: string[];\r\n\r\n /**\r\n * How to handle violations.\r\n * - 'error': Build error (recommended for production)\r\n * - 'warn': Warning only (for migration)\r\n * @default 'error'\r\n */\r\n violationBehavior?: 'error' | 'warn';\r\n\r\n /**\r\n * Custom error message template.\r\n */\r\n customErrorMessage?: (module: string, importer: string) => string;\r\n\r\n /**\r\n * Files to exclude from checking (e.g., tests).\r\n * @default ['**\\/*.test.*', '**\\/*.spec.*']\r\n */\r\n exclude?: string[];\r\n}\r\n\r\n// ============================================================================\r\n// Plugin\r\n// ============================================================================\r\n\r\n/**\r\n * Vite plugin to detect server-only imports in client code.\r\n * \r\n * This plugin is OPTIONAL. Use it if you want build-time\r\n * detection of server code leaking to the client.\r\n * \r\n * By default, it will:\r\n * - Block imports of *.server.ts files in client code\r\n * - Block imports from /server/ and /api/ directories\r\n * - Throw build errors with clear solutions\r\n */\r\nexport function serverBoundaryPlugin(\r\n options: ServerBoundaryPluginOptions = {}\r\n): Plugin {\r\n const {\r\n serverFilePatterns = ['.server.ts', '.server.tsx', '.server.js', '.server.jsx'],\r\n serverDirPatterns = ['/server/', '/api/'],\r\n violationBehavior = 'error',\r\n customErrorMessage,\r\n exclude = ['**/*.test.*', '**/*.spec.*', '**/node_modules/**'],\r\n } = options;\r\n\r\n // Build regex patterns\r\n const filePatterns = serverFilePatterns.map(p =>\r\n new RegExp(p.replace(/\\./g, '\\\\.') + '$', 'i')\r\n );\r\n const dirPatterns = serverDirPatterns.map(p =>\r\n new RegExp(p.replace(/\\//g, '[\\\\\\\\/]'), 'i')\r\n );\r\n\r\n function isServerOnlyModule(id: string): boolean {\r\n // Check file patterns\r\n for (const pattern of filePatterns) {\r\n if (pattern.test(id)) return true;\r\n }\r\n\r\n // Check directory patterns\r\n for (const pattern of dirPatterns) {\r\n if (pattern.test(id)) return true;\r\n }\r\n\r\n return false;\r\n }\r\n\r\n function isExcluded(id: string): boolean {\r\n for (const pattern of exclude) {\r\n // Simple glob matching\r\n const regex = new RegExp(\r\n pattern\r\n .replace(/\\*\\*/g, '.*')\r\n .replace(/\\*/g, '[^/\\\\\\\\]*')\r\n .replace(/\\./g, '\\\\.')\r\n );\r\n if (regex.test(id)) return true;\r\n }\r\n return false;\r\n }\r\n\r\n function createErrorMessage(module: string, importer: string): string {\r\n if (customErrorMessage) {\r\n return customErrorMessage(module, importer);\r\n }\r\n\r\n return (\r\n `[Flight] Cannot import server-only module in client code.\\n\\n` +\r\n ` Module: ${module}\\n` +\r\n ` Imported by: ${importer}\\n\\n` +\r\n `Solutions:\\n` +\r\n ` 1. Move the import to a 'use server' file\\n` +\r\n ` 2. Use an API route instead\\n` +\r\n ` 3. Use dynamic import with typeof window check\\n` +\r\n ` 4. Rename the importing file to *.server.ts\\n\\n` +\r\n `Learn more: https://flight-framework.dev/docs/server-client-boundaries`\r\n );\r\n }\r\n\r\n return {\r\n name: 'flight:server-boundary',\r\n enforce: 'pre',\r\n\r\n resolveId(source, importer, options) {\r\n // Skip SSR builds - server can import anything\r\n if (options?.ssr) return null;\r\n\r\n // Need an importer to check\r\n if (!importer) return null;\r\n\r\n // Skip excluded files\r\n if (isExcluded(importer)) return null;\r\n\r\n // Skip if importer is also server-only\r\n if (isServerOnlyModule(importer)) return null;\r\n\r\n // Check if the import target is server-only\r\n // This is a simplified check - the resolved ID would be more accurate\r\n if (isServerOnlyModule(source)) {\r\n const message = createErrorMessage(source, importer);\r\n\r\n if (violationBehavior === 'error') {\r\n this.error(message);\r\n } else {\r\n this.warn(message);\r\n }\r\n }\r\n\r\n return null;\r\n },\r\n\r\n transform(code, id, options) {\r\n // Skip SSR builds\r\n if (options?.ssr) return null;\r\n\r\n // Skip excluded files\r\n if (isExcluded(id)) return null;\r\n\r\n // Skip server-only files (they shouldn't be in client bundle anyway)\r\n if (isServerOnlyModule(id)) return null;\r\n\r\n // Check for 'use server' directive at file level\r\n const trimmed = code.trim();\r\n if (trimmed.startsWith(\"'use server'\") || trimmed.startsWith('\"use server\"')) {\r\n const message =\r\n `[Flight] 'use server' file imported in client bundle.\\n\\n` +\r\n ` File: ${id}\\n\\n` +\r\n `This file should only run on the server. ` +\r\n `Check your import graph to find where this is imported.`;\r\n\r\n if (violationBehavior === 'error') {\r\n this.error(message);\r\n } else {\r\n this.warn(message);\r\n }\r\n }\r\n\r\n return null;\r\n },\r\n };\r\n}\r\n"]}
@@ -0,0 +1,88 @@
1
+ // src/plugins/env-plugin.ts
2
+ function flightEnvPlugin(options = {}) {
3
+ const {
4
+ publicPrefix = "FLIGHT_PUBLIC_",
5
+ additionalPublicPrefixes = ["VITE_"],
6
+ privateVarBehavior = "warn",
7
+ allowList = ["NODE_ENV", "MODE"]
8
+ } = options;
9
+ const allPublicPrefixes = [publicPrefix, ...additionalPublicPrefixes];
10
+ return {
11
+ name: "flight:env",
12
+ enforce: "pre",
13
+ configResolved(resolvedConfig) {
14
+ },
15
+ config(_, { isSsrBuild }) {
16
+ if (isSsrBuild) return {};
17
+ const publicEnvVars = {};
18
+ for (const [key, value] of Object.entries(process.env)) {
19
+ if (value !== void 0 && isPublicKey(key, allPublicPrefixes, allowList)) {
20
+ publicEnvVars[`process.env.${key}`] = JSON.stringify(value);
21
+ }
22
+ }
23
+ return {
24
+ define: publicEnvVars
25
+ };
26
+ },
27
+ transform(code, id, options2) {
28
+ if (options2?.ssr) return null;
29
+ if (id.includes("node_modules")) return null;
30
+ if (!code.includes("process.env.")) return null;
31
+ const envVarPattern = /process\.env\.([A-Z_][A-Z0-9_]*)/g;
32
+ let hasModifications = false;
33
+ let modifiedCode = code;
34
+ let match;
35
+ const issues = [];
36
+ while ((match = envVarPattern.exec(code)) !== null) {
37
+ const varName = match[1];
38
+ if (isPublicKey(varName, allPublicPrefixes, allowList)) {
39
+ continue;
40
+ }
41
+ hasModifications = true;
42
+ if (privateVarBehavior === "error") {
43
+ issues.push(
44
+ `Private env var "${varName}" accessed in client code.
45
+ File: ${id}
46
+ Use FLIGHT_PUBLIC_${varName} if this should be public.`
47
+ );
48
+ } else if (privateVarBehavior === "warn") {
49
+ console.warn(
50
+ `[Flight] Private env var "process.env.${varName}" in ${id}
51
+ \u2192 Replaced with undefined for security.
52
+ \u2192 Use FLIGHT_PUBLIC_${varName} if this should be public.`
53
+ );
54
+ }
55
+ modifiedCode = modifiedCode.replace(
56
+ new RegExp(`process\\.env\\.${varName}`, "g"),
57
+ "undefined"
58
+ );
59
+ }
60
+ if (issues.length > 0 && privateVarBehavior === "error") {
61
+ throw new Error(
62
+ `[Flight] Environment variable security violations:
63
+
64
+ ${issues.join("\n\n")}`
65
+ );
66
+ }
67
+ if (hasModifications) {
68
+ return {
69
+ code: modifiedCode,
70
+ map: null
71
+ // TODO: source map
72
+ };
73
+ }
74
+ return null;
75
+ }
76
+ };
77
+ }
78
+ function isPublicKey(key, prefixes, allowList) {
79
+ if (allowList.includes(key)) return true;
80
+ for (const prefix of prefixes) {
81
+ if (key.startsWith(prefix)) return true;
82
+ }
83
+ return false;
84
+ }
85
+
86
+ export { flightEnvPlugin };
87
+ //# sourceMappingURL=chunk-RFTE6JVG.js.map
88
+ //# sourceMappingURL=chunk-RFTE6JVG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugins/env-plugin.ts"],"names":["options"],"mappings":"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","file":"chunk-RFTE6JVG.js","sourcesContent":["/**\r\n * @flight-framework/core - Environment Variables Plugin\r\n * \r\n * Vite plugin for environment variable protection.\r\n * OPTIONAL - use if you want build-time protection.\r\n * \r\n * Features:\r\n * - FLIGHT_PUBLIC_* and VITE_* accessible on client\r\n * - Other vars replaced with undefined on client\r\n * - Configurable behavior (warn, error, silent)\r\n * \r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { flightEnvPlugin } from '@flight-framework/core/plugins';\r\n * \r\n * export default {\r\n * plugins: [flightEnvPlugin()],\r\n * };\r\n * ```\r\n */\r\n\r\nimport type { Plugin, ResolvedConfig } from 'vite';\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\nexport interface FlightEnvPluginOptions {\r\n /**\r\n * Prefix for public environment variables.\r\n * Variables with this prefix will be accessible on the client.\r\n * @default 'FLIGHT_PUBLIC_'\r\n */\r\n publicPrefix?: string;\r\n\r\n /**\r\n * Additional prefixes to treat as public.\r\n * @default ['VITE_']\r\n */\r\n additionalPublicPrefixes?: string[];\r\n\r\n /**\r\n * How to handle private vars accessed in client code.\r\n * - 'warn': Log warning, replace with undefined\r\n * - 'error': Throw build error\r\n * - 'silent': Replace with undefined, no warning\r\n * @default 'warn'\r\n */\r\n privateVarBehavior?: 'warn' | 'error' | 'silent';\r\n\r\n /**\r\n * Specific variables to allow (bypass prefix check).\r\n * @default ['NODE_ENV', 'MODE']\r\n */\r\n allowList?: string[];\r\n}\r\n\r\n// ============================================================================\r\n// Plugin\r\n// ============================================================================\r\n\r\n/**\r\n * Vite plugin to protect environment variables.\r\n * \r\n * This plugin is OPTIONAL. Use it if you want build-time\r\n * protection for your environment variables.\r\n * \r\n * By default, it will:\r\n * - Allow FLIGHT_PUBLIC_* and VITE_* on client\r\n * - Allow NODE_ENV and MODE\r\n * - Replace other process.env.* with undefined on client\r\n * - Show warnings in development\r\n */\r\nexport function flightEnvPlugin(options: FlightEnvPluginOptions = {}): Plugin {\r\n const {\r\n publicPrefix = 'FLIGHT_PUBLIC_',\r\n additionalPublicPrefixes = ['VITE_'],\r\n privateVarBehavior = 'warn',\r\n allowList = ['NODE_ENV', 'MODE'],\r\n } = options;\r\n\r\n const allPublicPrefixes = [publicPrefix, ...additionalPublicPrefixes];\r\n let config: ResolvedConfig;\r\n\r\n return {\r\n name: 'flight:env',\r\n enforce: 'pre',\r\n\r\n configResolved(resolvedConfig) {\r\n config = resolvedConfig;\r\n },\r\n\r\n config(_, { isSsrBuild }) {\r\n // For SSR builds, don't modify anything\r\n if (isSsrBuild) return {};\r\n\r\n // Collect public environment variables\r\n const publicEnvVars: Record<string, string> = {};\r\n\r\n for (const [key, value] of Object.entries(process.env)) {\r\n if (value !== undefined && isPublicKey(key, allPublicPrefixes, allowList)) {\r\n publicEnvVars[`process.env.${key}`] = JSON.stringify(value);\r\n }\r\n }\r\n\r\n return {\r\n define: publicEnvVars,\r\n };\r\n },\r\n\r\n transform(code, id, options) {\r\n // Skip SSR builds - server can access everything\r\n if (options?.ssr) return null;\r\n\r\n // Skip node_modules\r\n if (id.includes('node_modules')) return null;\r\n\r\n // Skip if no process.env references\r\n if (!code.includes('process.env.')) return null;\r\n\r\n // Find all process.env.VARIABLE_NAME patterns\r\n const envVarPattern = /process\\.env\\.([A-Z_][A-Z0-9_]*)/g;\r\n let hasModifications = false;\r\n let modifiedCode = code;\r\n\r\n let match;\r\n const issues: string[] = [];\r\n\r\n while ((match = envVarPattern.exec(code)) !== null) {\r\n const varName = match[1];\r\n\r\n // Skip public vars\r\n if (isPublicKey(varName, allPublicPrefixes, allowList)) {\r\n continue;\r\n }\r\n\r\n // Private var accessed in client code\r\n hasModifications = true;\r\n\r\n if (privateVarBehavior === 'error') {\r\n issues.push(\r\n `Private env var \"${varName}\" accessed in client code.\\n` +\r\n ` File: ${id}\\n` +\r\n ` Use FLIGHT_PUBLIC_${varName} if this should be public.`\r\n );\r\n } else if (privateVarBehavior === 'warn') {\r\n console.warn(\r\n `[Flight] Private env var \"process.env.${varName}\" in ${id}\\n` +\r\n ` → Replaced with undefined for security.\\n` +\r\n ` → Use FLIGHT_PUBLIC_${varName} if this should be public.`\r\n );\r\n }\r\n\r\n // Replace with undefined\r\n modifiedCode = modifiedCode.replace(\r\n new RegExp(`process\\\\.env\\\\.${varName}`, 'g'),\r\n 'undefined'\r\n );\r\n }\r\n\r\n // Throw if using error mode\r\n if (issues.length > 0 && privateVarBehavior === 'error') {\r\n throw new Error(\r\n `[Flight] Environment variable security violations:\\n\\n${issues.join('\\n\\n')}`\r\n );\r\n }\r\n\r\n if (hasModifications) {\r\n return {\r\n code: modifiedCode,\r\n map: null, // TODO: source map\r\n };\r\n }\r\n\r\n return null;\r\n },\r\n };\r\n}\r\n\r\n// ============================================================================\r\n// Helpers\r\n// ============================================================================\r\n\r\nfunction isPublicKey(\r\n key: string,\r\n prefixes: string[],\r\n allowList: string[]\r\n): boolean {\r\n // Check allow list\r\n if (allowList.includes(key)) return true;\r\n\r\n // Check prefixes\r\n for (const prefix of prefixes) {\r\n if (key.startsWith(prefix)) return true;\r\n }\r\n\r\n return false;\r\n}\r\n"]}
@@ -0,0 +1,67 @@
1
+ // src/utils/boundaries.ts
2
+ function assertServer(context) {
3
+ if (typeof window !== "undefined") {
4
+ const name = context || "This function";
5
+ throw new Error(
6
+ `[Flight] ${name} can only be called on the server. If you need this on the client, use an API route or server action.`
7
+ );
8
+ }
9
+ }
10
+ function assertClient(context) {
11
+ if (typeof window === "undefined") {
12
+ const name = context || "This function";
13
+ throw new Error(
14
+ `[Flight] ${name} can only be called on the client. This code should not run during SSR.`
15
+ );
16
+ }
17
+ }
18
+ function getEnv(key) {
19
+ if (isPublicEnvVar(key)) {
20
+ return getEnvValue(key);
21
+ }
22
+ if (typeof window !== "undefined") {
23
+ if (process.env.NODE_ENV === "development") {
24
+ console.warn(
25
+ `[Flight] Attempted to access private env var "${key}" on client. This returns undefined for security. If this should be public, rename it to FLIGHT_PUBLIC_${key}`
26
+ );
27
+ }
28
+ return void 0;
29
+ }
30
+ return getEnvValue(key);
31
+ }
32
+ function requireEnv(key) {
33
+ const value = getEnv(key);
34
+ if (value === void 0) {
35
+ if (typeof window !== "undefined" && !isPublicEnvVar(key)) {
36
+ throw new Error(
37
+ `[Flight] Cannot access private env var "${key}" on client. Move this code to the server or use FLIGHT_PUBLIC_${key}`
38
+ );
39
+ }
40
+ throw new Error(
41
+ `[Flight] Required environment variable "${key}" is not set. Check your .env file or environment configuration.`
42
+ );
43
+ }
44
+ return value;
45
+ }
46
+ function isPublicEnvVar(key) {
47
+ return key.startsWith("FLIGHT_PUBLIC_") || key.startsWith("VITE_") || key === "NODE_ENV" || key === "MODE";
48
+ }
49
+ function getEnvValue(key) {
50
+ if (typeof process !== "undefined" && process.env) {
51
+ const value = process.env[key];
52
+ if (value !== void 0) return value;
53
+ }
54
+ try {
55
+ const meta = import.meta;
56
+ if (meta.env) {
57
+ const value = meta.env[key];
58
+ if (value !== void 0) return value;
59
+ }
60
+ } catch {
61
+ }
62
+ return void 0;
63
+ }
64
+
65
+ export { assertClient, assertServer, getEnv, requireEnv };
66
+ //# sourceMappingURL=chunk-WF46NESN.js.map
67
+ //# sourceMappingURL=chunk-WF46NESN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/boundaries.ts"],"names":[],"mappings":";AAyCO,SAAS,aAAa,OAAA,EAAwB;AACjD,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAC/B,IAAA,MAAM,OAAO,OAAA,IAAW,eAAA;AACxB,IAAA,MAAM,IAAI,KAAA;AAAA,MACN,YAAY,IAAI,CAAA,qGAAA;AAAA,KAEpB;AAAA,EACJ;AACJ;AAoBO,SAAS,aAAa,OAAA,EAAwB;AACjD,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAC/B,IAAA,MAAM,OAAO,OAAA,IAAW,eAAA;AACxB,IAAA,MAAM,IAAI,KAAA;AAAA,MACN,YAAY,IAAI,CAAA,uEAAA;AAAA,KAEpB;AAAA,EACJ;AACJ;AA2BO,SAAS,OAAO,GAAA,EAAiC;AAEpD,EAAA,IAAI,cAAA,CAAe,GAAG,CAAA,EAAG;AACrB,IAAA,OAAO,YAAY,GAAG,CAAA;AAAA,EAC1B;AAGA,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAE/B,IAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,aAAA,EAAe;AACxC,MAAA,OAAA,CAAQ,IAAA;AAAA,QACJ,CAAA,8CAAA,EAAiD,GAAG,CAAA,uGAAA,EAEI,GAAG,CAAA;AAAA,OAC/D;AAAA,IACJ;AACA,IAAA,OAAO,MAAA;AAAA,EACX;AAGA,EAAA,OAAO,YAAY,GAAG,CAAA;AAC1B;AAgBO,SAAS,WAAW,GAAA,EAAqB;AAC5C,EAAA,MAAM,KAAA,GAAQ,OAAO,GAAG,CAAA;AAExB,EAAA,IAAI,UAAU,MAAA,EAAW;AAErB,IAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,CAAC,cAAA,CAAe,GAAG,CAAA,EAAG;AACvD,MAAA,MAAM,IAAI,KAAA;AAAA,QACN,CAAA,wCAAA,EAA2C,GAAG,CAAA,+DAAA,EACO,GAAG,CAAA;AAAA,OAC5D;AAAA,IACJ;AACA,IAAA,MAAM,IAAI,KAAA;AAAA,MACN,2CAA2C,GAAG,CAAA,gEAAA;AAAA,KAElD;AAAA,EACJ;AAEA,EAAA,OAAO,KAAA;AACX;AASA,SAAS,eAAe,GAAA,EAAsB;AAC1C,EAAA,OACI,GAAA,CAAI,UAAA,CAAW,gBAAgB,CAAA,IAC/B,GAAA,CAAI,WAAW,OAAO,CAAA,IACtB,GAAA,KAAQ,UAAA,IACR,GAAA,KAAQ,MAAA;AAEhB;AAKA,SAAS,YAAY,GAAA,EAAiC;AAElD,EAAA,IAAI,OAAO,OAAA,KAAY,WAAA,IAAe,OAAA,CAAQ,GAAA,EAAK;AAC/C,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA;AAC7B,IAAA,IAAI,KAAA,KAAU,QAAW,OAAO,KAAA;AAAA,EACpC;AAGA,EAAA,IAAI;AACA,IAAA,MAAM,IAAA,GAAO,MAAA,CAAA,IAAA;AACb,IAAA,IAAI,KAAK,GAAA,EAAK;AACV,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA;AAC1B,MAAA,IAAI,KAAA,KAAU,QAAW,OAAO,KAAA;AAAA,IACpC;AAAA,EACJ,CAAA,CAAA,MAAQ;AAAA,EAER;AAEA,EAAA,OAAO,MAAA;AACX","file":"chunk-WF46NESN.js","sourcesContent":["/**\r\n * @flight-framework/core - Server/Client Boundaries\r\n * \r\n * Runtime utilities for server/client code separation.\r\n * These are OPTIONAL - use them if you want safety guards.\r\n * Flight doesn't force you to use them.\r\n * \r\n * @example\r\n * ```typescript\r\n * import { assertServer, getEnv } from '@flight-framework/core';\r\n * \r\n * export async function fetchFromDatabase() {\r\n * assertServer('fetchFromDatabase'); // Throws if called on client\r\n * const dbUrl = getEnv('DATABASE_URL'); // Returns undefined on client\r\n * // ... your code\r\n * }\r\n * ```\r\n */\r\n\r\n// ============================================================================\r\n// Runtime Assertions\r\n// ============================================================================\r\n\r\n/**\r\n * Assert that code is running on the server.\r\n * Throws an error if called from the client (browser).\r\n * \r\n * Use this to protect server-only code from being accidentally\r\n * called on the client.\r\n * \r\n * @param context - Optional context string for the error message\r\n * @throws Error if called on client\r\n * \r\n * @example\r\n * ```typescript\r\n * export async function queryDatabase() {\r\n * assertServer('queryDatabase');\r\n * // Safe to use process.env, database connections, etc.\r\n * }\r\n * ```\r\n */\r\nexport function assertServer(context?: string): void {\r\n if (typeof window !== 'undefined') {\r\n const name = context || 'This function';\r\n throw new Error(\r\n `[Flight] ${name} can only be called on the server. ` +\r\n `If you need this on the client, use an API route or server action.`\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * Assert that code is running on the client.\r\n * Throws an error if called from the server.\r\n * \r\n * Use this to protect client-only code (e.g., DOM manipulation,\r\n * browser APIs) from being accidentally called during SSR.\r\n * \r\n * @param context - Optional context string for the error message\r\n * @throws Error if called on server\r\n * \r\n * @example\r\n * ```typescript\r\n * export function scrollToTop() {\r\n * assertClient('scrollToTop');\r\n * window.scrollTo(0, 0);\r\n * }\r\n * ```\r\n */\r\nexport function assertClient(context?: string): void {\r\n if (typeof window === 'undefined') {\r\n const name = context || 'This function';\r\n throw new Error(\r\n `[Flight] ${name} can only be called on the client. ` +\r\n `This code should not run during SSR.`\r\n );\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Environment Variable Helpers\r\n// ============================================================================\r\n\r\n/**\r\n * Get an environment variable safely.\r\n * \r\n * On the server: Returns the variable value.\r\n * On the client: Returns undefined for non-public vars.\r\n * \r\n * Public variables (prefixed with FLIGHT_PUBLIC_) are always accessible.\r\n * \r\n * @param key - Environment variable name\r\n * @returns The value or undefined\r\n * \r\n * @example\r\n * ```typescript\r\n * // On server: returns actual value\r\n * // On client: returns undefined (safe!)\r\n * const dbUrl = getEnv('DATABASE_URL');\r\n * \r\n * // Works on both server and client\r\n * const apiUrl = getEnv('FLIGHT_PUBLIC_API_URL');\r\n * ```\r\n */\r\nexport function getEnv(key: string): string | undefined {\r\n // Public vars are always accessible\r\n if (isPublicEnvVar(key)) {\r\n return getEnvValue(key);\r\n }\r\n\r\n // Private vars only on server\r\n if (typeof window !== 'undefined') {\r\n // On client - return undefined safely\r\n if (process.env.NODE_ENV === 'development') {\r\n console.warn(\r\n `[Flight] Attempted to access private env var \"${key}\" on client. ` +\r\n `This returns undefined for security. ` +\r\n `If this should be public, rename it to FLIGHT_PUBLIC_${key}`\r\n );\r\n }\r\n return undefined;\r\n }\r\n\r\n // On server - return actual value\r\n return getEnvValue(key);\r\n}\r\n\r\n/**\r\n * Get an environment variable, throwing if not found.\r\n * Use this when a variable is required.\r\n * \r\n * @param key - Environment variable name\r\n * @returns The value\r\n * @throws Error if variable is not set or inaccessible\r\n * \r\n * @example\r\n * ```typescript\r\n * // Throws if DATABASE_URL is not set\r\n * const dbUrl = requireEnv('DATABASE_URL');\r\n * ```\r\n */\r\nexport function requireEnv(key: string): string {\r\n const value = getEnv(key);\r\n\r\n if (value === undefined) {\r\n // Check if we're on client accessing private var\r\n if (typeof window !== 'undefined' && !isPublicEnvVar(key)) {\r\n throw new Error(\r\n `[Flight] Cannot access private env var \"${key}\" on client. ` +\r\n `Move this code to the server or use FLIGHT_PUBLIC_${key}`\r\n );\r\n }\r\n throw new Error(\r\n `[Flight] Required environment variable \"${key}\" is not set. ` +\r\n `Check your .env file or environment configuration.`\r\n );\r\n }\r\n\r\n return value;\r\n}\r\n\r\n// ============================================================================\r\n// Helper Functions\r\n// ============================================================================\r\n\r\n/**\r\n * Check if an environment variable is public (safe for client).\r\n */\r\nfunction isPublicEnvVar(key: string): boolean {\r\n return (\r\n key.startsWith('FLIGHT_PUBLIC_') ||\r\n key.startsWith('VITE_') ||\r\n key === 'NODE_ENV' ||\r\n key === 'MODE'\r\n );\r\n}\r\n\r\n/**\r\n * Get environment variable value from available sources.\r\n */\r\nfunction getEnvValue(key: string): string | undefined {\r\n // Try process.env (Node.js / SSR)\r\n if (typeof process !== 'undefined' && process.env) {\r\n const value = process.env[key];\r\n if (value !== undefined) return value;\r\n }\r\n\r\n // Try import.meta.env (Vite) - use type-safe access\r\n try {\r\n const meta = import.meta as { env?: Record<string, string | undefined> };\r\n if (meta.env) {\r\n const value = meta.env[key];\r\n if (value !== undefined) return value;\r\n }\r\n } catch {\r\n // import.meta not available in this environment\r\n }\r\n\r\n return undefined;\r\n}\r\n\r\n// ============================================================================\r\n// Type Guards\r\n// ============================================================================\r\n\r\n/**\r\n * Check if code is running on the server.\r\n * This is a re-export for convenience.\r\n */\r\nexport { isServer } from './env.js';\r\n\r\n/**\r\n * Check if code is running in the browser.\r\n * This is a re-export for convenience.\r\n */\r\nexport { isBrowser } from './env.js';\r\n"]}
@@ -520,9 +520,117 @@ function createQueueManager(options) {
520
520
  };
521
521
  }
522
522
 
523
+ // src/actions/plugin.ts
524
+ function serverActionsPlugin(options = {}) {
525
+ const {
526
+ include = ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
527
+ exclude = ["**/node_modules/**"]
528
+ } = options;
529
+ return {
530
+ name: "flight:server-actions",
531
+ async transform(code, id) {
532
+ if (exclude.some((pattern) => id.includes(pattern.replace("**/", "")))) {
533
+ return null;
534
+ }
535
+ if (!hasUseServerDirective(code)) {
536
+ return null;
537
+ }
538
+ const transformed = transformServerActions(code, id);
539
+ return {
540
+ code: transformed,
541
+ map: null
542
+ // TODO: Generate source map
543
+ };
544
+ }
545
+ };
546
+ }
547
+ var USE_SERVER_REGEX = /^(['"])use server\1;?\s*$/m;
548
+ var FUNCTION_REGEX = /^(export\s+)?(async\s+)?function\s+(\w+)/gm;
549
+ var ARROW_FUNCTION_REGEX = /^(export\s+)?(const|let)\s+(\w+)\s*=\s*(async\s+)?\(/gm;
550
+ function hasUseServerDirective(code) {
551
+ return USE_SERVER_REGEX.test(code);
552
+ }
553
+ function transformServerActions(code, filePath) {
554
+ const isFileLevel = code.trim().startsWith("'use server'") || code.trim().startsWith('"use server"');
555
+ if (isFileLevel) {
556
+ return transformFileLevelActions(code, filePath);
557
+ }
558
+ return transformInlineActions(code, filePath);
559
+ }
560
+ function transformFileLevelActions(code, filePath) {
561
+ const transformed = code.replace(USE_SERVER_REGEX, "");
562
+ const functions = [];
563
+ let match;
564
+ FUNCTION_REGEX.lastIndex = 0;
565
+ while ((match = FUNCTION_REGEX.exec(code)) !== null) {
566
+ functions.push({
567
+ name: match[3],
568
+ isExported: !!match[1],
569
+ isAsync: !!match[2]
570
+ });
571
+ }
572
+ ARROW_FUNCTION_REGEX.lastIndex = 0;
573
+ while ((match = ARROW_FUNCTION_REGEX.exec(code)) !== null) {
574
+ functions.push({
575
+ name: match[3],
576
+ isExported: !!match[1],
577
+ isAsync: !!match[4]
578
+ });
579
+ }
580
+ const registrations = functions.filter((fn) => fn.isExported).map((fn) => {
581
+ const actionId = generateActionId(fn.name, filePath);
582
+ return `__flight_registerAction({ id: "${actionId}", name: "${fn.name}", filePath: "${filePath}", fn: ${fn.name} });`;
583
+ }).join("\n");
584
+ const imports = `import { registerAction as __flight_registerAction } from '@flight-framework/core/actions';
585
+ `;
586
+ return imports + transformed + "\n\n// Server action registrations\n" + registrations;
587
+ }
588
+ function transformInlineActions(code, filePath) {
589
+ const inlineServerRegex = /((async\s+)?function\s+(\w+)\s*\([^)]*\)\s*\{)\s*(['"])use server\4;?/g;
590
+ let transformed = code;
591
+ let match;
592
+ const actionsToRegister = [];
593
+ while ((match = inlineServerRegex.exec(code)) !== null) {
594
+ const functionName = match[3];
595
+ const actionId = generateActionId(functionName, filePath);
596
+ transformed = transformed.replace(match[0], match[1]);
597
+ actionsToRegister.push(
598
+ `__flight_registerAction({ id: "${actionId}", name: "${functionName}", filePath: "${filePath}", fn: ${functionName} });`
599
+ );
600
+ }
601
+ if (actionsToRegister.length > 0) {
602
+ const imports = `import { registerAction as __flight_registerAction } from '@flight-framework/core/actions';
603
+ `;
604
+ return imports + transformed + "\n\n// Server action registrations\n" + actionsToRegister.join("\n");
605
+ }
606
+ return transformed;
607
+ }
608
+ function createActionCaller(actionId) {
609
+ return async (...args) => {
610
+ const response = await fetch(`/__flight_action/${actionId}`, {
611
+ method: "POST",
612
+ headers: {
613
+ "Content-Type": "application/json"
614
+ },
615
+ body: JSON.stringify(args)
616
+ });
617
+ if (!response.ok) {
618
+ throw new Error(`Action failed: ${response.statusText}`);
619
+ }
620
+ const result = await response.json();
621
+ if (!result.success) {
622
+ throw new Error(result.error || "Action failed");
623
+ }
624
+ return result.data;
625
+ };
626
+ }
627
+ function formAction(actionId) {
628
+ return `/__flight_action/${actionId}`;
629
+ }
630
+
523
631
  // src/actions/index.ts
524
632
  var actionRegistry = /* @__PURE__ */ new Map();
525
- function registerAction(action) {
633
+ function registerAction2(action) {
526
634
  actionRegistry.set(action.id, action);
527
635
  }
528
636
  function getAction(id) {
@@ -662,6 +770,6 @@ async function handleActionRequest(request) {
662
770
  }
663
771
  }
664
772
 
665
- export { RedirectError, clearActions, collectStream, cookies, createActionContext, createActionMiddleware, createActionReference, createLoggingMiddleware, createMemoryAdapter, createProgressReporter, createQueueManager, createQueuedAction, createRetryMiddleware, createStreamableAction, createTimingMiddleware, defaultRetryStrategy, executeAction, executeFormAction, executeWithMiddleware, exponentialBackoff, fixedDelay, generateActionId, getAction, getAllActions, handleActionRequest, isRedirectError, parseFormData, pipeline, redirect, registerAction, responseToStream, streamToResponse };
666
- //# sourceMappingURL=chunk-3QP3E7HS.js.map
667
- //# sourceMappingURL=chunk-3QP3E7HS.js.map
773
+ export { RedirectError, clearActions, collectStream, cookies, createActionCaller, createActionContext, createActionMiddleware, createActionReference, createLoggingMiddleware, createMemoryAdapter, createProgressReporter, createQueueManager, createQueuedAction, createRetryMiddleware, createStreamableAction, createTimingMiddleware, defaultRetryStrategy, executeAction, executeFormAction, executeWithMiddleware, exponentialBackoff, fixedDelay, formAction, generateActionId, getAction, getAllActions, handleActionRequest, isRedirectError, parseFormData, pipeline, redirect, registerAction2 as registerAction, responseToStream, serverActionsPlugin, streamToResponse };
774
+ //# sourceMappingURL=chunk-XGP3C2ZE.js.map
775
+ //# sourceMappingURL=chunk-XGP3C2ZE.js.map