@palbase/backend 9.0.1 → 10.0.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/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { C as CacheClient, b as PalbaseDocsClient, c as PalbaseFlagsClient, L as Logger, d as PalbaseNotificationsClient, Q as QueueClient, e as PalbaseRealtimeClient, D as DBClient, f as PalbaseStorageClient, A as AuthSpec, R as RateLimitConfig, H as HttpError, U as User$1 } from './endpoint-CGI5cZMg.js';
2
- export { g as AuthConfig, B as BadRequest, h as ClientInfo, i as Conflict, j as DBOps, E as ErrorDef, k as ErrorMap, l as ErrorThrowers, F as FileContext, m as Forbidden, n as HttpMethod, M as Middleware, o as MiddlewareContext, p as MiddlewareHandler, N as NotFound, P as PBRequest, q as PalError, r as PalbaseAnalyticsClient, s as PalbaseAnalyticsManagementNamespace, t as PalbaseAnalyticsProperties, u as PalbaseAnalyticsQueryNamespace, v as PalbaseAttestAndroidParams, w as PalbaseAttestAndroidResult, x as PalbaseAttestiOSParams, y as PalbaseAttestiOSResult, z as PalbaseAuthClient, G as PalbaseBatchOverrideOperation, I as PalbaseBatchSetOverridesResult, J as PalbaseBindDeviceParams, K as PalbaseBucketClient, O as PalbaseClearAllOverridesResult, S as PalbaseClearOverrideResult, T as PalbaseCohortQueryInput, V as PalbaseCohortResult, W as PalbaseCollectionRef, X as PalbaseCountQueryInput, Y as PalbaseCountResult, Z as PalbaseCreateLinkParams, _ as PalbaseDeviceInfo, $ as PalbaseDeviceTokenView, a0 as PalbaseDocumentRef, a1 as PalbaseDocumentSnapshot, a2 as PalbaseEmailClient, a3 as PalbaseEmailSendParams, a4 as PalbaseEmailSendResponse, a5 as PalbaseEventNamesResult, a6 as PalbaseEventsQueryInput, a7 as PalbaseEventsResult, a8 as PalbaseFileObject, a9 as PalbaseFlag, aa as PalbaseFlagContext, ab as PalbaseFlagSource, ac as PalbaseFlagValue, ad as PalbaseFlagVariant, ae as PalbaseFlagsServiceClient, af as PalbaseFunctionsClient, ag as PalbaseFunnelQueryInput, ah as PalbaseFunnelResult, ai as PalbaseIdentifyTraits, aj as PalbaseInboxClient, ak as PalbaseInboxListOptions, al as PalbaseInboxListResult, am as PalbaseInboxMessage, an as PalbaseInboxSendParams, ao as PalbaseInboxSendResponse, ap as PalbaseInitialLink, aq as PalbaseInvokeOptions, ar as PalbaseLink, as as PalbaseLinkAnalytics, at as PalbaseLinkDetails, au as PalbaseLinksClient, av as PalbaseListLinksOptions, aw as PalbaseListLinksResult, ax as PalbaseListOptions, ay as PalbaseMatchParams, az as PalbaseMultiChannelResponse, aA as PalbaseOverviewResult, aB as PalbasePreferences, aC as PalbasePreferencesClient, aD as PalbasePublicUrlResponse, aE as PalbasePushClient, aF as PalbasePushSendParams, aG as PalbasePushSendResponse, aH as PalbaseQrCodeOptions, aI as PalbaseQuerySnapshot, aJ as PalbaseRegisterDeviceParams, aK as PalbaseResult, aL as PalbaseRetentionQueryInput, aM as PalbaseRetentionResult, aN as PalbaseSession, aO as PalbaseSetOverrideResult, aP as PalbaseSetOverridesResult, aQ as PalbaseSignedUrlResponse, aR as PalbaseSmsClient, aS as PalbaseSmsSendParams, aT as PalbaseSmsSendResponse, aU as PalbaseTransformOptions, aV as PalbaseUpdateLinkParams, aW as PalbaseUploadOptions, aX as PalbaseUser, aY as PalbaseUserDetailResult, aZ as PalbaseUsersQueryInput, a_ as PalbaseUsersResult, a$ as PalbaseVerifyRequestSignatureParams, b0 as PalbaseWhereOperator, b1 as TooManyRequests, b2 as TxClient, b3 as Unauthorized, b4 as defineMiddleware } from './endpoint-CGI5cZMg.js';
1
+ import { C as CacheClient, b as PalbaseDocsClient, c as PalbaseFlagsClient, L as Logger, d as PalbaseNotificationsClient, Q as QueueClient, e as PalbaseRealtimeClient, D as DBClient, f as PalbaseStorageClient, A as AuthSpec, R as RateLimitConfig, H as HttpError, U as User$1 } from './endpoint-Cn3ICGTf.js';
2
+ export { g as AuthConfig, B as BadRequest, h as ClientInfo, i as Conflict, j as DBOps, E as ErrorDef, k as ErrorMap, l as ErrorThrowers, F as FileContext, m as Forbidden, n as HttpMethod, M as Middleware, o as MiddlewareContext, p as MiddlewareHandler, N as NotFound, P as PBRequest, q as PalError, r as PalbaseAnalyticsClient, s as PalbaseAnalyticsManagementNamespace, t as PalbaseAnalyticsProperties, u as PalbaseAnalyticsQueryNamespace, v as PalbaseAttestAndroidParams, w as PalbaseAttestAndroidResult, x as PalbaseAttestiOSParams, y as PalbaseAttestiOSResult, z as PalbaseAuthClient, G as PalbaseBatchOverrideOperation, I as PalbaseBatchSetOverridesResult, J as PalbaseBindDeviceParams, K as PalbaseBucketClient, O as PalbaseClearAllOverridesResult, S as PalbaseClearOverrideResult, T as PalbaseCohortQueryInput, V as PalbaseCohortResult, W as PalbaseCollectionRef, X as PalbaseCountQueryInput, Y as PalbaseCountResult, Z as PalbaseCreateLinkParams, _ as PalbaseDeviceInfo, $ as PalbaseDeviceTokenView, a0 as PalbaseDocumentRef, a1 as PalbaseDocumentSnapshot, a2 as PalbaseEmailClient, a3 as PalbaseEmailSendParams, a4 as PalbaseEmailSendResponse, a5 as PalbaseEventNamesResult, a6 as PalbaseEventsQueryInput, a7 as PalbaseEventsResult, a8 as PalbaseFileObject, a9 as PalbaseFlag, aa as PalbaseFlagContext, ab as PalbaseFlagSource, ac as PalbaseFlagValue, ad as PalbaseFlagVariant, ae as PalbaseFlagsServiceClient, af as PalbaseFunctionsClient, ag as PalbaseFunnelQueryInput, ah as PalbaseFunnelResult, ai as PalbaseIdentifyTraits, aj as PalbaseInboxClient, ak as PalbaseInboxListOptions, al as PalbaseInboxListResult, am as PalbaseInboxMessage, an as PalbaseInboxSendParams, ao as PalbaseInboxSendResponse, ap as PalbaseInitialLink, aq as PalbaseInvokeOptions, ar as PalbaseLink, as as PalbaseLinkAnalytics, at as PalbaseLinkDetails, au as PalbaseLinksClient, av as PalbaseListLinksOptions, aw as PalbaseListLinksResult, ax as PalbaseListOptions, ay as PalbaseMatchParams, az as PalbaseMultiChannelResponse, aA as PalbaseOverviewResult, aB as PalbasePreferences, aC as PalbasePreferencesClient, aD as PalbasePublicUrlResponse, aE as PalbasePushClient, aF as PalbasePushSendParams, aG as PalbasePushSendResponse, aH as PalbaseQrCodeOptions, aI as PalbaseQuerySnapshot, aJ as PalbaseRegisterDeviceParams, aK as PalbaseResult, aL as PalbaseRetentionQueryInput, aM as PalbaseRetentionResult, aN as PalbaseSession, aO as PalbaseSetOverrideResult, aP as PalbaseSetOverridesResult, aQ as PalbaseSignedUrlResponse, aR as PalbaseSmsClient, aS as PalbaseSmsSendParams, aT as PalbaseSmsSendResponse, aU as PalbaseTransformOptions, aV as PalbaseUpdateLinkParams, aW as PalbaseUploadOptions, aX as PalbaseUser, aY as PalbaseUserDetailResult, aZ as PalbaseUsersQueryInput, a_ as PalbaseUsersResult, a$ as PalbaseVerifyRequestSignatureParams, b0 as PalbaseWhereOperator, b1 as TooManyRequests, b2 as TxClient, b3 as Unauthorized, b4 as VerifiedDevice, b5 as defineMiddleware } from './endpoint-Cn3ICGTf.js';
3
3
  import { AsyncLocalStorage } from 'node:async_hooks';
