@raisindb/functions-types 0.2.6 → 0.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/raisin.d.ts +179 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raisindb/functions-types",
3
- "version": "0.2.6",
3
+ "version": "0.4.0",
4
4
  "description": "TypeScript type definitions for the RaisinDB server-side function runtime (QuickJS)",
5
5
  "license": "MIT",
6
6
  "types": "raisin.d.ts",
package/raisin.d.ts CHANGED
@@ -298,6 +298,44 @@ declare namespace raisin {
298
298
  function diffDays(ts1: number, ts2: number): Promise<number>;
299
299
  }
300
300
 
301
+ /**
302
+ * One outbound transactional email. The sender is deliberately absent:
303
+ * `from`, the display name and `replyTo` come from the tenant's
304
+ * `/config/email` node, so a function cannot send as an unverified address.
305
+ */
306
+ interface EmailMessage {
307
+ /** One recipient address, or several. */
308
+ to: string | string[];
309
+ subject: string;
310
+ /** Plain-text body. Always required, even alongside `html`. */
311
+ text: string;
312
+ html?: string;
313
+ }
314
+
315
+ /** Proof that the provider accepted a message. Acceptance is not delivery. */
316
+ interface EmailReceipt {
317
+ /** The provider's message id — what a later bounce/webhook correlates to. */
318
+ message_id: string;
319
+ /** The provider that issued it, e.g. "resend" or "brevo". */
320
+ provider: string;
321
+ }
322
+
323
+ namespace email {
324
+ /**
325
+ * Send one transactional email through the tenant's configured provider.
326
+ *
327
+ * Every recipient must be allowed by the function's `email_policy`
328
+ * (`{ enabled, allowed_recipients }` in its `.node.yaml`, matched against
329
+ * the recipient DOMAIN); with no block declared the function cannot send,
330
+ * and one disallowed recipient rejects the whole message.
331
+ *
332
+ * Also rejects when email is not configured or not enabled for the tenant,
333
+ * when the function's `secret_policy` does not grant the credential the
334
+ * config references, or when the provider refuses the message.
335
+ */
336
+ function send(message: EmailMessage): Promise<EmailReceipt>;
337
+ }
338
+
301
339
  namespace events {
302
340
  function emit(eventType: string, data: any): Promise<void>;
303
341
  }
@@ -393,6 +431,147 @@ declare namespace raisin {
393
431
  function get(jobIdOrKey: string): Promise<any>;
394
432
  }
395
433
 
