@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
|
@@ -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
|
-
//
|
|
29
|
-
const
|
|
30
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
82
|
-
{ label: '
|
|
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
|
+
}
|