4
- import { E as EnvTypedDatabase, S as SchemaDef } from './index--dH5i4Nu.js';
5
- export { C as ColumnBuilder, a as ColumnDef, b as ColumnMap, c as ColumnType, d as EXTENSION_DEPENDENCIES, e as EnvServiceDatabase, f as EnvTables, g as EnvTypedTable, h as EnvTypedTx, I as InsertShape, O as OnDeleteAction, P as PALBASE_EXTENSIONS, i as PalbaseExtension, j as PolicyBuilder, k as PolicyCommand, l as PolicyDef, m as PolicyMode, R as RawConstraintDef, n as RowShape, o as SchemaInput, T as TableDef, p as TableInput, q as TypedDB, r as TypedTable, s as TypedTx, t as bigint, u as boolean, v as defineSchema, w as enumType, x as integer, y as isPalbaseExtension, z as jsonb, A as makeTypedDB, B as numeric, D as policy, F as raw, G as text, H as timestamp, J as uuid } from './index--dH5i4Nu.js';
6
- export { TableTypes, Tables } from './db/env.js';
4
+ import { E as EnvTypedDatabase, S as SchemaDef } from './index-V0ealeY-.js';
5
+ export { C as ColumnBuilder, a as ColumnDef, b as ColumnMap, c as ColumnType, d as EXTENSION_DEPENDENCIES, e as EnvServiceDatabase, f as EnvTables, g as EnvTypedTable, h as EnvTypedTx, I as InsertShape, O as OnDeleteAction, P as PALBASE_EXTENSIONS, i as PalbaseExtension, j as PolicyBuilder, k as PolicyCommand, l as PolicyDef, m as PolicyMode, R as RawConstraintDef, n as RowShape, o as SchemaInput, T as TableDef, p as TableInput, q as TypedDB, r as TypedTable, s as TypedTx, t as bigint, u as boolean, v as defineSchema, w as enumType, x as integer, y as isPalbaseExtension, z as jsonb, A as makeTypedDB, B as numeric, D as policy, F as raw, G as text, H as timestamp, J as uuid } from './index-V0ealeY-.js';
6
+ import { TableTypes, Tables } from './db/env.js';
7
7
  import { ZodTypeAny, z } from 'zod';
8
8
  export { z } from 'zod';
9
9
 
@@ -307,6 +307,47 @@ declare function bucket(opts?: BucketOptions): BucketDef;
307
307
  */
308
308
  declare function defineStorage(input: StorageInput): StorageConfig;
309
309
 
