@facetlayer/prism-framework 0.4.0 → 0.4.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.
Files changed (130) hide show
  1. package/README.md +176 -8
  2. package/dist/Errors.d.ts +38 -0
  3. package/dist/Errors.d.ts.map +1 -0
  4. package/dist/Metrics.d.ts +5 -0
  5. package/dist/Metrics.d.ts.map +1 -0
  6. package/dist/RequestContext.d.ts +17 -0
  7. package/dist/RequestContext.d.ts.map +1 -0
  8. package/dist/ServiceDefinition.d.ts +16 -0
  9. package/dist/ServiceDefinition.d.ts.map +1 -0
  10. package/dist/app/PrismApp.d.ts +31 -0
  11. package/dist/app/PrismApp.d.ts.map +1 -0
  12. package/dist/app/callEndpoint.d.ts +13 -0
  13. package/dist/app/callEndpoint.d.ts.map +1 -0
  14. package/dist/app/validateApp.d.ts +20 -0
  15. package/dist/app/validateApp.d.ts.map +1 -0
  16. package/dist/authorization/AuthSource.d.ts +8 -0
  17. package/dist/authorization/AuthSource.d.ts.map +1 -0
  18. package/dist/authorization/Authorization.d.ts +24 -0
  19. package/dist/authorization/Authorization.d.ts.map +1 -0
  20. package/dist/authorization/Resource.d.ts +5 -0
  21. package/dist/authorization/Resource.d.ts.map +1 -0
  22. package/dist/authorization/index.d.ts +5 -0
  23. package/dist/authorization/index.d.ts.map +1 -0
  24. package/dist/cli.js +1 -1
  25. package/dist/databases/DatabaseInitializationOptions.d.ts +9 -0
  26. package/dist/databases/DatabaseInitializationOptions.d.ts.map +1 -0
  27. package/dist/databases/DatabaseSetup.d.ts +3 -0
  28. package/dist/databases/DatabaseSetup.d.ts.map +1 -0
  29. package/dist/endpoints/createEndpoint.d.ts +4 -0
  30. package/dist/endpoints/createEndpoint.d.ts.map +1 -0
  31. package/dist/endpoints/getEffectiveOperationId.d.ts +19 -0
  32. package/dist/endpoints/getEffectiveOperationId.d.ts.map +1 -0
  33. package/dist/env/Env.d.ts +2 -0
  34. package/dist/env/Env.d.ts.map +1 -0
  35. package/dist/index.d.ts +34 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +1364 -0
  38. package/dist/launch/launchConfig.d.ts +18 -0
  39. package/dist/launch/launchConfig.d.ts.map +1 -0
  40. package/dist/logging/index.d.ts +9 -0
  41. package/dist/logging/index.d.ts.map +1 -0
  42. package/dist/sse/ConnectionManager.d.ts +23 -0
  43. package/dist/sse/ConnectionManager.d.ts.map +1 -0
  44. package/dist/stdin/StdinServer.d.ts +38 -0
  45. package/dist/stdin/StdinServer.d.ts.map +1 -0
  46. package/dist/web/EndpointListing.d.ts +3 -0
  47. package/dist/web/EndpointListing.d.ts.map +1 -0
  48. package/dist/web/ExpressAppSetup.d.ts +18 -0
  49. package/dist/web/ExpressAppSetup.d.ts.map +1 -0
  50. package/dist/web/ExpressEndpointSetup.d.ts +31 -0
  51. package/dist/web/ExpressEndpointSetup.d.ts.map +1 -0
  52. package/dist/web/SseResponse.d.ts +15 -0
  53. package/dist/web/SseResponse.d.ts.map +1 -0
  54. package/dist/web/ViteIntegration.d.ts +19 -0
  55. package/dist/web/ViteIntegration.d.ts.map +1 -0
  56. package/dist/web/corsMiddleware.d.ts +14 -0
  57. package/dist/web/corsMiddleware.d.ts.map +1 -0
  58. package/dist/web/localhostOnlyMiddleware.d.ts +3 -0
  59. package/dist/web/localhostOnlyMiddleware.d.ts.map +1 -0
  60. package/dist/web/openapi/OpenAPI.d.ts +37 -0
  61. package/dist/web/openapi/OpenAPI.d.ts.map +1 -0
  62. package/dist/web/openapi/validateServicesForOpenapi.d.ts +32 -0
  63. package/dist/web/openapi/validateServicesForOpenapi.d.ts.map +1 -0
  64. package/dist/web/requestContextMiddleware.d.ts +3 -0
  65. package/dist/web/requestContextMiddleware.d.ts.map +1 -0
  66. package/docs/authorization.md +281 -0
  67. package/docs/cors-setup.md +172 -0
  68. package/docs/creating-services.md +220 -0
  69. package/docs/database-setup.md +134 -0
  70. package/docs/endpoint-tools.md +1 -11
  71. package/docs/env-files.md +12 -1
  72. package/docs/error-handling.md +70 -0
  73. package/docs/getting-started.md +22 -12
  74. package/docs/launch-configuration.md +223 -0
  75. package/docs/overview.md +62 -0
  76. package/docs/server-setup.md +144 -0
  77. package/docs/source-directory-organization.md +115 -0
  78. package/docs/stdin-protocol.md +176 -0
  79. package/package.json +42 -9
  80. package/src/Errors.ts +120 -0
  81. package/src/Metrics.ts +53 -0
  82. package/src/RequestContext.ts +36 -0
  83. package/src/ServiceDefinition.ts +35 -0
  84. package/src/__tests__/Authorization.test.ts +350 -0
  85. package/src/__tests__/Errors.test.ts +378 -0
  86. package/src/__tests__/ListEndpoints.test.ts +98 -0
  87. package/src/__tests__/PrismApp.test.ts +274 -0
  88. package/src/__tests__/RequestContext.test.ts +295 -0
  89. package/src/__tests__/SseResponse.test.ts +189 -0
  90. package/src/__tests__/StdinServer.test.ts +304 -0
  91. package/src/__tests__/corsMiddleware.test.ts +293 -0
  92. package/src/__tests__/createEndpoint.test.ts +412 -0
  93. package/src/__tests__/validateApp.test.ts +206 -0
  94. package/src/app/PrismApp.ts +117 -0
  95. package/src/app/callEndpoint.ts +55 -0
  96. package/src/app/validateApp.ts +78 -0
  97. package/src/authorization/AuthSource.ts +14 -0
  98. package/src/authorization/Authorization.ts +78 -0
  99. package/src/authorization/Resource.ts +8 -0
  100. package/src/authorization/index.ts +4 -0
  101. package/src/databases/DatabaseInitializationOptions.ts +9 -0
  102. package/src/databases/DatabaseSetup.ts +19 -0
  103. package/src/endpoints/createEndpoint.ts +39 -0
  104. package/src/endpoints/getEffectiveOperationId.ts +90 -0
  105. package/src/env/Env.ts +23 -0
  106. package/src/index.ts +78 -0
  107. package/src/launch/launchConfig.ts +59 -0
  108. package/src/list-endpoints-command.ts +1 -1
  109. package/src/logging/index.ts +25 -0
  110. package/src/sse/ConnectionManager.ts +79 -0
  111. package/src/stdin/StdinServer.ts +129 -0
  112. package/src/web/EndpointListing.ts +166 -0
  113. package/src/web/ExpressAppSetup.ts +125 -0
  114. package/src/web/ExpressEndpointSetup.ts +178 -0
  115. package/src/web/SseResponse.ts +78 -0
  116. package/src/web/ViteIntegration.ts +72 -0
  117. package/src/web/__tests__/OpenAPI.invalidZodSchemas.test.ts +250 -0
  118. package/src/web/corsMiddleware.ts +63 -0
  119. package/src/web/localhostOnlyMiddleware.ts +19 -0
  120. package/src/web/openapi/OpenAPI.ts +248 -0
  121. package/src/web/openapi/validateServicesForOpenapi.ts +76 -0
  122. package/src/web/requestContextMiddleware.ts +25 -0
  123. package/.claude/settings.local.json +0 -20
  124. package/CHANGELOG +0 -28
  125. package/CLAUDE.md +0 -44
  126. package/build.mts +0 -8
  127. package/test/call-command.test.ts +0 -96
  128. package/test/generate-api-clients.test.ts +0 -33
  129. package/test/generate-api-clients.test.ts.disabled +0 -75
  130. package/tsconfig.json +0 -21
