pipework 0.212.0 → 0.242.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/CHANGELOG.md +136 -0
- package/REFERENCE.md +94 -2
- package/dist/cli/commands/openapi.d.ts +8 -0
- package/dist/cli/commands/openapi.d.ts.map +1 -0
- package/dist/cli/commands/openapi.js +49 -0
- package/dist/cli/commands/openapi.js.map +1 -0
- package/dist/cli/commands/serve.d.ts +8 -0
- package/dist/cli/commands/serve.d.ts.map +1 -1
- package/dist/cli/commands/serve.js +22 -3
- package/dist/cli/commands/serve.js.map +1 -1
- package/dist/cli/dev-bootstrap.js +6 -1
- package/dist/cli/dev-bootstrap.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +15 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/surface-kinds.d.ts +5 -0
- package/dist/cli/surface-kinds.d.ts.map +1 -0
- package/dist/cli/surface-kinds.js +15 -0
- package/dist/cli/surface-kinds.js.map +1 -0
- package/dist/core/config/load.d.ts +1 -0
- package/dist/core/config/load.d.ts.map +1 -1
- package/dist/core/config/load.js +1 -0
- package/dist/core/config/load.js.map +1 -1
- package/dist/core/config/namespace.d.ts +6 -0
- package/dist/core/config/namespace.d.ts.map +1 -1
- package/dist/core/config/schema.d.ts +15 -0
- package/dist/core/config/schema.d.ts.map +1 -1
- package/dist/core/config/schema.js +11 -2
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/lifecycle/health.js +2 -2
- package/dist/core/lifecycle/health.js.map +1 -1
- package/dist/core/lifecycle/types.d.ts +2 -0
- package/dist/core/lifecycle/types.d.ts.map +1 -1
- package/dist/core/pipework.d.ts +14 -1
- package/dist/core/pipework.d.ts.map +1 -1
- package/dist/core/pipework.js +18 -2
- package/dist/core/pipework.js.map +1 -1
- package/dist/core/surface/http.d.ts +3 -0
- package/dist/core/surface/http.d.ts.map +1 -1
- package/dist/core/surface/http.js +27 -8
- package/dist/core/surface/http.js.map +1 -1
- package/dist/core/surface/start.d.ts.map +1 -1
- package/dist/core/surface/start.js +19 -3
- package/dist/core/surface/start.js.map +1 -1
- package/dist/core/surface/types.d.ts +8 -0
- package/dist/core/surface/types.d.ts.map +1 -1
- package/dist/data/db/filter.d.ts +3 -0
- package/dist/data/db/filter.d.ts.map +1 -1
- package/dist/data/db/filter.js +3 -0
- package/dist/data/db/filter.js.map +1 -1
- package/dist/data/db/in-list.d.ts +15 -0
- package/dist/data/db/in-list.d.ts.map +1 -0
- package/dist/data/db/in-list.js +51 -0
- package/dist/data/db/in-list.js.map +1 -0
- package/dist/data/db/namespace.d.ts +2 -0
- package/dist/data/db/namespace.d.ts.map +1 -1
- package/dist/data/db/pool.d.ts +1 -1
- package/dist/data/db/pool.d.ts.map +1 -1
- package/dist/data/db/pool.js +4 -2
- package/dist/data/db/pool.js.map +1 -1
- package/dist/data/db/reservation.d.ts +40 -0
- package/dist/data/db/reservation.d.ts.map +1 -0
- package/dist/data/db/reservation.js +108 -0
- package/dist/data/db/reservation.js.map +1 -0
- package/dist/data/domain/build-table.js +32 -1
- package/dist/data/domain/build-table.js.map +1 -1
- package/dist/data/domain/index.d.ts +2 -1
- package/dist/data/domain/index.d.ts.map +1 -1
- package/dist/data/domain/types.d.ts +9 -1
- package/dist/data/domain/types.d.ts.map +1 -1
- package/dist/data/domain/types.js.map +1 -1
- package/dist/data/migrate/apply.d.ts.map +1 -1
- package/dist/data/migrate/apply.js +3 -11
- package/dist/data/migrate/apply.js.map +1 -1
- package/dist/data/migrate/check.d.ts.map +1 -1
- package/dist/data/migrate/check.js +3 -6
- package/dist/data/migrate/check.js.map +1 -1
- package/dist/data/migrate/diff.d.ts +16 -0
- package/dist/data/migrate/diff.d.ts.map +1 -1
- package/dist/data/migrate/diff.js +44 -4
- package/dist/data/migrate/diff.js.map +1 -1
- package/dist/data/migrate/generate.d.ts +1 -1
- package/dist/data/migrate/generate.d.ts.map +1 -1
- package/dist/data/migrate/generate.js +19 -51
- package/dist/data/migrate/generate.js.map +1 -1
- package/dist/data/migrate/history.d.ts +36 -0
- package/dist/data/migrate/history.d.ts.map +1 -0
- package/dist/data/migrate/history.js +185 -0
- package/dist/data/migrate/history.js.map +1 -0
- package/dist/data/migrate/internal-definitions.d.ts.map +1 -1
- package/dist/data/migrate/internal-definitions.js +22 -1
- package/dist/data/migrate/internal-definitions.js.map +1 -1
- package/dist/data/migrate/provision-framework.d.ts +2 -0
- package/dist/data/migrate/provision-framework.d.ts.map +1 -1
- package/dist/data/migrate/provision-framework.js +11 -3
- package/dist/data/migrate/provision-framework.js.map +1 -1
- package/dist/data/migrate/snapshot.d.ts +4 -0
- package/dist/data/migrate/snapshot.d.ts.map +1 -1
- package/dist/data/migrate/snapshot.js +2 -0
- package/dist/data/migrate/snapshot.js.map +1 -1
- package/dist/data/migrate/sql-emit.js +66 -12
- package/dist/data/migrate/sql-emit.js.map +1 -1
- package/dist/data/query/pg-core/foreign-keys.d.ts +5 -0
- package/dist/data/query/pg-core/foreign-keys.d.ts.map +1 -1
- package/dist/data/query/pg-core/foreign-keys.js +9 -0
- package/dist/data/query/pg-core/foreign-keys.js.map +1 -1
- package/dist/data/query/pg-core/indexes.d.ts +5 -0
- package/dist/data/query/pg-core/indexes.d.ts.map +1 -1
- package/dist/data/query/pg-core/indexes.js +7 -0
- package/dist/data/query/pg-core/indexes.js.map +1 -1
- package/dist/data/schema/json-schema.js +29 -1
- package/dist/data/schema/json-schema.js.map +1 -1
- package/dist/data/schema/types.d.ts +2 -2
- package/dist/data/schema/types.d.ts.map +1 -1
- package/dist/index.d.ts +18 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/infra/alerts/handle.d.ts +33 -5
- package/dist/infra/alerts/handle.d.ts.map +1 -1
- package/dist/infra/alerts/handle.js +56 -12
- package/dist/infra/alerts/handle.js.map +1 -1
- package/dist/infra/alerts/http.d.ts +2 -2
- package/dist/infra/alerts/http.d.ts.map +1 -1
- package/dist/infra/alerts/http.js +21 -3
- package/dist/infra/alerts/http.js.map +1 -1
- package/dist/infra/alerts/index.d.ts +1 -1
- package/dist/infra/alerts/index.d.ts.map +1 -1
- package/dist/infra/alerts/schema.d.ts +5 -0
- package/dist/infra/alerts/schema.d.ts.map +1 -1
- package/dist/infra/alerts/schema.js +1 -0
- package/dist/infra/alerts/schema.js.map +1 -1
- package/dist/infra/crawl/audit.d.ts +6 -0
- package/dist/infra/crawl/audit.d.ts.map +1 -0
- package/dist/infra/crawl/audit.js +38 -0
- package/dist/infra/crawl/audit.js.map +1 -0
- package/dist/infra/crawl/errors.d.ts +14 -0
- package/dist/infra/crawl/errors.d.ts.map +1 -0
- package/dist/infra/crawl/errors.js +22 -0
- package/dist/infra/crawl/errors.js.map +1 -0
- package/dist/infra/crawl/handle.d.ts +149 -0
- package/dist/infra/crawl/handle.d.ts.map +1 -0
- package/dist/infra/crawl/handle.js +550 -0
- package/dist/infra/crawl/handle.js.map +1 -0
- package/dist/infra/crawl/index.d.ts +7 -0
- package/dist/infra/crawl/index.d.ts.map +1 -0
- package/dist/infra/crawl/index.js +5 -0
- package/dist/infra/crawl/index.js.map +1 -0
- package/dist/infra/crawl/links.d.ts +9 -0
- package/dist/infra/crawl/links.d.ts.map +1 -0
- package/dist/infra/crawl/links.js +194 -0
- package/dist/infra/crawl/links.js.map +1 -0
- package/dist/infra/crawl/namespace.d.ts +36 -0
- package/dist/infra/crawl/namespace.d.ts.map +1 -0
- package/dist/infra/crawl/namespace.js +36 -0
- package/dist/infra/crawl/namespace.js.map +1 -0
- package/dist/infra/crawl/robots.d.ts +16 -0
- package/dist/infra/crawl/robots.d.ts.map +1 -0
- package/dist/infra/crawl/robots.js +98 -0
- package/dist/infra/crawl/robots.js.map +1 -0
- package/dist/infra/crawl/schema.d.ts +222 -0
- package/dist/infra/crawl/schema.d.ts.map +1 -0
- package/dist/infra/crawl/schema.js +84 -0
- package/dist/infra/crawl/schema.js.map +1 -0
- package/dist/infra/credit-line/handle.d.ts +250 -0
- package/dist/infra/credit-line/handle.d.ts.map +1 -0
- package/dist/infra/credit-line/handle.js +618 -0
- package/dist/infra/credit-line/handle.js.map +1 -0
- package/dist/infra/credit-line/index.d.ts +3 -0
- package/dist/infra/credit-line/index.d.ts.map +1 -0
- package/dist/infra/credit-line/index.js +2 -0
- package/dist/infra/credit-line/index.js.map +1 -0
- package/dist/infra/credit-line/namespace.d.ts +7 -0
- package/dist/infra/credit-line/namespace.d.ts.map +1 -0
- package/dist/infra/credit-line/namespace.js +7 -0
- package/dist/infra/credit-line/namespace.js.map +1 -0
- package/dist/infra/credit-line/schema.d.ts +194 -0
- package/dist/infra/credit-line/schema.d.ts.map +1 -0
- package/dist/infra/credit-line/schema.js +89 -0
- package/dist/infra/credit-line/schema.js.map +1 -0
- package/dist/infra/email/inbound.d.ts +14 -2
- package/dist/infra/email/inbound.d.ts.map +1 -1
- package/dist/infra/email/inbound.js +5 -1
- package/dist/infra/email/inbound.js.map +1 -1
- package/dist/infra/email/index.d.ts +2 -2
- package/dist/infra/email/index.d.ts.map +1 -1
- package/dist/infra/email/index.js.map +1 -1
- package/dist/infra/email/mail.d.ts +43 -5
- package/dist/infra/email/mail.d.ts.map +1 -1
- package/dist/infra/email/mail.js +124 -9
- package/dist/infra/email/mail.js.map +1 -1
- package/dist/infra/email/mime.d.ts +6 -0
- package/dist/infra/email/mime.d.ts.map +1 -1
- package/dist/infra/email/mime.js +10 -2
- package/dist/infra/email/mime.js.map +1 -1
- package/dist/infra/email/schema.d.ts +17 -3
- package/dist/infra/email/schema.d.ts.map +1 -1
- package/dist/infra/email/schema.js +15 -4
- package/dist/infra/email/schema.js.map +1 -1
- package/dist/infra/email/sns.js +2 -2
- package/dist/infra/email/sns.js.map +1 -1
- package/dist/infra/email/unsubscribe.d.ts +13 -0
- package/dist/infra/email/unsubscribe.d.ts.map +1 -0
- package/dist/infra/email/unsubscribe.js +43 -0
- package/dist/infra/email/unsubscribe.js.map +1 -0
- package/dist/infra/fetch/handle.d.ts +20 -1
- package/dist/infra/fetch/handle.d.ts.map +1 -1
- package/dist/infra/fetch/handle.js +60 -28
- package/dist/infra/fetch/handle.js.map +1 -1
- package/dist/infra/fetch/index.d.ts +2 -2
- package/dist/infra/fetch/index.d.ts.map +1 -1
- package/dist/infra/fetch/index.js +1 -1
- package/dist/infra/fetch/index.js.map +1 -1
- package/dist/infra/fetch/namespace.d.ts +20 -2
- package/dist/infra/fetch/namespace.d.ts.map +1 -1
- package/dist/infra/fetch/namespace.js +20 -2
- package/dist/infra/fetch/namespace.js.map +1 -1
- package/dist/infra/join-requests/handle.d.ts +86 -0
- package/dist/infra/join-requests/handle.d.ts.map +1 -0
- package/dist/infra/join-requests/handle.js +233 -0
- package/dist/infra/join-requests/handle.js.map +1 -0
- package/dist/infra/join-requests/index.d.ts +3 -0
- package/dist/infra/join-requests/index.d.ts.map +1 -0
- package/dist/infra/join-requests/index.js +2 -0
- package/dist/infra/join-requests/index.js.map +1 -0
- package/dist/infra/join-requests/namespace.d.ts +7 -0
- package/dist/infra/join-requests/namespace.d.ts.map +1 -0
- package/dist/infra/join-requests/namespace.js +7 -0
- package/dist/infra/join-requests/namespace.js.map +1 -0
- package/dist/infra/join-requests/schema.d.ts +58 -0
- package/dist/infra/join-requests/schema.d.ts.map +1 -0
- package/dist/infra/join-requests/schema.js +31 -0
- package/dist/infra/join-requests/schema.js.map +1 -0
- package/dist/infra/outbox/core.d.ts +9 -0
- package/dist/infra/outbox/core.d.ts.map +1 -1
- package/dist/infra/outbox/core.js +14 -0
- package/dist/infra/outbox/core.js.map +1 -1
- package/dist/infra/support/handle.d.ts +43 -0
- package/dist/infra/support/handle.d.ts.map +1 -1
- package/dist/infra/support/handle.js +35 -7
- package/dist/infra/support/handle.js.map +1 -1
- package/dist/infra/support/http.d.ts +28 -0
- package/dist/infra/support/http.d.ts.map +1 -0
- package/dist/infra/support/http.js +217 -0
- package/dist/infra/support/http.js.map +1 -0
- package/dist/infra/support/index.d.ts +2 -0
- package/dist/infra/support/index.d.ts.map +1 -1
- package/dist/infra/support/index.js +1 -0
- package/dist/infra/support/index.js.map +1 -1
- package/dist/infra/support/namespace.d.ts +3 -0
- package/dist/infra/support/namespace.d.ts.map +1 -1
- package/dist/infra/support/namespace.js +3 -0
- package/dist/infra/support/namespace.js.map +1 -1
- package/dist/infra/support/schema.d.ts +4 -4
- package/dist/infra/webhook/outbound.d.ts +16 -0
- package/dist/infra/webhook/outbound.d.ts.map +1 -1
- package/dist/infra/webhook/outbound.js +78 -27
- package/dist/infra/webhook/outbound.js.map +1 -1
- package/dist/request/di/builder.d.ts +24 -1
- package/dist/request/di/builder.d.ts.map +1 -1
- package/dist/request/di/builder.js +35 -0
- package/dist/request/di/builder.js.map +1 -1
- package/dist/request/di/types.d.ts +30 -0
- package/dist/request/di/types.d.ts.map +1 -1
- package/dist/request/http/server.d.ts.map +1 -1
- package/dist/request/http/server.js +111 -20
- package/dist/request/http/server.js.map +1 -1
- package/dist/test/auto-setup.js +2 -2
- package/dist/test/auto-setup.js.map +1 -1
- package/dist/test/crawl-fixture.d.ts +53 -0
- package/dist/test/crawl-fixture.d.ts.map +1 -0
- package/dist/test/crawl-fixture.js +130 -0
- package/dist/test/crawl-fixture.js.map +1 -0
- package/dist/test/index.d.ts +2 -1
- package/dist/test/index.d.ts.map +1 -1
- package/dist/test/index.js +2 -1
- package/dist/test/index.js.map +1 -1
- package/dist/test/progress.d.ts +7 -1
- package/dist/test/progress.d.ts.map +1 -1
- package/dist/test/progress.js +6 -0
- package/dist/test/progress.js.map +1 -1
- package/dist/test/reap-after-run.d.ts +3 -0
- package/dist/test/reap-after-run.d.ts.map +1 -0
- package/dist/test/reap-after-run.js +29 -0
- package/dist/test/reap-after-run.js.map +1 -0
- package/dist/test/reap.d.ts +11 -2
- package/dist/test/reap.d.ts.map +1 -1
- package/dist/test/reap.js +19 -12
- package/dist/test/reap.js.map +1 -1
- package/dist/test/setup.d.ts +2 -0
- package/dist/test/setup.d.ts.map +1 -1
- package/dist/test/setup.js +87 -30
- package/dist/test/setup.js.map +1 -1
- package/dist/test/vitest-plugin.d.ts.map +1 -1
- package/dist/test/vitest-plugin.js +8 -1
- package/dist/test/vitest-plugin.js.map +1 -1
- package/dist/test/vitest-workspace.d.ts +1 -0
- package/dist/test/vitest-workspace.d.ts.map +1 -1
- package/dist/test/vitest-workspace.js +1 -1
- package/dist/test/vitest-workspace.js.map +1 -1
- package/dist/test/vitest.d.ts +20 -0
- package/dist/test/vitest.d.ts.map +1 -1
- package/dist/test/vitest.js +41 -1
- package/dist/test/vitest.js.map +1 -1
- package/dist/trace/partition/sync.d.ts +8 -2
- package/dist/trace/partition/sync.d.ts.map +1 -1
- package/dist/trace/partition/sync.js +6 -3
- package/dist/trace/partition/sync.js.map +1 -1
- package/dist/workspace/generate.d.ts.map +1 -1
- package/dist/workspace/generate.js +1 -0
- package/dist/workspace/generate.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { untrustedGet } from './handle.js';
|
|
1
|
+
import { untrustedGet, untrustedPost } from './handle.js';
|
|
2
2
|
import { retryUnsent, fetchRetryingUnsent, isUnsentFault } from './connect-retry.js';
|
|
3
3
|
/** Untrusted fetch — fetch one URL a stranger gave without letting them reach the app's private network. */
|
|
4
4
|
export declare const untrustedFetch: {
|
|
5
5
|
/**
|
|
6
|
-
* Fetches `url` with GET and returns `{ ok: true, url, status, contentType, body, bytes }
|
|
6
|
+
* Fetches `url` with GET and returns `{ ok: true, url, status, contentType, etag, lastModified, body, bytes }` (`etag` and `lastModified` are the response headers, null when absent), or `{ ok: false, reason, message }`
|
|
7
7
|
* naming the rule that refused: `scheme`, `address`, `redirects`, `too_large`, `timeout`, `status`, `rejected` or `network`.
|
|
8
8
|
* It does not throw for these.
|
|
9
9
|
*
|
|
@@ -15,6 +15,9 @@ export declare const untrustedFetch: {
|
|
|
15
15
|
* `accept({ status, contentType, firstBytes })` sees the first 1024 bytes before the body is kept: return false, or a string
|
|
16
16
|
* (your own message), to refuse with `rejected`. Messages never contain an address or anything a server sent.
|
|
17
17
|
*
|
|
18
|
+
* `followRedirect(to, from)` is asked before each redirect hop: true follows it; false, or a string (your message), refuses with `rejected`.
|
|
19
|
+
* A 304 answer is a `status` refusal with `status: 304`; send `If-None-Match` / `If-Modified-Since` through `headers`.
|
|
20
|
+
*
|
|
18
21
|
* `resolve` and `connect` are for tests: `resolve(hostname)` replaces DNS and `connect(target)` replaces the socket after the
|
|
19
22
|
* address check passed, so a test sends a fake public address to a local server. The address check always runs on what `resolve` returns.
|
|
20
23
|
*
|
|
@@ -28,6 +31,21 @@ export declare const untrustedFetch: {
|
|
|
28
31
|
* ```
|
|
29
32
|
*/
|
|
30
33
|
readonly get: typeof untrustedGet;
|
|
34
|
+
/**
|
|
35
|
+
* Posts `body` to `url` with every rule of `get`: scheme, every resolved address checked and the connection made to a checked one,
|
|
36
|
+
* each redirect hop re-checked, `maxBytes`, one `timeoutMs` for the whole fetch, `headers` to the first origin only.
|
|
37
|
+
* A 307 or 308 resends the body; 301, 302 and 303 become a GET without a body; `maxRedirects: 0` refuses any redirect.
|
|
38
|
+
* A non-2xx answer is `{ ok: false, reason: 'status', status }` with no body, as in `get`; a 2xx body over `maxBytes` is
|
|
39
|
+
* `too_large` and carries `status`. `resolve` and `connect` are for tests, as in `get`.
|
|
40
|
+
* The call is the trace step `pipework/fetch:post`; the full URL is recorded only when `urlIsSafeToTrace` is true.
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* const sent = await untrustedFetch.post(endpoint.url, { body: json, headers: { 'content-type': 'application/json' },
|
|
44
|
+
* maxBytes: 65_536, timeoutMs: 10_000, maxRedirects: 0 })
|
|
45
|
+
* if (!sent.ok) return record(sent.reason)
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
readonly post: typeof untrustedPost;
|
|
31
49
|
};
|
|
32
50
|
/** Connect retry — send again only when the connection could not be made, so nothing reached the server. */
|
|
33
51
|
export declare const connectRetry: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"namespace.d.ts","sourceRoot":"","sources":["../../../src/infra/fetch/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"namespace.d.ts","sourceRoot":"","sources":["../../../src/infra/fetch/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEpF,4GAA4G;AAC5G,eAAO,MAAM,cAAc;IACzB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;;IAEH;;;;;;;;;;;;;OAaG;;CAEK,CAAA;AAEV,4GAA4G;AAC5G,eAAO,MAAM,YAAY;IACvB;;;;;;;;;;OAUG;;IAEH,0HAA0H;;IAE1H,+HAA+H;;CAEvH,CAAA"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { untrustedGet } from './handle.js';
|
|
1
|
+
import { untrustedGet, untrustedPost } from './handle.js';
|
|
2
2
|
import { retryUnsent, fetchRetryingUnsent, isUnsentFault } from './connect-retry.js';
|
|
3
3
|
/** Untrusted fetch — fetch one URL a stranger gave without letting them reach the app's private network. */
|
|
4
4
|
export const untrustedFetch = {
|
|
5
5
|
/**
|
|
6
|
-
* Fetches `url` with GET and returns `{ ok: true, url, status, contentType, body, bytes }
|
|
6
|
+
* Fetches `url` with GET and returns `{ ok: true, url, status, contentType, etag, lastModified, body, bytes }` (`etag` and `lastModified` are the response headers, null when absent), or `{ ok: false, reason, message }`
|
|
7
7
|
* naming the rule that refused: `scheme`, `address`, `redirects`, `too_large`, `timeout`, `status`, `rejected` or `network`.
|
|
8
8
|
* It does not throw for these.
|
|
9
9
|
*
|
|
@@ -15,6 +15,9 @@ export const untrustedFetch = {
|
|
|
15
15
|
* `accept({ status, contentType, firstBytes })` sees the first 1024 bytes before the body is kept: return false, or a string
|
|
16
16
|
* (your own message), to refuse with `rejected`. Messages never contain an address or anything a server sent.
|
|
17
17
|
*
|
|
18
|
+
* `followRedirect(to, from)` is asked before each redirect hop: true follows it; false, or a string (your message), refuses with `rejected`.
|
|
19
|
+
* A 304 answer is a `status` refusal with `status: 304`; send `If-None-Match` / `If-Modified-Since` through `headers`.
|
|
20
|
+
*
|
|
18
21
|
* `resolve` and `connect` are for tests: `resolve(hostname)` replaces DNS and `connect(target)` replaces the socket after the
|
|
19
22
|
* address check passed, so a test sends a fake public address to a local server. The address check always runs on what `resolve` returns.
|
|
20
23
|
*
|
|
@@ -28,6 +31,21 @@ export const untrustedFetch = {
|
|
|
28
31
|
* ```
|
|
29
32
|
*/
|
|
30
33
|
get: untrustedGet,
|
|
34
|
+
/**
|
|
35
|
+
* Posts `body` to `url` with every rule of `get`: scheme, every resolved address checked and the connection made to a checked one,
|
|
36
|
+
* each redirect hop re-checked, `maxBytes`, one `timeoutMs` for the whole fetch, `headers` to the first origin only.
|
|
37
|
+
* A 307 or 308 resends the body; 301, 302 and 303 become a GET without a body; `maxRedirects: 0` refuses any redirect.
|
|
38
|
+
* A non-2xx answer is `{ ok: false, reason: 'status', status }` with no body, as in `get`; a 2xx body over `maxBytes` is
|
|
39
|
+
* `too_large` and carries `status`. `resolve` and `connect` are for tests, as in `get`.
|
|
40
|
+
* The call is the trace step `pipework/fetch:post`; the full URL is recorded only when `urlIsSafeToTrace` is true.
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* const sent = await untrustedFetch.post(endpoint.url, { body: json, headers: { 'content-type': 'application/json' },
|
|
44
|
+
* maxBytes: 65_536, timeoutMs: 10_000, maxRedirects: 0 })
|
|
45
|
+
* if (!sent.ok) return record(sent.reason)
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
post: untrustedPost,
|
|
31
49
|
};
|
|
32
50
|
/** Connect retry — send again only when the connection could not be made, so nothing reached the server. */
|
|
33
51
|
export const connectRetry = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"namespace.js","sourceRoot":"","sources":["../../../src/infra/fetch/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"namespace.js","sourceRoot":"","sources":["../../../src/infra/fetch/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEpF,4GAA4G;AAC5G,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,GAAG,EAAE,YAAY;IACjB;;;;;;;;;;;;;OAaG;IACH,IAAI,EAAE,aAAa;CACX,CAAA;AAEV,4GAA4G;AAC5G,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B;;;;;;;;;;OAUG;IACH,KAAK,EAAE,mBAAmB;IAC1B,0HAA0H;IAC1H,IAAI,EAAE,WAAW;IACjB,+HAA+H;IAC/H,aAAa;CACL,CAAA"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { DB } from '../../data/db/index.js';
|
|
2
|
+
import { JOIN_REQUEST_STATUSES } from './schema.js';
|
|
3
|
+
export type JoinRequestStatus = typeof JOIN_REQUEST_STATUSES[number];
|
|
4
|
+
export interface JoinRequest {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly orgId: string;
|
|
7
|
+
readonly userId: string;
|
|
8
|
+
readonly status: JoinRequestStatus;
|
|
9
|
+
readonly reason: string | null;
|
|
10
|
+
/** App data given to `request`; may be personal data, so the audit record leaves it out. */
|
|
11
|
+
readonly evidence: unknown;
|
|
12
|
+
readonly requestedAt: Date;
|
|
13
|
+
readonly expiresAt: Date;
|
|
14
|
+
readonly settledAt: Date | null;
|
|
15
|
+
/** Who settled it; `null` while open, and for an expiry. */
|
|
16
|
+
readonly settledBy: string | null;
|
|
17
|
+
/** The role given on approval. */
|
|
18
|
+
readonly role: string | null;
|
|
19
|
+
/** The note given on refusal. */
|
|
20
|
+
readonly note: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface JoinRequestsConfig {
|
|
23
|
+
/** The app's owner check; roles are the app's. Called inside the org's transaction. */
|
|
24
|
+
isOwner(tx: DB, args: {
|
|
25
|
+
orgId: string;
|
|
26
|
+
userId: string;
|
|
27
|
+
}): Promise<boolean>;
|
|
28
|
+
/** Adds the member; called inside the approve transaction, so a throw rolls the approval back. */
|
|
29
|
+
addMember(tx: DB, args: {
|
|
30
|
+
orgId: string;
|
|
31
|
+
userId: string;
|
|
32
|
+
role: string | undefined;
|
|
33
|
+
requestId: string;
|
|
34
|
+
by: string;
|
|
35
|
+
}): Promise<void>;
|
|
36
|
+
/** How long a request stays open. Default 30 days. */
|
|
37
|
+
readonly expiresInMs?: number;
|
|
38
|
+
/** Awaited inside the transaction that created the request; a throw rolls it back. */
|
|
39
|
+
onRequested?(tx: DB, request: JoinRequest): void | Promise<void>;
|
|
40
|
+
/** Awaited inside the transaction that settled the request (approved, refused, withdrawn, expired); a throw rolls it back. */
|
|
41
|
+
onSettled?(tx: DB, request: JoinRequest): void | Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
export type SettleResult = {
|
|
44
|
+
readonly ok: true;
|
|
45
|
+
readonly request: JoinRequest;
|
|
46
|
+
} | {
|
|
47
|
+
readonly ok: false;
|
|
48
|
+
readonly reason: 'not-found' | 'expired' | 'already-settled';
|
|
49
|
+
};
|
|
50
|
+
export interface JoinRequests {
|
|
51
|
+
/**
|
|
52
|
+
* Asks to join `orgId` as `userId`. Runs in the named org (the joiner is not in it). One open request
|
|
53
|
+
* per (org, user): while one is open and unexpired it is returned with `created: false`, with no audit
|
|
54
|
+
* record and no hook. An open one past its expiry is marked expired first. Writes a `JoinRequest:request`
|
|
55
|
+
* audit record when the Flow has a trace with an open step.
|
|
56
|
+
*/
|
|
57
|
+
request(orgId: string, userId: string, opts?: {
|
|
58
|
+
reason?: string;
|
|
59
|
+
evidence?: unknown;
|
|
60
|
+
}): Promise<{
|
|
61
|
+
request: JoinRequest;
|
|
62
|
+
created: boolean;
|
|
63
|
+
}>;
|
|
64
|
+
/** The Flow tenant's owner (`isOwner`) approves: marks it approved and calls `addMember`, in one transaction. A non-owner gets `ForbiddenError`. */
|
|
65
|
+
approve(requestId: string, opts: {
|
|
66
|
+
role?: string;
|
|
67
|
+
}, by: string): Promise<SettleResult>;
|
|
68
|
+
/** The Flow tenant's owner refuses it. A non-owner gets `ForbiddenError`. */
|
|
69
|
+
refuse(requestId: string, opts: {
|
|
70
|
+
note?: string;
|
|
71
|
+
}, by: string): Promise<SettleResult>;
|
|
72
|
+
/** The joiner withdraws their own open request; another user's id is `not-found`. */
|
|
73
|
+
withdraw(requestId: string, userId: string): Promise<SettleResult>;
|
|
74
|
+
/** The Flow tenant's requests with `status` (default `open`), newest first. */
|
|
75
|
+
list(opts?: {
|
|
76
|
+
status?: JoinRequestStatus;
|
|
77
|
+
}): Promise<JoinRequest[]>;
|
|
78
|
+
/** One user's requests across orgs, read through the system channel, newest first. */
|
|
79
|
+
mine(userId: string): Promise<JoinRequest[]>;
|
|
80
|
+
/** Marks every open request past its expiry as expired; schedule it (a cron job). */
|
|
81
|
+
sweep(): Promise<{
|
|
82
|
+
expired: number;
|
|
83
|
+
}>;
|
|
84
|
+
}
|
|
85
|
+
export declare function createJoinRequests(config: JoinRequestsConfig): JoinRequests;
|
|
86
|
+
//# sourceMappingURL=handle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handle.d.ts","sourceRoot":"","sources":["../../../src/infra/join-requests/handle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { sql } from '../../data/db/sql.js';
|
|
2
|
+
import { pipe, pipeRaw, pipeSystem } from '../../data/db/db.js';
|
|
3
|
+
import { requireContext } from '../../request/context/store.js';
|
|
4
|
+
import { writeInfraAudit } from '../../data/write/audit/infra-audit.js';
|
|
5
|
+
import { ValidationError, ForbiddenError } from '../../core/errors/index.js';
|
|
6
|
+
import { inTenant as runAsTenant } from '../../auth/tenant/in-tenant.js';
|
|
7
|
+
import { JOIN_REQUEST_STATUSES } from './schema.js';
|
|
8
|
+
const DEFAULT_EXPIRES_IN_MS = 30 * 24 * 60 * 60 * 1000;
|
|
9
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10
|
+
const MAX_INSERT_ATTEMPTS = 3;
|
|
11
|
+
function toDate(value) {
|
|
12
|
+
return value instanceof Date ? value : new Date(String(value));
|
|
13
|
+
}
|
|
14
|
+
function toDateOrNull(value) {
|
|
15
|
+
return value === null || value === undefined ? null : toDate(value);
|
|
16
|
+
}
|
|
17
|
+
function rowToRequest(row) {
|
|
18
|
+
return {
|
|
19
|
+
id: row['id'],
|
|
20
|
+
orgId: row['tenant_id'],
|
|
21
|
+
userId: row['user_id'],
|
|
22
|
+
status: row['status'],
|
|
23
|
+
reason: row['reason'] ?? null,
|
|
24
|
+
evidence: row['evidence'] ?? null,
|
|
25
|
+
requestedAt: toDate(row['requested_at']),
|
|
26
|
+
expiresAt: toDate(row['expires_at']),
|
|
27
|
+
settledAt: toDateOrNull(row['settled_at']),
|
|
28
|
+
settledBy: row['settled_by'] ?? null,
|
|
29
|
+
role: row['role'] ?? null,
|
|
30
|
+
note: row['note'] ?? null,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function isUniqueViolation(err) {
|
|
34
|
+
let current = err;
|
|
35
|
+
for (let depth = 0; depth < 4 && typeof current === 'object' && current !== null; depth++) {
|
|
36
|
+
if (current.code === '23505')
|
|
37
|
+
return true;
|
|
38
|
+
current = current.cause;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
function requireUuid(value, name) {
|
|
43
|
+
if (typeof value !== 'string' || !UUID.test(value)) {
|
|
44
|
+
throw new ValidationError({ [name]: ['must be a uuid'] }, `joinRequests: ${name} must be a uuid, got ${JSON.stringify(value)}.`);
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
function requireText(value, name) {
|
|
49
|
+
if (typeof value !== 'string' || value === '') {
|
|
50
|
+
throw new ValidationError({ [name]: ['must be a non-empty string'] }, `joinRequests: ${name} must be a non-empty string.`);
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
// The audit record leaves out `evidence`, which may be personal data.
|
|
55
|
+
function auditProjection(request) {
|
|
56
|
+
const { id, orgId, userId, status, reason, requestedAt, expiresAt, settledAt, settledBy, role, note } = request;
|
|
57
|
+
return { id, orgId, userId, status, reason, requestedAt, expiresAt, settledAt, settledBy, role, note };
|
|
58
|
+
}
|
|
59
|
+
export function createJoinRequests(config) {
|
|
60
|
+
if (typeof config.isOwner !== 'function' || typeof config.addMember !== 'function') {
|
|
61
|
+
throw new Error('[pipework] joinRequests.create needs `isOwner` and `addMember`.\n\n Roles are the app\'s: isOwner(tx, { orgId, userId }) says who may settle a request, and\n addMember(tx, { orgId, userId, role, requestId, by }) adds the member inside the approval.\n');
|
|
62
|
+
}
|
|
63
|
+
const expiresInMs = config.expiresInMs ?? DEFAULT_EXPIRES_IN_MS;
|
|
64
|
+
if (!Number.isFinite(expiresInMs) || expiresInMs <= 0) {
|
|
65
|
+
throw new Error('[pipework] joinRequests expiresInMs must be a positive number of milliseconds.\n');
|
|
66
|
+
}
|
|
67
|
+
function requireTenant() {
|
|
68
|
+
const flow = requireContext();
|
|
69
|
+
if (flow.tenant === null) {
|
|
70
|
+
throw new Error('[pipework] This joinRequests method needs a tenant in the Flow.\n\n' +
|
|
71
|
+
' approve/refuse/list act for one org. Call them from an authenticated tenant route;\n' +
|
|
72
|
+
' request/withdraw/mine are the ones for the joiner, who is not in the org.\n');
|
|
73
|
+
}
|
|
74
|
+
return { flow, tenantId: flow.tenant };
|
|
75
|
+
}
|
|
76
|
+
function inTenant(flow, tenantId, fn) {
|
|
77
|
+
return runAsTenant(flow, tenantId, fn);
|
|
78
|
+
}
|
|
79
|
+
function audit(flow, tx, verb, request) {
|
|
80
|
+
return writeInfraAudit(flow, tx, {
|
|
81
|
+
tenantId: request.orgId, entity: 'JoinRequest', verb, entityId: request.id, after: auditProjection(request),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async function settled(flow, tx, verb, request) {
|
|
85
|
+
await audit(flow, tx, verb, request);
|
|
86
|
+
await config.onSettled?.(tx, request);
|
|
87
|
+
}
|
|
88
|
+
// Says why an open row was not there to settle.
|
|
89
|
+
async function whyNot(tx, requestId, orgId) {
|
|
90
|
+
const rows = await tx.execute(sql `
|
|
91
|
+
SELECT status, (expires_at <= now()) AS past FROM join_request WHERE id = ${requestId} AND tenant_id = ${orgId}`);
|
|
92
|
+
const row = rows[0];
|
|
93
|
+
if (row === undefined)
|
|
94
|
+
return 'not-found';
|
|
95
|
+
if (row['status'] === 'expired' || (row['status'] === 'open' && row['past'] === true))
|
|
96
|
+
return 'expired';
|
|
97
|
+
return 'already-settled';
|
|
98
|
+
}
|
|
99
|
+
async function settleAsOwner(kind, requestId, by, opts) {
|
|
100
|
+
requireUuid(requestId, 'requestId');
|
|
101
|
+
requireText(by, 'by');
|
|
102
|
+
const { flow, tenantId } = requireTenant();
|
|
103
|
+
return pipe.transaction(async (tx) => {
|
|
104
|
+
if (!(await config.isOwner(tx, { orgId: tenantId, userId: by }))) {
|
|
105
|
+
throw new ForbiddenError(`Only an owner of the organisation can ${kind} a join request.`, { orgId: tenantId, userId: by });
|
|
106
|
+
}
|
|
107
|
+
const rows = kind === 'approve'
|
|
108
|
+
? await tx.execute(sql `
|
|
109
|
+
UPDATE join_request SET status = 'approved', settled_at = now(), settled_by = ${by}, role = ${opts.role ?? null}
|
|
110
|
+
WHERE id = ${requestId} AND tenant_id = ${tenantId} AND status = 'open' AND expires_at > now()
|
|
111
|
+
RETURNING *`)
|
|
112
|
+
: await tx.execute(sql `
|
|
113
|
+
UPDATE join_request SET status = 'refused', settled_at = now(), settled_by = ${by}, note = ${opts.note ?? null}
|
|
114
|
+
WHERE id = ${requestId} AND tenant_id = ${tenantId} AND status = 'open' AND expires_at > now()
|
|
115
|
+
RETURNING *`);
|
|
116
|
+
if (rows.length === 0)
|
|
117
|
+
return { ok: false, reason: await whyNot(tx, requestId, tenantId) };
|
|
118
|
+
const request = rowToRequest(rows[0]);
|
|
119
|
+
if (kind === 'approve') {
|
|
120
|
+
await config.addMember(tx, { orgId: tenantId, userId: request.userId, role: opts.role, requestId, by });
|
|
121
|
+
}
|
|
122
|
+
await settled(flow, tx, kind, request);
|
|
123
|
+
return { ok: true, request };
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
// Marks the open requests of one org that are past expiry (optionally one user's) as expired.
|
|
127
|
+
async function expireOpen(flow, tx, orgId, userId) {
|
|
128
|
+
const rows = await tx.execute(sql `
|
|
129
|
+
UPDATE join_request SET status = 'expired', settled_at = now()
|
|
130
|
+
WHERE tenant_id = ${orgId} AND status = 'open' AND expires_at <= now()
|
|
131
|
+
${userId === undefined ? sql `` : sql `AND user_id = ${userId}`}
|
|
132
|
+
RETURNING *`);
|
|
133
|
+
for (const row of rows)
|
|
134
|
+
await settled(flow, tx, 'expire', rowToRequest(row));
|
|
135
|
+
return rows.length;
|
|
136
|
+
}
|
|
137
|
+
const handle = {
|
|
138
|
+
async request(orgId, userId, opts) {
|
|
139
|
+
requireUuid(orgId, 'orgId');
|
|
140
|
+
requireText(userId, 'userId');
|
|
141
|
+
const flow = requireContext();
|
|
142
|
+
const evidence = opts?.evidence === undefined || opts.evidence === null ? null : JSON.stringify(opts.evidence);
|
|
143
|
+
return inTenant(flow, orgId, async (tx) => {
|
|
144
|
+
for (let attempt = 1; attempt <= MAX_INSERT_ATTEMPTS; attempt++) {
|
|
145
|
+
const open = await tx.execute(sql `
|
|
146
|
+
SELECT *, (expires_at <= now()) AS past FROM join_request
|
|
147
|
+
WHERE tenant_id = ${orgId} AND user_id = ${userId} AND status = 'open'`);
|
|
148
|
+
if (open.length > 0) {
|
|
149
|
+
if (open[0]['past'] !== true)
|
|
150
|
+
return { request: rowToRequest(open[0]), created: false };
|
|
151
|
+
await expireOpen(flow, tx, orgId, userId);
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
// A savepoint: losing the race to another request aborts only this insert.
|
|
155
|
+
const rows = await tx.transaction((sp) => sp.execute(sql `
|
|
156
|
+
INSERT INTO join_request (tenant_id, user_id, status, reason, evidence, expires_at)
|
|
157
|
+
VALUES (${orgId}, ${userId}, 'open', ${opts?.reason ?? null},
|
|
158
|
+
${evidence === null ? sql `NULL` : sql `${evidence}::jsonb`},
|
|
159
|
+
now() + ${expiresInMs}::float8 * interval '1 millisecond')
|
|
160
|
+
RETURNING *`));
|
|
161
|
+
const request = rowToRequest(rows[0]);
|
|
162
|
+
await audit(flow, tx, 'request', request);
|
|
163
|
+
await config.onRequested?.(tx, request);
|
|
164
|
+
return { request, created: true };
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
if (!isUniqueViolation(err))
|
|
168
|
+
throw err;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
throw new Error('[pipework] joinRequests.request could not settle on an open request after repeated conflicts; retry.\n');
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
approve(requestId, opts, by) {
|
|
175
|
+
return settleAsOwner('approve', requestId, by, { ...(opts.role !== undefined ? { role: opts.role } : {}) });
|
|
176
|
+
},
|
|
177
|
+
refuse(requestId, opts, by) {
|
|
178
|
+
return settleAsOwner('refuse', requestId, by, { ...(opts.note !== undefined ? { note: opts.note } : {}) });
|
|
179
|
+
},
|
|
180
|
+
async withdraw(requestId, userId) {
|
|
181
|
+
requireUuid(requestId, 'requestId');
|
|
182
|
+
requireText(userId, 'userId');
|
|
183
|
+
const flow = requireContext();
|
|
184
|
+
const found = await pipeSystem().execute(sql `SELECT tenant_id FROM join_request WHERE id = ${requestId}`);
|
|
185
|
+
if (found.length === 0)
|
|
186
|
+
return { ok: false, reason: 'not-found' };
|
|
187
|
+
const orgId = found[0]['tenant_id'];
|
|
188
|
+
return inTenant(flow, orgId, async (tx) => {
|
|
189
|
+
const rows = await tx.execute(sql `
|
|
190
|
+
UPDATE join_request SET status = 'withdrawn', settled_at = now(), settled_by = ${userId}
|
|
191
|
+
WHERE id = ${requestId} AND tenant_id = ${orgId} AND user_id = ${userId} AND status = 'open' AND expires_at > now()
|
|
192
|
+
RETURNING *`);
|
|
193
|
+
if (rows.length === 0) {
|
|
194
|
+
const own = await tx.execute(sql `SELECT 1 FROM join_request WHERE id = ${requestId} AND tenant_id = ${orgId} AND user_id = ${userId}`);
|
|
195
|
+
return { ok: false, reason: own.length === 0 ? 'not-found' : await whyNot(tx, requestId, orgId) };
|
|
196
|
+
}
|
|
197
|
+
const request = rowToRequest(rows[0]);
|
|
198
|
+
await settled(flow, tx, 'withdraw', request);
|
|
199
|
+
return { ok: true, request };
|
|
200
|
+
});
|
|
201
|
+
},
|
|
202
|
+
async list(opts) {
|
|
203
|
+
const { tenantId } = requireTenant();
|
|
204
|
+
const status = opts?.status ?? 'open';
|
|
205
|
+
if (!JOIN_REQUEST_STATUSES.includes(status)) {
|
|
206
|
+
throw new ValidationError({ status: [`must be one of ${JOIN_REQUEST_STATUSES.join(', ')}`] }, 'joinRequests list status is not valid.');
|
|
207
|
+
}
|
|
208
|
+
const rows = await pipeRaw().execute(sql `
|
|
209
|
+
SELECT * FROM join_request WHERE tenant_id = ${tenantId} AND status = ${status}
|
|
210
|
+
ORDER BY requested_at DESC, id DESC`);
|
|
211
|
+
return rows.map(rowToRequest);
|
|
212
|
+
},
|
|
213
|
+
async mine(userId) {
|
|
214
|
+
requireText(userId, 'userId');
|
|
215
|
+
const rows = await pipeSystem().execute(sql `
|
|
216
|
+
SELECT * FROM join_request WHERE user_id = ${userId} ORDER BY requested_at DESC, id DESC`);
|
|
217
|
+
return rows.map(rowToRequest);
|
|
218
|
+
},
|
|
219
|
+
async sweep() {
|
|
220
|
+
const flow = requireContext();
|
|
221
|
+
const orgs = await pipeSystem().execute(sql `
|
|
222
|
+
SELECT DISTINCT tenant_id FROM join_request WHERE status = 'open' AND expires_at <= now()`);
|
|
223
|
+
let expired = 0;
|
|
224
|
+
for (const org of orgs) {
|
|
225
|
+
const orgId = org['tenant_id'];
|
|
226
|
+
expired += await inTenant(flow, orgId, tx => expireOpen(flow, tx, orgId));
|
|
227
|
+
}
|
|
228
|
+
return { expired };
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
return handle;
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=handle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handle.js","sourceRoot":"","sources":["../../../src/infra/join-requests/handle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/infra/join-requests/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,WAAW,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/infra/join-requests/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createJoinRequests } from './handle.js';
|
|
2
|
+
/** Join requests namespace — a user asks to join an existing org and one of its owners settles it. */
|
|
3
|
+
export declare const joinRequests: {
|
|
4
|
+
/** Creates a join-request handle: `request`, `approve`, `refuse`, `withdraw`, `list`, `mine` and `sweep`. Needs blueprint `joinRequests: {}`. */
|
|
5
|
+
readonly create: typeof createJoinRequests;
|
|
6
|
+
};
|
|
7
|
+
//# sourceMappingURL=namespace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"namespace.d.ts","sourceRoot":"","sources":["../../../src/infra/join-requests/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAEhD,sGAAsG;AACtG,eAAO,MAAM,YAAY;IACvB,iJAAiJ;;CAEzI,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createJoinRequests } from './handle.js';
|
|
2
|
+
/** Join requests namespace — a user asks to join an existing org and one of its owners settles it. */
|
|
3
|
+
export const joinRequests = {
|
|
4
|
+
/** Creates a join-request handle: `request`, `approve`, `refuse`, `withdraw`, `list`, `mine` and `sweep`. Needs blueprint `joinRequests: {}`. */
|
|
5
|
+
create: createJoinRequests,
|
|
6
|
+
};
|
|
7
|
+
//# sourceMappingURL=namespace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"namespace.js","sourceRoot":"","sources":["../../../src/infra/join-requests/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAEhD,sGAAsG;AACtG,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,iJAAiJ;IACjJ,MAAM,EAAE,kBAAkB;CAClB,CAAA"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export declare const JOIN_REQUEST_STATUSES: readonly ["open", "approved", "refused", "withdrawn", "expired"];
|
|
2
|
+
export declare const joinRequest: import("../../data/domain/types.js").DefinedTable<{
|
|
3
|
+
id: import("../../data/domain/types.js").FieldDescriptor<string, {
|
|
4
|
+
defaultRandom: true;
|
|
5
|
+
hasDefault: true;
|
|
6
|
+
} & {
|
|
7
|
+
primaryKey: true;
|
|
8
|
+
}> & import("../../data/domain/field.js").FieldModifiers<string, {
|
|
9
|
+
defaultRandom: true;
|
|
10
|
+
hasDefault: true;
|
|
11
|
+
} & {
|
|
12
|
+
primaryKey: true;
|
|
13
|
+
}>;
|
|
14
|
+
tenantId: import("../../data/domain/types.js").FieldDescriptor<string, {
|
|
15
|
+
tenant: true;
|
|
16
|
+
}> & import("../../data/domain/field.js").FieldModifiers<string, {
|
|
17
|
+
tenant: true;
|
|
18
|
+
}>;
|
|
19
|
+
userId: import("../../data/domain/types.js").FieldDescriptor<string, {}> & import("../../data/domain/field.js").FieldModifiers<string, {}>;
|
|
20
|
+
status: import("../../data/domain/types.js").FieldDescriptor<"expired" | "open" | "refused" | "approved" | "withdrawn", {}> & import("../../data/domain/field.js").FieldModifiers<"expired" | "open" | "refused" | "approved" | "withdrawn", {}>;
|
|
21
|
+
reason: import("../../data/domain/types.js").FieldDescriptor<string | null, {
|
|
22
|
+
nullable: true;
|
|
23
|
+
}> & import("../../data/domain/field.js").FieldModifiers<string | null, {
|
|
24
|
+
nullable: true;
|
|
25
|
+
}>;
|
|
26
|
+
evidence: import("../../data/domain/types.js").FieldDescriptor<import("zod/v4").JSONType, {
|
|
27
|
+
nullable: true;
|
|
28
|
+
}> & import("../../data/domain/field.js").FieldModifiers<import("zod/v4").JSONType, {
|
|
29
|
+
nullable: true;
|
|
30
|
+
}>;
|
|
31
|
+
requestedAt: import("../../data/domain/types.js").FieldDescriptor<Date, {
|
|
32
|
+
hasDefault: true;
|
|
33
|
+
}> & import("../../data/domain/field.js").FieldModifiers<Date, {
|
|
34
|
+
hasDefault: true;
|
|
35
|
+
}>;
|
|
36
|
+
expiresAt: import("../../data/domain/types.js").FieldDescriptor<Date, {}> & import("../../data/domain/field.js").FieldModifiers<Date, {}>;
|
|
37
|
+
settledAt: import("../../data/domain/types.js").FieldDescriptor<Date | null, {
|
|
38
|
+
nullable: true;
|
|
39
|
+
}> & import("../../data/domain/field.js").FieldModifiers<Date | null, {
|
|
40
|
+
nullable: true;
|
|
41
|
+
}>;
|
|
42
|
+
settledBy: import("../../data/domain/types.js").FieldDescriptor<string | null, {
|
|
43
|
+
nullable: true;
|
|
44
|
+
}> & import("../../data/domain/field.js").FieldModifiers<string | null, {
|
|
45
|
+
nullable: true;
|
|
46
|
+
}>;
|
|
47
|
+
role: import("../../data/domain/types.js").FieldDescriptor<string | null, {
|
|
48
|
+
nullable: true;
|
|
49
|
+
}> & import("../../data/domain/field.js").FieldModifiers<string | null, {
|
|
50
|
+
nullable: true;
|
|
51
|
+
}>;
|
|
52
|
+
note: import("../../data/domain/types.js").FieldDescriptor<string | null, {
|
|
53
|
+
nullable: true;
|
|
54
|
+
}> & import("../../data/domain/field.js").FieldModifiers<string | null, {
|
|
55
|
+
nullable: true;
|
|
56
|
+
}>;
|
|
57
|
+
}, "join_request">;
|
|
58
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/infra/join-requests/schema.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,qBAAqB,kEAAmE,CAAA;AAErG,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAmBtB,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// The table behind join requests. `join_request` carries a `.tenant()` key (the
|
|
2
|
+
// org the user asks to join), so generated migrations put it under forced
|
|
3
|
+
// row-level security. One open request per (org, user) is enforced by a
|
|
4
|
+
// partial unique index.
|
|
5
|
+
import { define } from '../../data/domain/index.js';
|
|
6
|
+
import { field } from '../../data/domain/field.js';
|
|
7
|
+
import { schema } from '../../data/schema/namespace.js';
|
|
8
|
+
import { sql } from '../../data/db/sql.js';
|
|
9
|
+
import { index, uniqueIndex } from '../../data/db/idx/index.js';
|
|
10
|
+
export const JOIN_REQUEST_STATUSES = ['open', 'approved', 'refused', 'withdrawn', 'expired'];
|
|
11
|
+
export const joinRequest = define('join_request', {
|
|
12
|
+
id: field.uuid().defaultRandom().primaryKey(),
|
|
13
|
+
tenantId: field.uuid().tenant(),
|
|
14
|
+
userId: field.text(),
|
|
15
|
+
status: field.enum(JOIN_REQUEST_STATUSES),
|
|
16
|
+
reason: field.text().nullable(),
|
|
17
|
+
evidence: field.jsonb(schema.check.json()).nullable(),
|
|
18
|
+
requestedAt: field.timestamp().default('now'),
|
|
19
|
+
expiresAt: field.timestamp(),
|
|
20
|
+
settledAt: field.timestamp().nullable(),
|
|
21
|
+
settledBy: field.text().nullable(),
|
|
22
|
+
role: field.text().nullable(),
|
|
23
|
+
note: field.text().nullable(),
|
|
24
|
+
}, {
|
|
25
|
+
indexes: (t) => ({
|
|
26
|
+
oneOpen: uniqueIndex('uq_join_request_open').on(t['tenantId'], t['userId']).where(sql.raw(`status = 'open'`)),
|
|
27
|
+
byUser: index('idx_join_request_user').on(t['userId']),
|
|
28
|
+
byStatus: index('idx_join_request_status').on(t['tenantId'], t['status']),
|
|
29
|
+
}),
|
|
30
|
+
});
|
|
31
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/infra/join-requests/schema.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,0EAA0E;AAC1E,wEAAwE;AACxE,wBAAwB;AAExB,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAA;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,gCAAgC,CAAA;AACvD,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAC1C,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AAE/D,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,CAAU,CAAA;AAErG,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,cAAc,EAAE;IAChD,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,aAAa,EAAE,CAAC,UAAU,EAAE;IAC7C,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE;IAC/B,MAAM,EAAE,KAAK,CAAC,IAAI,EAAE;IACpB,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC;IACzC,MAAM,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;IAC/B,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE;IACrD,WAAW,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IAC7C,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE;IAC5B,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE;IACvC,SAAS,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;IAClC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;IAC7B,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;CAC9B,EAAE;IACD,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACf,OAAO,EAAE,WAAW,CAAC,sBAAsB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;QAC7G,MAAM,EAAE,KAAK,CAAC,uBAAuB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACtD,QAAQ,EAAE,KAAK,CAAC,yBAAyB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;KAC1E,CAAC;CACH,CAAC,CAAA"}
|
|
@@ -47,6 +47,15 @@ export interface OutboxOptions<D> {
|
|
|
47
47
|
export interface Outbox {
|
|
48
48
|
/** Delivers what is due now, up to 20 batches; rows that fail wait for the next call. */
|
|
49
49
|
drain(): Promise<OutboxTotals>;
|
|
50
|
+
/**
|
|
51
|
+
* Puts given-up rows (`failed_at` set, never delivered) back on the queue: `failed_at` cleared, `attempts` 0, due now.
|
|
52
|
+
* `last_error` stays until the next attempt. Ids are unchanged. Returns each requeued row as it now is, plus
|
|
53
|
+
* `prev_attempts`, the attempts it had when given up.
|
|
54
|
+
*/
|
|
55
|
+
requeue(options: {
|
|
56
|
+
sink?: string;
|
|
57
|
+
failedSince?: Date;
|
|
58
|
+
}): Promise<Record<string, unknown>[]>;
|
|
50
59
|
}
|
|
51
60
|
export declare function createOutbox<D>(options: OutboxOptions<D>): Outbox;
|
|
52
61
|
//# sourceMappingURL=core.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../src/infra/outbox/core.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../src/infra/outbox/core.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;CACf;AAED,4HAA4H;AAC5H,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE;IAC/D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAA;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;IACnB,0BAA0B;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,IAAI,CAAC,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE;IACvD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC/C;AAED,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,IAAI,CAAC,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACjC;AAED,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,mKAAmK;IACnK,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IACrC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;IAClD,QAAQ,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC,CAAA;IACzE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACtF,QAAQ,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACpG;AAED,MAAM,WAAW,MAAM;IACrB,yFAAyF;IACzF,KAAK,IAAI,OAAO,CAAC,YAAY,CAAC,CAAA;IAC9B;;;;OAIG;IACH,OAAO,CAAC,OAAO,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAA;CAC5F;AAUD,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,MAAM,CAkFjE"}
|