@shivaedev/platform 0.25.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +76 -3
  3. package/dist/http-client/body.d.ts +7 -0
  4. package/dist/http-client/body.d.ts.map +1 -0
  5. package/dist/http-client/body.js +32 -0
  6. package/dist/http-client/body.js.map +1 -0
  7. package/dist/http-client/errors.d.ts +51 -0
  8. package/dist/http-client/errors.d.ts.map +1 -0
  9. package/dist/http-client/errors.js +25 -0
  10. package/dist/http-client/errors.js.map +1 -0
  11. package/dist/http-client/filterStatus.d.ts +4 -0
  12. package/dist/http-client/filterStatus.d.ts.map +1 -0
  13. package/dist/http-client/filterStatus.js +38 -0
  14. package/dist/http-client/filterStatus.js.map +1 -0
  15. package/dist/http-client/nextLink.d.ts +2 -0
  16. package/dist/http-client/nextLink.d.ts.map +1 -0
  17. package/dist/http-client/nextLink.js +11 -0
  18. package/dist/http-client/nextLink.js.map +1 -0
  19. package/dist/http-client/paginate.d.ts +5 -0
  20. package/dist/http-client/paginate.d.ts.map +1 -0
  21. package/dist/http-client/paginate.js +31 -0
  22. package/dist/http-client/paginate.js.map +1 -0
  23. package/dist/http-client/parseRetryAfter.d.ts +3 -0
  24. package/dist/http-client/parseRetryAfter.d.ts.map +1 -0
  25. package/dist/http-client/parseRetryAfter.js +17 -0
  26. package/dist/http-client/parseRetryAfter.js.map +1 -0
  27. package/dist/rpc/publicConfig.d.ts +5 -9
  28. package/dist/rpc/publicConfig.d.ts.map +1 -1
  29. package/dist/server/MigrationOptions.d.ts +2 -0
  30. package/dist/server/MigrationOptions.d.ts.map +1 -1
  31. package/dist/server/appHost.d.ts +1 -1
  32. package/dist/server/better-auth/mount.d.ts +2 -2
  33. package/dist/server/better-auth/mount.d.ts.map +1 -1
  34. package/dist/server/better-auth/mount.js +2 -2
  35. package/dist/server/better-auth/mount.js.map +1 -1
  36. package/dist/server/better-auth/testing.d.ts +20 -3
  37. package/dist/server/better-auth/testing.d.ts.map +1 -1
  38. package/dist/server/better-auth/testing.js +17 -2
  39. package/dist/server/better-auth/testing.js.map +1 -1
  40. package/dist/server/better-auth/userProfiles.d.ts +14 -0
  41. package/dist/server/better-auth/userProfiles.d.ts.map +1 -0
  42. package/dist/server/better-auth/userProfiles.js +20 -0
  43. package/dist/server/better-auth/userProfiles.js.map +1 -0
  44. package/dist/server/databaseLayer.d.ts.map +1 -1
  45. package/dist/server/databaseLayer.js +42 -10
  46. package/dist/server/databaseLayer.js.map +1 -1
  47. package/dist/server/hostChanges.d.ts.map +1 -1
  48. package/dist/server/hostChanges.js +4 -2
  49. package/dist/server/hostChanges.js.map +1 -1
  50. package/dist/server/rpc-server/local.d.ts.map +1 -1
  51. package/dist/server/rpc-server/local.js +3 -2
  52. package/dist/server/rpc-server/local.js.map +1 -1
  53. package/dist/server/rpc-server/proxyRanges.d.ts +4 -0
  54. package/dist/server/rpc-server/proxyRanges.d.ts.map +1 -0
  55. package/dist/server/rpc-server/proxyRanges.js +62 -0
  56. package/dist/server/rpc-server/proxyRanges.js.map +1 -0
  57. package/dist/server/rpc-server/trustedProxy.d.ts.map +1 -1
  58. package/dist/server/rpc-server/trustedProxy.js +2 -58
  59. package/dist/server/rpc-server/trustedProxy.js.map +1 -1
  60. package/dist/server/rpc-server/trustedProxyRanges.d.ts +2 -0
  61. package/dist/server/rpc-server/trustedProxyRanges.d.ts.map +1 -0
  62. package/dist/server/rpc-server/trustedProxyRanges.js +5 -0
  63. package/dist/server/rpc-server/trustedProxyRanges.js.map +1 -0
  64. package/package.json +13 -13
  65. package/src/http-client/body.ts +37 -0
  66. package/src/http-client/errors.ts +23 -0
  67. package/src/http-client/filterStatus.ts +38 -0
  68. package/src/http-client/nextLink.ts +10 -0
  69. package/src/http-client/paginate.ts +39 -0
  70. package/src/http-client/parseRetryAfter.ts +17 -0
  71. package/src/server/MigrationOptions.ts +2 -0
  72. package/src/server/better-auth/mount.ts +6 -3
  73. package/src/server/better-auth/testing.ts +28 -2
  74. package/src/server/better-auth/userProfiles.ts +37 -0
  75. package/src/server/databaseLayer.ts +52 -12
  76. package/src/server/hostChanges.ts +4 -3
  77. package/src/server/rpc-server/local.ts +4 -2
  78. package/src/server/rpc-server/proxyRanges.ts +61 -0
  79. package/src/server/rpc-server/trustedProxy.ts +3 -57
  80. package/src/server/rpc-server/trustedProxyRanges.ts +5 -0
  81. package/src/server/testing/makePlatformIt.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.28.0 - 2026-10-25