@@ -0,0 +1,206 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { PrismApp } from '../app/PrismApp.ts';
3
+ import { validateApp, validateAppOrThrow } from '../app/validateApp.ts';
4
+
5
+ describe('validateApp', () => {
6
+ describe('duplicate operationId detection', () => {
7
+ it('should pass when all operationIds are unique', () => {
8
+ const app = new PrismApp({
9
+ services: [
10
+ {
11
+ name: 'test-service',
12
+ endpoints: [
13
+ {
14
+ method: 'GET',
15
+ path: '/users',
16
+ operationId: 'getUsers',
17
+ handler: async () => [],
18
+ },
19
+ {
20
+ method: 'POST',
21
+ path: '/users',
22
+ operationId: 'createUser',
23
+ handler: async () => ({}),
24
+ },
25
+ {
26
+ method: 'GET',
27
+ path: '/users/:id',
28
+ operationId: 'getUserById',
29
+ handler: async () => ({}),
30
+ },
31
+ ],
32
+ },
33
+ ],
34
+ });
35
+
36
+ const result = validateApp(app);
37
+
38
+ expect(result.valid).toBe(true);
39
+ expect(result.errors).toHaveLength(0);
40
+ });
41
+
42
+ it('should fail when endpoints have duplicate explicit operationIds', () => {
43
+ const app = new PrismApp({
44
+ services: [
45
+ {
46
+ name: 'test-service',
47
+ endpoints: [
48
+ {
49
+ method: 'GET',
50
+ path: '/users',
51
+ operationId: 'getUsers',
52
+ handler: async () => [],
53
+ },
54
+ {
55
+ method: 'GET',
56
+ path: '/customers',
57
+ operationId: 'getUsers', // Duplicate!
58
+ handler: async () => [],
59
+ },
60
+ ],
61
+ },
62
+ ],
63
+ });
64
+
65
+ const result = validateApp(app);
66
+
67
+ expect(result.valid).toBe(false);
68
+ expect(result.errors).toHaveLength(1);
69
+ expect(result.errors[0].message).toContain('Duplicate operationIds');
70
+ expect(result.errors[0].message).toContain('getUsers');
71
+ });
72
+
73
+ it('should detect duplicates across multiple services', () => {
74
+ const app = new PrismApp({
75
+ services: [
76
+ {
77
+ name: 'service-a',
78
+ endpoints: [
79
+ {
80
+ method: 'GET',
81
+ path: '/items',
82
+ operationId: 'listItems',
83
+ handler: async () => [],
84
+ },
85
+ ],
86
+ },
87
+ {
88
+ name: 'service-b',
89
+ endpoints: [
90
+ {
91
+ method: 'GET',
92
+ path: '/products',
93
+ operationId: 'listItems', // Duplicate from service-a
94
+ handler: async () => [],
95
+ },
96
+ ],
97
+ },
98
+ ],
99
+ });
100
+
101
+ const result = validateApp(app);
102
+
103
+ expect(result.valid).toBe(false);
104
+ expect(result.errors[0].message).toContain('listItems');
105
+ });
106
+
107
+ it('should pass when auto-generated operationIds are unique', () => {
108
+ const app = new PrismApp({
109
+ services: [
110
+ {
111
+ name: 'test-service',
112
+ endpoints: [
113
+ {
114
+ method: 'GET',
115
+ path: '/users',
116
+ handler: async () => [], // Will auto-generate "getUsers"
117
+ },
118
+ {
119
+ method: 'POST',
120
+ path: '/users',
121
+ handler: async () => ({}), // Will auto-generate "postUsers"
122
+ },
123
+ ],
124
+ },
125
+ ],
126
+ });
127
+
128
+ const result = validateApp(app);
129
+
130
+ expect(result.valid).toBe(true);
131
+ });
132
+
133
+ it('should pass for empty app', () => {
134
+ const app = new PrismApp();
135
+
136
+ const result = validateApp(app);
137
+
138
+ expect(result.valid).toBe(true);
139
+ expect(result.errors).toHaveLength(0);
140
+ });
141
+
142
+ it('should pass for app with services but no endpoints', () => {
143
+ const app = new PrismApp({
144
+ services: [
145
+ {
146
+ name: 'empty-service',
147
+ endpoints: [],
148
+ },
149
+ ],
150
+ });
151
+
152
+ const result = validateApp(app);
153
+
154
+ expect(result.valid).toBe(true);
155
+ });
156
+ });
157
+ });
158
+
159
+ describe('validateAppOrThrow', () => {
160
+ it('should not throw for valid app', () => {
161
+ const app = new PrismApp({
162
+ services: [
163
+ {
164
+ name: 'test-service',
165
+ endpoints: [
166
+ {
167
+ method: 'GET',
168
+ path: '/users',
169
+ operationId: 'getUsers',
170
+ handler: async () => [],
171
+ },
172
+ ],
173
+ },
174
+ ],
175
+ });
176
+
177
+ expect(() => validateAppOrThrow(app)).not.toThrow();
178
+ });
179
+
180
+ it('should throw for app with duplicate operationIds', () => {
181
+ const app = new PrismApp({
182
+ services: [
183
+ {
184
+ name: 'test-service',
185
+ endpoints: [
186
+ {
187
+ method: 'GET',
188
+ path: '/users',
189
+ operationId: 'duplicateId',
190
+ handler: async () => [],
191
+ },
192
+ {
193
+ method: 'GET',
194
+ path: '/items',
195
+ operationId: 'duplicateId',
196
+ handler: async () => [],
197
+ },
198
+ ],
199
+ },
200
+ ],
201
+ });
202
+
203
+ expect(() => validateAppOrThrow(app)).toThrow('PrismApp validation failed');
204
+ expect(() => validateAppOrThrow(app)).toThrow('Duplicate operationIds');
205
+ });
206
+ });
@@ -0,0 +1,117 @@
1
+ import type { EndpointDefinition } from '../web/ExpressEndpointSetup.ts';
2
+ import type { ServiceDefinition } from '../ServiceDefinition.ts';
3
+ import { callEndpoint, type CallEndpointOptions } from './callEndpoint.ts';
4
+
5
+ export interface PrismAppConfig {
6
+ name?: string;
7
+ description?: string;
8
+ services?: ServiceDefinition[];
9
+ }
10
+
11
+ export function endpointKey(method: string, path: string): string {
12
+ return `${method} ${path}`;
13
+ }
14
+
15
+ export class PrismApp {
16
+ endpointMap: Map<string, EndpointDefinition>;
17
+ services: ServiceDefinition[];
18
+ name: string;
19
+ description: string;
20
+
21
+ constructor(config: PrismAppConfig = {}) {
22
+ this.name = config.name ?? 'Prism App';
23
+ this.description = config.description ?? '';
24
+ this.services = [];
25
+ this.endpointMap = new Map();
26
+
27
+ // Register initial services if provided
28
+ if (config.services) {
29
+ for (const service of config.services) {
30
+ this.addService(service);
31
+ }
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Add a service to the app. Endpoints from the service will be registered
37
+ * and available for routing.
38
+ */
39
+ addService(service: ServiceDefinition): void {
40
+ this.services.push(service);
41
+
42
+ if (service.endpoints) {
43
+ for (const endpoint of service.endpoints) {
44
+ const key = endpointKey(endpoint.method, endpoint.path);
45
+ this.endpointMap.set(key, endpoint);
46
+ }
47
+ }
48
+ }
49
+
50
+ getAllServices(): ServiceDefinition[] {
51
+ return this.services;
52
+ }
53
+
54
+ getEndpoint(method: string, path: string): EndpointDefinition | undefined {
55
+ const key = endpointKey(method, path);
56
+ return this.endpointMap.get(key);
57
+ }
58
+
59
+ matchEndpoint(method: string, path: string): { endpoint: EndpointDefinition; params: Record<string, string> } | undefined {
60
+ // First try exact match
61
+ const key = endpointKey(method, path);
62
+ const exactMatch = this.endpointMap.get(key);
63
+ if (exactMatch) {
64
+ return { endpoint: exactMatch, params: {} };
65
+ }
66
+
67
+ // Try to match path patterns with parameters
68
+ for (const [endpointKey, endpoint] of this.endpointMap.entries()) {
69
+ if (!endpointKey.startsWith(method + ' ')) {
70
+ continue;
71
+ }
72
+
73
+ const endpointPath = endpoint.path;
74
+ const match = this.matchPath(endpointPath, path);
75
+
76
+ if (match) {
77
+ return { endpoint, params: match };
78
+ }
79
+ }
80
+
81
+ return undefined;
82
+ }
83
+
84
+ private matchPath(pattern: string, path: string): Record<string, string> | null {
85
+ // Convert Express-style path parameters (:param) to regex with named groups
86
+ const paramNames: string[] = [];
87
+ const regexString = pattern
88
+ .replace(/:([^/]+)/g, (_, paramName) => {
89
+ paramNames.push(paramName);
90
+ return '([^/]+)';
91
+ })
92
+ .replace(/\//g, '\\/');
93
+
94
+ const regex = new RegExp(`^${regexString}$`);
95
+ const match = path.match(regex);
96
+
97
+ if (!match) {
98
+ return null;
99
+ }
100
+
101
+ // Extract parameter values
102
+ const params: Record<string, string> = {};
103
+ for (let i = 0; i < paramNames.length; i++) {
104
+ params[paramNames[i]] = match[i + 1];
105
+ }
106
+
107
+ return params;
108
+ }
109
+
110
+ listAllEndpoints(): EndpointDefinition[] {
111
+ return Array.from(this.endpointMap.values());
112
+ }
113
+
114
+ callEndpoint(options: CallEndpointOptions): Promise<any> {
115
+ return callEndpoint(this, options);
116
+ }
117
+ }
@@ -0,0 +1,55 @@
1
+ import { PrismApp } from './PrismApp.ts';
2
+ import type { ServiceDefinition } from '../ServiceDefinition.ts';
3
+ import { isHttpError, ResponseSchemaValidationError, SchemaValidationError } from '../Errors.ts';
4
+
5
+ export interface CallEndpointOptions {
6
+ method: string;
7
+ path: string;
8
+ input?: any;
9
+ onResponseSchemaFail?: (error: any, result: any) => void;
10
+ }
11
+
12
+ /**
13
+ * Call an endpoint programmatically without going through HTTP
14
+ * This is useful for testing or for calling endpoints from scripts/tools
15
+ */
16
+ export async function callEndpoint(app: PrismApp, options: CallEndpointOptions) {
17
+ // Find the endpoint using pattern matching
18
+ const match = app.matchEndpoint(options.method, options.path);
19
+
20
+ if (!match) {
21
+ throw new Error(`Endpoint not found: ${options.method} ${options.path}`);
22
+ }
23
+
24
+ const { endpoint, params } = match;
25
+
26
+ // Merge path parameters with input data
27
+ let input = { ...params, ...(options.input || {}) };
28
+
29
+ // Validate input if schema is provided
30
+ if (endpoint.requestSchema) {
31
+ const validationResult = endpoint.requestSchema.safeParse(input);
32
+ if (!validationResult.success) {
33
+ throw new SchemaValidationError('Schema validation failed', validationResult.error.issues);
34
+ }
35
+ input = validationResult.data;
36
+ }
37
+
38
+ // Call the handler
39
+ const result = await endpoint.handler(input);
40
+
41
+ // Validate output if schema is provided
42
+ if (endpoint.responseSchema) {
43
+ const validationResult = endpoint.responseSchema.safeParse(result);
44
+ if (!validationResult.success) {
45
+ const error = new ResponseSchemaValidationError('Response schema validation failed', validationResult.error.issues);
46
+ if (options.onResponseSchemaFail) {
47
+ options.onResponseSchemaFail(error, result);
48
+ } else {
49
+ throw error;
50
+ }
51
+ }
52
+ }
53
+
54
+ return result;
55
+ }
@@ -0,0 +1,78 @@
1
+ import type { PrismApp } from './PrismApp.ts';
2
+ import { getEffectiveOperationId } from '../endpoints/createEndpoint.ts';
3
+
4
+ export interface ValidationError {
5
+ message: string;
6
+ endpoints?: string[];
7
+ }
8
+
9
+ export interface ValidationResult {
10
+ valid: boolean;
11
+ errors: ValidationError[];
12
+ }
13
+
14
+ /**
15
+ * Validates a PrismApp configuration, checking for issues like duplicate operationIds.
16
+ * This should be run at server startup to catch configuration errors early.
17
+ */
18
+ export function validateApp(app: PrismApp): ValidationResult {
19
+ const errors: ValidationError[] = [];
20
+
21
+ // Check for duplicate operationIds
22
+ const duplicateError = checkDuplicateOperationIds(app);
23
+ if (duplicateError) {
24
+ errors.push(duplicateError);
25
+ }
26
+
27
+ return {
28
+ valid: errors.length === 0,
29
+ errors,
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Checks for duplicate operationIds across all endpoints.
35
+ * Returns an error if duplicates are found, null otherwise.
36
+ */
37
+ function checkDuplicateOperationIds(app: PrismApp): ValidationError | null {
38
+ const endpoints = app.listAllEndpoints();
39
+ const operationIdToEndpoints = new Map<string, string[]>();
40
+
41
+ for (const endpoint of endpoints) {
42
+ const operationId = getEffectiveOperationId(endpoint);
43
+ const endpointKey = `${endpoint.method} ${endpoint.path}`;
44
+
45
+ const existing = operationIdToEndpoints.get(operationId) || [];
46
+ existing.push(endpointKey);
47
+ operationIdToEndpoints.set(operationId, existing);
48
+ }
49
+
50
+ // Find duplicates
51
+ const duplicates: string[] = [];
52
+ for (const [operationId, endpointKeys] of operationIdToEndpoints) {
53
+ if (endpointKeys.length > 1) {
54
+ duplicates.push(`operationId "${operationId}" is used by: ${endpointKeys.join(', ')}`);
55
+ }
56
+ }
57
+
58
+ if (duplicates.length > 0) {
59
+ return {
60
+ message: `Duplicate operationIds found. Each endpoint must have a unique operationId.\n${duplicates.join('\n')}`,
61
+ endpoints: duplicates,
62
+ };
63
+ }
64
+
65
+ return null;
66
+ }
67
+
68
+ /**
69
+ * Validates the app and throws an error if validation fails.
70
+ * Use this at server startup to ensure the app is properly configured.
71
+ */
72
+ export function validateAppOrThrow(app: PrismApp): void {
73
+ const result = validateApp(app);
74
+ if (!result.valid) {
75
+ const errorMessages = result.errors.map(e => e.message).join('\n\n');
76
+ throw new Error(`PrismApp validation failed:\n${errorMessages}`);
77
+ }
78
+ }
@@ -0,0 +1,14 @@
1
+
2
+ /*
3
+ * An 'auth source' is a verified authentication token or session that has been
4
+ * extracted from a request. This represents the source of identity for authorization
5
+ * decisions. Examples: a validated session cookie, a verified API key, etc.
6
+ */
7
+ export interface AuthSource {
8
+ type: string;
9
+ }
10
+
11
+ export interface CookieAuthSource extends AuthSource {
12
+ type: 'cookie';
13
+ sessionId: string;
14
+ }
@@ -0,0 +1,78 @@
1
+ import type { CookieAuthSource, AuthSource } from './AuthSource.ts';
2
+ import type { Resource } from './Resource.ts';
3
+
4
+ // Base permission type - applications can extend this
5
+ export type Permission = string;
6
+
7
+ export interface UserPermissions {
8
+ userId: string;
9
+ permissions: Permission[];
10
+ }
11
+
12
+ /*
13
+ * Authorization
14
+ *
15
+ * Contains all of the authorization data for a single request.
16
+ * Stored on every RequestContext.
17
+ *
18
+ * This includes:
19
+ * - The verified 'auth sources' which are the original source of all authorization.
20
+ * - The 'resources' which this request has been granted access to.
21
+ * - The 'permissions' which are fine-level permissions that the request has been granted.
22
+ */
23
+ export class Authorization {
24
+ private resources: Map<Resource['type'], Resource>;
25
+ private authSources: AuthSource[];
26
+ private userPermissions?: UserPermissions;
27
+
28
+ constructor(resources: Resource[] = [], authSources: AuthSource[] = []) {
29
+ this.resources = new Map();
30
+ for (const resource of resources) {
31
+ this.resources.set(resource.type, resource);
32
+ }
33
+ this.authSources = [...authSources];
34
+ }
35
+
36
+ addResource(resource: Resource): void {
37
+ this.resources.set(resource.type, resource);
38
+ }
39
+
40
+ hasResource(type: Resource['type']): boolean {
41
+ return this.resources.has(type);
42
+ }
43
+
44
+ getResource(type: Resource['type']): Resource | undefined {
45
+ return this.resources.get(type);
46
+ }
47
+
48
+ getAllResources(): Resource[] {
49
+ return Array.from(this.resources.values());
50
+ }
51
+
52
+ addAuthSource(authSource: AuthSource): void {
53
+ this.authSources.push(authSource);
54
+ }
55
+
56
+ getAuthSources(): AuthSource[] {
57
+ return [...this.authSources];
58
+ }
59
+
60
+ getCookieAuthSource(): CookieAuthSource | undefined {
61
+ return this.authSources.find((c): c is CookieAuthSource => c.type === 'cookie');
62
+ }
63
+
64
+ setUserPermissions(userPermissions: UserPermissions): void {
65
+ this.userPermissions = userPermissions;
66
+ }
67
+
68
+ getUserPermissions(): UserPermissions | undefined {
69
+ return this.userPermissions;
70
+ }
71
+
72
+ hasPermission(permission: Permission): boolean {
73
+ if (!this.userPermissions) {
74
+ return false;
75
+ }
76
+ return this.userPermissions.permissions.includes(permission);
77
+ }
78
+ }
@@ -0,0 +1,8 @@
1
+ /*
2
+ * A 'resource' is a piece of data that the user can have access to.
3
+ * This can be a user, a project, a session, or something else.
4
+ */
5
+ export interface Resource {
6
+ type: 'user' | 'project' | 'session' | 'custom';
7
+ id: string;
8
+ }
@@ -0,0 +1,4 @@
1
+ export { Authorization } from './Authorization.ts';
2
+ export type { Permission, UserPermissions } from './Authorization.ts';
3
+ export type { CookieAuthSource, AuthSource } from './AuthSource.ts';
4
+ export type { Resource } from './Resource.ts';
@@ -0,0 +1,9 @@
1
+ import type { ServiceDefinition } from '../ServiceDefinition.ts';
2
+ import type { LoadDatabaseFn, MigrationBehavior } from '@facetlayer/sqlite-wrapper';
3
+
4
+ export interface DatabaseInitializationOptions {
5
+ migrationBehavior: MigrationBehavior;
6
+ databasePath: string;
7
+ services?: ServiceDefinition[];
8
+ loadDatabase: LoadDatabaseFn;
9
+ }
@@ -0,0 +1,19 @@
1
+ import type { ServiceDefinition } from '../ServiceDefinition.ts';
2
+ import type { LoadDatabaseFn, MigrationBehavior } from '@facetlayer/sqlite-wrapper';
3
+
4
+ /*
5
+ * getStatementsForDatabase
6
+ *
7
+ * Returns all of the SQL statements for a given database name from the given services.
8
+ */
9
+ export function getStatementsForDatabase(
10
+ databaseName: string,
11
+ services: ServiceDefinition[]
12
+ ): string[] {
13
+ return (services || [])
14
+ .map(service => {
15
+ const databases = service.databases as any;
16
+ return databases?.[databaseName]?.statements || [];
17
+ })
18
+ .flat();
19
+ }
@@ -0,0 +1,39 @@
1
+ import { logWarn } from "../logging/index.ts";
2
+ import type { EndpointDefinition } from "../web/ExpressEndpointSetup.ts";
3
+ import { validateEndpointForOpenapi } from "../web/openapi/validateServicesForOpenapi.ts";
4
+ import { isValidOperationId } from "./getEffectiveOperationId.ts";
5
+
6
+ export { getEffectiveOperationId, isValidOperationId, generateOperationIdFromPath } from "./getEffectiveOperationId.ts";
7
+
8
+ export function createEndpoint(
9
+ definition: EndpointDefinition
10
+ ): EndpointDefinition {
11
+ // Validate operationId if explicitly provided
12
+ if (definition.operationId !== undefined && !isValidOperationId(definition.operationId)) {
13
+ logWarn(`Misconfigured endpoint ${definition.path}: operationId "${definition.operationId}" is not allowed`);
14
+
15
+ return {
16
+ ...definition,
17
+ handler: () => {
18
+ throw new Error(`Misconfigured endpoint ${definition.path}: operationId "${definition.operationId}" is not allowed. Use a descriptive unique identifier.`);
19
+ },
20
+ }
21
+ }
22
+
23
+ const validationResult = validateEndpointForOpenapi(definition);
24
+ if (validationResult?.error) {
25
+ logWarn(`Misconfigured endpoint ${definition.path}: ${validationResult.error.errorMessage}`);
26
+ // Remove invalid schemas so they don't break OpenAPI generation for the entire service.
27
+ // The endpoint will still work, but won't appear correctly in OpenAPI docs.
28
+ return {
29
+ ...definition,
30
+ requestSchema: undefined,
31
+ responseSchema: undefined,
32
+ handler: () => {
33
+ throw new Error(`Misconfigured endpoint ${definition.path}: ${validationResult.error.errorMessage}`);
34
+ },
35
+ }
36
+ }
37
+
38
+ return definition;
39
+ }