310
+ /**
311
+ * egress.ts — the tenant outbound-HTTP allowlist config-as-code DSL.
312
+ *
313
+ * `defineEgress({ hosts })` declares the external hosts a backend may fetch().
314
+ * config/egress.ts default-exports it; the deploy evals it to JSON
315
+ * (SchemaExtractor.EvaluateConfigFile → config_extract.js) and bakes the
316
+ * allowlist into the artifact manifest → the isolate loader → Tenant.egressAllow.
317
+ * The tenantFetch capability rejects any host NOT on it. NO config/egress.ts (or
318
+ * an empty list) ⇒ the backend has NO outbound network — fail-closed.
319
+ *
320
+ * @example
321
+ * // config/egress.ts
322
+ * import { defineEgress } from "@palbase/backend";
323
+ * export default defineEgress({
324
+ * hosts: ["api.open-meteo.com", "restcountries.com", "zenquotes.io"],
325
+ * });
326
+ *
327
+ * Each host is a bare hostname (no scheme/port/path/wildcard). A leading dot,
328
+ * `.example.com`, also covers subdomains (Squid dstdomain semantics). Egress is
329
+ * https-only on :443; the deploy REJECTS a malformed / IP-literal / internal host
330
+ * (fail-closed — a security allowlist is never best-effort).
331
+ *
332
+ * Emits: { __config: "egress", hosts: [...] }
333
+ */
334
+ /** The discriminant written under `__config` so the deploy eval knows the kind. */
335
+ declare const EGRESS_CONFIG_KIND: "egress";
336
+ /** The author-facing input to defineEgress. */
337
+ interface EgressInput {
338
+ hosts: string[];
339
+ }
340
+ /** The evaluated config/egress.ts default export (what the deploy reads). */
341
+ interface EgressConfig {
342
+ __config: typeof EGRESS_CONFIG_KIND;
343
+ hosts: string[];
344
+ }
345
+ /**
346
+ * Declare the backend's outbound-HTTP allowlist. Client-side this only shapes +
347
+ * validates the list; the authoritative fail-closed validation runs at deploy.
348
+ */
349
+ declare function defineEgress(input: EgressInput): EgressConfig;
350
+
310
351
  /**
311
352
  * notifications.ts — the notification-providers config-as-code DSL.
312
353
  *
@@ -554,6 +595,173 @@ declare function defineNotifications(input: NotificationsInput): NotificationsCo
554
595
  */
555
596
  declare function reservedSecretKey(provider: ProviderName, secretField: string): string;
556
597
 
