@terrascale/sdk 0.0.2 → 0.0.4

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 (84) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +193 -56
  3. package/package.json +14 -5
  4. package/sdk-public-v1-route-manifest.json +81 -0
  5. package/sdk-v1-route-manifest.json +8996 -1697
  6. package/src/database-codec.js +21 -26
  7. package/src/database-types.js +11 -11
  8. package/src/database-view.js +6 -6
  9. package/src/http.js +13 -48
  10. package/src/ids.js +22 -0
  11. package/src/index.js +77 -2
  12. package/src/local/better-auth.js +17 -6
  13. package/src/local/environment.js +0 -1
  14. package/src/local/index.js +2 -2
  15. package/src/local/legacy-client.js +5 -5
  16. package/src/local/router.js +12 -12
  17. package/src/local/store.js +29 -28
  18. package/src/local/test-environment.js +3 -2
  19. package/src/postgres.js +5 -4
  20. package/src/react/core.js +2 -2
  21. package/src/ts-auth-ids.js +122 -0
  22. package/src/ts-auth.js +75 -77
  23. package/src/v1/backups.js +111 -0
  24. package/src/v1/catalog.js +210 -0
  25. package/src/v1/changes.js +225 -0
  26. package/src/v1/database-schema.js +45 -0
  27. package/src/v1/decode.js +20 -8
  28. package/src/v1/errors.js +23 -3
  29. package/src/v1/ids.js +9 -59
  30. package/src/v1/imports.js +72 -0
  31. package/src/v1/indexes.js +76 -0
  32. package/src/v1/json.js +4 -3
  33. package/src/v1/management-decode.js +24 -0
  34. package/src/v1/management-routes.js +67 -0
  35. package/src/v1/management-validation.js +25 -0
  36. package/src/v1/routes.js +36 -11
  37. package/src/v1/saved-queries.js +58 -0
  38. package/src/v1/shape-crypto.js +125 -0
  39. package/src/v1/shape-manifest.js +31 -0
  40. package/src/v1/shape-reader.js +212 -0
  41. package/src/v1/shape-subscription.js +208 -0
  42. package/src/v1/shapes.js +15 -10
  43. package/src/v1/sql.js +98 -0
  44. package/src/v1/system.js +80 -0
  45. package/src/v1/terrabase-v1.js +192 -33
  46. package/src/v1/transport.js +80 -34
  47. package/src/v1/usage.js +101 -0
  48. package/types/database-codec.d.ts +1 -6
  49. package/types/database-types.d.ts +11 -11
  50. package/types/http.d.ts +2 -8
  51. package/types/ids.d.ts +10 -0
  52. package/types/index.d.ts +70 -1
  53. package/types/local/environment.d.ts +1 -1
  54. package/types/local/store.d.ts +10 -0
  55. package/types/local/test-environment.d.ts +2 -2
  56. package/types/postgres.d.ts +4 -4
  57. package/types/ts-auth-ids.d.ts +14 -0
  58. package/types/ts-auth.d.ts +99 -101
  59. package/types/v1/backups.d.ts +128 -0
  60. package/types/v1/catalog.d.ts +92 -0
  61. package/types/v1/changes.d.ts +53 -0
  62. package/types/v1/database-schema.d.ts +82 -0
  63. package/types/v1/decode.d.ts +17 -4
  64. package/types/v1/errors.d.ts +16 -1
  65. package/types/v1/ids.d.ts +4 -16
  66. package/types/v1/imports.d.ts +132 -0
  67. package/types/v1/indexes.d.ts +75 -0
  68. package/types/v1/management-decode.d.ts +15 -0
  69. package/types/v1/management-routes.d.ts +3 -0
  70. package/types/v1/management-validation.d.ts +8 -0
  71. package/types/v1/routes.d.ts +5 -2
  72. package/types/v1/saved-queries.d.ts +33 -0
  73. package/types/v1/shape-crypto.d.ts +39 -0
  74. package/types/v1/shape-manifest.d.ts +28 -0
  75. package/types/v1/shape-reader.d.ts +45 -0
  76. package/types/v1/shape-subscription.d.ts +35 -0
  77. package/types/v1/shapes.d.ts +14 -0
  78. package/types/v1/sql.d.ts +47 -0
  79. package/types/v1/system.d.ts +85 -0
  80. package/types/v1/terrabase-v1.d.ts +116 -7
  81. package/types/v1/transport.d.ts +27 -10
  82. package/types/v1/usage.d.ts +171 -0
  83. package/src/ts-auth-discovery.js +0 -207
  84. package/types/ts-auth-discovery.d.ts +0 -19
package/src/ts-auth.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { TsAuthHttp } from "./http.js";
2
2
  import { err } from "./result.js";
3
+ import { isValidId, requireId } from "./ids.js";
4
+ import { requireDeadLetterId } from "./ts-auth-ids.js";
3
5
  /** @import { TerraScaleResult } from "./result.js" */
4
6
 
5
7
  /** @typedef {import("./config.js").TsAuthRuntimeConfig} TsAuthRuntimeConfig */
