@pylonsync/functions 0.3.26 → 0.3.28
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 +1 -1
- package/src/define.ts +34 -3
- package/src/runtime.ts +31 -0
- package/src/types.ts +31 -0
package/package.json
CHANGED
package/src/define.ts
CHANGED
|
@@ -15,16 +15,32 @@ import type {
|
|
|
15
15
|
interface QueryDef<TArgs, TReturn> {
|
|
16
16
|
args?: Record<string, Validator>;
|
|
17
17
|
handler: (ctx: QueryCtx, args: TArgs) => Promise<TReturn>;
|
|
18
|
+
/**
|
|
19
|
+
* When true, the function is callable only via `ctx.runQuery()`
|
|
20
|
+
* from another function — never via the public `/api/fn/<name>`
|
|
21
|
+
* HTTP endpoint. The router refuses external calls with
|
|
22
|
+
* `404 FN_NOT_FOUND` so probing can't even confirm the name exists.
|
|
23
|
+
*
|
|
24
|
+
* Use for queries that are meant as helpers for trusted action /
|
|
25
|
+
* mutation flows but would be unsafe if any caller could invoke
|
|
26
|
+
* them directly (e.g. they trust args without re-checking caller
|
|
27
|
+
* authority).
|
|
28
|
+
*/
|
|
29
|
+
internal?: boolean;
|
|
18
30
|
}
|
|
19
31
|
|
|
20
32
|
interface MutationDef<TArgs, TReturn> {
|
|
21
33
|
args?: Record<string, Validator>;
|
|
22
34
|
handler: (ctx: MutationCtx, args: TArgs) => Promise<TReturn>;
|
|
35
|
+
/** See QueryDef.internal — applies the same way to mutations. */
|
|
36
|
+
internal?: boolean;
|
|
23
37
|
}
|
|
24
38
|
|
|
25
39
|
interface ActionDef<TArgs, TReturn> {
|
|
26
40
|
args?: Record<string, Validator>;
|
|
27
41
|
handler: (ctx: ActionCtx, args: TArgs) => Promise<TReturn>;
|
|
42
|
+
/** See QueryDef.internal — applies the same way to actions. */
|
|
43
|
+
internal?: boolean;
|
|
28
44
|
}
|
|
29
45
|
|
|
30
46
|
/**
|
|
@@ -49,7 +65,12 @@ interface ActionDef<TArgs, TReturn> {
|
|
|
49
65
|
export function query<TArgs = Record<string, unknown>, TReturn = unknown>(
|
|
50
66
|
def: QueryDef<TArgs, TReturn>
|
|
51
67
|
): FnDefinition<TArgs, TReturn> {
|
|
52
|
-
return {
|
|
68
|
+
return {
|
|
69
|
+
type: "query",
|
|
70
|
+
args: def.args,
|
|
71
|
+
handler: def.handler,
|
|
72
|
+
internal: def.internal,
|
|
73
|
+
};
|
|
53
74
|
}
|
|
54
75
|
|
|
55
76
|
/**
|
|
@@ -78,7 +99,12 @@ export function query<TArgs = Record<string, unknown>, TReturn = unknown>(
|
|
|
78
99
|
export function mutation<TArgs = Record<string, unknown>, TReturn = unknown>(
|
|
79
100
|
def: MutationDef<TArgs, TReturn>
|
|
80
101
|
): FnDefinition<TArgs, TReturn> {
|
|
81
|
-
return {
|
|
102
|
+
return {
|
|
103
|
+
type: "mutation",
|
|
104
|
+
args: def.args,
|
|
105
|
+
handler: def.handler,
|
|
106
|
+
internal: def.internal,
|
|
107
|
+
};
|
|
82
108
|
}
|
|
83
109
|
|
|
84
110
|
/**
|
|
@@ -105,5 +131,10 @@ export function mutation<TArgs = Record<string, unknown>, TReturn = unknown>(
|
|
|
105
131
|
export function action<TArgs = Record<string, unknown>, TReturn = unknown>(
|
|
106
132
|
def: ActionDef<TArgs, TReturn>
|
|
107
133
|
): FnDefinition<TArgs, TReturn> {
|
|
108
|
-
return {
|
|
134
|
+
return {
|
|
135
|
+
type: "action",
|
|
136
|
+
args: def.args,
|
|
137
|
+
handler: def.handler,
|
|
138
|
+
internal: def.internal,
|
|
139
|
+
};
|
|
109
140
|
}
|
package/src/runtime.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import type {
|
|
19
19
|
DbReader,
|
|
20
20
|
DbWriter,
|
|
21
|
+
EmailSender,
|
|
21
22
|
Stream,
|
|
22
23
|
Scheduler,
|
|
23
24
|
QueryCtx,
|
|
@@ -441,11 +442,33 @@ function buildScheduler(callId: string): Scheduler {
|
|
|
441
442
|
};
|
|
442
443
|
}
|
|
443
444
|
|
|
445
|
+
/**
|
|
446
|
+
* Build the email sender that round-trips through the host runtime.
|
|
447
|
+
*
|
|
448
|
+
* Each `send` emits a `send_email` protocol message; the runtime
|
|
449
|
+
* forwards to whatever transport PYLON_EMAIL_PROVIDER points at and
|
|
450
|
+
* replies success or error. Errors arrive as thrown exceptions on
|
|
451
|
+
* the action's await, just like every other RPC. No silent failures.
|
|
452
|
+
*/
|
|
453
|
+
function buildEmail(callId: string): EmailSender {
|
|
454
|
+
return {
|
|
455
|
+
async send(to, subject, body) {
|
|
456
|
+
await rpc(callId, {
|
|
457
|
+
type: "send_email",
|
|
458
|
+
to,
|
|
459
|
+
subject,
|
|
460
|
+
body,
|
|
461
|
+
});
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
444
466
|
function buildActionCtx(
|
|
445
467
|
callId: string,
|
|
446
468
|
auth: AuthInfo,
|
|
447
469
|
stream: Stream,
|
|
448
470
|
scheduler: Scheduler,
|
|
471
|
+
email: EmailSender,
|
|
449
472
|
request?: unknown
|
|
450
473
|
): ActionCtx {
|
|
451
474
|
// The host sends `request` as snake_case JSON (`raw_body`); normalize it
|
|
@@ -465,6 +488,7 @@ function buildActionCtx(
|
|
|
465
488
|
auth,
|
|
466
489
|
stream,
|
|
467
490
|
scheduler,
|
|
491
|
+
email,
|
|
468
492
|
env: process.env as Record<string, string>,
|
|
469
493
|
async runQuery(fnName, args) {
|
|
470
494
|
return rpc(callId, {
|
|
@@ -544,6 +568,7 @@ async function handleCall(msg: CallMessage): Promise<void> {
|
|
|
544
568
|
|
|
545
569
|
const stream = buildStream(msg.call_id);
|
|
546
570
|
const scheduler = buildScheduler(msg.call_id);
|
|
571
|
+
const email = buildEmail(msg.call_id);
|
|
547
572
|
|
|
548
573
|
// Normalize the Rust-side auth envelope (snake_case) to the camelCase
|
|
549
574
|
// shape that AuthInfo documents. Handlers read `ctx.auth.userId`; the
|
|
@@ -597,6 +622,7 @@ async function handleCall(msg: CallMessage): Promise<void> {
|
|
|
597
622
|
auth,
|
|
598
623
|
stream,
|
|
599
624
|
scheduler,
|
|
625
|
+
email,
|
|
600
626
|
(msg as unknown as { request?: unknown }).request,
|
|
601
627
|
);
|
|
602
628
|
break;
|
|
@@ -689,6 +715,11 @@ async function main() {
|
|
|
689
715
|
name,
|
|
690
716
|
fn_type: def.type,
|
|
691
717
|
args_schema: def.args || null,
|
|
718
|
+
// Whether the function is callable only via runQuery/runMutation/
|
|
719
|
+
// runAction from another function. The Rust router refuses /api/fn
|
|
720
|
+
// requests for internal fns; the Bun runtime here doesn't gate
|
|
721
|
+
// (nested calls go through the same dispatcher).
|
|
722
|
+
internal: def.internal === true,
|
|
692
723
|
}));
|
|
693
724
|
send({ type: "ready", functions });
|
|
694
725
|
|
package/src/types.ts
CHANGED
|
@@ -171,6 +171,27 @@ export interface Scheduler {
|
|
|
171
171
|
cancel(scheduleId: string): Promise<void>;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Transactional email transport.
|
|
176
|
+
*
|
|
177
|
+
* Sends through whatever provider the runtime is configured for
|
|
178
|
+
* (PYLON_EMAIL_PROVIDER env var → SendGrid / Resend / Stack0 / SMTP /
|
|
179
|
+
* webhook). Available on action ctx only — sending email is external
|
|
180
|
+
* I/O, not allowed in mutation transactions.
|
|
181
|
+
*
|
|
182
|
+
* The runtime owns provider config + credentials; functions only
|
|
183
|
+
* supply the (to, subject, body) tuple. Failures are surfaced as
|
|
184
|
+
* thrown errors; on success the return is void.
|
|
185
|
+
*
|
|
186
|
+
* Use cases: invite emails, password-reset hand-offs, notifications,
|
|
187
|
+
* digest reports. NOT for marketing email — those should go through
|
|
188
|
+
* a dedicated bulk transport, not the transactional path.
|
|
189
|
+
*/
|
|
190
|
+
export interface EmailSender {
|
|
191
|
+
/** Send a plain-text email. `to` is a single address. */
|
|
192
|
+
send(to: string, subject: string, body: string): Promise<void>;
|
|
193
|
+
}
|
|
194
|
+
|
|
174
195
|
// ---------------------------------------------------------------------------
|
|
175
196
|
// Context objects — what handlers receive
|
|
176
197
|
// ---------------------------------------------------------------------------
|
|
@@ -200,6 +221,8 @@ export interface ActionCtx {
|
|
|
200
221
|
auth: AuthInfo;
|
|
201
222
|
stream: Stream;
|
|
202
223
|
scheduler: Scheduler;
|
|
224
|
+
/** Send transactional email via the runtime's configured provider. */
|
|
225
|
+
email: EmailSender;
|
|
203
226
|
/** Environment variables / secrets. */
|
|
204
227
|
env: Record<string, string>;
|
|
205
228
|
/** Run a registered query within its own read transaction. */
|
|
@@ -254,6 +277,14 @@ export interface FnDefinition<TArgs = unknown, TReturn = unknown> {
|
|
|
254
277
|
type: FnType;
|
|
255
278
|
args?: Record<string, Validator>;
|
|
256
279
|
handler: (ctx: any, args: TArgs) => Promise<TReturn>;
|
|
280
|
+
/**
|
|
281
|
+
* When true, this function is reachable only via `ctx.runQuery()` /
|
|
282
|
+
* `ctx.runMutation()` / `ctx.runAction()` from another function —
|
|
283
|
+
* the public `/api/fn/<name>` endpoint refuses external calls.
|
|
284
|
+
* The router enforces this; the runtime treats internal == external
|
|
285
|
+
* for execution.
|
|
286
|
+
*/
|
|
287
|
+
internal?: boolean;
|
|
257
288
|
}
|
|
258
289
|
|
|
259
290
|
// ---------------------------------------------------------------------------
|