4
+
5
+ ### Added
6
+
7
+ - effect-sql records the database release after successful migrations and coordinates worker drain with typed PostgreSQL heartbeat rows.
8
+ - effect-pg-boss pauses fetching while the database belongs to another release, finishes active jobs, and resumes when its release owns the database again.
9
+ - Platform migration options accept an optional `release` for deployment migration and worker readiness.
10
+ - Generated workers step aside for a newer release and drain on SIGTERM, with queue expiry, stop timeout and Railway draining configured together.
11
+
12
+ ## 0.27.0 - 2026-10-25
13
+
14
+ ### Added
15
+
16
+ - Look up other users' public names and images with `userProfiles(auth, userIds)` through Better Auth's adapter, without exposing email, role, session or account data.
17
+ - `@shivaedev/platform`: `authDatabase` accepts optional `max` and `application_name` pool settings.
18
+
19
+ ### Changed
20
+
21
+ - `@shivaedev/create-app`: Starter stories run the web host and worker with test-sized SQL, authentication and jobs pools.
22
+
23
+ ## 0.26.0 - 2026-10-25
24
+
25
+ ### Added
26
+
27
+ - `@shivaedev/effect-sql` exports `pendingPostgresMigrations` to read pending ledger migration IDs and names and schema component names and details.
28
+ - `@shivaedev/platform` logs migration wait start, periodic progress, completion and timeout through Effect's logger with pending migration and component annotations. `migrations.progressInterval` controls progress logging independently of polling and defaults to 30 seconds.
29
+ - Outbound HTTP client status errors, clock-based Retry-After parsing, lazy same-origin Link pagination, and bounded bytes, text and Schema-decoded JSON response readers.
30
+ - Let `signedInUser` create provider accounts without credentials and expose `storedProviderAccounts` to read account IDs and credential-presence booleans without returning secret values.
31
+
32
+ ### Changed
33
+
34
+ - `@shivaedev/create-app`'s starter worker uses the host's migration wait logs and keeps its job readiness line.
35
+
36
+ ## 0.25.4 - 2026-10-25
37
+
38
+ ### Changed
39
+
40
+ - Release with updated workspace dependencies.
41
+
42
+ ## 0.25.3 - 2026-10-25
43
+
44
+ ### Changed
45
+
46
+ - Release with updated workspace dependencies.
47
+
48
+ ## 0.25.2 - 2026-10-25
49
+
50
+ ### Changed
51
+
52
+ - Release with updated workspace dependencies.
53
+
54
+ ## 0.25.1 - 2026-10-25
55
+
56
+ ### Changed
57
+
58
+ - Release with updated workspace dependencies.
59
+
3
60
  ## 0.25.0 - 2026-10-25
4
61
 
5
62
  ### Added
package/README.md CHANGED
@@ -96,7 +96,7 @@ Omit `migrations` to host an app without a database. This form creates no SQL po
96
96
 
97
97
  `appHost` serves unauthenticated `GET /health`. It queries its SQL client on each request and returns `200` only when the query succeeds within two seconds; a missing SQL client, query failure or timeout returns `503`. Both responses have an empty body and `Cache-Control: no-store`. This route also works when the SQL client comes from the application’s `services` Layer. Use it for database readiness checks.
98
98
 
99
- Set `migrations.mode: "check"` on `appDatabase` or a database-backed `appHost` when a separate pre-deploy command owns migrations. Startup reads the ledger and fails with native `MigrationError` (`kind: "BadState"`) if any required migration ID or name is missing or a schema component is pending; it does not run migrations or create a ledger. Later ledger entries are allowed for rolling deployments. Use `migrations.mode: "wait"` for services that can start before that pre-deploy command finishes. It re-reads the ledger and every registered component every second until all are current, then starts the application. Set `migrations.pollInterval` and `migrations.waitTimeout` with Effect duration inputs to change the interval and five-minute timeout. Timeout fails with the same `MigrationError` as `"check"`; interruption stops polling and closes the pool. SQL failures fail immediately. With `mode` omitted or `"migrate"`, startup applies pending ledger migrations with a five-second lock timeout unless `migrations.lockTimeout` overrides it, then applies schema components outside the ledger transaction.
99
+ Set `migrations.mode: "check"` on `appDatabase` or a database-backed `appHost` when a separate pre-deploy command owns migrations. Startup reads the ledger and fails with native `MigrationError` (`kind: "BadState"`) if any required migration ID or name is missing or a schema component is pending; it does not run migrations or create a ledger. Later ledger entries are allowed for rolling deployments. Use `migrations.mode: "wait"` for services that can start before that pre-deploy command finishes. It re-reads the ledger and every registered component every second until all are current, then starts the application. Set `migrations.pollInterval` and `migrations.waitTimeout` with Effect duration inputs to change the interval and five-minute timeout. If the first check finds pending work, Effect’s logger emits `Waiting for database migrations` at info, then `Still waiting for database migrations` every 30 seconds independently of polling. Set `migrations.progressInterval` with an Effect duration input to change that progress interval. These lines carry `pendingMigrations` (ID and name) and `pendingComponents` (name and detail) annotations. A later successful check logs `Database migrations current` once; timeout logs `Timed out waiting for database migrations` with the pending annotations before failing. An initially current schema emits none of these lines. The host adds no connection details or secrets to these diagnostics. Timeout fails with the same `MigrationError` as `"check"`; interruption stops polling and closes the pool. SQL failures fail immediately. With `mode` omitted or `"migrate"`, startup applies pending ledger migrations with a five-second lock timeout unless `migrations.lockTimeout` overrides it, then applies schema components outside the ledger transaction.
100
100
 
101
101
  Pass `migrations.components` beside `loader` for storage managed by another engine. For pg-boss, register `pgBossSchema({ schema: "app_jobs" })` from `@shivaedev/effect-pg-boss/server/schema.ts`, then start every jobs Layer with `migrate: false`. `check` and `wait` use the same `MigrationError` readout for pending ledger IDs and component names and details, including pg-boss’s installed and required versions.
102
102
 
