@ismaelsoilet/synapsejs 1.3.0 → 1.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ismaelsoilet/synapsejs",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Bun-only fullstack framework where a feature is a single contiguous file",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -25,26 +25,30 @@ export function parseTableDependencies(ddl: string): { created: string[]; refere
25
25
  const created: string[] = [];
26
26
  const referenced: string[] = [];
27
27
 
28
- // Match CREATE TABLE [IF NOT EXISTS] <name>
29
- const createRegex = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["'`]?([a-zA-Z0-9_]+)["'`]?/gi;
30
- let match: RegExpExecArray | null = createRegex.exec(ddl);
28
+ // Strip SQL comments to avoid false-positive dependencies
29
+ const cleanDdl = ddl.replace(/--.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
30
+
31
+ // Match CREATE TABLE [IF NOT EXISTS] [schema.]<name>
32
+ const createRegex =
33
+ /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:["'`]?[a-zA-Z0-9_]+["'`]?\.)?["'`]?([a-zA-Z0-9_]+)["'`]?/gi;
34
+ let match: RegExpExecArray | null = createRegex.exec(cleanDdl);
31
35
  while (match !== null) {
32
36
  const tableName = match[1].toLowerCase();
33
37
  if (!created.includes(tableName)) {
34
38
  created.push(tableName);
35
39
  }
36
- match = createRegex.exec(ddl);
40
+ match = createRegex.exec(cleanDdl);
37
41
  }
38
42
 
39
- // Match REFERENCES <name>
40
- const refRegex = /REFERENCES\s+["'`]?([a-zA-Z0-9_]+)["'`]?/gi;
41
- let refMatch: RegExpExecArray | null = refRegex.exec(ddl);
43
+ // Match REFERENCES [schema.]<name>[(columns)]
44
+ const refRegex = /REFERENCES\s+(?:["'`]?[a-zA-Z0-9_]+["'`]?\.)?["'`]?([a-zA-Z0-9_]+)["'`]?/gi;
45
+ let refMatch: RegExpExecArray | null = refRegex.exec(cleanDdl);
42
46
  while (refMatch !== null) {
43
47
  const tableName = refMatch[1].toLowerCase();
44
48
  if (!created.includes(tableName) && !referenced.includes(tableName)) {
45
49
  referenced.push(tableName);
46
50
  }
47
- refMatch = refRegex.exec(ddl);
51
+ refMatch = refRegex.exec(cleanDdl);
48
52
  }
49
53
 
50
54
  return { created, referenced };
@@ -75,11 +75,19 @@ const SERVER_ONLY_PATTERNS: Array<{ label: string; pattern: RegExp }> = [
75
75
  { label: 'UPDATE statement', pattern: /\bUPDATE\s+\w+\s+SET\b/i },
76
76
  { label: 'DELETE statement', pattern: /\bDELETE\s+FROM\b/i },
77
77
  { label: 'CREATE TABLE statement', pattern: /\bCREATE\s+TABLE\b/i },
78
+ { label: 'ALTER TABLE statement', pattern: /\bALTER\s+TABLE\b/i },
79
+ { label: 'DROP TABLE statement', pattern: /\bDROP\s+TABLE\b/i },
78
80
  { label: 'database call', pattern: /\bdb\s*\.\s*(?:query|findMany|findOne|insert|update|delete|sql|sqlOne)\b/ },
79
81
  { label: 'sliceSchema symbol', pattern: /\bsliceSchema\b/ },
80
82
  { label: 'test runner import', pattern: /from\s+['"]bun:test['"]/ },
81
- { label: 'process.env access', pattern: /\bprocess\.env\b/ },
82
- { label: 'Bun global', pattern: /\bBun\./ }
83
+ { label: 'process.env access', pattern: /\bprocess\s*(?:\.\s*env|\[\s*['"]env['"]\s*\])/ },
84
+ { label: 'process destructuring', pattern: /\b(?:const|let|var)\s*\{[^}]*\benv\b[^}]*\}\s*=\s*process\b/ },
85
+ { label: 'Bun global', pattern: /\bBun\./ },
86
+ {
87
+ label: 'node server module import',
88
+ pattern: /from\s+['"](?:node:)?(?:fs|child_process|cluster|dgram|dns|net|tls|v8|vm)['"]/
89
+ },
90
+ { label: 'dynamic code evaluation', pattern: /\b(?:eval|new\s+Function)\s*\(/ }
83
91
  ];
84
92
 
85
93
  interface DeclEntry {
@@ -0,0 +1,89 @@
1
+ /**
2
+ * SynapseJS - Declarative Action Definition Primitive
3
+ *
4
+ * Implements "Secure by Default" (Fail-Closed) semantics and wire-speed automatic
5
+ * TypeBox schema validation. Prevents inadvertent public exposure of RPC endpoints
6
+ * and eliminates repetitive manual `Value.Check` and `requireAuth` boilerplate.
7
+ */
8
+
9
+ import type { Static, TSchema } from '@sinclair/typebox';
10
+ import { Value } from '@sinclair/typebox/value';
11
+ import type { DatabaseClient } from './database-client';
12
+ import { Err, type Result } from './machine-types';
13
+ import { requireAuth, type SessionContext } from './session-context';
14
+
15
+ export type ActionAuthRule = 'public' | string[];
16
+
17
+ export interface ActionDefinitionConfig<TSchemaInput extends TSchema | undefined, TOutput> {
18
+ /**
19
+ * Optional TypeBox schema for input payload validation.
20
+ * If supplied, incoming requests are automatically validated before the handler runs.
21
+ */
22
+ input?: TSchemaInput;
23
+
24
+ /**
25
+ * Authorization rules for this action.
26
+ * - Omitted / undefined: Requires an authenticated user session (Fail-Closed by default).
27
+ * - Array of strings (e.g. ['admin', 'manager']): Requires the session to possess at least one of these roles.
28
+ * - 'public': Explicitly marks the action as accessible to unauthenticated callers.
29
+ */
30
+ auth?: ActionAuthRule;
31
+
32
+ /**
33
+ * Handler function executing business logic.
34
+ * Receives strongly-typed validated input, database client and authenticated session context.
35
+ */
36
+ handler: (ctx: {
37
+ input: TSchemaInput extends TSchema ? Static<TSchemaInput> : unknown;
38
+ db?: DatabaseClient;
39
+ session?: SessionContext;
40
+ }) => Promise<TOutput> | TOutput;
41
+ }
42
+
43
+ export type ActionHandlerFn<TSchemaInput extends TSchema | undefined, TOutput> = {
44
+ (
45
+ payload: unknown,
46
+ db?: DatabaseClient,
47
+ session?: SessionContext
48
+ ): Promise<TOutput | Result<never, 'INVALID_SCHEMA' | 'UNAUTHORIZED' | 'FORBIDDEN'>>;
49
+ readonly __isSynapseAction: true;
50
+ readonly config: ActionDefinitionConfig<TSchemaInput, TOutput>;
51
+ };
52
+
53
+ export function defineAction<TSchemaInput extends TSchema | undefined, TOutput>(
54
+ config: ActionDefinitionConfig<TSchemaInput, TOutput>
55
+ ): ActionHandlerFn<TSchemaInput, TOutput> {
56
+ const actionFn = async (
57
+ payload: unknown,
58
+ db?: DatabaseClient,
59
+ session?: SessionContext
60
+ ): Promise<TOutput | Result<never, 'INVALID_SCHEMA' | 'UNAUTHORIZED' | 'FORBIDDEN'>> => {
61
+ // 1. Fail-Closed Authentication Guard
62
+ if (config.auth !== 'public') {
63
+ const requiredRoles = Array.isArray(config.auth) ? config.auth : undefined;
64
+ const auth = requireAuth(session, requiredRoles);
65
+ if (!auth.ok) {
66
+ return Err(auth.error);
67
+ }
68
+ }
69
+
70
+ // 2. Automatic Wire-Speed Schema Validation
71
+ if (config.input) {
72
+ if (!Value.Check(config.input, payload)) {
73
+ return Err('INVALID_SCHEMA');
74
+ }
75
+ }
76
+
77
+ // 3. Typed Handler Invocation
78
+ return config.handler({
79
+ input: payload as TSchemaInput extends TSchema ? Static<TSchemaInput> : unknown,
80
+ db,
81
+ session
82
+ });
83
+ };
84
+
85
+ Object.defineProperty(actionFn, '__isSynapseAction', { value: true, writable: false });
86
+ Object.defineProperty(actionFn, 'config', { value: config, writable: false });
87
+
88
+ return actionFn as ActionHandlerFn<TSchemaInput, TOutput>;
89
+ }
package/src/core/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './action-context';
2
+ export * from './action-definition';
2
3
  export * from './config';
3
4
  export * from './database-client';
4
5
  export * from './database-factory';