598
+ /**
599
+ * test-users.ts — the test-user fixtures config-as-code DSL.
600
+ *
601
+ * `defineTestUsers({ users })` is a MODULE config-as-code surface (a sibling of
602
+ * `config/storage.ts`'s `defineStorage` and `config/flags.ts`'s `defineFlags`).
603
+ * A `config/test-users.ts` file default-exports a `defineTestUsers(...)` result;
604
+ * on deploy the runtime evaluates it to JSON and hands it to Studio, which owns
605
+ * the single apply engine (mint + seed). Studio, the CLI and the deploy all go
606
+ * through that one engine — there is no second interpreter of this JSON.
607
+ *
608
+ * TWO SHAPES, ONE DSL:
609
+ *
610
+ * - `email` GIVEN → a FIXTURE. Deploy materializes it create-if-missing, so
611
+ * the app can always be signed into with the same credentials. Deliberately
612
+ * NOT applied to a production Environment (the password lives in git).
613
+ * - `email` OMITTED → a TEMPLATE. Deploy creates nothing; Studio and the CLI
614
+ * mint as many fresh instances from it as you want (random credentials).
615
+ *
616
+ * Every minted user is an `is_test` user: excluded from MAU/billing, carrying
617
+ * `test: true` in its token, and refused by the Test Data write path if it ever
618
+ * turns out not to be one.
619
+ *
620
+ * @example
621
+ * import { defineTestUsers, testUser } from "@palbase/backend";
622
+ *
623
+ * export default defineTestUsers({
624
+ * users: {
625
+ * demo: testUser({
626
+ * email: "demo@test.local",
627
+ * password: "demo-password-1234",
628
+ * seed: {
629
+ * profiles: [{ display_name: "Demo", tier: "pro" }],
630
+ * lists: [
631
+ * { title: "Groceries", todos: [{ title: "Milk" }, { title: "Eggs", done: true }] },
632
+ * ],
633
+ * },
634
+ * }),
635
+ * heavy_user: testUser({ seed: { lists: [{ title: "L", todos: [{ title: "t" }] }] } }),
636
+ * },
637
+ * });
638
+ *
639
+ * The returned value is the EXACT JSON shape the runtime's generic config
640
+ * extractor emits and the apply engine consumes:
641
+ * { __config: "test-users", users: { <name>: { email, password, seed } } }
642
+ * `email` / `password` are the string or `null`; `seed` is `{}` when omitted.
643
+ */
644
+
645
+ /** The `__config` discriminant the eval/apply reads to confirm a config/*.ts is
646
+ * a test-users config. */
647
+ declare const TEST_USERS_CONFIG_KIND: "test-users";
648
+ /**
649
+ * A table graph in the shape the env `Tables` interface carries. The
650
+ * derivations below are parameterized over it (rather than reading the global
651
+ * `Tables` directly) so they can be type-tested against a fixture graph without
652
+ * augmenting the global interface — augmentation is program-wide and would leak
653
+ * into every other type test.
654
+ *
655
+ * The `Record<keyof G, …>` constraint is deliberate: a plain
656
+ * `Record<string, TableTypes>` would reject the `Tables` INTERFACE, which has
657
+ * no index signature.
658
+ */
659
+ type TableGraph = Record<string, TableTypes>;
660
+ /** The user-rooted tables of `G` — the ones a test user owns rows in directly. */
661
+ type OwnedTablesOf<G extends Record<keyof G, TableTypes>> = {
662
+ [T in keyof G]: G[T]["owner"] extends string ? T : never;
663
+ }[keyof G];
664
+ /**
665
+ * One authored seed row for table `T` of graph `G`.
666
+ *
667
+ * Starts from the table's INSERT shape and removes the two columns the engine
668
+ * owns: the owner column (set to the minted user) and `Via` — the FK column
669
+ * this row hangs off its parent by, when the row is nested under one. What
670
+ * remains is extended with the table's children, each an optional array of
671
+ * their own seed rows.
672
+ *
673
+ * Recursion terminates because `makeEnvDts` drops self-FKs and cycle
674
+ * back-edges when it emits `children`.
675
+ *
676
+ * Name collision note: if a table has a COLUMN whose name equals a CHILD
677
+ * table's name, the intersection makes that key unusable (its type collapses).
678
+ * That is a schema-naming problem the author sees at compile time; the apply
679
+ * engine resolves the same ambiguity live in favour of the column, because it
680
+ * introspects the real table.
681
+ */
682
+ type SeedRowFor<G extends Record<keyof G, TableTypes>, T extends keyof G, Via extends string = never> = Omit<G[T]["insert"], (G[T]["owner"] & string) | Via> & {
683
+ [C in keyof G[T]["children"]]?: Array<SeedRowFor<G, C & keyof G, G[T]["children"][C] & string>>;
684
+ };
685
+ /** The typed seed over graph `G`: top-level keys are user-rooted tables only;
686
+ * everything else is reached by nesting under its parent. */
687
+ type SeedFor<G extends Record<keyof G, TableTypes>> = {
688
+ [T in OwnedTablesOf<G>]?: Array<SeedRowFor<G, T>>;
689
+ };
690
+ /** The serialized seed — what travels as JSON and what the engine receives. */
691
+ type SeedJson = Record<string, Array<Record<string, unknown>>>;
692
+ /**
693
+ * The author-facing seed type. Falls back to the loose shape when `Tables` is
694
+ * still empty (no `db/schema.ts`, or `palbase-env.d.ts` not generated yet) so
695
+ * an unseeded project gets a usable DSL instead of an inscrutable `{}` error.
696
+ */
697
+ type Seed = [keyof Tables] extends [never] ? SeedJson : SeedFor<Tables>;
698
+ /**
699
+ * The options for one declared test user.
700
+ *
701
+ * - `email` / `password`: supply BOTH to make this a fixture with stable
702
+ * credentials (deploy materializes it, the app signs in with them), or
703
+ * NEITHER to make it a template the server generates credentials for.
704
+ * - `seed`: the user's data tree, derived from `db/schema.ts`. Top-level keys
705
+ * are user-rooted tables; FK children nest inside their parent's rows.
706
+ */
707
+ interface TestUserOptions {
708
+ email?: string;
709
+ password?: string;
710
+ seed?: Seed;
711
+ }
712
+ /** The compiled, serializable test-user definition — the EXACT shape emitted to
713
+ * JSON and consumed by the apply engine. */
714
+ interface TestUserDef {
715
+ /** Fixed login e-mail (fixture), or `null` when the server should generate one. */
716
+ email: string | null;
717
+ /** Fixed password (fixture), or `null` when the server should generate one. */
718
+ password: string | null;
719
+ /** The user's data tree; `{}` when nothing is seeded. */
720
+ seed: SeedJson;
721
+ }
722
+ /** A test-users config: the discriminant + the declared users by name. */
723
+ interface TestUsersConfig {
724
+ __config: typeof TEST_USERS_CONFIG_KIND;
725
+ users: Record<string, TestUserDef>;
726
+ }
727
+ /** The author-facing input to `defineTestUsers`. */
728
+ interface TestUsersInput {
729
+ users: Record<string, TestUserDef>;
730
+ }
731
+ /**
732
+ * Define a single test user. The NAME is supplied by the object key in
733
+ * `defineTestUsers({ users: { <name>: testUser({...}) } })`, so `testUser()`
734
+ * takes only the options.
735
+ *
736
+ * Validates eagerly (at config-author time):
737
+ * - `email` and `password` are supplied together or not at all — half a
738
+ * credential pair would silently degrade a fixture into a template.
739
+ * - `email` looks like an address; `password` is at least
740
+ * {@link MIN_PASSWORD_LENGTH} characters.
741
+ * - `seed` is structurally `{ <table>: [ {...}, ... ] }` with non-empty arrays.
742
+ *
743
+ * The user NAME is validated by `defineTestUsers`, which is where it is known.
744
+ */
745
+ declare function testUser(opts?: TestUserOptions): TestUserDef;
746
+ /**
747
+ * Define a project's test users. The user NAME comes from each object key
748
+ * (authors never repeat it). Returns the discriminated {@link TestUsersConfig}
749
+ * the runtime config extractor serializes and the apply engine consumes.
750
+ *
751
+ * Validates eagerly:
752
+ * - each name matches `^[a-zA-Z][a-zA-Z0-9_]*$` (the CLI's `--template <name>`
753
+ * and control-pg's scenario name share this vocabulary).
754
+ * - no two fixtures declare the SAME e-mail — they would race to
755
+ * create-if-missing the one account and the second would silently adopt the
756
+ * first one's data tree.
757
+ *
758
+ * @example
759
+ * export default defineTestUsers({
760
+ * users: { demo: testUser({ email: "demo@test.local", password: "demo-password-1234" }) },
761
+ * });
762
+ */
763
+ declare function defineTestUsers(input: TestUsersInput): TestUsersConfig;
764
+
557
765
  /**
558
766
  * flags.ts — the feature-flag-definitions config-as-code DSL.
559
767
  *
@@ -804,6 +1012,21 @@ interface RouteOptions {
804
1012
  * runs as the completion handler. See {@link UploadConfig} (decorators/upload.ts). */
805
1013
  uploadConfig?: UploadConfig;
806
1014
  }