@@ -263,6 +263,18 @@ export const sessions = {
263
263
 
264
264
  The application's `auth` owns its storage and session configuration. A null lookup becomes a missing session. A thrown or rejected lookup is logged in redacted form and fails with the generic `AuthUnavailable` message. It remains a failure even on an RPC with optional authentication.
265
265
 
266
+ ### Reading other users' public profiles
267
+
268
+ Use `userProfiles(auth, userIds)` in server code to display the names of users who made changes. Pass the `auth` instance returned by `betterAuthMount` and a list of user IDs.
269
+
270
+ ```ts
271
+ import { userProfiles } from "@shivaedev/platform/server/better-auth/userProfiles.ts";
272
+
273
+ const profiles = yield* userProfiles(auth, changedByUserIds);
274
+ ```
275
+
276
+ The Effect returns an array of `{ id, name, image }`, with `image: null` when unset and unknown IDs omitted. It reads duplicate IDs once in one storage query through Better Auth's adapter, honoring the application's model and field mappings. An empty list returns an empty array without querying storage. Each lookup reads the current profile, including names changed through Better Auth. The result never includes email, role, session or account data. A storage failure logs its cause in redacted form and fails with a generic `AuthUnavailable`, without exposing provider exception details to the caller.
277
+
266
278
  ### Reading the signed-in user
267
279
 
268
280
  When a handler needs the current user's name and email, declare `AuthenticatedUser` and read `SessionUser`. `Identity` keeps its `{ id }` shape; the new middleware supplies both services for that invocation. Its provider must return `user: { id, name, email }`, described by `UserSessionShape`.
@@ -520,6 +532,33 @@ For native RPC serialization, `schemaSensitiveKeys(group, extraKeys?)` returns a
520
532
 
521
533
  For a native RPC server composed without `appHost`, `requestTracingLayer({ sensitive })` accepts a key predicate; `isSensitiveKey` supplies the default credential rules. `redactingErrorReporter(reporter, sensitive?)` wraps an Effect error reporter and passes it redacted failure and defect causes while preserving reporting hints such as severity and redacted attributes.
522
534
 
535
+ ### Calling outside APIs
536
+
537
+ Use Effect's HTTP client with `filterStatus`, `paginate` and the bounded body readers:
538
+
539
+ ```ts
540
+ import { Effect, Schema, Stream } from "effect";
541
+ import { HttpClient, HttpClientRequest } from "effect/http";
542
+ import { readBoundedJson } from "@shivaedev/platform/http-client/body.ts";
543
+ import { filterStatus } from "@shivaedev/platform/http-client/filterStatus.ts";
544
+ import { paginate } from "@shivaedev/platform/http-client/paginate.ts";
545
+
546
+ const items = Effect.gen(function* () {
547
+ const client = filterStatus(yield* HttpClient.HttpClient);
548
+ return yield* paginate(client, HttpClientRequest.get("https://api.example/items")).pipe(
549
+ Stream.mapEffect((response) => readBoundedJson(response, 64 * 1024, Schema.Array(Schema.String))),
550
+ Stream.runCollect,
551
+ Effect.map((pages) => pages.flat()),
552
+ );
553
+ });
554
+ ```
555
+
556
+ Provide a native client Layer such as `FetchHttpClient.layer` from `effect/http`. `filterStatus` passes 2xx responses through and fails other statuses with Schema tagged errors from `http-client/errors.ts`: `RemoteUnauthorized` (401), `RemoteNotFound` (404), `RemoteGone` (410), `RemoteRateLimited` (429, or 503 carrying `Retry-After`) and `RemoteHttpError` for other statuses. Errors carry the method, URL without its query string and status, without headers or body. `RemoteRateLimited.retryAfter` is an optional UTC `DateTime`: delay-seconds use Effect's clock, and HTTP dates name the instant directly. Missing or invalid values leave it undefined.
557
+
558
+ `paginate` follows quoted or unquoted `rel=next`, including headers with multiple links and relations, resolves relative links and stops at the last page. Taking one page sends one request. A next link on another origin fails with `PaginationLinkError` before that request is sent.
559
+
560
+ `readBoundedBody(response, limit)` returns bytes; `readBoundedText` decodes UTF-8, and `readBoundedJson(response, limit, schema)` decodes JSON through the supplied Schema. Limits are bytes. An oversized `Content-Length` fails with `ResponseBodyTooLarge` before reading the body; a chunked body that exceeds the limit fails during the read. The error carries `limit`.
561
+
523
562
  ### Promise callers: one application runtime
524
563
 
525
564
  Build the application Layer once. `runWithServices` supplies an invocation's overrides to subsequent calls on the same runtime, across Promise awaits:
@@ -593,6 +632,8 @@ const authentication = Effect.gen(function* () {
593
632
  });
594
633
  ```
595
634
 
635
+ `authDatabase(url, { max, application_name })` accepts an optional maximum connection count and PostgreSQL application name, passed to `pg.Pool`. Omit the settings to use pg’s defaults.
636
+
596
637
  Run this Effect in the host's scope. The pool logs idle-client errors at Debug and replaces failed connections on the next query; closing the scope disconnects its clients. A rejected auth handler returns HTTP 503 with `Authentication is temporarily unavailable`.
597
638
 
598
639
  The mount reads `BETTER_AUTH_SECRET` unless options supply `secret`. `PUBLIC_URL` supplies the default base URL, trusted origin and HTTPS secure-cookie setting; without it, the defaults accept loopback HTTP. Application options can override those defaults. RPC Origin policy remains an application choice.
@@ -654,11 +695,43 @@ const testAuth = betterAuth({
654
695
  const { user, headers, cookies } = await signedInUser(testAuth, {
655
696
  email: "member@example.test",
656
697
  name: "Member",
698
+ providerAccounts: [{ providerId: "github", accountId: "123" }],
657
699
  role: "user,admin",
658
700
  });
659
701
  ```
660
702
 
661
- `options` is the application's auth configuration. `signedInUser` saves the supplied user fields and mints one session through Better Auth's `testUtils`, without OAuth or password hashing. It returns Better Auth's login result: the user, session, signed request headers, browser cookies and token. Roles require the application's role plugin, such as Better Auth's `admin()`. Send `headers` to the real host, or pass `cookies` to test-story's `seedCookies` in a DOM story after setting its host location with `domFetch`. Keep a real sign-in story for each sign-in flow; feature stories start from the minted precondition.
703
+ `options` is the application's auth configuration. `signedInUser` saves the supplied user fields, creates each optional `providerAccounts` entry for that user through Better Auth's internal adapter, and mints one session through Better Auth's `testUtils`, without OAuth or password hashing. The accounts carry no tokens or password. Omitting `providerAccounts` creates only the user and session. It returns Better Auth's login result: the user, session, signed request headers, browser cookies and token. Roles require the application's role plugin, such as Better Auth's `admin()`. Send `headers` to the real host, or pass `cookies` to test-story's `seedCookies` in a DOM story after setting its host location with `domFetch`. Keep a real sign-in story for each sign-in flow; feature stories start from the minted precondition.
704
+
705
+ Use `storedProviderAccounts(auth, userId)` to observe credential storage through Better Auth's internal adapter. It returns account and provider IDs with `hasAccessToken`, `hasRefreshToken`, `hasIdToken` and `hasPassword` booleans. It never returns token values, passwords or password hashes. A DOM story kit that exposes its test auth instance and a session readout can use both helpers:
706
+
707
+ ```ts
708
+ import { Effect } from "effect";
709
+ import { expect } from "vitest";
710
+ import { seedCookies } from "@shivaedev/test-story/server/seedCookies.ts";
711
+ import { signedInUser, storedProviderAccounts } from "@shivaedev/platform/server/better-auth/testing.ts";
712
+
713
+ const member = app.trait("account", "a member starts signed in with GitHub", function* ({ auth }) {
714
+ const { cookies } = yield* Effect.promise(() => signedInUser(auth, {
715
+ email: "member@example.test",
716
+ name: "Member",
717
+ providerAccounts: [{ providerId: "github", accountId: "123" }],
718
+ }));
719
+ yield* seedCookies(cookies);
720
+ });
721
+
722
+ app.it("the member's provider account stores no credentials", [member], function* ({ session, story }) {
723
+ const { user } = yield* session();
724
+ const accounts = yield* Effect.promise(() => storedProviderAccounts(story.engine.auth, user.id));
725
+ expect(accounts).toEqual([{
726
+ providerId: "github",
727
+ accountId: "123",
728
+ hasAccessToken: false,
729
+ hasRefreshToken: false,
730
+ hasIdToken: false,
731
+ hasPassword: false,
732
+ }]);
733
+ });
734
+ ```
662
735
 
663
736
  With database-backed sessions and cookie caching disabled, revocation rejects that same minted session on the next protected HTTP or RPC request. Cookie caching and secondary storage can change revocation timing.
664
737
 
@@ -755,7 +828,7 @@ Import the module that defines the name; there is no package-root entry. Paths b
755
828
  | `server/redactingDiagnostics.ts`, `server/redactingTracer.ts`, `server/redactingRpcSerialization.ts` | `redactingDiagnostics(sensitive)`, `redactRecord(values, sensitive)`, `redactingTracer(sensitive)`, `redactingRpcSerialization(wire, sensitive)`: compose native diagnostic and NDJSON boundaries. |
756
829
  | `server/runtime/makePlatform.ts` | `makePlatformRuntime(layer, { developmentCacheKey? })`. |
757
830
  | `server/node-http/subscriptionSignal.ts` | `nodeSubscriptionSignal({ nodeRequest?, request?, signals? })`, returning `{ signal, dispose }`. |
758
- | `server/better-auth/mount.ts` | `betterAuthMount(options, { signIn? })`, returning `{ auth, routes, admittedSessions, revokeUnadmitted }`, and `authDatabase(url)`: native auth routes, the sign-in policy and a scoped PostgreSQL pool. |
831
+ | `server/better-auth/mount.ts` | `betterAuthMount(options, { signIn? })`, returning `{ auth, routes, admittedSessions, revokeUnadmitted }`, and `authDatabase(url, { max?, application_name? }?)`: native auth routes, the sign-in policy and a scoped PostgreSQL pool. |
759
832
  | `server/better-auth/allowedAccounts.ts` | `AllowedAccounts`, `allowedAccounts`: the provider-account list Schema and its `AUTH_ALLOWED_ACCOUNTS` Config. |
760
833
  | `server/better-auth/signInGate.ts` | `SignInPolicy`: the `accounts`, `rule` and `recheckInterval` of a sign-in policy. |
761
834
  | `server/better-auth/revokeUnadmitted.ts` | `RevokedSessions`: the `{ users, sessions }` counts of a revocation. |
@@ -0,0 +1,7 @@
1
+ import { Effect, Schema } from "effect";
2
+ import type { HttpClientResponse } from "effect/http";
3
+ import { ResponseBodyTooLarge } from "./errors.ts";
4
+ export declare const readBoundedBody: (response: HttpClientResponse.HttpClientResponse, limit: number) => Effect.Effect<Uint8Array<ArrayBuffer>, import("effect/http/HttpClientError").HttpClientError | ResponseBodyTooLarge, never>;
5
+ export declare function readBoundedText(response: HttpClientResponse.HttpClientResponse, limit: number): Effect.Effect<string, import("effect/http/HttpClientError").HttpClientError | ResponseBodyTooLarge, never>;
6
+ export declare function readBoundedJson<TSchema extends Schema.Constraint>(response: HttpClientResponse.HttpClientResponse, limit: number, schema: TSchema): Effect.Effect<TSchema["Type"], import("effect/http/HttpClientError").HttpClientError | ResponseBodyTooLarge | Schema.SchemaError, TSchema["DecodingServices"]>;
7
+ //# sourceMappingURL=body.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../../src/http-client/body.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AAChD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,eAAO,MAAM,eAAe,iMAwB1B,CAAC;AAEH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,kBAAkB,CAAC,kBAAkB,EAAE,KAAK,EAAE,MAAM,8GAE7F;AAED,wBAAgB,eAAe,CAAC,OAAO,SAAS,MAAM,CAAC,UAAU,EAAE,QAAQ,EAAE,kBAAkB,CAAC,kBAAkB,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,kKAEjJ"}
@@ -0,0 +1,32 @@
1
+ import { Effect, Schema, Stream } from "effect";
2
+ import { ResponseBodyTooLarge } from "./errors.js";
3
+ export const readBoundedBody = Effect.fnUntraced(function* (response, limit) {
4
+ const length = response.headers["content-length"];
5
+ if (length !== undefined && Number(length) > limit) {
6
+ return yield* new ResponseBodyTooLarge({ limit });
7
+ }
8
+ const chunks = [];
9
+ let size = 0;
10
+ yield* response.stream.pipe(Stream.runForEach((chunk) => {
11
+ size += chunk.byteLength;
12
+ if (size > limit) {
13
+ return Effect.fail(new ResponseBodyTooLarge({ limit }));
14
+ }
15
+ chunks.push(chunk);
16
+ return Effect.void;
17
+ }));
18
+ const bytes = new Uint8Array(size);
19
+ let offset = 0;
20
+ for (const chunk of chunks) {
21
+ bytes.set(chunk, offset);
22
+ offset += chunk.byteLength;
23
+ }
24
+ return bytes;
25
+ });
26
+ export function readBoundedText(response, limit) {
27
+ return Effect.map(readBoundedBody(response, limit), (bytes) => new TextDecoder().decode(bytes));
28
+ }
29
+ export function readBoundedJson(response, limit, schema) {
30
+ return Effect.flatMap(readBoundedText(response, limit), Schema.decodeUnknownEffect(Schema.fromJsonString(schema)));
31
+ }
32
+ //# sourceMappingURL=body.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"body.js","sourceRoot":"","sources":["../../src/http-client/body.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhD,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,QAA+C,EAAE,KAAa;IACzH,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAClD,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;QACpD,OAAO,KAAK,CAAC,CAAC,IAAI,oBAAoB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACnD,CAAC;IACD,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAC1B,MAAM,CAAC,UAAU,CAAC,CAAC,KAAK,EAAE,EAAE;QAC3B,IAAI,IAAI,KAAK,CAAC,UAAU,CAAC;QACzB,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;YAClB,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,oBAAoB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QACzD,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnB,OAAO,MAAM,CAAC,IAAI,CAAC;IACpB,CAAC,CAAC,CACF,CAAC;IACF,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC;IAC5B,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC,CAAC,CAAC;AAEH,MAAM,UAAU,eAAe,CAAC,QAA+C,EAAE,KAAa;IAC7F,OAAO,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACjG,CAAC;AAED,MAAM,UAAU,eAAe,CAAoC,QAA+C,EAAE,KAAa,EAAE,MAAe;IACjJ,OAAO,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AACpH,CAAC"}
@@ -0,0 +1,51 @@
1
+ import { Schema } from "effect";
2
+ declare const RemoteUnauthorized_base: Schema.Class<RemoteUnauthorized, Schema.TaggedStruct<"RemoteUnauthorized", {
3
+ method: Schema.String;
4
+ status: Schema.Number;
5
+ url: Schema.String;
6
+ }>, import("effect/Cause").YieldableError>;
7
+ export declare class RemoteUnauthorized extends RemoteUnauthorized_base {
8
+ }
9
+ declare const RemoteNotFound_base: Schema.Class<RemoteNotFound, Schema.TaggedStruct<"RemoteNotFound", {
10
+ method: Schema.String;
11
+ status: Schema.Number;
12
+ url: Schema.String;
13
+ }>, import("effect/Cause").YieldableError>;
14
+ export declare class RemoteNotFound extends RemoteNotFound_base {
15
+ }
16
+ declare const RemoteGone_base: Schema.Class<RemoteGone, Schema.TaggedStruct<"RemoteGone", {
17
+ method: Schema.String;
18
+ status: Schema.Number;
19
+ url: Schema.String;
20
+ }>, import("effect/Cause").YieldableError>;
21
+ export declare class RemoteGone extends RemoteGone_base {
22
+ }
23
+ declare const RemoteRateLimited_base: Schema.Class<RemoteRateLimited, Schema.TaggedStruct<"RemoteRateLimited", {
24
+ readonly method: Schema.String;
25
+ readonly status: Schema.Number;
26
+ readonly url: Schema.String;
27
+ readonly retryAfter: Schema.optionalKey<Schema.DateTimeUtc>;
28
+ }>, import("effect/Cause").YieldableError>;
29
+ export declare class RemoteRateLimited extends RemoteRateLimited_base {
30
+ }
31
+ declare const RemoteHttpError_base: Schema.Class<RemoteHttpError, Schema.TaggedStruct<"RemoteHttpError", {
32
+ method: Schema.String;
33
+ status: Schema.Number;
34
+ url: Schema.String;
35
+ }>, import("effect/Cause").YieldableError>;
36
+ export declare class RemoteHttpError extends RemoteHttpError_base {
37
+ }
38
+ export type RemoteStatusError = RemoteUnauthorized | RemoteNotFound | RemoteGone | RemoteRateLimited | RemoteHttpError;
39
+ declare const PaginationLinkError_base: Schema.Class<PaginationLinkError, Schema.TaggedStruct<"PaginationLinkError", {
40
+ readonly reason: Schema.Literals<readonly ["cross-origin", "invalid"]>;
41
+ readonly url: Schema.String;
42
+ }>, import("effect/Cause").YieldableError>;
43
+ export declare class PaginationLinkError extends PaginationLinkError_base {
44
+ }
45
+ declare const ResponseBodyTooLarge_base: Schema.Class<ResponseBodyTooLarge, Schema.TaggedStruct<"ResponseBodyTooLarge", {
46
+ readonly limit: Schema.Number;
47
+ }>, import("effect/Cause").YieldableError>;
48
+ export declare class ResponseBodyTooLarge extends ResponseBodyTooLarge_base {
49
+ }
50
+ export {};
51
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/http-client/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;;;;;;AAIhC,qBAAa,kBAAmB,SAAQ,uBAAsE;CAAG;;;;;;AACjH,qBAAa,cAAe,SAAQ,mBAA8D;CAAG;;;;;;AACrG,qBAAa,UAAW,SAAQ,eAAsD;CAAG;;;;;;;AACzF,qBAAa,iBAAkB,SAAQ,sBAGrC;CAAG;;;;;;AACL,qBAAa,eAAgB,SAAQ,oBAAgE;CAAG;AAExG,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,GAAG,cAAc,GAAG,UAAU,GAAG,iBAAiB,GAAG,eAAe,CAAC;;;;;AAEvH,qBAAa,mBAAoB,SAAQ,wBAGvC;CAAG;;;;AAEL,qBAAa,oBAAqB,SAAQ,yBAExC;CAAG"}
@@ -0,0 +1,25 @@
1
+ import { Schema } from "effect";
2
+ const answer = { method: Schema.String, status: Schema.Number, url: Schema.String };
3
+ export class RemoteUnauthorized extends Schema.TaggedError()("RemoteUnauthorized", answer) {
4
+ }
5
+ export class RemoteNotFound extends Schema.TaggedError()("RemoteNotFound", answer) {
6
+ }
7
+ export class RemoteGone extends Schema.TaggedError()("RemoteGone", answer) {
8
+ }
9
+ export class RemoteRateLimited extends Schema.TaggedError()("RemoteRateLimited", {
10
+ ...answer,
11
+ retryAfter: Schema.optionalKey(Schema.DateTimeUtc),
12
+ }) {
13
+ }
14
+ export class RemoteHttpError extends Schema.TaggedError()("RemoteHttpError", answer) {
15
+ }
16
+ export class PaginationLinkError extends Schema.TaggedError()("PaginationLinkError", {
17
+ reason: Schema.Literals(["cross-origin", "invalid"]),
18
+ url: Schema.String,
19
+ }) {
20
+ }
21
+ export class ResponseBodyTooLarge extends Schema.TaggedError()("ResponseBodyTooLarge", {
22
+ limit: Schema.Number,
23
+ }) {
24
+ }
25
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/http-client/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,MAAM,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;AAEpF,MAAM,OAAO,kBAAmB,SAAQ,MAAM,CAAC,WAAW,EAAsB,CAAC,oBAAoB,EAAE,MAAM,CAAC;CAAG;AACjH,MAAM,OAAO,cAAe,SAAQ,MAAM,CAAC,WAAW,EAAkB,CAAC,gBAAgB,EAAE,MAAM,CAAC;CAAG;AACrG,MAAM,OAAO,UAAW,SAAQ,MAAM,CAAC,WAAW,EAAc,CAAC,YAAY,EAAE,MAAM,CAAC;CAAG;AACzF,MAAM,OAAO,iBAAkB,SAAQ,MAAM,CAAC,WAAW,EAAqB,CAAC,mBAAmB,EAAE;IACnG,GAAG,MAAM;IACT,UAAU,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,WAAW,CAAC;CAClD,CAAC;CAAG;AACL,MAAM,OAAO,eAAgB,SAAQ,MAAM,CAAC,WAAW,EAAmB,CAAC,iBAAiB,EAAE,MAAM,CAAC;CAAG;AAIxG,MAAM,OAAO,mBAAoB,SAAQ,MAAM,CAAC,WAAW,EAAuB,CAAC,qBAAqB,EAAE;IACzG,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;IACpD,GAAG,EAAE,MAAM,CAAC,MAAM;CAClB,CAAC;CAAG;AAEL,MAAM,OAAO,oBAAqB,SAAQ,MAAM,CAAC,WAAW,EAAwB,CAAC,sBAAsB,EAAE;IAC5G,KAAK,EAAE,MAAM,CAAC,MAAM;CACpB,CAAC;CAAG"}
@@ -0,0 +1,4 @@
1
+ import { HttpClient } from "effect/http";
2
+ import { type RemoteStatusError } from "./errors.ts";
3
+ export declare function filterStatus<E, R>(client: HttpClient.HttpClient.With<E, R>): HttpClient.HttpClient.With<E | RemoteStatusError, R>;
4
+ //# sourceMappingURL=filterStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filterStatus.d.ts","sourceRoot":"","sources":["../../src/http-client/filterStatus.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAA2B,MAAM,aAAa,CAAC;AAClE,OAAO,EAAkE,KAAK,iBAAiB,EAAsB,MAAM,aAAa,CAAC;AAiCzI,wBAAgB,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,iBAAiB,EAAE,CAAC,CAAC,CAEjI"}
@@ -0,0 +1,38 @@
1
+ import { DateTime, Effect } from "effect";
2
+ import { HttpClient } from "effect/http";
3
+ import { RemoteGone, RemoteHttpError, RemoteNotFound, RemoteRateLimited, RemoteUnauthorized } from "./errors.js";
4
+ import { parseRetryAfter } from "./parseRetryAfter.js";
5
+ const checkStatus = Effect.fnUntraced(function* (response) {
6
+ const { status, request } = response;
7
+ if (status >= 200 && status < 300) {
8
+ return response;
9
+ }
10
+ const fields = { method: request.method, status, url: request.url.split(/[?#]/u)[0] ?? request.url };
11
+ let error;
12
+ switch (status) {
13
+ case 401:
14
+ error = new RemoteUnauthorized(fields);
15
+ break;
16
+ case 404:
17
+ error = new RemoteNotFound(fields);
18
+ break;
19
+ case 410:
20
+ error = new RemoteGone(fields);
21
+ break;
22
+ default: {
23
+ const header = response.headers["retry-after"];
24
+ if (status === 429 || (status === 503 && header !== undefined)) {
25
+ const retryAfter = parseRetryAfter(header, yield* DateTime.now);
26
+ error = new RemoteRateLimited({ ...fields, ...(retryAfter === undefined ? {} : { retryAfter }) });
27
+ }
28
+ else {
29
+ error = new RemoteHttpError(fields);
30
+ }
31
+ }
32
+ }
33
+ return yield* Effect.fail(error);
34
+ });
35
+ export function filterStatus(client) {
36
+ return HttpClient.transformResponse(client, Effect.flatMap(checkStatus));
37
+ }
38
+ //# sourceMappingURL=filterStatus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filterStatus.js","sourceRoot":"","sources":["../../src/http-client/filterStatus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC1C,OAAO,EAAE,UAAU,EAA2B,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,cAAc,EAAE,iBAAiB,EAA0B,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACzI,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,MAAM,WAAW,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,QAA+C;IAC/F,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,CAAC;IACrC,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG,EAAE,CAAC;QACnC,OAAO,QAAQ,CAAC;IACjB,CAAC;IACD,MAAM,MAAM,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACrG,IAAI,KAAwB,CAAC;IAC7B,QAAQ,MAAM,EAAE,CAAC;QAChB,KAAK,GAAG;YACP,KAAK,GAAG,IAAI,kBAAkB,CAAC,MAAM,CAAC,CAAC;YACvC,MAAM;QACP,KAAK,GAAG;YACP,KAAK,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC;YACnC,MAAM;QACP,KAAK,GAAG;YACP,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;YAC/B,MAAM;QACP,SAAS,CAAC;YACT,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;YAC/C,IAAI,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,SAAS,CAAC,EAAE,CAAC;gBAChE,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;gBAChE,KAAK,GAAG,IAAI,iBAAiB,CAAC,EAAE,GAAG,MAAM,EAAE,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;YACnG,CAAC;iBAAM,CAAC;gBACP,KAAK,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC;YACrC,CAAC;QACF,CAAC;IACF,CAAC;IACD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAClC,CAAC,CAAC,CAAC;AAEH,MAAM,UAAU,YAAY,CAAO,MAAwC;IAC1E,OAAO,UAAU,CAAC,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;AAC1E,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function nextLink(header: string | undefined): string | undefined;
2
+ //# sourceMappingURL=nextLink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nextLink.d.ts","sourceRoot":"","sources":["../../src/http-client/nextLink.ts"],"names":[],"mappings":"AAAA,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CASvE"}
@@ -0,0 +1,11 @@
1
+ export function nextLink(header) {
2
+ for (const link of header?.matchAll(/<(?<url>[^>]*)>(?<parameters>(?:[^<",]|"(?:\\.|[^"\\])*")*)/gu) ?? []) {
3
+ const relation = link.groups?.parameters?.match(/(?:^|;)\s*rel\s*=\s*(?:"(?<quoted>[^"]*)"|(?<unquoted>[^;\s,]+))/iu);
4
+ const relations = relation?.groups?.quoted ?? relation?.groups?.unquoted;
5
+ if (relations?.toLowerCase().split(/\s+/u).includes("next")) {
6
+ return link.groups?.url;
7
+ }
8
+ }
9
+ return undefined;
10
+ }
11
+ //# sourceMappingURL=nextLink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nextLink.js","sourceRoot":"","sources":["../../src/http-client/nextLink.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,QAAQ,CAAC,MAA0B;IAClD,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,QAAQ,CAAC,+DAA+D,CAAC,IAAI,EAAE,EAAE,CAAC;QAC5G,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,oEAAoE,CAAC,CAAC;QACtH,MAAM,SAAS,GAAG,QAAQ,EAAE,MAAM,EAAE,MAAM,IAAI,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;QACzE,IAAI,SAAS,EAAE,WAAW,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7D,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC;QACzB,CAAC;IACF,CAAC;IACD,OAAO,SAAS,CAAC;AAClB,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { Stream } from "effect";
2
+ import { type HttpClient, HttpClientRequest, type HttpClientResponse } from "effect/http";
3
+ import { PaginationLinkError } from "./errors.ts";
4
+ export declare function paginate<E, R>(client: HttpClient.HttpClient.With<E, R>, first: HttpClientRequest.HttpClientRequest): Stream.Stream<HttpClientResponse.HttpClientResponse, E | PaginationLinkError, R>;
5
+ //# sourceMappingURL=paginate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paginate.d.ts","sourceRoot":"","sources":["../../src/http-client/paginate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,KAAK,UAAU,EAAE,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAoBlD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,iBAAiB,CAAC,iBAAiB,oFAgBlH"}
@@ -0,0 +1,31 @@
1
+ import { Effect, Stream } from "effect";
2
+ import { HttpClientRequest } from "effect/http";
3
+ import { PaginationLinkError } from "./errors.js";
4
+ import { nextLink } from "./nextLink.js";
5
+ const nextRequest = Effect.fnUntraced(function* (response) {
6
+ const link = nextLink(response.headers.link);
7
+ if (link === undefined) {
8
+ return undefined;
9
+ }
10
+ const current = response.url || response.request.url;
11
+ const url = current.split(/[?#]/u)[0] ?? current;
12
+ const next = yield* Effect.try({
13
+ catch: () => new PaginationLinkError({ reason: "invalid", url }),
14
+ try: () => new URL(link, current),
15
+ });
16
+ if (next.origin !== new URL(current).origin) {
17
+ return yield* new PaginationLinkError({ reason: "cross-origin", url });
18
+ }
19
+ return HttpClientRequest.setUrl(response.request, next);
20
+ });
21
+ export function paginate(client, first) {
22
+ return Stream.unfold(first, (state) => Effect.gen(function* () {
23
+ const request = HttpClientRequest.isHttpClientRequest(state) ? state : yield* nextRequest(state);
24
+ if (request === undefined) {
25
+ return undefined;
26
+ }
27
+ const response = yield* client.execute(request);
28
+ return [response, response];
29
+ }));
30
+ }
31
+ //# sourceMappingURL=paginate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paginate.js","sourceRoot":"","sources":["../../src/http-client/paginate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAmB,iBAAiB,EAA2B,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,MAAM,WAAW,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,QAA+C;IAC/F,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,IAAI,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC;IACrD,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC;IACjD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;QAC9B,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,mBAAmB,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;QAChE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC;KACjC,CAAC,CAAC;IACH,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC;QAC7C,OAAO,KAAK,CAAC,CAAC,IAAI,mBAAmB,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AACzD,CAAC,CAAC,CAAC;AAEH,MAAM,UAAU,QAAQ,CAAO,MAAwC,EAAE,KAA0C;IAClH,OAAO,MAAM,CAAC,MAAM,CAKlB,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAClB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,MAAM,OAAO,GAAG,iBAAiB,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACjG,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAChD,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAU,CAAC;IACtC,CAAC,CAAC,CACF,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { DateTime } from "effect";
2
+ export declare function parseRetryAfter(header: string | undefined, now: DateTime.Utc): DateTime.Utc | undefined;
3
+ //# sourceMappingURL=parseRetryAfter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseRetryAfter.d.ts","sourceRoot":"","sources":["../../src/http-client/parseRetryAfter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAU,MAAM,QAAQ,CAAC;AAI1C,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,GAAG,QAAQ,CAAC,GAAG,GAAG,SAAS,CAYvG"}
@@ -0,0 +1,17 @@
1
+ import { DateTime, Option } from "effect";
2
+ const httpDate = /^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT$/u;
3
+ export function parseRetryAfter(header, now) {
4
+ if (header === undefined) {
5
+ return undefined;
6
+ }
7
+ const value = header.trim();
8
+ let millis = Number.NaN;
9
+ if (/^\d+$/u.test(value)) {
10
+ millis = DateTime.toEpochMillis(now) + Number(value) * 1000;
11
+ }
12
+ else if (httpDate.test(value)) {
13
+ millis = Date.parse(value);
14
+ }
15
+ return Option.getOrUndefined(DateTime.make(millis));
16
+ }
17
+ //# sourceMappingURL=parseRetryAfter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseRetryAfter.js","sourceRoot":"","sources":["../../src/http-client/parseRetryAfter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE1C,MAAM,QAAQ,GAAG,qFAAqF,CAAC;AAEvG,MAAM,UAAU,eAAe,CAAC,MAA0B,EAAE,GAAiB;IAC5E,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC;IACxB,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAC7D,CAAC;SAAM,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;AACrD,CAAC"}
@@ -1,13 +1,9 @@
1
1
  import type { Schema } from "effect";
2
2
  export declare function publicConfig<T, E>(schema: Schema.Codec<T, E>): import("@shivaedev/effect-contract/contract.ts").Contract<"platform.publicConfig", readonly [NoInfer<import("@shivaedev/effect-contract/operation.ts").Query<"get", Schema.Void, Schema.Codec<T, E, never, never>, {}>>], readonly [], import("effect/rpc/Rpc").Rpc<"platform.publicConfig.get", Schema.Void, Schema.Codec<T, E, never, never>, Schema.Never, never, never> | import("effect/rpc/Rpc").Rpc<"platform.publicConfig.$changes", Schema.Struct<{
3
- keys: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
4
- readonly _tag: Schema.Literal<"List">;
5
- readonly collection: Schema.String;
6
- }>, Schema.Struct<{
7
- readonly _tag: Schema.Literal<"Item">;
8
- readonly collection: Schema.String;
9
- readonly id: Schema.Union<readonly [Schema.String, Schema.Number]>;
10
- }>]>>;
3
+ queries: Schema.$Array<Schema.Union<readonly Schema.Struct<{
4
+ query: Schema.Literal<"get">;
5
+ payload: Schema.Void;
6
+ }>[]>>;
11
7
  }>, import("effect/rpc/RpcSchema").Stream<Schema.Union<readonly [Schema.Struct<{
12
8
  readonly _tag: Schema.Literal<"Changed">;
13
9
  readonly keys: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
@@ -20,5 +16,5 @@ export declare function publicConfig<T, E>(schema: Schema.Codec<T, E>): import("
20
16
  }>]>>;
21
17
  }>, Schema.Struct<{
22
18
  readonly _tag: Schema.Literal<"Resync">;
23
- }>]>, Schema.Never>, Schema.Never, never, never>, readonly []>;
19
+ }>]>, Schema.Never>, Schema.Never, never, never>, readonly [], undefined>;
24
20
  //# sourceMappingURL=publicConfig.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"publicConfig.d.ts","sourceRoot":"","sources":["../../src/rpc/publicConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIrC,wBAAgB,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;+DAI5D"}
1
+ {"version":3,"file":"publicConfig.d.ts","sourceRoot":"","sources":["../../src/rpc/publicConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIrC,wBAAgB,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;;;;;;;;;;;;;;;;;0EAI5D"}
@@ -7,6 +7,8 @@ export interface MigrationOptions<R = never> {
7
7
  readonly lockTimeout?: Duration.Input;
8
8
  readonly mode?: "migrate" | "check" | "wait";
9
9
  readonly pollInterval?: Duration.Input;
10
+ readonly progressInterval?: Duration.Input;
11
+ readonly release?: string;
10
12
  readonly table?: string;
11
13
  readonly waitTimeout?: Duration.Input;
12
14
  }
@@ -1 +1 @@
1
- {"version":3,"file":"MigrationOptions.d.ts","sourceRoot":"","sources":["../../src/server/MigrationOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0CAA0C,CAAC;AAEhF,MAAM,WAAW,gBAAgB,CAAC,CAAC,GAAG,KAAK;IAC1C,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;IACpD,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;IACtC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;IAC7C,QAAQ,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;CACtC"}
1
+ {"version":3,"file":"MigrationOptions.d.ts","sourceRoot":"","sources":["../../src/server/MigrationOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0CAA0C,CAAC;AAEhF,MAAM,WAAW,gBAAgB,CAAC,CAAC,GAAG,KAAK;IAC1C,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;IACpD,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;IACtC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;IAC7C,QAAQ,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;IACvC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC3C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;CACtC"}
@@ -8,7 +8,7 @@ import type { MigrationOptions } from "#server/MigrationOptions.ts";
8
8
  import type { SessionPolicy, SessionShape } from "#server/rpc-server/session.ts";
9
9
  declare const AppChanges_base: Context.ServiceClass<AppChanges, "@shivaedev/platform/AppChanges", {
10
10
  resync: Effect.Effect<void, never, never>;
11
- stream: (keys: readonly import("@shivaedev/effect-contract/keys.ts").Key[]) => import("effect/Stream").Stream<{
11
+ stream: (keys: readonly import("@shivaedev/effect-contract/keys.ts").Key[], context?: import("@shivaedev/effect-contract/keys.ts").KeyContext) => import("effect/Stream").Stream<{
12
12
  readonly _tag: "Changed";
13
13
  readonly keys: readonly ({
14
14
  readonly _tag: "List";
@@ -1,7 +1,7 @@
1
1
  import { type BetterAuthOptions } from "better-auth";
2
2
  import { Config, Effect, Redacted } from "effect";
3
3
  import { HttpRouter } from "effect/http";
4
- import { Pool } from "pg";
4
+ import { Pool, type PoolConfig } from "pg";
5
5
  import { type RevokeUnadmitted } from "#server/better-auth/revokeUnadmitted.ts";
6
6
  import { type SignInPolicy } from "#server/better-auth/signInGate.ts";
7
7
  import type { GetSession } from "#server/rpc-server/adapters/betterAuthSessions.ts";
@@ -247,5 +247,5 @@ export declare const betterAuthMount: <const TOptions extends BetterAuthOptions,
247
247
  revokeUnadmitted: RevokeUnadmitted<E>;
248
248
  routes: import("effect/Layer").Layer<never, never, HttpRouter.HttpRouter>;
249
249
  }, Config.ConfigError, R>;
250
- export declare const authDatabase: (url: Redacted.Redacted<string>) => Effect.Effect<Pool, never, import("effect/Scope").Scope>;
250
+ export declare const authDatabase: (url: Redacted.Redacted<string>, options?: Pick<PoolConfig, "application_name" | "max"> | undefined) => Effect.Effect<Pool, never, import("effect/Scope").Scope>;
251
251
  //# sourceMappingURL=mount.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../../src/server/better-auth/mount.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAc,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAU,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAyC,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,IAAI,EAAmB,MAAM,IAAI,CAAC;AAC3C,OAAO,EAAE,KAAK,gBAAgB,EAAoB,MAAM,yCAAyC,CAAC;AAClG,OAAO,EAAsB,KAAK,YAAY,EAAc,MAAM,mCAAmC,CAAC;AAEtG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mDAAmD,CAAC;AAGpF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAElE,MAAM,WAAW,sBAAsB,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,KAAK;IAC3D,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC;CACjD;AAED,eAAO,MAAM,eAAe,SAA0D,QAAQ,SAAS,iBAAiB,EAAE,CAAC,UAAU,CAAC;uBAsD3G,QAAQ,SAAS,YAAY,cAAc,UAAU,CAAC,QAAQ,CAAC,KAAG,UAAU,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA0B/G,CAAC;AAEH,eAAO,MAAM,YAAY,8FAqBvB,CAAC"}
1
+ {"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../../src/server/better-auth/mount.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAc,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAU,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAyC,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,IAAI,EAAmB,KAAK,UAAU,EAAE,MAAM,IAAI,CAAC;AAC5D,OAAO,EAAE,KAAK,gBAAgB,EAAoB,MAAM,yCAAyC,CAAC;AAClG,OAAO,EAAsB,KAAK,YAAY,EAAc,MAAM,mCAAmC,CAAC;AAEtG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mDAAmD,CAAC;AAGpF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAElE,MAAM,WAAW,sBAAsB,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,KAAK;IAC3D,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC;CACjD;AAED,eAAO,MAAM,eAAe,SAA0D,QAAQ,SAAS,iBAAiB,EAAE,CAAC,UAAU,CAAC;uBAsD3G,QAAQ,SAAS,YAAY,cAAc,UAAU,CAAC,QAAQ,CAAC,KAAG,UAAU,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA0B/G,CAAC;AAEH,eAAO,MAAM,YAAY,kKAwBvB,CAAC"}
@@ -71,11 +71,11 @@ export const betterAuthMount = Effect.fn("Platform.betterAuthMount")(function* (
71
71
  const revoke = Effect.provideContext(revokeUnadmitted(gate, () => auth.$context), context);
72
72
  return { admittedSessions, auth, revokeUnadmitted: revoke, routes };
73
73
  });
74
- export const authDatabase = Effect.fn("Platform.authDatabase")(function* (url) {
74
+ export const authDatabase = Effect.fn("Platform.authDatabase")(function* (url, options = {}) {
75
75
  const clients = new Set();
76
76
  const run = Effect.runForkWith(yield* Effect.context());
77
77
  return yield* Effect.acquireRelease(Effect.sync(() => {
78
- const pool = new Pool({ connectionString: Redacted.value(url) });
78
+ const pool = new Pool({ ...options, connectionString: Redacted.value(url) });
79
79
  pool.on("connect", (client) => clients.add(client));
80
80
  pool.on("remove", (client) => clients.delete(client));
81
81
  pool.on("error", (error) => {