@palbase/backend 9.0.0 → 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/{chunk-SO6YV35D.js → chunk-4WOQWFUP.js} +38 -6
- package/dist/chunk-4WOQWFUP.js.map +1 -0
- package/dist/db/env.cjs.map +1 -1
- package/dist/db/env.d.cts +18 -4
- package/dist/db/env.d.ts +18 -4
- package/dist/db/index.cjs +37 -5
- package/dist/db/index.cjs.map +1 -1
- package/dist/db/index.d.cts +2 -2
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +1 -1
- package/dist/{endpoint-CGI5cZMg.d.cts → endpoint-Cn3ICGTf.d.cts} +26 -4
- package/dist/{endpoint-CGI5cZMg.d.ts → endpoint-Cn3ICGTf.d.ts} +26 -4
- package/dist/{index-osv2jMmZ.d.cts → index-Bt7UHkAM.d.cts} +40 -5
- package/dist/{index--dH5i4Nu.d.ts → index-V0ealeY-.d.ts} +40 -5
- package/dist/index.cjs +241 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +262 -14
- package/dist/index.d.ts +262 -14
- package/dist/index.js +199 -3
- package/dist/index.js.map +1 -1
- package/dist/test/index.d.cts +1 -1
- package/dist/test/index.d.ts +1 -1
- package/docs/README.md +12 -10
- package/docs/background.md +4 -4
- package/docs/events.md +4 -4
- package/docs/getting-started.md +6 -5
- package/docs/llms-full.txt +39 -36
- package/docs/migrations.md +10 -10
- package/docs/resources.md +3 -3
- package/package.json +1 -1
- package/dist/chunk-SO6YV35D.js.map +0 -1
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-
|
|
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-
|
|
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
|
|
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
|
|
6
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
/**
|
|
1212
|
+
/** Environment-scoped env vars. */
|
|
965
1213
|
env: Record<string, string>;
|
|
966
|
-
|
|
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
|
-
/**
|
|
1260
|
+
/** Environment-scoped env vars. */
|
|
1013
1261
|
env: Record<string, string>;
|
|
1014
1262
|
/** Per-invocation id for correlation. */
|
|
1015
1263
|
requestId: string;
|
|
1016
|
-
|
|
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
|
-
/**
|
|
1492
|
+
/** Environment-scoped env vars. */
|
|
1245
1493
|
env: Record<string, string>;
|
|
1246
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
|
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,10 +623,42 @@ 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) {
|
|
471
656
|
return function(subpath, options = {}) {
|
|
657
|
+
if (typeof subpath !== "string") {
|
|
658
|
+
throw new Error(
|
|
659
|
+
`@${method[0]}${method.slice(1).toLowerCase()}(subpath) expects a string subpath, got ${typeof subpath}.` + (method === "QUERY" ? " If this is a zod schema on a method parameter: the query-string param decorator was renamed @QueryParams(schema) in @palbase/backend 9.0.0." : "")
|
|
660
|
+
);
|
|
661
|
+
}
|
|
472
662
|
return function(target, propertyKey) {
|
|
473
663
|
recordRoute(target, String(propertyKey), method, subpath, options);
|
|
474
664
|
};
|
|
@@ -1071,6 +1261,7 @@ export {
|
|
|
1071
1261
|
Database,
|
|
1072
1262
|
Delete,
|
|
1073
1263
|
Documents,
|
|
1264
|
+
EGRESS_CONFIG_KIND,
|
|
1074
1265
|
EXTENSION_DEPENDENCIES,
|
|
1075
1266
|
FLAGS_CONFIG_KIND,
|
|
1076
1267
|
Flags,
|
|
@@ -1101,6 +1292,7 @@ export {
|
|
|
1101
1292
|
Resource,
|
|
1102
1293
|
STORAGE_CONFIG_KIND,
|
|
1103
1294
|
Storage,
|
|
1295
|
+
TEST_USERS_CONFIG_KIND,
|
|
1104
1296
|
TooManyRequests,
|
|
1105
1297
|
TraceId,
|
|
1106
1298
|
Unauthorized,
|
|
@@ -1119,6 +1311,7 @@ export {
|
|
|
1119
1311
|
boolean,
|
|
1120
1312
|
bucket,
|
|
1121
1313
|
buildProvider,
|
|
1314
|
+
defineEgress,
|
|
1122
1315
|
defineError,
|
|
1123
1316
|
defineFlags,
|
|
1124
1317
|
defineJob,
|
|
@@ -1126,12 +1319,14 @@ export {
|
|
|
1126
1319
|
defineNotifications,
|
|
1127
1320
|
defineSchema,
|
|
1128
1321
|
defineStorage,
|
|
1322
|
+
defineTestUsers,
|
|
1129
1323
|
defineWebhook,
|
|
1130
1324
|
defineWorker,
|
|
1131
1325
|
documents,
|
|
1132
1326
|
enumType,
|
|
1133
1327
|
flag,
|
|
1134
1328
|
getErrorRegistry,
|
|
1329
|
+
getRoutes,
|
|
1135
1330
|
integer,
|
|
1136
1331
|
isPalbaseExtension,
|
|
1137
1332
|
jsonb,
|
|
@@ -1144,6 +1339,7 @@ export {
|
|
|
1144
1339
|
recordThrows,
|
|
1145
1340
|
reservedSecretKey,
|
|
1146
1341
|
storage,
|
|
1342
|
+
testUser,
|
|
1147
1343
|
text,
|
|
1148
1344
|
timestamp,
|
|
1149
1345
|
uuid,
|