@@ -13,7 +15,6 @@ import { err } from "./result.js";
13
15
  * @typedef {Omit<TsAuthRuntimeConfig, "apiBaseUrl"> & {
14
16
  * readonly apiBaseUrl: string; // The ts-auth deployment's public origin; there is no default.
15
17
  * readonly instanceSlug: string; // The instance the `sk_` key belongs to; it prefixes every route.
16
- * readonly useReverseProxy?: boolean; // Route public auth calls through the reverse proxy instead of regional discovery.
17
18
  * }} TsAuthClientConfig
18
19
  */
19
20
 
@@ -27,7 +28,7 @@ import { err } from "./result.js";
27
28
 
28
29
  /**
29
30
  * @typedef {{
30
- * readonly id: string;
31
+ * readonly id: import("./ids.js").EntityId;
31
32
  * readonly email: string | null;
32
33
  * readonly email_verified: boolean;
33
34
  * readonly username: string | null;
@@ -43,7 +44,7 @@ import { err } from "./result.js";
43
44
 
44
45
  /**
45
46
  * @typedef {{
46
- * readonly id: string;
47
+ * readonly id: import("./ids.js").EntityId;
47
48
  * readonly status: string;
48
49
  * readonly created_at: number; // Unix seconds.
49
50
  * readonly expires_at: number; // Unix seconds.
@@ -55,7 +56,7 @@ import { err } from "./result.js";
55
56
 
56
57
  /**
57
58
  * @typedef {{
58
- * readonly id: string;
59
+ * readonly id: import("./ids.js").EntityId;
59
60
  * readonly type: string;
60
61
  * readonly confirmed: boolean;
61
62
  * readonly createdAt: number; // Unix seconds.
@@ -64,7 +65,7 @@ import { err } from "./result.js";
64
65
 
65
66
  /**
66
67
  * @typedef {{
67
- * readonly id: string;
68
+ * readonly id: import("./ids.js").EntityId;
68
69
  * readonly nickname: string | null;
69
70
  * readonly algorithm: string;
70
71
  * readonly createdAt: number; // Unix seconds.
@@ -74,7 +75,7 @@ import { err } from "./result.js";
74
75
 
75
76
  /**
76
77
  * @typedef {{
77
- * readonly id: string;
78
+ * readonly id: import("./ids.js").EntityId;
78
79
  * readonly name: string;
79
80
  * readonly slug: string;
80
81
  * readonly created_at: number; // Unix seconds.
@@ -83,7 +84,7 @@ import { err } from "./result.js";
83
84
 
84
85
  /**
85
86
  * @typedef {{
86
- * readonly id: string;
87
+ * readonly id: import("./ids.js").EntityId;
87
88
  * readonly role: string;
88
89
  * readonly created_at: number; // Unix seconds.
89
90
  * readonly organization: TsAuthOrganization | null;
@@ -320,7 +321,7 @@ export class TsAuthUsersClient {
320
321
  ...(options.limit === undefined ? {} : { limit: options.limit }),
321
322
  ...(options.startingAfter === undefined
322
323
  ? {}
323
- : { starting_after: options.startingAfter }),
324
+ : { starting_after: requireId(options.startingAfter, "startingAfter") }),
324
325
  },
325
326
  ...(options.signal === undefined ? {} : { signal: options.signal }),
326
327
  });
@@ -586,7 +587,7 @@ export class TsAuthSessionsClient {
586
587
 
587
588
  /**
588
589
  * @typedef {{
589
- * readonly id: string;
590
+ * readonly id: import("./ids.js").EntityId;
590
591
  * readonly name: string;
591
592
  * readonly api_key: string; // One-time full sk_ bearer; only the creating call returns it.
592
593
  * readonly scopes: readonly string[];
@@ -595,7 +596,7 @@ export class TsAuthSessionsClient {
595
596
 
596
597
  /**
597
598
  * @typedef {{
598
- * readonly id: string;
599
+ * readonly id: import("./ids.js").EntityId;
599
600
  * readonly name: string;
600
601
  * readonly scopes: readonly string[];
601
602
  * readonly created_at: number; // Unix seconds.
@@ -674,9 +675,9 @@ export class TsAuthApiKeysClient {
674
675
 
675
676
  /**
676
677
  * @typedef {{
677
- * readonly id: string;
678
- * readonly organization_id: string;
679
- * readonly user_id: string;
678
+ * readonly id: import("./ids.js").EntityId;
679
+ * readonly organization_id: import("./ids.js").EntityId;
680
+ * readonly user_id: import("./ids.js").EntityId;
680
681
  * readonly role: string;
681
682
  * readonly created_at: number; // Unix seconds.
682
683
  * }} TsAuthMembership
@@ -685,7 +686,7 @@ export class TsAuthApiKeysClient {
685
686
  /**
686
687
  * @typedef {TsAuthMembership & {
687
688
  * readonly user: {
688
- * readonly id: string;
689
+ * readonly id: import("./ids.js").EntityId;
689
690
  * readonly email: string;
690
691
  * readonly name: string;
691
692
  * } | null;
@@ -694,8 +695,8 @@ export class TsAuthApiKeysClient {
694
695
 
695
696
  /**
696
697
  * @typedef {{
697
- * readonly id: string;
698
- * readonly organization_id: string;
698
+ * readonly id: import("./ids.js").EntityId;
699
+ * readonly organization_id: import("./ids.js").EntityId;
699
700
  * readonly email: string;
700
701
  * readonly role: string;
701
702
  * readonly status: string;
@@ -706,14 +707,14 @@ export class TsAuthApiKeysClient {
706
707
 
707
708
  /**
708
709
  * @typedef {{
709
- * readonly id: string;
710
- * readonly organization_id: string;
710
+ * readonly id: import("./ids.js").EntityId;
711
+ * readonly organization_id: import("./ids.js").EntityId;
711
712
  * readonly domain: string;
712
713
  * readonly created_at: number; // Unix seconds.
713
714
  * }} TsAuthOrganizationDomain
714
715
  */