434
+ /**
435
+ * Metadata about one secret. Never carries the secret itself — the type has
436
+ * no field that could hold ciphertext or plaintext.
437
+ */
438
+ interface SecretMetadata {
439
+ name: string;
440
+ /** Human-facing ordinal, from 1. Use it as `secrets.get(name, version)`. */
441
+ version: number;
442
+ /** Which master key sealed it. */
443
+ key_id: number;
444
+ /** RFC 3339. */
445
+ created_at: string;
446
+ created_by: string;
447
+ /** Set only by `rotate`. */
448
+ rotated_at?: string | null;
449
+ /** The node this secret backs, when it is a vaulted schema field. */
450
+ owner_node?: string | null;
451
+ owner_field?: string | null;
452
+ /** True when the newest version is a tombstone. */
453
+ deleted: boolean;
454
+ ciphertext_len: number;
455
+ }
456
+
457
+ /**
458
+ * Encrypted secret store, scoped to the current `{tenant, repo, branch}`.
459
+ *
460
+ * **Access is denied by default.** A function reaches these only if its
461
+ * `.node.yaml` declares a matching grant:
462
+ *
463
+ * ```yaml
464
+ * secret_policy:
465
+ * enabled: true
466
+ * allowed_names:
467
+ * - "stripe/*"
468
+ * - "sendgrid_api_key"
469
+ * ```
470
+ *
471
+ * Without one, every call below throws a `policy_denied` error naming the
472
+ * secret. That default is deliberate: adapters run privileged and hold
473
+ * `raisin.http`, so an ungated secrets binding would be a one-line
474
+ * exfiltration path for every credential in the repo.
475
+ *
476
+ * Every method THROWS on failure (denial, missing secret, deleted secret,
477
+ * unconfigured store) — none of them degrade to `null`, which would be
478
+ * indistinguishable from an empty credential.
479
+ *
480
+ * ## The main flow: a secret stored in a node property
481
+ *
482
+ * The common case is not a standalone secret — it is a field on a regular
483
+ * node declared `encrypted: true` in its NodeType. Three steps:
484
+ *
485
+ * 1. The property does not hold the credential. It holds a REFERENCE:
486
+ * `"secret://node/01H8XY.../api_key@1"`.
487
+ * 2. A node read returns that string verbatim. **Reads never resolve** —
488
+ * there is no query flag and no endpoint that returns a value.
489
+ * 3. The function passes the string straight to `get` (or `resolve`):
490
+ *
491
+ * ```javascript
492
+ * const node = await raisin.nodes.get('data', '/connections/stripe');
493
+ * const key = raisin.secrets.get(node.properties.api_key);
494
+ * ```
495
+ *
496
+ * Pass the reference through **as-is**. Do not strip `secret://` or the
497
+ * `@version` suffix yourself: a name may itself contain `@` (an operator
498
+ * name like `ops@example.com`), so only a trailing all-digit run after the
499
+ * LAST `@` is a version. `get` applies that rule using the same parser the
500
+ * storage layer uses.
501
+ */
502
+ /**
503
+ * Declared as an interface rather than a `namespace`, because `delete` is a
504
+ * reserved word that TypeScript rejects as an ambient `function` name but
505
+ * accepts as an interface method. (The `http`, `nodes` and `admin.nodes`
506
+ * namespaces above still use `function delete(...)`, which does not parse —
507
+ * a pre-existing break, unnoticed because this package has no drift test.)
508
+ */
509
+ interface SecretsApi {
510
+ /**
511
+ * Read a secret's plaintext.
512
+ *
513
+ * `nameOrRef` is EITHER a bare name (`"stripe_key"`) or a full reference
514
+ * (`"secret://node/01H8XY.../api_key@1"`) — see the three-step flow above
515
+ * for why the reference form is what you will usually be holding.
516
+ *
517
+ * A version pinned in the reference is HONOURED: `secret://k@1` returns
518
+ * version 1, not the latest. That is what makes reading an older node
519
+ * revision give the value that revision actually held.
520
+ *
521
+ * Passing the `version` argument **and** a pinned reference throws. Two
522
+ * stated versions cannot both be satisfied, and silently preferring either
523
+ * could return a value the node revision never held — which is the exact
524
+ * guarantee a pinned reference exists to provide. Pass one or the other;
525
+ * `get('k', 2)` and `get('secret://k', 2)` are both fine.
526
+ *
527
+ * The policy allow-list is matched against the parsed NAME, so both
528
+ * spellings of one secret always get the same allow/deny answer.
529
+ *
530
+ * Throws if the policy denies the name, or the secret is missing or
531
+ * deleted.
532
+ */
533
+ get(nameOrRef: string, version?: number): Promise<string>;
534
+ /**
535
+ * Resolve a value that MAY be a `secret://` reference.
536
+ *
537
+ * Returns the plaintext when it is one, or the value unchanged when it is
538
+ * not — so a config field that is a literal password on one deployment and
539
+ * a vaulted reference on another is read the same way:
540
+ *
541
+ * ```javascript
542
+ * const password = raisin.secrets.resolve(conn.password);
543
+ * ```
544
+ *
545
+ * A reference that fails to resolve THROWS; it never falls back to
546
+ * returning the reference text, which would send `secret://...` to a
547
+ * provider as a credential. A plain literal is passed through without any
548
+ * policy check, since no secret was touched.
549
+ */
550
+ resolve(value: string): Promise<string>;
551
+ /**
552
+ * Append a new version. Never overwrites — prior versions stay readable.
553
+ *
554
+ * Accepts a bare name or an UNPINNED reference; a pinned one
555
+ * (`secret://k@1`) is refused, because a write appends a new version
556
+ * rather than replacing that one.
557
+ */
558
+ put(name: string, value: string): Promise<{ name: string; version: number }>;
559
+ /**
560
+ * Metadata for the newest version of every secret this function may read.
561
+ * Never returns values, and is filtered to the policy's allowed names.
562
+ */
563
+ list(): Promise<SecretMetadata[]>;
564
+ /**
565
+ * Append a new version stamped as a rotation. Pinned `secret://name@N`
566
+ * references keep resolving to the old version.
567
+ */
568
+ rotate(name: string, value: string): Promise<{ name: string; version: number }>;
569
+ /** Append a tombstone. Prior versions remain readable by pinned reference. */
570
+ delete(name: string): Promise<{ name: string; version: number }>;
571
+ }
572
+
573
+ const secrets: SecretsApi;
574
+
396
575
  namespace sql {
397
576
  function query(sql: string, params: any[]): Promise<any>;
398
577
  function execute(sql: string, params: any[]): Promise<number>;