1015
+ /** The kind of value a parameter decorator injects. Drives both dispatch
1016
+ * (which request slice to inject) and codegen (which OpenAPI parameter source a
1017
+ * schema-bearing kind maps to). */
1018
+ type ParamKind = "body" | "query" | "param" | "headers" | "user" | "optionalUser" | "client" | "requestId" | "traceId" | "req" | "uploadedObject";
1019
+ /** One parameter decorator's recorded metadata. `index` is the parameter
1020
+ * position esbuild/tsc preserve; `schema` is present for the schema-bearing
1021
+ * kinds (`body`/`query`/`headers`); `name` is the path-param name for `param`. */
1022
+ interface ParamMeta {
1023
+ index: number;
1024
+ kind: ParamKind;
1025
+ /** Zod schema for `body`/`query`/`headers` (validation + codegen source). */
1026
+ schema?: ZodTypeAny;
1027
+ /** Path-param name for `@Param("id")`. */
1028
+ name?: string;
1029
+ }
807
1030
  /** One inferred throw site: the error CLASS name (e.g. "TodoLocked") and its
808
1031
  * wire code (e.g. "todo_locked"). `status`, `hasData`, and the data JSON schema
809
1032
  * are NOT carried here — they resolve from the error registry by `code` at
@@ -812,12 +1035,37 @@ interface ThrowDescriptor {
812
1035
  name: string;
813
1036
  code: string;
814
1037
  }
1038
+ /** One route's recorded metadata: the verb + subpath + method name + options,
1039
+ * the ordered parameter metas, and the resolved return schema (injected by the
1040
+ * codegen step — see `returnSchema`). */
1041
+ interface RouteMeta {
1042
+ method: HttpMethodUpper;
1043
+ subpath: string;
1044
+ fnName: string;
1045
+ options: RouteOptions;
1046
+ params: ParamMeta[];
1047
+ /** Response schema for the route, if any. Derived from the method's RETURN
1048
+ * TYPE by codegen and written here via `recordReturn` (a generated top-level
1049
+ * IIFE injected per controller), not by an author-written decorator. */
1050
+ returnSchema?: ZodTypeAny;
1051
+ /** Error classes this route can throw, if inferred. Derived from the method
1052
+ * body + service call graph by the deploy stager's throw analysis and written
1053
+ * here via `recordThrows` (a generated top-level IIFE injected per controller,
1054
+ * the `recordReturn` twin), not by an author-written decorator. */
1055
+ throws?: ThrowDescriptor[];
1056
+ }
815
1057
  /** Attach the inferred throw descriptors to the route for `fnName` (called by
816
1058
  * the stager-injected IIFE that carries the throw analysis result — the
817
1059
  * `recordReturn` twin). If the route does not exist yet, the descriptors are
818
1060
  * buffered (THROWS_BUFFER) and drained into the route by `recordRoute` when the
819
1061
  * method decorator runs. */
820
1062
  declare function recordThrows(target: object, fnName: string, throws: ThrowDescriptor[]): void;
1063
+ /** Read the route metadata for a controller class (the deploy/dispatch entry
1064
+ * point). Applies any buffered return schemas + throw descriptors (for the
1065
+ * recordReturn/recordThrows-runs-before orderings) and returns a defensive copy
1066
+ * so callers cannot mutate the registry.
1067
+ */
1068
+ declare function getRoutes(ctor: object): RouteMeta[];
821
1069
 
822
1070
  /** A legacy method decorator. */
