@forumone/throughline-core 0.0.1 → 0.2.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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +104 -28
- package/dist/audit/collection.d.ts +19 -0
- package/dist/audit/collection.d.ts.map +1 -0
- package/dist/audit/collection.js +107 -0
- package/dist/audit/collection.js.map +1 -0
- package/dist/audit/index.d.ts +9 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +5 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/plugin.d.ts +20 -0
- package/dist/audit/plugin.d.ts.map +1 -0
- package/dist/audit/plugin.js +55 -0
- package/dist/audit/plugin.js.map +1 -0
- package/dist/audit/types.d.ts +11 -0
- package/dist/audit/types.d.ts.map +1 -0
- package/dist/audit/types.js +44 -0
- package/dist/audit/types.js.map +1 -0
- package/dist/audit/writer.d.ts +50 -0
- package/dist/audit/writer.d.ts.map +1 -0
- package/dist/audit/writer.js +101 -0
- package/dist/audit/writer.js.map +1 -0
- package/dist/auth/api-keys.d.ts +21 -0
- package/dist/auth/api-keys.d.ts.map +1 -0
- package/dist/auth/api-keys.js +115 -0
- package/dist/auth/api-keys.js.map +1 -0
- package/dist/auth/authenticator.d.ts +15 -0
- package/dist/auth/authenticator.d.ts.map +1 -0
- package/dist/auth/authenticator.js +63 -0
- package/dist/auth/authenticator.js.map +1 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +3 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/env/index.d.ts +51 -0
- package/dist/env/index.d.ts.map +1 -0
- package/dist/env/index.js +59 -0
- package/dist/env/index.js.map +1 -0
- package/dist/events/index.d.ts +4 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +2 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/inngest.d.ts +24 -0
- package/dist/events/inngest.d.ts.map +1 -0
- package/dist/events/inngest.js +24 -0
- package/dist/events/inngest.js.map +1 -0
- package/dist/events/taxonomy.d.ts +84 -0
- package/dist/events/taxonomy.d.ts.map +1 -0
- package/dist/events/taxonomy.js +10 -0
- package/dist/events/taxonomy.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/index.d.ts +9 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +41 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/mcp/handler.d.ts +20 -0
- package/dist/mcp/handler.d.ts.map +1 -0
- package/dist/mcp/handler.js +109 -0
- package/dist/mcp/handler.js.map +1 -0
- package/dist/mcp/index.d.ts +5 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/meta.d.ts +74 -0
- package/dist/mcp/meta.d.ts.map +1 -0
- package/dist/mcp/meta.js +27 -0
- package/dist/mcp/meta.js.map +1 -0
- package/dist/utils/diff.d.ts +13 -0
- package/dist/utils/diff.d.ts.map +1 -0
- package/dist/utils/diff.js +31 -0
- package/dist/utils/diff.js.map +1 -0
- package/dist/utils/id.d.ts +6 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +13 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +82 -7
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a fire-and-forget audit writer. The writer never throws — failures
|
|
3
|
+
* log via `options.logger` (or `console`). Audit failures must not break
|
|
4
|
+
* the originating action.
|
|
5
|
+
*
|
|
6
|
+
* If `inngest` is provided the writer also fires an `audit/event.recorded`
|
|
7
|
+
* event so subscribers (email, integrations, healthchecks) can react.
|
|
8
|
+
*/
|
|
9
|
+
export function createAuditWriter(options) {
|
|
10
|
+
const { payload, inngest, collectionSlug = 'audit-events', logger } = options;
|
|
11
|
+
return async function recordAuditEvent(event) {
|
|
12
|
+
try {
|
|
13
|
+
const summary = event.summary ?? generateSummary(event);
|
|
14
|
+
const created = await payload.create({
|
|
15
|
+
collection: collectionSlug,
|
|
16
|
+
data: stripUndefined({
|
|
17
|
+
createdAt: new Date().toISOString(),
|
|
18
|
+
actor: stripUndefined({ ...event.actor }),
|
|
19
|
+
action: event.action,
|
|
20
|
+
mcpServer: event.mcpServer,
|
|
21
|
+
mcpTool: event.mcpTool,
|
|
22
|
+
targetCollection: event.targetCollection,
|
|
23
|
+
targetId: event.targetId,
|
|
24
|
+
targetTitle: event.targetTitle,
|
|
25
|
+
prompt: event.prompt,
|
|
26
|
+
reasoning: event.reasoning,
|
|
27
|
+
changesSummary: event.changesSummary,
|
|
28
|
+
summary,
|
|
29
|
+
diff: event.diff ?? null,
|
|
30
|
+
success: event.success ?? true,
|
|
31
|
+
errorMessage: event.errorMessage,
|
|
32
|
+
approvalRequestId: event.approvalRequestId,
|
|
33
|
+
integrationId: event.integrationId,
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
if (inngest) {
|
|
37
|
+
try {
|
|
38
|
+
await inngest.send({
|
|
39
|
+
name: 'audit/event.recorded',
|
|
40
|
+
data: stripUndefined({
|
|
41
|
+
auditEventId: String(created.id),
|
|
42
|
+
action: event.action,
|
|
43
|
+
actorId: event.actor.userId,
|
|
44
|
+
targetCollection: event.targetCollection,
|
|
45
|
+
targetId: event.targetId,
|
|
46
|
+
approvalRequestId: event.approvalRequestId,
|
|
47
|
+
integrationId: event.integrationId,
|
|
48
|
+
}),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch (eventError) {
|
|
52
|
+
logger?.warn('Audit Inngest event send failed', { error: String(eventError) });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (writeError) {
|
|
57
|
+
logger?.error('Audit event write failed', {
|
|
58
|
+
error: String(writeError),
|
|
59
|
+
action: event.action,
|
|
60
|
+
targetCollection: event.targetCollection,
|
|
61
|
+
targetId: event.targetId,
|
|
62
|
+
});
|
|
63
|
+
// Intentionally swallowed: never let audit failures break the original action.
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const SUMMARY_TEMPLATES = {
|
|
68
|
+
'content.update': (e) => `Updated ${e.targetTitle ?? e.targetId ?? '(unknown)'} in ${e.targetCollection ?? '(unknown)'}`,
|
|
69
|
+
'content.create': (e) => `Created ${e.targetTitle ?? e.targetId ?? '(unknown)'} in ${e.targetCollection ?? '(unknown)'}`,
|
|
70
|
+
'content.delete': (e) => `Deleted ${e.targetTitle ?? e.targetId ?? '(unknown)'} from ${e.targetCollection ?? '(unknown)'}`,
|
|
71
|
+
'content.find': (e) => `Queried ${e.targetCollection ?? '(unknown)'}`,
|
|
72
|
+
'design.suggest': (e) => `Searched for components matching: ${e.prompt?.slice(0, 80) ?? 'an intent'}`,
|
|
73
|
+
'design.validate': () => 'Validated a composition',
|
|
74
|
+
'design.get_contract': (e) => `Fetched contract for ${e.targetTitle ?? '(a component)'}`,
|
|
75
|
+
'publishing.publish': (e) => `Published ${e.targetTitle ?? e.targetId ?? '(unknown)'}`,
|
|
76
|
+
'publishing.unpublish': (e) => `Unpublished ${e.targetTitle ?? e.targetId ?? '(unknown)'}`,
|
|
77
|
+
'publishing.schedule': (e) => `Scheduled publish for ${e.targetTitle ?? e.targetId ?? '(unknown)'}`,
|
|
78
|
+
'publishing.rollback': (e) => `Rolled back ${e.targetTitle ?? e.targetId ?? '(unknown)'}`,
|
|
79
|
+
'approval.requested': (e) => `Requested approval for ${e.targetTitle ?? e.targetId ?? '(unknown)'}`,
|
|
80
|
+
'approval.granted': (e) => `Granted approval for ${e.targetTitle ?? e.targetId ?? '(unknown)'}`,
|
|
81
|
+
'approval.declined': (e) => `Declined approval for ${e.targetTitle ?? e.targetId ?? '(unknown)'}`,
|
|
82
|
+
'approval.expired': (e) => `Approval expired for ${e.targetTitle ?? e.targetId ?? '(unknown)'}`,
|
|
83
|
+
'form.submission_received': (e) => `Form submission received for ${e.targetTitle ?? e.targetId ?? '(unknown)'}`,
|
|
84
|
+
'integration.synced': (e) => `Synced ${e.integrationId ?? '(unknown integration)'}`,
|
|
85
|
+
'integration.failed': (e) => `Integration failed: ${e.integrationId ?? '(unknown integration)'}`,
|
|
86
|
+
};
|
|
87
|
+
function generateSummary(event) {
|
|
88
|
+
const template = SUMMARY_TEMPLATES[event.action];
|
|
89
|
+
if (template)
|
|
90
|
+
return template(event);
|
|
91
|
+
return `${event.action} (${event.mcpServer}:${event.mcpTool})`;
|
|
92
|
+
}
|
|
93
|
+
function stripUndefined(obj) {
|
|
94
|
+
const out = {};
|
|
95
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
96
|
+
if (value !== undefined)
|
|
97
|
+
out[key] = value;
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer.js","sourceRoot":"","sources":["../../src/audit/writer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CollectionConfig } from 'payload';
|
|
2
|
+
export interface ApiKeysCollectionOptions {
|
|
3
|
+
/** Override the collection slug. Default: `'mcp-api-keys'`. */
|
|
4
|
+
slug?: string;
|
|
5
|
+
/** Slug of the users collection the key links to. Default: `'users'`. */
|
|
6
|
+
usersSlug?: string;
|
|
7
|
+
/** Allowed scope strings shown in the admin UI. */
|
|
8
|
+
availableScopes?: string[];
|
|
9
|
+
}
|
|
10
|
+
export declare const DEFAULT_API_KEYS_SLUG = "mcp-api-keys";
|
|
11
|
+
/**
|
|
12
|
+
* Builds the API-keys collection config. Stores keys as SHA-256 hashes;
|
|
13
|
+
* raw keys are surfaced one-time via the `keyDisplay` field on create
|
|
14
|
+
* so the admin UI can show them to the operator.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createApiKeysCollection(options?: ApiKeysCollectionOptions): CollectionConfig;
|
|
17
|
+
/** Generates a random API key prefixed with `tl_`. */
|
|
18
|
+
export declare function generateApiKey(): string;
|
|
19
|
+
/** Hashes a string with SHA-256 using the Web Crypto API; returns lowercase hex. */
|
|
20
|
+
export declare function sha256Hex(input: string): Promise<string>;
|
|
21
|
+
//# sourceMappingURL=api-keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-keys.d.ts","sourceRoot":"","sources":["../../src/auth/api-keys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAEvD,MAAM,WAAW,wBAAwB;IACvC,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mDAAmD;IACnD,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAED,eAAO,MAAM,qBAAqB,iBAAiB,CAAA;AAwBnD;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,wBAA6B,GACrC,gBAAgB,CAyElB;AAED,sDAAsD;AACtD,wBAAgB,cAAc,IAAI,MAAM,CAOvC;AAED,oFAAoF;AACpF,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAM9D"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export const DEFAULT_API_KEYS_SLUG = 'mcp-api-keys';
|
|
2
|
+
const DEFAULT_SCOPES = [
|
|
3
|
+
'content.read',
|
|
4
|
+
'content.write',
|
|
5
|
+
'design.read',
|
|
6
|
+
'publishing.execute',
|
|
7
|
+
'approvals.request',
|
|
8
|
+
'approvals.decide',
|
|
9
|
+
'forms.manage',
|
|
10
|
+
'integrations.trigger',
|
|
11
|
+
'audit.read',
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* Returns true when the requesting user has the `admin` role. Used as the
|
|
15
|
+
* default access check for the keys collection — keys grant capability,
|
|
16
|
+
* so creating/reading them must be tightly scoped.
|
|
17
|
+
*/
|
|
18
|
+
const adminOnly = ({ req }) => {
|
|
19
|
+
const roles = req.user?.['roles'] ?? [];
|
|
20
|
+
return roles.includes('admin');
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Builds the API-keys collection config. Stores keys as SHA-256 hashes;
|
|
24
|
+
* raw keys are surfaced one-time via the `keyDisplay` field on create
|
|
25
|
+
* so the admin UI can show them to the operator.
|
|
26
|
+
*/
|
|
27
|
+
export function createApiKeysCollection(options = {}) {
|
|
28
|
+
const slug = options.slug ?? DEFAULT_API_KEYS_SLUG;
|
|
29
|
+
const usersSlug = options.usersSlug ?? 'users';
|
|
30
|
+
const availableScopes = options.availableScopes ?? [...DEFAULT_SCOPES];
|
|
31
|
+
return {
|
|
32
|
+
slug,
|
|
33
|
+
admin: {
|
|
34
|
+
useAsTitle: 'name',
|
|
35
|
+
defaultColumns: ['name', 'linkedUser', 'enabled', 'lastUsedAt'],
|
|
36
|
+
description: 'API keys for MCP clients. Each key inherits the linked user\'s access control; keys are stored as SHA-256 hashes.',
|
|
37
|
+
},
|
|
38
|
+
access: {
|
|
39
|
+
read: adminOnly,
|
|
40
|
+
create: adminOnly,
|
|
41
|
+
update: adminOnly,
|
|
42
|
+
delete: adminOnly,
|
|
43
|
+
},
|
|
44
|
+
fields: [
|
|
45
|
+
{ name: 'name', type: 'text', required: true },
|
|
46
|
+
{
|
|
47
|
+
name: 'linkedUser',
|
|
48
|
+
type: 'relationship',
|
|
49
|
+
relationTo: usersSlug,
|
|
50
|
+
required: true,
|
|
51
|
+
admin: { description: 'The user whose access control this key inherits.' },
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'scopes',
|
|
55
|
+
type: 'select',
|
|
56
|
+
hasMany: true,
|
|
57
|
+
required: true,
|
|
58
|
+
options: availableScopes.map((value) => ({ label: value, value })),
|
|
59
|
+
},
|
|
60
|
+
{ name: 'enabled', type: 'checkbox', defaultValue: true, required: true },
|
|
61
|
+
{
|
|
62
|
+
name: 'keyHash',
|
|
63
|
+
type: 'text',
|
|
64
|
+
required: true,
|
|
65
|
+
admin: {
|
|
66
|
+
readOnly: true,
|
|
67
|
+
description: 'SHA-256 hash of the key. The raw key is never stored.',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'keyDisplay',
|
|
72
|
+
type: 'text',
|
|
73
|
+
admin: {
|
|
74
|
+
readOnly: true,
|
|
75
|
+
description: 'First and last 4 characters of the key for identification.',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{ name: 'expiresAt', type: 'date' },
|
|
79
|
+
{ name: 'lastUsedAt', type: 'date', admin: { readOnly: true } },
|
|
80
|
+
],
|
|
81
|
+
hooks: {
|
|
82
|
+
beforeChange: [
|
|
83
|
+
async ({ data, operation }) => {
|
|
84
|
+
if (operation !== 'create')
|
|
85
|
+
return data;
|
|
86
|
+
if (data['keyHash'])
|
|
87
|
+
return data;
|
|
88
|
+
const rawKey = generateApiKey();
|
|
89
|
+
data['keyHash'] = await sha256Hex(rawKey);
|
|
90
|
+
data['keyDisplay'] = `${rawKey.slice(0, 8)}...${rawKey.slice(-4)}`;
|
|
91
|
+
data.__rawKey = rawKey;
|
|
92
|
+
return data;
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** Generates a random API key prefixed with `tl_`. */
|
|
99
|
+
export function generateApiKey() {
|
|
100
|
+
const bytes = new Uint8Array(32);
|
|
101
|
+
crypto.getRandomValues(bytes);
|
|
102
|
+
const hex = Array.from(bytes)
|
|
103
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
104
|
+
.join('');
|
|
105
|
+
return `tl_${hex}`;
|
|
106
|
+
}
|
|
107
|
+
/** Hashes a string with SHA-256 using the Web Crypto API; returns lowercase hex. */
|
|
108
|
+
export async function sha256Hex(input) {
|
|
109
|
+
const data = new TextEncoder().encode(input);
|
|
110
|
+
const buffer = await crypto.subtle.digest('SHA-256', data);
|
|
111
|
+
return Array.from(new Uint8Array(buffer))
|
|
112
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
113
|
+
.join('');
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=api-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-keys.js","sourceRoot":"","sources":["../../src/auth/api-keys.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Payload } from 'payload';
|
|
2
|
+
import type { McpAuthenticator } from '@forumone/throughline-plugin-contract';
|
|
3
|
+
export interface BearerTokenAuthenticatorOptions {
|
|
4
|
+
payload: Payload;
|
|
5
|
+
/** Slug for the API-keys collection. Default: `'mcp-api-keys'`. */
|
|
6
|
+
collectionSlug?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Authenticates incoming MCP requests by looking up the bearer token's hash
|
|
10
|
+
* against the API-keys collection. Returns the linked user plus key metadata
|
|
11
|
+
* on success, `null` on any failure (no token, unknown token, disabled key,
|
|
12
|
+
* key without linked user, expired key).
|
|
13
|
+
*/
|
|
14
|
+
export declare function createBearerTokenAuthenticator(options: BearerTokenAuthenticatorOptions): McpAuthenticator;
|
|
15
|
+
//# sourceMappingURL=authenticator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authenticator.d.ts","sourceRoot":"","sources":["../../src/auth/authenticator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,uCAAuC,CAAA;AAG9C,MAAM,WAAW,+BAA+B;IAC9C,OAAO,EAAE,OAAO,CAAA;IAChB,mEAAmE;IACnE,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,+BAA+B,GACvC,gBAAgB,CAwClB"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { DEFAULT_API_KEYS_SLUG, sha256Hex } from './api-keys.js';
|
|
2
|
+
/**
|
|
3
|
+
* Authenticates incoming MCP requests by looking up the bearer token's hash
|
|
4
|
+
* against the API-keys collection. Returns the linked user plus key metadata
|
|
5
|
+
* on success, `null` on any failure (no token, unknown token, disabled key,
|
|
6
|
+
* key without linked user, expired key).
|
|
7
|
+
*/
|
|
8
|
+
export function createBearerTokenAuthenticator(options) {
|
|
9
|
+
const { payload, collectionSlug = DEFAULT_API_KEYS_SLUG } = options;
|
|
10
|
+
return {
|
|
11
|
+
async authenticate(request) {
|
|
12
|
+
const token = extractBearerToken(request);
|
|
13
|
+
if (!token)
|
|
14
|
+
return null;
|
|
15
|
+
const hash = await sha256Hex(token);
|
|
16
|
+
const result = await payload.find({
|
|
17
|
+
collection: collectionSlug,
|
|
18
|
+
where: {
|
|
19
|
+
and: [{ keyHash: { equals: hash } }, { enabled: { equals: true } }],
|
|
20
|
+
},
|
|
21
|
+
limit: 1,
|
|
22
|
+
depth: 1,
|
|
23
|
+
});
|
|
24
|
+
const apiKey = result.docs[0];
|
|
25
|
+
if (!apiKey)
|
|
26
|
+
return null;
|
|
27
|
+
const expiresAt = apiKey['expiresAt'];
|
|
28
|
+
if (typeof expiresAt === 'string' && Date.parse(expiresAt) <= Date.now()) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const linkedUser = apiKey['linkedUser'];
|
|
32
|
+
if (!linkedUser || typeof linkedUser !== 'object')
|
|
33
|
+
return null;
|
|
34
|
+
const user = toAuthenticatedUser(linkedUser);
|
|
35
|
+
if (!user)
|
|
36
|
+
return null;
|
|
37
|
+
return {
|
|
38
|
+
user,
|
|
39
|
+
apiKeyName: String(apiKey['name'] ?? ''),
|
|
40
|
+
apiKeyId: String(apiKey['id']),
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function extractBearerToken(request) {
|
|
46
|
+
const header = request.headers.get('authorization');
|
|
47
|
+
if (!header)
|
|
48
|
+
return null;
|
|
49
|
+
const match = /^Bearer\s+(.+)$/i.exec(header);
|
|
50
|
+
return match?.[1]?.trim() || null;
|
|
51
|
+
}
|
|
52
|
+
function toAuthenticatedUser(raw) {
|
|
53
|
+
if (raw['id'] === undefined || raw['email'] === undefined)
|
|
54
|
+
return null;
|
|
55
|
+
return {
|
|
56
|
+
id: String(raw['id']),
|
|
57
|
+
email: String(raw['email']),
|
|
58
|
+
name: String(raw['name'] ?? raw['email']),
|
|
59
|
+
roles: raw['roles'] ?? [],
|
|
60
|
+
groups: raw['groups'] ?? [],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=authenticator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authenticator.js","sourceRoot":"","sources":["../../src/auth/authenticator.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAQhE;;;;;GAKG;AACH,MAAM,UAAU,8BAA8B,CAC5C,OAAwC;IAExC,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,qBAAqB,EAAE,GAAG,OAAO,CAAA;IAEnE,OAAO;QACL,KAAK,CAAC,YAAY,CAAC,OAAgB;YACjC,MAAM,KAAK,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAA;YACzC,IAAI,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAA;YAEvB,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,KAAK,CAAC,CAAA;YAEnC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;gBAChC,UAAU,EAAE,cAAc;gBAC1B,KAAK,EAAE;oBACL,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC;iBACpE;gBACD,KAAK,EAAE,CAAC;gBACR,KAAK,EAAE,CAAC;aACT,CAAC,CAAA;YAEF,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YAC7B,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAA;YAExB,MAAM,SAAS,GAAG,MAAM,CAAC,WAAW,CAAC,CAAA;YACrC,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;gBACzE,OAAO,IAAI,CAAA;YACb,CAAC;YAED,MAAM,UAAU,GAAG,MAAM,CAAC,YAAY,CAAC,CAAA;YACvC,IAAI,CAAC,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAA;YAE9D,MAAM,IAAI,GAAG,mBAAmB,CAAC,UAAqC,CAAC,CAAA;YACvE,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAA;YAEtB,OAAO;gBACL,IAAI;gBACJ,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;gBACxC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;aAC/B,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAED,SAAS,kBAAkB,CAAC,OAAgB;IAC1C,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;IACnD,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IACxB,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC7C,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,CAAA;AACnC,CAAC;AAED,SAAS,mBAAmB,CAAC,GAA4B;IACvD,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,SAAS,IAAI,GAAG,CAAC,OAAO,CAAC,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IACtE,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC3B,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;QACzC,KAAK,EAAG,GAAG,CAAC,OAAO,CAA0B,IAAI,EAAE;QACnD,MAAM,EAAG,GAAG,CAAC,QAAQ,CAA0B,IAAI,EAAE;KACtD,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createBearerTokenAuthenticator } from './authenticator.js';
|
|
2
|
+
export type { BearerTokenAuthenticatorOptions } from './authenticator.js';
|
|
3
|
+
export { createApiKeysCollection, generateApiKey, sha256Hex, DEFAULT_API_KEYS_SLUG, } from './api-keys.js';
|
|
4
|
+
export type { ApiKeysCollectionOptions } from './api-keys.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAE,MAAM,oBAAoB,CAAA;AACnE,YAAY,EAAE,+BAA+B,EAAE,MAAM,oBAAoB,CAAA;AAEzE,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,SAAS,EACT,qBAAqB,GACtB,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAE,MAAM,oBAAoB,CAAA;AAGnE,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,SAAS,EACT,qBAAqB,GACtB,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Canonical env-var names used across Throughline packages. Plugins read
|
|
4
|
+
* `process.env[ENV_VARS.X]` rather than hard-coding strings so renames stay
|
|
5
|
+
* coordinated.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ENV_VARS: {
|
|
8
|
+
readonly PAYLOAD_SECRET: "PAYLOAD_SECRET";
|
|
9
|
+
readonly DATABASE_URI: "DATABASE_URI";
|
|
10
|
+
readonly NEXT_PUBLIC_SERVER_URL: "NEXT_PUBLIC_SERVER_URL";
|
|
11
|
+
readonly INNGEST_EVENT_KEY: "INNGEST_EVENT_KEY";
|
|
12
|
+
readonly INNGEST_SIGNING_KEY: "INNGEST_SIGNING_KEY";
|
|
13
|
+
readonly RESEND_API_KEY: "RESEND_API_KEY";
|
|
14
|
+
readonly EMAIL_FROM_ADDRESS: "EMAIL_FROM_ADDRESS";
|
|
15
|
+
readonly EMAIL_FROM_NAME: "EMAIL_FROM_NAME";
|
|
16
|
+
readonly EMAIL_REPLY_TO: "EMAIL_REPLY_TO";
|
|
17
|
+
readonly APPROVAL_TOKEN_SECRET: "APPROVAL_TOKEN_SECRET";
|
|
18
|
+
readonly COMPONENT_SERVER_API_KEY: "COMPONENT_SERVER_API_KEY";
|
|
19
|
+
readonly PUBLISHING_SERVER_API_KEY: "PUBLISHING_SERVER_API_KEY";
|
|
20
|
+
readonly APPROVALS_SERVER_API_KEY: "APPROVALS_SERVER_API_KEY";
|
|
21
|
+
readonly AUDIT_SERVER_API_KEY: "AUDIT_SERVER_API_KEY";
|
|
22
|
+
readonly FORMS_SERVER_API_KEY: "FORMS_SERVER_API_KEY";
|
|
23
|
+
readonly INTEGRATIONS_SERVER_API_KEY: "INTEGRATIONS_SERVER_API_KEY";
|
|
24
|
+
};
|
|
25
|
+
export type EnvVarName = (typeof ENV_VARS)[keyof typeof ENV_VARS];
|
|
26
|
+
declare const BaseEnvSchema: z.ZodObject<{
|
|
27
|
+
PAYLOAD_SECRET: z.ZodString;
|
|
28
|
+
DATABASE_URI: z.ZodString;
|
|
29
|
+
NEXT_PUBLIC_SERVER_URL: z.ZodString;
|
|
30
|
+
}, "strip", z.ZodTypeAny, {
|
|
31
|
+
PAYLOAD_SECRET: string;
|
|
32
|
+
DATABASE_URI: string;
|
|
33
|
+
NEXT_PUBLIC_SERVER_URL: string;
|
|
34
|
+
}, {
|
|
35
|
+
PAYLOAD_SECRET: string;
|
|
36
|
+
DATABASE_URI: string;
|
|
37
|
+
NEXT_PUBLIC_SERVER_URL: string;
|
|
38
|
+
}>;
|
|
39
|
+
export type BaseEnv = z.infer<typeof BaseEnvSchema>;
|
|
40
|
+
/**
|
|
41
|
+
* Validates the base env vars every Throughline deployment requires.
|
|
42
|
+
* Plugins extend with their own checks. Throws on validation failure with
|
|
43
|
+
* a multi-line, path-qualified message.
|
|
44
|
+
*/
|
|
45
|
+
export declare function validateBaseEnv(env?: NodeJS.ProcessEnv): BaseEnv;
|
|
46
|
+
/** Returns an env var, throwing with a clear error if missing or empty. */
|
|
47
|
+
export declare function requireEnv(name: string, message?: string): string;
|
|
48
|
+
/** Returns an optional env var or a fallback. */
|
|
49
|
+
export declare function optionalEnv(name: string, fallback?: string): string | undefined;
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/env/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;GAIG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;CAiBX,CAAA;AAEV,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,OAAO,QAAQ,CAAC,CAAA;AAEjE,QAAA,MAAM,aAAa;;;;;;;;;;;;EAMjB,CAAA;AAEF,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAA;AAEnD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAS7E;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAMjE;AAED,iDAAiD;AACjD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE/E"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Canonical env-var names used across Throughline packages. Plugins read
|
|
4
|
+
* `process.env[ENV_VARS.X]` rather than hard-coding strings so renames stay
|
|
5
|
+
* coordinated.
|
|
6
|
+
*/
|
|
7
|
+
export const ENV_VARS = {
|
|
8
|
+
PAYLOAD_SECRET: 'PAYLOAD_SECRET',
|
|
9
|
+
DATABASE_URI: 'DATABASE_URI',
|
|
10
|
+
NEXT_PUBLIC_SERVER_URL: 'NEXT_PUBLIC_SERVER_URL',
|
|
11
|
+
INNGEST_EVENT_KEY: 'INNGEST_EVENT_KEY',
|
|
12
|
+
INNGEST_SIGNING_KEY: 'INNGEST_SIGNING_KEY',
|
|
13
|
+
RESEND_API_KEY: 'RESEND_API_KEY',
|
|
14
|
+
EMAIL_FROM_ADDRESS: 'EMAIL_FROM_ADDRESS',
|
|
15
|
+
EMAIL_FROM_NAME: 'EMAIL_FROM_NAME',
|
|
16
|
+
EMAIL_REPLY_TO: 'EMAIL_REPLY_TO',
|
|
17
|
+
APPROVAL_TOKEN_SECRET: 'APPROVAL_TOKEN_SECRET',
|
|
18
|
+
COMPONENT_SERVER_API_KEY: 'COMPONENT_SERVER_API_KEY',
|
|
19
|
+
PUBLISHING_SERVER_API_KEY: 'PUBLISHING_SERVER_API_KEY',
|
|
20
|
+
APPROVALS_SERVER_API_KEY: 'APPROVALS_SERVER_API_KEY',
|
|
21
|
+
AUDIT_SERVER_API_KEY: 'AUDIT_SERVER_API_KEY',
|
|
22
|
+
FORMS_SERVER_API_KEY: 'FORMS_SERVER_API_KEY',
|
|
23
|
+
INTEGRATIONS_SERVER_API_KEY: 'INTEGRATIONS_SERVER_API_KEY',
|
|
24
|
+
};
|
|
25
|
+
const BaseEnvSchema = z.object({
|
|
26
|
+
[ENV_VARS.PAYLOAD_SECRET]: z
|
|
27
|
+
.string()
|
|
28
|
+
.min(32, `${ENV_VARS.PAYLOAD_SECRET} must be at least 32 characters`),
|
|
29
|
+
[ENV_VARS.DATABASE_URI]: z.string().min(1, `${ENV_VARS.DATABASE_URI} must be set`),
|
|
30
|
+
[ENV_VARS.NEXT_PUBLIC_SERVER_URL]: z.string().url(),
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* Validates the base env vars every Throughline deployment requires.
|
|
34
|
+
* Plugins extend with their own checks. Throws on validation failure with
|
|
35
|
+
* a multi-line, path-qualified message.
|
|
36
|
+
*/
|
|
37
|
+
export function validateBaseEnv(env = process.env) {
|
|
38
|
+
const result = BaseEnvSchema.safeParse(env);
|
|
39
|
+
if (!result.success) {
|
|
40
|
+
const issues = result.error.issues
|
|
41
|
+
.map((i) => ` - ${String(i.path[0] ?? '(root)')}: ${i.message}`)
|
|
42
|
+
.join('\n');
|
|
43
|
+
throw new Error(`Invalid environment:\n${issues}`);
|
|
44
|
+
}
|
|
45
|
+
return result.data;
|
|
46
|
+
}
|
|
47
|
+
/** Returns an env var, throwing with a clear error if missing or empty. */
|
|
48
|
+
export function requireEnv(name, message) {
|
|
49
|
+
const value = process.env[name];
|
|
50
|
+
if (!value) {
|
|
51
|
+
throw new Error(message ?? `Environment variable ${name} is required but not set`);
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
/** Returns an optional env var or a fallback. */
|
|
56
|
+
export function optionalEnv(name, fallback) {
|
|
57
|
+
return process.env[name] ?? fallback;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/env/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,cAAc,EAAE,gBAAgB;IAChC,YAAY,EAAE,cAAc;IAC5B,sBAAsB,EAAE,wBAAwB;IAChD,iBAAiB,EAAE,mBAAmB;IACtC,mBAAmB,EAAE,qBAAqB;IAC1C,cAAc,EAAE,gBAAgB;IAChC,kBAAkB,EAAE,oBAAoB;IACxC,eAAe,EAAE,iBAAiB;IAClC,cAAc,EAAE,gBAAgB;IAChC,qBAAqB,EAAE,uBAAuB;IAC9C,wBAAwB,EAAE,0BAA0B;IACpD,yBAAyB,EAAE,2BAA2B;IACtD,wBAAwB,EAAE,0BAA0B;IACpD,oBAAoB,EAAE,sBAAsB;IAC5C,oBAAoB,EAAE,sBAAsB;IAC5C,2BAA2B,EAAE,6BAA6B;CAClD,CAAA;AAIV,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7B,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;SACzB,MAAM,EAAE;SACR,GAAG,CAAC,EAAE,EAAE,GAAG,QAAQ,CAAC,cAAc,iCAAiC,CAAC;IACvE,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,QAAQ,CAAC,YAAY,cAAc,CAAC;IAClF,CAAC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;CACpD,CAAC,CAAA;AAIF;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,MAAyB,OAAO,CAAC,GAAG;IAClE,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;IAC3C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM;aAC/B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;aAChE,IAAI,CAAC,IAAI,CAAC,CAAA;QACb,MAAM,IAAI,KAAK,CAAC,yBAAyB,MAAM,EAAE,CAAC,CAAA;IACpD,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAA;AACpB,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,OAAgB;IACvD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,OAAO,IAAI,wBAAwB,IAAI,0BAA0B,CAAC,CAAA;IACpF,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,QAAiB;IACzD,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAA;AACtC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/events/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAClD,YAAY,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACxD,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/events/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Inngest } from 'inngest';
|
|
2
|
+
export interface InngestClientOptions {
|
|
3
|
+
/** App identifier used in Inngest's UI; should be stable across deployments. */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Inngest event key. Falls back to `process.env.INNGEST_EVENT_KEY`. */
|
|
6
|
+
eventKey?: string;
|
|
7
|
+
/** Base URL for self-hosted Inngest deployments. */
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
/** Override the environment ("development", "preview", "production", or a branch name). */
|
|
10
|
+
env?: string;
|
|
11
|
+
/** Force development mode regardless of environment. */
|
|
12
|
+
isDev?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Creates an Inngest client preconfigured for Throughline. Plugins and the
|
|
16
|
+
* audit writer accept this client to fire events; client apps construct it
|
|
17
|
+
* once at module load and pass it everywhere.
|
|
18
|
+
*
|
|
19
|
+
* The signing key (used to verify incoming Inngest webhook calls) is read
|
|
20
|
+
* from `INNGEST_SIGNING_KEY` by the framework's serve handler — it is not
|
|
21
|
+
* a constructor option in Inngest 4.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createInngestClient(options: InngestClientOptions): Inngest;
|
|
24
|
+
//# sourceMappingURL=inngest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inngest.d.ts","sourceRoot":"","sources":["../../src/events/inngest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAEjC,MAAM,WAAW,oBAAoB;IACnC,gFAAgF;IAChF,EAAE,EAAE,MAAM,CAAA;IACV,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2FAA2F;IAC3F,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,wDAAwD;IACxD,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAQ1E"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Inngest } from 'inngest';
|
|
2
|
+
/**
|
|
3
|
+
* Creates an Inngest client preconfigured for Throughline. Plugins and the
|
|
4
|
+
* audit writer accept this client to fire events; client apps construct it
|
|
5
|
+
* once at module load and pass it everywhere.
|
|
6
|
+
*
|
|
7
|
+
* The signing key (used to verify incoming Inngest webhook calls) is read
|
|
8
|
+
* from `INNGEST_SIGNING_KEY` by the framework's serve handler — it is not
|
|
9
|
+
* a constructor option in Inngest 4.
|
|
10
|
+
*/
|
|
11
|
+
export function createInngestClient(options) {
|
|
12
|
+
const eventKey = options.eventKey ?? process.env['INNGEST_EVENT_KEY'];
|
|
13
|
+
const clientOptions = { id: options.id };
|
|
14
|
+
if (eventKey !== undefined)
|
|
15
|
+
clientOptions.eventKey = eventKey;
|
|
16
|
+
if (options.baseUrl !== undefined)
|
|
17
|
+
clientOptions.baseUrl = options.baseUrl;
|
|
18
|
+
if (options.env !== undefined)
|
|
19
|
+
clientOptions.env = options.env;
|
|
20
|
+
if (options.isDev !== undefined)
|
|
21
|
+
clientOptions.isDev = options.isDev;
|
|
22
|
+
return new Inngest(clientOptions);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=inngest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inngest.js","sourceRoot":"","sources":["../../src/events/inngest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAejC;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAA6B;IAC/D,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAA;IACrE,MAAM,aAAa,GAA6C,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAA;IAClF,IAAI,QAAQ,KAAK,SAAS;QAAE,aAAa,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC7D,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS;QAAE,aAAa,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAA;IAC1E,IAAI,OAAO,CAAC,GAAG,KAAK,SAAS;QAAE,aAAa,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,CAAA;IAC9D,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS;QAAE,aAAa,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAA;IACpE,OAAO,IAAI,OAAO,CAAC,aAAa,CAAC,CAAA;AACnC,CAAC"}
|