715
716
 
716
- /** @typedef {{ readonly org_id: string }} TsAuthDomainEnrollment */
717
+ /** @typedef {{ readonly org_id: import("./ids.js").EntityId }} TsAuthDomainEnrollment */
717
718
 
718
719
  /**
719
720
  * @typedef {{
@@ -730,7 +731,7 @@ export class TsAuthApiKeysClient {
730
731
 
731
732
  /**
732
733
  * @typedef {{
733
- * readonly user_id: string;
734
+ * readonly user_id: import("./ids.js").EntityId;
734
735
  * readonly role: string;
735
736
  * }} TsAuthCreateMembershipRequest
736
737
  */
@@ -750,7 +751,7 @@ export class TsAuthApiKeysClient {
750
751
 
751
752
  /**
752
753
  * @typedef {{
753
- * readonly user_id: string;
754
+ * readonly user_id: import("./ids.js").EntityId;
754
755
  * }} TsAuthEnrollRequest
755
756
  */
756
757
 
@@ -844,7 +845,7 @@ export class TsAuthOrganizationsClient {
844
845
  * @returns {Promise<TerraScaleResult<TsAuthMembership>>}
845
846
  */
846
847
  createMembership(organizationId, request) {
847
- requireText(request?.user_id, "user_id");
848
+ requireId(request?.user_id, "user_id");
848
849
  requireText(request?.role, "role");
849
850
  return this.#http.request({
850
851
  method: "POST",
@@ -982,7 +983,7 @@ export class TsAuthOrganizationsClient {
982
983
  * @returns {Promise<TerraScaleResult<TsAuthDomainEnrollment>>}
983
984
  */
984
985
  enrollDomain(domain, request) {
985
- requireText(request?.user_id, "user_id");
986
+ requireId(request?.user_id, "user_id");
986
987
  return this.#http.request({
987
988
  method: "POST",
988
989
  path: `v1/${this.#slug}/domains/${segment(domain, "domain")}/enroll`,
@@ -1028,8 +1029,8 @@ export class TsAuthOrganizationsClient {
1028
1029
  */
1029
1030
  /**
1030
1031
  * @typedef {{
1031
- * readonly id: string;
1032
- * readonly client_id: string;
1032
+ * readonly id: import("./ids.js").EntityId;
1033
+ * readonly client_id: import("./ids.js").EntityId;
1033
1034
  * readonly name: string;
1034
1035
  * readonly redirect_uris: readonly string[];
1035
1036
  * readonly post_logout_redirect_uris: readonly string[];
@@ -1146,7 +1147,7 @@ export class TsAuthOauthClientsClient {
1146
1147
  /**
1147
1148
  * @typedef {{
1148
1149
  * readonly audience: string;
1149
- * readonly allowed_client_ids: readonly string[];
1150
+ * readonly allowed_client_ids: readonly import("./ids.js").EntityId[];
1150
1151
  * readonly allowed_grant_types: readonly TsAuthOauthGrantType[];
1151
1152
  * readonly allowed_scopes: readonly string[];
1152
1153
  * }} TsAuthCreateOauthResourceRequest
@@ -1154,7 +1155,7 @@ export class TsAuthOauthClientsClient {
1154
1155
  /**
1155
1156
  * @typedef {{
1156
1157
  * readonly expected_auth_epoch: number;
1157
- * readonly allowed_client_ids?: readonly string[] | undefined;
1158
+ * readonly allowed_client_ids?: readonly import("./ids.js").EntityId[] | undefined;
1158
1159
  * readonly allowed_grant_types?: readonly TsAuthOauthGrantType[] | undefined;
1159
1160
  * readonly allowed_scopes?: readonly string[] | undefined;
1160
1161
  * readonly enabled?: boolean | undefined;
@@ -1163,8 +1164,8 @@ export class TsAuthOauthClientsClient {
1163
1164
  */
1164
1165
  /**
1165
1166
  * @typedef {TsAuthCreateOauthResourceRequest & {
1166
- * readonly id: string;
1167
- * readonly introspection_client_id: string;
1167
+ * readonly id: import("./ids.js").EntityId;
1168
+ * readonly introspection_client_id: import("./ids.js").EntityId;
1168
1169
  * readonly token_endpoint_auth_method: "client_secret_basic";
1169
1170
  * readonly auth_epoch: number;
1170
1171
  * readonly enabled: boolean;
@@ -1265,11 +1266,11 @@ export class TsAuthOauthResourcesClient {
1265
1266
 
1266
1267
  /**
1267
1268
  * @typedef {{
1268
- * readonly id: string;
1269
+ * readonly id: import("./ids.js").EntityId;
1269
1270
  * readonly type: "oidc" | "saml";
1270
1271
  * readonly name: string;
1271
1272
  * readonly enabled: boolean;
1272
- * readonly org_id: string | null;
1273
+ * readonly org_id: import("./ids.js").EntityId | null;
1273
1274
  * readonly issuer_url: string | null;
1274
1275
  * readonly client_id: string | null;
1275
1276
  * readonly authorization_endpoint: string | null;
@@ -1342,7 +1343,7 @@ export class TsAuthConnectionsClient {
1342
1343
  requireText(request?.name, "name");
1343
1344
  if (request.type === "oidc") {
1344
1345
  requireAbsoluteHttpUrl(request.issuer_url, "issuer_url");
1345
- requireText(request.client_id, "client_id");
1346
+ requireText(request.client_id, "client_id"); // This client belongs to the external IdP.
1346
1347
  requireText(request.client_secret, "client_secret");
1347
1348
  } else {
1348
1349
  requireText(request.idp_entity_id, "idp_entity_id");
@@ -1399,16 +1400,16 @@ export class TsAuthConnectionsClient {
1399
1400
 
1400
1401
  /**
1401
1402
  * @typedef {{
1402
- * readonly id: string;
1403
- * readonly org_id: string | null;
1403
+ * readonly id: import("./ids.js").EntityId;
1404
+ * readonly org_id: import("./ids.js").EntityId | null;
1404
1405
  * readonly token: string; // One-time SCIM bearer; only create and rotate return it.
1405
1406
  * }} TsAuthDirectoryWithToken
1406
1407
  */
1407
1408
 
1408
1409
  /**
1409
1410
  * @typedef {{
1410
- * readonly id: string;
1411
- * readonly org_id: string | null;
1411
+ * readonly id: import("./ids.js").EntityId;
1412
+ * readonly org_id: import("./ids.js").EntityId | null;
1412
1413
  * readonly created_at: number; // Unix seconds.
1413
1414
  * }} TsAuthDirectory
1414
1415
  */
@@ -1439,7 +1440,7 @@ export class TsAuthDirectoriesClient {
1439
1440
  * @returns {Promise<TerraScaleResult<TsAuthDirectoryWithToken>>}
1440
1441
  */
1441
1442
  create(request = {}) {
1442
- if (request.org_id !== undefined) requireText(request.org_id, "org_id");
1443
+ if (request.org_id !== undefined) requireId(request.org_id, "org_id");
1443
1444
  return this.#http.request({
1444
1445
  method: "POST",
1445
1446
  path: `v1/${this.#slug}/directories`,
@@ -1488,7 +1489,7 @@ export class TsAuthDirectoriesClient {
1488
1489
 
1489
1490
  /**
1490
1491
  * @typedef {{
1491
- * readonly id: string;
1492
+ * readonly id: import("./ids.js").EntityId;
1492
1493
  * readonly name: string;
1493
1494
  * readonly claims: Record<string, string>;
1494
1495
  * readonly lifetime_seconds: number;
@@ -1600,12 +1601,12 @@ export class TsAuthJwtTemplatesClient {
1600
1601
 
1601
1602
  /**
1602
1603
  * @param {string} name
1603
- * @param {{ readonly user_id: string }} request
1604
+ * @param {{ readonly user_id: import("./ids.js").EntityId }} request
1604
1605
  * @returns {Promise<TerraScaleResult<TsAuthJwtTokenResponse>>}
1605
1606
  */
1606
1607
  issueToken(name, request) {
1607
1608
  validateJwtTemplateName(name);
1608
- requireText(request?.user_id, "user_id");
1609
+ requireId(request?.user_id, "user_id");
1609
1610
  return this.#http.request({
1610
1611
  method: "POST",
1611
1612
  path: `v1/${this.#slug}/jwt-templates/${segment(name, "template name")}/tokens`,
@@ -1619,7 +1620,7 @@ export class TsAuthJwtTemplatesClient {
1619
1620
 
1620
1621
  /**
1621
1622
  * @typedef {{
1622
- * readonly id: string;
1623
+ * readonly id: import("./ids.js").EntityId;
1623
1624
  * readonly url: string;
1624
1625
  * readonly events: readonly string[];
1625
1626
  * readonly signing_secret: string; // One-time HMAC key for verifying deliveries; only create returns it.
@@ -1628,7 +1629,7 @@ export class TsAuthJwtTemplatesClient {
1628
1629
 
1629
1630
  /**
1630
1631
  * @typedef {{
1631
- * readonly id: string;
1632
+ * readonly id: import("./ids.js").EntityId;
1632
1633
  * readonly url: string;
1633
1634
  * readonly events: readonly string[];
1634
1635
  * readonly enabled: boolean;
@@ -1638,9 +1639,9 @@ export class TsAuthJwtTemplatesClient {
1638
1639
 
1639
1640
  /**
1640
1641
  * @typedef {{
1641
- * readonly id: string; // endpointId:eventId
1642
- * readonly endpoint_id: string;
1643
- * readonly event_id: string;
1642
+ * readonly id: `${import("./ids.js").EntityId}:${import("./ids.js").EntityId}`; // endpointId:eventId delivery selector.
1643
+ * readonly endpoint_id: import("./ids.js").EntityId;
1644
+ * readonly event_id: import("./ids.js").EntityId;
1644
1645
  * readonly attempts: number;
1645
1646
  * readonly last_error?: string | undefined;
1646
1647
  * readonly updated_at: number; // Unix seconds.
@@ -1741,7 +1742,7 @@ export class TsAuthWebhooksClient {
1741
1742
  replayDeadLetter(entryId) {
1742
1743
  return this.#http.request({
1743
1744
  method: "POST",
1744
- path: `v1/${this.#slug}/webhooks/dead-letter/${segment(entryId, "dead-letter id")}/replay`,
1745
+ path: `v1/${this.#slug}/webhooks/dead-letter/${encodeURIComponent(requireDeadLetterId(entryId))}/replay`,
1745
1746
  routeName: "tsauth.webhooks.replayDeadLetter",
1746
1747
  });
1747
1748
  }
@@ -1751,10 +1752,10 @@ export class TsAuthWebhooksClient {
1751
1752
 
1752
1753
  /**
1753
1754
  * @typedef {{
1754
- * readonly id: string;
1755
- * readonly instance_id: string;
1755
+ * readonly id: import("./ids.js").EntityId;
1756
+ * readonly instance_id: import("./ids.js").EntityId;
1756
1757
  * readonly type: string;
1757
- * readonly actor_id?: string | undefined; // Omitted from the wire when null.
1758
+ * readonly actor_id?: import("./ids.js").EntityId | undefined; // Omitted from the wire when null.
1758
1759
  * readonly data: Record<string, unknown>;
1759
1760
  * readonly created_at: number; // Unix seconds.
1760
1761
  * readonly integrity_version: number;
@@ -1918,7 +1919,7 @@ export class TsAuthBillingClient {
1918
1919
 
1919
1920
  /**
1920
1921
  * @typedef {{
1921
- * readonly id: string;
1922
+ * readonly id: import("./ids.js").EntityId;
1922
1923
  * readonly slug: string;
1923
1924
  * readonly name: string;
1924
1925
  * readonly logo_url?: string | null | undefined;
@@ -2033,7 +2034,7 @@ export class TsAuthInstanceClient {
2033
2034
 
2034
2035
  /**
2035
2036
  * @typedef {{
2036
- * readonly client_id: string;
2037
+ * readonly client_id: import("./ids.js").EntityId;
2037
2038
  * readonly redirect_uri: string;
2038
2039
  * readonly response_type: "code";
2039
2040
  * readonly scope?: string | undefined;
@@ -2108,20 +2109,20 @@ export class TsAuthInstanceClient {
2108
2109
  * readonly active: true;
2109
2110
  * readonly token_type: "Bearer";
2110
2111
  * readonly iss: string;
2111
- * readonly sub: string;
2112
+ * readonly sub: import("./ids.js").EntityId;
2112
2113
  * readonly aud: string;
2113
- * readonly client_id: string;
2114
+ * readonly client_id: import("./ids.js").EntityId;
2114
2115
  * readonly scope: string;
2115
2116
  * readonly exp: number;
2116
2117
  * readonly iat: number;
2117
- * readonly sid: string | null;
2118
+ * readonly sid: import("./ids.js").EntityId | null;
2118
2119
  * readonly principal_kind: "user" | "service";
2119
2120
  * }} TsAuthOidcAccessIntrospection
2120
2121
  */
2121
2122
  /**
2122
2123
  * @typedef {Omit<TsAuthOidcAccessIntrospection, "token_type" | "principal_kind"> & {
2123
2124
  * readonly token_type: "refresh_token";
2124
- * readonly family_id: string;
2125
+ * readonly family_id: import("./ids.js").EntityId;
2125
2126
  * }} TsAuthOidcRefreshIntrospection
2126
2127
  */
2127
2128
  /**
@@ -2132,7 +2133,7 @@ export class TsAuthInstanceClient {
2132
2133
  /**
2133
2134
  * @typedef {Omit<TsAuthOidcAccessIntrospection, "sid" | "principal_kind"> &
2134
2135
  * (
2135
- * | { readonly principal_kind: "user"; readonly sid: string }
2136
+ * | { readonly principal_kind: "user"; readonly sid: import("./ids.js").EntityId }
2136
2137
  * | { readonly principal_kind: "service"; readonly sid: null }
2137
2138
  * )} TsAuthOidcResourceAccessIntrospection
2138
2139
  */
@@ -2144,14 +2145,14 @@ export class TsAuthInstanceClient {
2144
2145
  * @typedef {{
2145
2146
  * readonly issuer: string; // Configured canonical provider issuer, independently trusted by the caller.
2146
2147
  * readonly audience: string; // Exact registered resource URI; never derived from the response.
2147
- * readonly introspection_client_id: string;
2148
+ * readonly introspection_client_id: import("./ids.js").EntityId;
2148
2149
  * readonly introspection_secret: string;
2149
2150
  * }} TsAuthOidcResourceCredentials
2150
2151
  */
2151
2152
 
2152
2153
  /**
2153
2154
  * @typedef {{
2154
- * readonly sub: string;
2155
+ * readonly sub: import("./ids.js").EntityId;
2155
2156
  * readonly email?: string | undefined;
2156
2157
  * readonly email_verified?: boolean | undefined;
2157
2158
  * readonly name?: string | undefined;
@@ -2358,7 +2359,6 @@ export class TsAuthOidcClient {
2358
2359
  routeName: "tsauth.oidc.resourceIntrospect",
2359
2360
  formBody: form,
2360
2361
  resourceIntrospectionBasic: basic,
2361
- issuer: expectedIssuer,
2362
2362
  }),
2363
2363
  /** @returns {value is TsAuthOidcResourceIntrospection} */
2364
2364
  (value) => {
@@ -2518,9 +2518,7 @@ export class TsAuthClient {
2518
2518
  );
2519
2519
  }
2520
2520
  const slug = validateInstanceSlug(config?.instanceSlug);
2521
- this.#http = new TsAuthHttp(config, {
2522
- authReverseProxy: config.useReverseProxy === true,
2523
- });
2521
+ this.#http = new TsAuthHttp(config);
2524
2522
  requireBareOrigin(this.#http.config.apiBaseUrl);
2525
2523
  this.users = new TsAuthUsersClient(this.#http, slug);
2526
2524
  this.sessions = new TsAuthSessionsClient(this.#http, slug);
@@ -2726,6 +2724,7 @@ const utf8Encoder = new TextEncoder();
2726
2724
  * @returns {string}
2727
2725
  */
2728
2726
  function segment(value, name) {
2727
+ if (name.endsWith(" id")) return requireId(value, name);
2729
2728
  if (
2730
2729
  typeof value !== "string" ||
2731
2730
  value.length === 0 ||
@@ -2840,7 +2839,7 @@ function validateJwtTemplateClaims(claims) {
2840
2839
  */
2841
2840
  function clientCredentialForm(clientId, clientSecret) {
2842
2841
  return new URLSearchParams({
2843
- client_id: requireText(clientId, "clientId"),
2842
+ client_id: requireId(clientId, "clientId"),
2844
2843
  client_secret: requireText(clientSecret, "clientSecret"),
2845
2844
  });
2846
2845
  }
@@ -3166,7 +3165,7 @@ function validateOauthResourceRequest(request, update) {
3166
3165
  ]);
3167
3166
  if (!update) requireResourceAudience(request.audience);
3168
3167
  for (const [field, accepts] of /** @type {const} */ ([
3169
- ["allowed_client_ids", oauthText],
3168
+ ["allowed_client_ids", isValidId],
3170
3169
  ["allowed_grant_types", oauthGrant],
3171
3170
  ["allowed_scopes", oauthScopeToken],
3172
3171
  ])) {
@@ -3198,7 +3197,7 @@ function validateAuthorizationRequest(request) {
3198
3197
  "code_challenge_method",
3199
3198
  "prompt",
3200
3199
  ]);
3201
- requireText(request.client_id, "client_id");
3200
+ requireId(request.client_id, "client_id");
3202
3201
  requireText(request.redirect_uri, "redirect_uri");
3203
3202
  if (request.response_type !== "code")
3204
3203
  throw new TypeError("response_type must be code.");
@@ -3227,7 +3226,8 @@ function validateAuthorizationRequest(request) {
3227
3226
  * @returns {string}
3228
3227
  */
3229
3228
  function resourceBasic(clientId, secret) {
3230
- if (!oauthText(clientId) || !oauthText(secret))
3229
+ requireId(clientId, "introspection_client_id");
3230
+ if (!oauthText(secret))
3231
3231
  throw new TypeError(
3232
3232
  "Resource introspection credentials must be nonempty well-formed text.",
3233
3233
  );
@@ -3264,7 +3264,7 @@ const clientViewFields = [
3264
3264
  function isOauthClientView(value) {
3265
3265
  return (
3266
3266
  exactOauthFields(value, clientViewFields) &&
3267
- [value.id, value.client_id, value.name].every(oauthText) &&
3267
+ isValidId(value.id) && isValidId(value.client_id) && oauthText(value.name) &&
3268
3268
  [
3269
3269
  value.redirect_uris,
3270
3270
  value.post_logout_redirect_uris,
@@ -3324,12 +3324,12 @@ const resourceViewFields = [
3324
3324
  function isOauthResourceView(value) {
3325
3325
  return (
3326
3326
  exactOauthFields(value, resourceViewFields) &&
3327
- oauthText(value.id) &&
3327
+ isValidId(value.id) &&
3328
3328
  validResourceAudience(value.audience) &&
3329
- oauthStrings(value.allowed_client_ids) &&
3329
+ Array.isArray(value.allowed_client_ids) && value.allowed_client_ids.every(isValidId) &&
3330
3330
  oauthGrantsList(value.allowed_grant_types) &&
3331
3331
  oauthStrings(value.allowed_scopes) &&
3332
- oauthText(value.introspection_client_id) &&
3332
+ isValidId(value.introspection_client_id) &&
3333
3333
  value.token_endpoint_auth_method === "client_secret_basic" &&
3334
3334
  exactUnsignedInteger(value.auth_epoch) &&
3335
3335
  typeof value.enabled === "boolean" &&
@@ -3442,13 +3442,13 @@ function isAccessIntrospection(value) {
3442
3442
  exactOauthFields(value, [...introspectionFields, "principal_kind"]) &&
3443
3443
  value.active === true &&
3444
3444
  value.token_type === "Bearer" &&
3445
- [value.iss, value.sub, value.aud, value.client_id].every(oauthText) &&
3445
+ [value.iss, value.aud].every(oauthText) && [value.sub, value.client_id].every(isValidId) &&
3446
3446
  typeof value.scope === "string" &&
3447
3447
  exactUnsignedInteger(value.exp) &&
3448
3448
  exactUnsignedInteger(value.iat) &&
3449
3449
  value.exp > value.iat &&
3450
3450
  ((value.principal_kind === "user" &&
3451
- (value.sid === null || oauthText(value.sid))) ||
3451
+ (value.sid === null || isValidId(value.sid))) ||
3452
3452
  (value.principal_kind === "service" &&
3453
3453
  value.sid === null &&
3454
3454
  value.sub === value.client_id))
@@ -3462,7 +3462,7 @@ function isResourceAccessIntrospection(value) {
3462
3462
  return (
3463
3463
  isAccessIntrospection(value) &&
3464
3464
  (value.principal_kind === "user"
3465
- ? oauthText(value.sid)
3465
+ ? isValidId(value.sid)
3466
3466
  : value.sid === null && value.sub === value.client_id)
3467
3467
  );
3468
3468
  }
@@ -3475,14 +3475,12 @@ function isRefreshIntrospection(value) {
3475
3475
  exactOauthFields(value, [...introspectionFields, "family_id"]) &&
3476
3476
  value.active === true &&
3477
3477
  value.token_type === "refresh_token" &&
3478
- [value.iss, value.sub, value.aud, value.client_id, value.family_id].every(
3479
- oauthText,
3480
- ) &&
3478
+ [value.iss, value.aud].every(oauthText) && [value.sub, value.client_id, value.family_id].every(isValidId) &&
3481
3479
  typeof value.scope === "string" &&
3482
3480
  exactUnsignedInteger(value.exp) &&
3483
3481
  exactUnsignedInteger(value.iat) &&
3484
3482
  value.exp > value.iat &&
3485
- (value.sid === null || oauthText(value.sid))
3483
+ (value.sid === null || isValidId(value.sid))
3486
3484
  );
3487
3485
  }
3488
3486
  /**
@@ -0,0 +1,111 @@
1
+ import { requireId } from "../ids.js";
2
+ import { requireName } from "./ids.js";
3
+ import { flag, list, nullable, numeric, optional, record, text } from "./management-decode.js";
4
+ import { members, mutationOptions, pageQuery } from "./management-validation.js";
5
+ import { malformed, object } from "./decode.js";
6
+ import { stringifyExactJson } from "./json.js";
7
+ /** @import { V1Transport } from "./transport.js" */
8
+ /** @import { V1RequestOptions } from "./terrabase-v1.js" */
9
+ const backup = record({ id: requireId, databaseId: requireId, status: text, trigger: text, sizeBytes: numeric, createdAt: text });
10
+ const backupPage = record({ items: list(backup), next: nullable(text) });
11
+ const policy = record({ fullBackupCron: nullable(text), retentionDays: numeric, disabled: flag, updatedAt: nullable(text) });
12
+ const window = record({ earliest: text, latest: text });
13
+ const plan = record({ window, pointInTime: text, snapshotAt: text, latestSegmentAt: nullable(text), segmentCount: numeric, historyCheck: text });
14
+ const restored = record({ id: requireId, projectId: requireId, name: text, branchedFrom: nullable(requireId), createdAt: text,
15
+ homeRegion: optional(nullable(text)), replicaRegions: optional(nullable(list(text))), placementVersion: optional(numeric), replicas: list(text) });
16
+ const destination = record({ endpoint: text, region: text, bucket: text, mode: text, updatedAt: text });
17
+ /** @typedef {ReturnType<typeof backup>} V1Backup */
18
+ /** @typedef {ReturnType<typeof backupPage>} V1BackupPage */
19
+ /** @typedef {ReturnType<typeof policy>} V1BackupPolicy */
20
+ /** @typedef {ReturnType<typeof window>} V1RestoreWindow */
21
+ /** @typedef {ReturnType<typeof plan>} V1PointInTimePlan */
22
+ /** @typedef {ReturnType<typeof restored>} V1RestoredDatabase */
23
+ /** @typedef {ReturnType<typeof destination>} V1BackupDestination */
24
+ /** @typedef {{ readonly fullBackupCron?: string | null; readonly retentionDays?: number | null; readonly disabled?: boolean | null }} V1BackupPolicyRequest */
25
+ /** @typedef {{ readonly pointInTime: string; readonly destination?: "platform" | "customer" | null }} V1PointInTimeRequest */
26
+ /** @typedef {V1PointInTimeRequest & { readonly name?: string | null }} V1PointInTimeRestoreRequest */
27
+ /** @typedef {{ readonly endpoint: string; readonly region: string; readonly bucket: string; readonly accessKeyId: string; readonly secretKey: string; readonly mode: "both" | "customer" }} V1BackupDestinationRequest */
28
+ /** @param {unknown} value @returns {"platform" | "customer"} */
29
+ function source(value) {
30
+ if (value !== "platform" && value !== "customer") throw new TypeError("destination must be platform or customer.");
31
+ return value;
32
+ }
33
+ /** @param {V1PointInTimeRequest} request */
34
+ function pointInTime(request) {
35
+ if (typeof request.pointInTime !== "string" || !Number.isFinite(Date.parse(request.pointInTime))) throw new TypeError("pointInTime must be an RFC 3339 instant.");
36
+ if (request.destination !== undefined && request.destination !== null) source(request.destination);
37
+ }
38
+
39
+ /** Backups and explicit point-in-time restore sources for one database. */
40
+ export class V1Backups {
41
+ /** @type {V1Transport} */
42
+ #transport;
43
+ /** @type {{database: string}} */
44
+ #params;
45
+ /** @param {V1Transport} transport @param {string} database */
46
+ constructor(transport, database) { this.#transport = transport; this.#params = { database: requireName("database", database) }; Object.freeze(this); }
47
+ /** @param {V1RequestOptions} [options] @returns {Promise<V1Backup>} */
48
+ async create(options = {}) {
49
+ mutationOptions(options);
50
+ return backup((await this.#transport.call("createBackup", this.#params, { signal: options.signal })).value);
51
+ }
52
+ /** @param {import("./catalog.js").V1DatabaseListRequest} [request] @param {V1RequestOptions} [options] @returns {Promise<V1BackupPage>} */
53
+ async list(request = {}, options = {}) { return backupPage((await this.#transport.call("listBackups", this.#params, { query: pageQuery(request), signal: options.signal })).value); }
54
+ /** @param {string} id @param {{readonly name?: string | null}} [request] @param {V1RequestOptions} [options] @returns {Promise<V1RestoredDatabase>} */
55
+ async restore(id, request = {}, options = {}) {
56
+ members(request, ["name"]);
57
+ if (request.name !== undefined && request.name !== null) requireName("database", request.name);
58
+ mutationOptions(options);
59
+ return restored((await this.#transport.call("restoreBackup", { ...this.#params, backup: requireId(id) }, {
60
+ body: stringifyExactJson(request, "restore"), signal: options.signal,
61
+ })).value);
62
+ }
63
+ /** @param {V1RequestOptions} [options] @returns {Promise<V1BackupPolicy>} */
64
+ async getPolicy(options = {}) { return policy((await this.#transport.call("getBackupPolicy", this.#params, { signal: options.signal })).value); }
65
+ /** @param {V1BackupPolicyRequest} request @param {V1RequestOptions} [options] @returns {Promise<V1BackupPolicy>} */
66
+ async setPolicy(request, options = {}) {
67
+ members(request, ["fullBackupCron", "retentionDays", "disabled"]);
68
+ mutationOptions(options);
69
+ return policy((await this.#transport.call("setBackupPolicy", this.#params, { body: stringifyExactJson(request, "backup policy"), signal: options.signal })).value);
70
+ }
71
+ /** @param {{readonly destination?: "platform" | "customer"}} [request] @param {V1RequestOptions} [options] @returns {Promise<V1RestoreWindow>} */
72
+ async window(request = {}, options = {}) {
73
+ members(request, ["destination"]);
74
+ const query = new URLSearchParams();
75
+ if (request.destination !== undefined) query.set("destination", source(request.destination));
76
+ return window((await this.#transport.call("getRestoreWindow", this.#params, { query, signal: options.signal })).value);
77
+ }
78
+ /** @param {V1PointInTimeRequest} request @param {V1RequestOptions} [options] @returns {Promise<V1PointInTimePlan>} */
79
+ async plan(request, options = {}) {
80
+ members(request, ["pointInTime", "destination"]);
81
+ pointInTime(request);
82
+ return plan((await this.#transport.call("planPointInTime", this.#params, { body: stringifyExactJson(request, "restore plan"), signal: options.signal })).value);
83
+ }
84
+ /** @param {V1PointInTimeRestoreRequest} request @param {V1RequestOptions} [options] @returns {Promise<V1RestoredDatabase>} */
85
+ async restorePointInTime(request, options = {}) {
86
+ members(request, ["pointInTime", "destination", "name"]);
87
+ pointInTime(request);
88
+ if (request.name !== undefined && request.name !== null) requireName("database", request.name);
89
+ mutationOptions(options);
90
+ return restored((await this.#transport.call("restorePointInTime", this.#params, { body: stringifyExactJson(request, "point-in-time restore"), signal: options.signal })).value);
91
+ }
92
+ }
93
+ /** Write-only destination credentials are sent only to the configured API, never retained in the client. */
94
+ export class V1BackupDestinationClient {
95
+ /** @type {V1Transport} */
96
+ #transport;
97
+ /** @param {V1Transport} transport */
98
+ constructor(transport) { this.#transport = transport; Object.freeze(this); }
99
+ /** @param {V1RequestOptions} [options] @returns {Promise<V1BackupDestination>} */
100
+ async get(options = {}) { return destination((await this.#transport.call("getBackupDestination", {}, { signal: options.signal })).value); }
101
+ /** @param {V1BackupDestinationRequest} request @param {V1RequestOptions} [options] @returns {Promise<V1BackupDestination>} */
102
+ async set(request, options = {}) {
103
+ members(request, ["endpoint", "region", "bucket", "accessKeyId", "secretKey", "mode"]);
104
+ mutationOptions(options);
105
+ const raw = object((await this.#transport.call("setBackupDestination", {}, { body: stringifyExactJson(request, "backup destination"), signal: options.signal })).value, "backup destination");
106
+ if (raw.accessKeyId !== undefined || raw.secretKey !== undefined) malformed("write-only destination credentials");
107
+ return destination(raw);
108
+ }
109
+ /** @param {V1RequestOptions} [options] @returns {Promise<void>} */
110
+ async delete(options = {}) { mutationOptions(options); await this.#transport.call("deleteBackupDestination", {}, { signal: options.signal }); }
111
+ }