823
1071
  type MethodDecorator = (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
@@ -906,13 +1154,13 @@ declare function defineError(code: string, status: number): DefinedError;
906
1154
  /** Non-service, per-invocation data for background worker handlers.
907
1155
  * Services (Database, Log, …) are imported as singletons, not passed here. */
908
1156
  interface WorkerMeta {
909
- /** Branch-scoped env vars. */
1157
+ /** Environment-scoped env vars. */
910
1158
  env: Record<string, string>;
911
1159
  /** The user that enqueued the job, if any (background jobs are usually system-initiated). */
912
1160
  user: User$1 | null;
913
1161
  /** Per-invocation id for correlation. */
914
1162
  requestId: string;
915
- projectId: string;
1163
+ /** The globally unique Environment runtime identifier. */
916
1164
  environmentId: string;
917
1165
  }
918
1166
  /** Backoff strategy for worker retries. */
@@ -961,9 +1209,9 @@ declare function defineWorker<TPayload = unknown>(config: WorkerConfig<TPayload>
961
1209
  /** Non-service, per-invocation data for job handlers.
962
1210
  * Services (Database, Log, …) are imported as singletons, not passed here. */
963
1211
  interface JobMeta {
964
- /** Branch-scoped env vars. */
1212
+ /** Environment-scoped env vars. */
965
1213
  env: Record<string, string>;
966
- projectId: string;
1214
+ /** The globally unique Environment runtime identifier. */
967
1215
  environmentId: string;
968
1216
  }
969
1217
  /** Configuration for defining a scheduled job. */
@@ -1009,11 +1257,11 @@ type WebhookProvider = "stripe" | "github" | "twilio" | "sendgrid" | "slack" | "
1009
1257
  /** Non-service, per-invocation data for webhook handlers.
1010
1258
  * Services (Database, Log, …) are imported as singletons, not passed here. */
1011
1259
  interface WebhookMeta {
1012
- /** Branch-scoped env vars. */
1260
+ /** Environment-scoped env vars. */
1013
1261
  env: Record<string, string>;
1014
1262
  /** Per-invocation id for correlation. */
1015
1263
  requestId: string;
1016
- projectId: string;
1264
+ /** The globally unique Environment runtime identifier. */
1017
1265
  environmentId: string;
1018
1266
  }
1019
1267
  /** Secret reference — resolves from environment variables at runtime. */
@@ -1241,9 +1489,9 @@ declare function __shutdownResources(): Promise<void>;
1241
1489
  /** Non-service, per-invocation data for hook handlers.
1242
1490
  * Services (Database, Log, …) are imported as singletons, not passed here. */
1243
1491
  interface HookMeta {
1244
- /** Branch-scoped env vars. */
1492
+ /** Environment-scoped env vars. */
1245
1493
  env: Record<string, string>;
1246
- projectId: string;
1494
+ /** The globally unique Environment runtime identifier. */
1247
1495
  environmentId: string;
1248
1496
  }
1249
1497
  /** Payload for auth.onUserCreated hook. */
@@ -1355,4 +1603,4 @@ declare const documents: {
1355
1603
  onDocumentDeleted(handler: HookHandler<DocumentDeletedEvent>): ResolvedHook<DocumentDeletedEvent>;
1356
1604
  };
1357
1605
 
1358
- export { type AcsOptions, type ApnsOptions, type BackoffStrategy, Body, type BucketDef, type BucketOptions, Cache, CacheClient, Client, Controller, type ControllerOptions, type CustomWebhookConfig, DBClient, Database, type DefinedError, type DefinedErrorWithData, Delete, type DocumentCreatedEvent, type DocumentDeletedEvent, type DocumentUpdatedEvent, Documents, type EnvSecretRef, EnvTypedDatabase, FLAGS_CONFIG_KIND, type FcmOptions, type FileDeletedEvent, type FileUploadedEvent, type FlagDef, type FlagOptions, type FlagType, type FlagValue, Flags, type FlagsConfig, type FlagsInput, Get, Headers, type HookHandler, type HookMeta, HttpError, type HttpMethodUpper, type JobConfig, type JobMeta, Log, Logger, NOTIFICATIONS_CONFIG_KIND, Notifications, type NotificationsConfig, type NotificationsInput, OptionalUser, PROVIDER_CATALOG, PalbaseDocsClient, PalbaseFlagsClient, PalbaseNotificationsClient, PalbaseRealtimeClient, PalbaseStorageClient, Param, type PasswordResetEvent, Patch, Post, type ProviderCatalogEntry, type ProviderDef, type ProviderEventMap, type ProviderName, type ProviderOptions, type ProviderWebhookConfig, Put, Query, QueryParams, Queue, QueueClient, RESERVED_SECRET_PREFIX, RateLimitConfig, Realtime, type RegisteredError, Req, RequestId, type ResolvedCustomWebhook, type ResolvedHook, type ResolvedJobConfig, type ResolvedProviderWebhook, type ResolvedWebhookConfig, type ResolvedWorkerConfig, Resource, type ResourceEnv, type RouteOptions, type RuntimeServices, STORAGE_CONFIG_KIND, SchemaDef, type SendgridOptions, type SesOptions, type SignInEvent, type SignOutEvent, type SmtpOptions, Storage, type StorageConfig, type StorageInput, type ThrowDescriptor, TraceId, type TwilioOptions, Upload, type UploadConfig, UploadedObject, User, type UserCreatedEvent, User$1 as UserT, type WebhookEventHandler, type WebhookMeta, type WebhookProvider, type WebhookRequest, type WorkerConfig, type WorkerMeta, __getRuntime, __registerResource, __requestALS, __runResourceBoot, __runWithRuntime, __setRuntime, __shutdownResources, auth, bucket, buildProvider, defineError, defineFlags, defineJob, defineNotifications, defineStorage, defineWebhook, defineWorker, documents, flag, getErrorRegistry, makeEnvDts, parseFileSizeLimit, recordThrows, reservedSecretKey, storage, validateUploadAgainstStorage };
1606
+ export { type AcsOptions, type ApnsOptions, type BackoffStrategy, Body, type BucketDef, type BucketOptions, Cache, CacheClient, Client, Controller, type ControllerOptions, type CustomWebhookConfig, DBClient, Database, type DefinedError, type DefinedErrorWithData, Delete, type DocumentCreatedEvent, type DocumentDeletedEvent, type DocumentUpdatedEvent, Documents, EGRESS_CONFIG_KIND, type EgressConfig, type EgressInput, type EnvSecretRef, EnvTypedDatabase, FLAGS_CONFIG_KIND, type FcmOptions, type FileDeletedEvent, type FileUploadedEvent, type FlagDef, type FlagOptions, type FlagType, type FlagValue, Flags, type FlagsConfig, type FlagsInput, Get, Headers, type HookHandler, type HookMeta, HttpError, type HttpMethodUpper, type JobConfig, type JobMeta, Log, Logger, NOTIFICATIONS_CONFIG_KIND, Notifications, type NotificationsConfig, type NotificationsInput, OptionalUser, type OwnedTablesOf, PROVIDER_CATALOG, PalbaseDocsClient, PalbaseFlagsClient, PalbaseNotificationsClient, PalbaseRealtimeClient, PalbaseStorageClient, Param, type ParamKind, type ParamMeta, type PasswordResetEvent, Patch, Post, type ProviderCatalogEntry, type ProviderDef, type ProviderEventMap, type ProviderName, type ProviderOptions, type ProviderWebhookConfig, Put, Query, QueryParams, Queue, QueueClient, RESERVED_SECRET_PREFIX, RateLimitConfig, Realtime, type RegisteredError, Req, RequestId, type ResolvedCustomWebhook, type ResolvedHook, type ResolvedJobConfig, type ResolvedProviderWebhook, type ResolvedWebhookConfig, type ResolvedWorkerConfig, Resource, type ResourceEnv, type RouteMeta, type RouteOptions, type RuntimeServices, STORAGE_CONFIG_KIND, SchemaDef, type Seed, type SeedFor, type SeedJson, type SeedRowFor, type SendgridOptions, type SesOptions, type SignInEvent, type SignOutEvent, type SmtpOptions, Storage, type StorageConfig, type StorageInput, TEST_USERS_CONFIG_KIND, type TableGraph, TableTypes, Tables, type TestUserDef, type TestUserOptions, type TestUsersConfig, type TestUsersInput, type ThrowDescriptor, TraceId, type TwilioOptions, Upload, type UploadConfig, UploadedObject, User, type UserCreatedEvent, User$1 as UserT, type WebhookEventHandler, type WebhookMeta, type WebhookProvider, type WebhookRequest, type WorkerConfig, type WorkerMeta, __getRuntime, __registerResource, __requestALS, __runResourceBoot, __runWithRuntime, __setRuntime, __shutdownResources, auth, bucket, buildProvider, defineEgress, defineError, defineFlags, defineJob, defineNotifications, defineStorage, defineTestUsers, defineWebhook, defineWorker, documents, flag, getErrorRegistry, getRoutes, makeEnvDts, parseFileSizeLimit, recordThrows, reservedSecretKey, storage, testUser, validateUploadAgainstStorage };
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ import {
31
31
  text,
32
32
  timestamp,
33
33
  uuid
34
- } from "./chunk-SO6YV35D.js";
34
+ } from "./chunk-4WOQWFUP.js";
35
35
 
36
36
  // src/db/env-gen.ts
37
37
  function baseTsType(def) {
@@ -64,7 +64,50 @@ function rowType(def) {
64
64
  function optionalOnInsert(def) {
65
65
  return def.nullable === true || def.defaultRandom === true || def.defaultNow === true || def.defaultValue !== void 0;
66
66
  }
67
- function tableBlock(table, indent) {
67
+ var AUTH_USERS_TABLE = "auth.users";
68
+ function ownerColumnOf(table) {
69
+ for (const [col, builder] of Object.entries(table.columns)) {
70
+ if (builder._def.references?.table === AUTH_USERS_TABLE) return col;
71
+ }
72
+ return null;
73
+ }
74
+ function buildChildEdges(schema) {
75
+ const edges = /* @__PURE__ */ new Map();
76
+ for (const name of Object.keys(schema.tables)) edges.set(name, []);
77
+ for (const childName of Object.keys(schema.tables)) {
78
+ const table = schema.tables[childName];
79
+ if (table === void 0) continue;
80
+ for (const [col, builder] of Object.entries(table.columns)) {
81
+ const ref = builder._def.references;
82
+ if (ref === void 0) continue;
83
+ if (ref.table === childName) continue;
84
+ const bucket2 = edges.get(ref.table);
85
+ if (bucket2 === void 0) continue;
86
+ if (bucket2.some((e) => e.child === childName)) continue;
87
+ bucket2.push({ child: childName, column: col });
88
+ }
89
+ }
90
+ return edges;
91
+ }
92
+ function pruneCycles(raw2) {
93
+ const pruned = /* @__PURE__ */ new Map();
94
+ for (const table of raw2.keys()) pruned.set(table, []);
95
+ const state = /* @__PURE__ */ new Map();
96
+ function visit(table) {
97
+ state.set(table, "visiting");
98
+ for (const edge of raw2.get(table) ?? []) {
99
+ if (state.get(edge.child) === "visiting") continue;
100
+ pruned.get(table)?.push(edge);
101
+ if (state.get(edge.child) === void 0) visit(edge.child);
102
+ }
103
+ state.set(table, "done");
104
+ }
105
+ for (const table of raw2.keys()) {
106
+ if (state.get(table) === void 0) visit(table);
107
+ }
108
+ return pruned;
109
+ }
110
+ function tableBlock(table, children, indent) {
68
111
  const cols = Object.entries(table.columns);
69
112
  const rowLines = cols.map(([col, builder]) => {
70
113
  return `${indent} ${col}: ${rowType(builder._def)};`;
@@ -74,6 +117,8 @@ function tableBlock(table, indent) {
74
117
  const opt = optionalOnInsert(def) ? "?" : "";
75
118
  return `${indent} ${col}${opt}: ${rowType(def)};`;
76
119
  });
120
+ const owner = ownerColumnOf(table);
121
+ const childEntries = children.map((e) => `${JSON.stringify(e.child)}: ${JSON.stringify(e.column)}`).join("; ");
77
122
  return [
78
123
  `${indent}${table.name}: {`,
79
124
  `${indent} row: {`,
@@ -82,12 +127,17 @@ function tableBlock(table, indent) {
82
127
  `${indent} insert: {`,
83
128
  ...insertLines,
84
129
  `${indent} };`,
130
+ `${indent} owner: ${owner === null ? "null" : JSON.stringify(owner)};`,
131
+ `${indent} children: {${childEntries === "" ? "" : ` ${childEntries} `}};`,
85
132
  `${indent}};`
86
133
  ].join("\n");
87
134
  }
88
135
  function makeEnvDts(schema) {
89
136
  const tableNames = Object.keys(schema.tables);
90
- const blocks = tableNames.map((name) => tableBlock(schema.tables[name], " "));
137
+ const children = pruneCycles(buildChildEdges(schema));
138
+ const blocks = tableNames.map(
139
+ (name) => tableBlock(schema.tables[name], children.get(name) ?? [], " ")
140
+ );
91
141
  const body = blocks.length > 0 ? `
92
142
  ${blocks.join("\n")}
93
143
  ` : "";
@@ -182,6 +232,22 @@ function defineStorage(input) {
182
232
  return { __config: STORAGE_CONFIG_KIND, buckets };
183
233
  }
184
234
 
235
+ // src/config/egress.ts
236
+ var EGRESS_CONFIG_KIND = "egress";
237
+ function defineEgress(input) {
238
+ if (input === null || typeof input !== "object" || !Array.isArray(input.hosts)) {
239
+ throw new Error("defineEgress expects { hosts: string[] }");
240
+ }
241
+ const hosts = [];
242
+ for (const h of input.hosts) {
243
+ if (typeof h !== "string" || h.trim().length === 0) {
244
+ throw new Error("defineEgress: every host must be a non-empty string");
245
+ }
246
+ hosts.push(h.trim().toLowerCase());
247
+ }
248
+ return { __config: EGRESS_CONFIG_KIND, hosts };
249
+ }
250
+
185
251
  // src/config/notifications.ts
186
252
  var NOTIFICATIONS_CONFIG_KIND = "notifications";
187
253
  var RESERVED_SECRET_PREFIX = "PB_NOTIFICATIONS";
@@ -293,6 +359,98 @@ function camelToUpperSnake(s) {
293
359
  return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase();
294
360
  }
295
361
 
362
+ // src/config/test-users.ts
363
+ var TEST_USERS_CONFIG_KIND = "test-users";
364
+ var TEST_USER_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_]*$/;
365
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
366
+ var MIN_PASSWORD_LENGTH = 8;
367
+ function isPlainObject(value) {
368
+ return typeof value === "object" && value !== null && !Array.isArray(value);
369
+ }
370
+ function normalizeSeed(seed, userName) {
371
+ if (seed === void 0) return {};
372
+ if (!isPlainObject(seed)) {
373
+ throw new Error(`testUser ${JSON.stringify(userName)}: seed must be { <table>: [ {...} ] }`);
374
+ }
375
+ const out = {};
376
+ for (const [table, rows] of Object.entries(seed)) {
377
+ if (!Array.isArray(rows)) {
378
+ throw new Error(
379
+ `testUser ${JSON.stringify(userName)}: seed.${table} must be an array of rows`
380
+ );
381
+ }
382
+ if (rows.length === 0) {
383
+ throw new Error(
384
+ `testUser ${JSON.stringify(userName)}: seed.${table} is empty \u2014 remove it or add a row`
385
+ );
386
+ }
387
+ for (const row of rows) {
388
+ if (!isPlainObject(row)) {
389
+ throw new Error(
390
+ `testUser ${JSON.stringify(userName)}: every row in seed.${table} must be an object`
391
+ );
392
+ }
393
+ }
394
+ out[table] = rows;
395
+ }
396
+ return out;
397
+ }
398
+ function testUser(opts = {}) {
399
+ if (!isPlainObject(opts)) {
400
+ throw new Error("testUser() expects { email?, password?, seed? }");
401
+ }
402
+ const hasEmail = opts.email !== void 0;
403
+ const hasPassword = opts.password !== void 0;
404
+ if (hasEmail !== hasPassword) {
405
+ throw new Error(
406
+ "testUser() needs email AND password together (a fixture with stable credentials) or neither (a template the server generates credentials for)"
407
+ );
408
+ }
409
+ let email = null;
410
+ let password = null;
411
+ if (hasEmail) {
412
+ if (typeof opts.email !== "string" || !EMAIL_RE.test(opts.email)) {
413
+ throw new Error(`testUser email ${JSON.stringify(opts.email)} is not a valid address`);
414
+ }
415
+ if (typeof opts.password !== "string" || opts.password.length < MIN_PASSWORD_LENGTH) {
416
+ throw new Error(
417
+ `testUser password must be at least ${MIN_PASSWORD_LENGTH} characters (got ${typeof opts.password === "string" ? opts.password.length : 0})`
418
+ );
419
+ }
420
+ email = opts.email;
421
+ password = opts.password;
422
+ }
423
+ return { email, password, seed: normalizeSeed(opts.seed, email ?? "<template>") };
424
+ }
425
+ function defineTestUsers(input) {
426
+ if (!isPlainObject(input) || !isPlainObject(input.users)) {
427
+ throw new Error("defineTestUsers expects { users: { <name>: testUser({...}) } }");
428
+ }
429
+ const users = {};
430
+ const emails = /* @__PURE__ */ new Map();
431
+ for (const name of Object.keys(input.users)) {
432
+ if (!TEST_USER_NAME_RE.test(name)) {
433
+ throw new Error(
434
+ `test user name ${JSON.stringify(name)} is invalid \u2014 must start with a letter and contain only letters, digits, and underscores`
435
+ );
436
+ }
437
+ const def = input.users[name];
438
+ if (def === void 0) continue;
439
+ if (def.email !== null) {
440
+ const lower = def.email.toLowerCase();
441
+ const already = emails.get(lower);
442
+ if (already !== void 0) {
443
+ throw new Error(
444
+ `test users ${JSON.stringify(already)} and ${JSON.stringify(name)} declare the same e-mail ${JSON.stringify(def.email)}`
445
+ );
446
+ }
447
+ emails.set(lower, name);
448
+ }
449
+ users[name] = def;
450
+ }
451
+ return { __config: TEST_USERS_CONFIG_KIND, users };
452
+ }
453
+
296
454
  // src/config/flags.ts
297
455
  var FLAGS_CONFIG_KIND = "flags";
298
456
  var FLAG_KEY_RE = /^[a-zA-Z][a-zA-Z0-9_]*$/;
@@ -465,6 +623,33 @@ function recordThrows(target, fnName, throws) {
465
623
  const throwsBuffer = carrier[THROWS_BUFFER];
466
624
  if (throwsBuffer) throwsBuffer[fnName] = throws;
467
625
  }
626
+ function getRoutes(ctor) {
627
+ const carrier = carrierOf(ctor);
628
+ const routes = carrier[ROUTES] ?? [];
629
+ const returnBuffer = carrier[RETURN_BUFFER];
630
+ if (returnBuffer) {
631
+ for (const route of routes) {
632
+ const buffered = returnBuffer[route.fnName];
633
+ if (buffered && route.returnSchema === void 0) {
634
+ route.returnSchema = buffered;
635
+ }
636
+ }
637
+ }
638
+ const throwsBuffer = carrier[THROWS_BUFFER];
639
+ if (throwsBuffer) {
640
+ for (const route of routes) {
641
+ const buffered = throwsBuffer[route.fnName];
642
+ if (buffered && route.throws === void 0) {
643
+ route.throws = buffered;
644
+ }
645
+ }
646
+ }
647
+ return routes.map((r) => ({
648
+ ...r,
649
+ params: r.params.slice(),
650
+ ...r.throws !== void 0 ? { throws: r.throws.slice() } : {}
651
+ }));
652
+ }
468
653
 
469
654
  // src/decorators/methods.ts
470
655
  function makeMethodDecorator(method) {
@@ -1076,6 +1261,7 @@ export {
1076
1261
  Database,
1077
1262
  Delete,
1078
1263
  Documents,
1264
+ EGRESS_CONFIG_KIND,
1079
1265
  EXTENSION_DEPENDENCIES,
1080
1266
  FLAGS_CONFIG_KIND,
1081
1267
  Flags,
@@ -1106,6 +1292,7 @@ export {
1106
1292
  Resource,
1107
1293
  STORAGE_CONFIG_KIND,
1108
1294
  Storage,
1295
+ TEST_USERS_CONFIG_KIND,
1109
1296
  TooManyRequests,
1110
1297
  TraceId,
1111
1298
  Unauthorized,
@@ -1124,6 +1311,7 @@ export {
1124
1311
  boolean,
1125
1312
  bucket,
1126
1313
  buildProvider,
1314
+ defineEgress,
1127
1315
  defineError,
1128
1316
  defineFlags,
1129
1317
  defineJob,
@@ -1131,12 +1319,14 @@ export {
1131
1319
  defineNotifications,
1132
1320
  defineSchema,
1133
1321
  defineStorage,
1322
+ defineTestUsers,
1134
1323
  defineWebhook,
1135
1324
  defineWorker,
1136
1325
  documents,
1137
1326
  enumType,
1138
1327
  flag,
1139
1328
  getErrorRegistry,
1329
+ getRoutes,
1140
1330
  integer,
1141
1331
  isPalbaseExtension,
1142
1332
  jsonb,
@@ -1149,6 +1339,7 @@ export {
1149
1339
  recordThrows,
1150
1340
  reservedSecretKey,
1151
1341
  storage,
1342
+ testUser,
1152
1343
  text,
1153
1344
  timestamp,
1154
1345
  uuid,