@byok-sdk/cloud-dataplane 0.4.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/LICENSE +21 -0
- package/README.md +224 -0
- package/dist/cleanup.d.ts +106 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +4284 -0
- package/dist/index.js.map +1 -0
- package/dist/migrate.d.ts +54 -0
- package/dist/migrations-dir.d.ts +7 -0
- package/dist/pool.d.ts +39 -0
- package/dist/sql/0001_cloud_local.sql +184 -0
- package/dist/sql/0002_core_domain.sql +416 -0
- package/dist/sql/0003_cloud_cleanup.sql +97 -0
- package/dist/sql/0004_device_proof_truth.sql +39 -0
- package/dist/sql/0005_skill_packs.sql +78 -0
- package/dist/sql/0006_device_presence_toolsets.sql +18 -0
- package/dist/stores/core/board.d.ts +38 -0
- package/dist/stores/core/index.d.ts +37 -0
- package/dist/stores/core/mailbox-sequence.d.ts +10 -0
- package/dist/stores/core/mailbox.d.ts +32 -0
- package/dist/stores/core/objects.d.ts +49 -0
- package/dist/stores/core/presence.d.ts +34 -0
- package/dist/stores/core/quota.d.ts +56 -0
- package/dist/stores/core/skill-pack.d.ts +41 -0
- package/dist/stores/core/truth.d.ts +32 -0
- package/dist/stores/dedup.d.ts +22 -0
- package/dist/stores/devices.d.ts +23 -0
- package/dist/stores/index.d.ts +55 -0
- package/dist/stores/nonces.d.ts +26 -0
- package/dist/stores/pairing-codes.d.ts +26 -0
- package/dist/stores/proof-receipts.d.ts +12 -0
- package/dist/stores/r2-blobs.d.ts +249 -0
- package/dist/stores/receipts.d.ts +25 -0
- package/dist/stores/task-attempts.d.ts +36 -0
- package/dist/truth-committer.d.ts +15 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ancienttwo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# @byok-sdk/cloud-dataplane
|
|
2
|
+
|
|
3
|
+
The durable data plane for the BYOK SDK's hosted device surface: Postgres
|
|
4
|
+
implementations of **all nine cloud-local store ports and all seven `@byok-sdk/core`
|
|
5
|
+
ports**, the R2/S3 object adapter that backs the blob port, and the forward-only
|
|
6
|
+
migration runner that creates the tables they read.
|
|
7
|
+
|
|
8
|
+
Three store/maintenance compositions plus one transaction authority ship from here. `createPostgresCloudStores` supplies the full
|
|
9
|
+
`CloudStores` bundle (`devices`, `pairingCodes`, `nonces`, `dedup`, `tasks`,
|
|
10
|
+
`receipts`, `proofReceipts`, `blobs`, `rateLimiter`); `createPostgresCoreStores`
|
|
11
|
+
supplies the full `CoreStores` bundle (`mailbox`, `board`, `truth`, `presence`,
|
|
12
|
+
`activity`, `objects`, `quota`). Both return every port rather than a subset,
|
|
13
|
+
because the conformance suites certify a composition as a whole — there is no
|
|
14
|
+
partial bundle for them to run. `createPostgresCloudMaintenance` is the third,
|
|
15
|
+
host-only operational composition; it is deliberately outside both port
|
|
16
|
+
inventories.
|
|
17
|
+
|
|
18
|
+
`PostgresTruthCommitter` is the S6 transaction authority rather than another
|
|
19
|
+
raw store bundle. It owns the one transaction that couples proof receipt,
|
|
20
|
+
terminal/snapshot preconditions, committed object checks, object references,
|
|
21
|
+
tenant/hash inline logical accounting and the stored response. A production
|
|
22
|
+
cloud composition declares `truth.records` only when it supplies both this
|
|
23
|
+
committer and `stores.blobs` as the content-hash keyed `TruthObjectDownloads`
|
|
24
|
+
authority; `R2CloudBlobStore` uses the content hash as its blob id, so it
|
|
25
|
+
satisfies that contract directly.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
const stores = createPostgresCloudStores(options);
|
|
29
|
+
const truthCommitter = new PostgresTruthCommitter({ pool, clock, crypto });
|
|
30
|
+
|
|
31
|
+
createByokCloud({
|
|
32
|
+
// core, cloud: stores, crypto, tokenSigner, clock, capabilities, ...
|
|
33
|
+
truthCommitter,
|
|
34
|
+
truthObjectDownloads: stores.blobs,
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Two of those nine are not tables. `rateLimiter` is the allow-all reference and
|
|
39
|
+
gets no table by design: persisting an allow-all would be a table that is always
|
|
40
|
+
empty, and a real limiter is edge work rather than a per-request write. `blobs`
|
|
41
|
+
is the R2 adapter described below.
|
|
42
|
+
|
|
43
|
+
## Blobs
|
|
44
|
+
|
|
45
|
+
`blobs` mints grants and never carries a byte. `createUpload` writes the
|
|
46
|
+
`pending` `object_manifest` row first, then signs a PUT bound to the tenant, the
|
|
47
|
+
key, the declared `Content-Length`, the declared `Content-Type`, and an expiry;
|
|
48
|
+
the device uploads straight to the object store. `pending → committed` happens
|
|
49
|
+
on explicit finalize, behind an unconditional `HEAD` that compares what the store
|
|
50
|
+
actually holds against what was declared — a signed length proves what one
|
|
51
|
+
client sent, not what is at the key now.
|
|
52
|
+
|
|
53
|
+
Two consequences worth stating outright:
|
|
54
|
+
|
|
55
|
+
- **This composition supplies no `BlobContentProxy`.** The two
|
|
56
|
+
`/byok/blobs/:id/content` routes exist for compositions that have nowhere else
|
|
57
|
+
to put bytes; a device uploading directly to R2 is exactly what having no
|
|
58
|
+
byte-proxy path means. A hosted deployment therefore declares
|
|
59
|
+
`blobs.presigned` and **not** `blobs.contentproxy`, and the routes do not
|
|
60
|
+
mount. See `deploy/env/hosted.env.example`.
|
|
61
|
+
- **The `blobId` is the content hash.** There is no surrogate object id, so
|
|
62
|
+
every read is `(tenant, hash)` against the manifest primary key and object
|
|
63
|
+
keys are built at one point from a value core already validated. A non-hex id
|
|
64
|
+
cannot become a `ContentHash`, so it cannot reach key construction.
|
|
65
|
+
|
|
66
|
+
### `keyPrefix`: one bucket, several deployments
|
|
67
|
+
|
|
68
|
+
Object keys default to `tenants/<tenant>/objects/sha256/<hex>` at the bucket
|
|
69
|
+
root. `R2BlobStoreOptions.keyPrefix` puts a namespace in front of that —
|
|
70
|
+
`keyPrefix: 'acme/prod'` writes `acme/prod/tenants/<tenant>/objects/sha256/<hex>`
|
|
71
|
+
— so a host running more than one product against one R2 account no longer needs
|
|
72
|
+
a bucket per product. Omit the option and the key is the unprefixed layout, byte
|
|
73
|
+
for byte.
|
|
74
|
+
|
|
75
|
+
**It is immutable per deployment, and it is only for a new one.** The same field
|
|
76
|
+
builds the key on write and on read; nothing falls back to a second layout, and
|
|
77
|
+
nothing will be added that does — a dual-read across an old and a new prefix
|
|
78
|
+
would make two key layouts authoritative for the same object at the same time.
|
|
79
|
+
So changing `keyPrefix` on a deployment that has already stored objects strands
|
|
80
|
+
them: still in the bucket, no longer addressable, invisible to the cleanup
|
|
81
|
+
maintenance surface. Moving an existing deployment onto a prefix means a
|
|
82
|
+
separate, operator-invoked, one-shot copy of the objects themselves, which this
|
|
83
|
+
SDK does not provide.
|
|
84
|
+
|
|
85
|
+
The value is validated when the store is constructed, never afterwards (there is
|
|
86
|
+
no setter): slash-joined segments of lowercase alphanumerics, `.`, `_` and `-`,
|
|
87
|
+
each segment starting with an alphanumeric — no leading or trailing slash, no
|
|
88
|
+
empty segment, no uppercase, nothing that would be percent-encoded. Anything else
|
|
89
|
+
throws a `ByokCoreError` with code `object_key_prefix_invalid` at construction.
|
|
90
|
+
`keyPrefix: ''` is refused rather than treated as "no prefix": an empty string is
|
|
91
|
+
what an unset environment variable looks like, and accepting it would silently
|
|
92
|
+
decide where a deployment's objects live.
|
|
93
|
+
|
|
94
|
+
`x-amz-checksum-sha256` is deliberately not signed. MinIO honors it, but R2's S3
|
|
95
|
+
compatibility table implements SHA-256 as `COMPOSITE` only — not the
|
|
96
|
+
`FULL_OBJECT` type a single-shot PutObject uses — so signing it would mint URLs
|
|
97
|
+
that pass against the test substrate and fail in production. `HEAD` only
|
|
98
|
+
observes existence, size and content type; it never verifies SHA-256.
|
|
99
|
+
|
|
100
|
+
## Cleanup maintenance
|
|
101
|
+
|
|
102
|
+
`createPostgresCloudMaintenance` builds the separate host-operations
|
|
103
|
+
composition. It intentionally does not add methods to the conformance-certified
|
|
104
|
+
`CloudBlobStore`: LIST/HEAD/DELETE, retention policy, dead-letter replay and
|
|
105
|
+
usage rebuild are host operations, not device blob capabilities.
|
|
106
|
+
|
|
107
|
+
The host calls `runTenant(tenant, jobId)` from its scheduler. Postgres stores the
|
|
108
|
+
job/cursor readback, eligible objects are tombstoned before R2 DELETE, and
|
|
109
|
+
manifest plus usage settle once after deletion. An R2 key without a manifest is
|
|
110
|
+
first recorded as a pending witness and waits the tenant's orphan grace; it is
|
|
111
|
+
never deleted from a single LIST observation. See
|
|
112
|
+
`deploy/runbooks/cloud-cleanup.md` for metrics, alerts, replay/discard,
|
|
113
|
+
crash recovery and rollback.
|
|
114
|
+
|
|
115
|
+
`@byok-sdk/cloud` is a stateless handler package — it serves the frozen v1 device
|
|
116
|
+
wire contract over ports and owns no storage. This package is one composition of
|
|
117
|
+
those ports. It sits here rather than inside `@byok-sdk/cloud` for two reasons: a
|
|
118
|
+
`hono` user should not be made to install a database driver, and `@byok-sdk/core`
|
|
119
|
+
and `@byok-sdk/cloud` stay loadable on Workers precisely because `pg` never enters
|
|
120
|
+
their dependency graph.
|
|
121
|
+
|
|
122
|
+
Dependency direction is one-way: `cloud-dataplane → core + cloud + protocol +
|
|
123
|
+
pg +` the explicit S3 signer/XML parser. The protocol edge is used only by the
|
|
124
|
+
host-owned dead-letter replay path to rebind frozen envelope bytes to the new
|
|
125
|
+
mailbox sequence; core remains protocol-free. Nothing depends back on it; no ambient AWS
|
|
126
|
+
credential-provider chain is installed.
|
|
127
|
+
|
|
128
|
+
## Migrations
|
|
129
|
+
|
|
130
|
+
Schema is authored in the repository's `deploy/sql/` directory as plain SQL
|
|
131
|
+
files named `NNNN_description.sql`. The four-digit prefix is the only ordering
|
|
132
|
+
authority — the same one `pnpm run check:deploy-sql` enforces in CI.
|
|
133
|
+
|
|
134
|
+
Those files ship inside this package: the build copies them into `dist/sql/`,
|
|
135
|
+
and `migrationsDir()` returns that directory from wherever the package is
|
|
136
|
+
installed. A host owns **when** migrations run, not a copy of their bytes —
|
|
137
|
+
vendoring the SQL into your own repository would make that copy a second source
|
|
138
|
+
of truth, free to drift from the runner installed beside it.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { createByokPool, migrate, migrationsDir } from '@byok-sdk/cloud-dataplane';
|
|
142
|
+
|
|
143
|
+
const pool = createByokPool({ connectionString: process.env.DATABASE_URL! });
|
|
144
|
+
const result = await migrate(pool, migrationsDir());
|
|
145
|
+
console.log(result.applied); // e.g. ['0001_cloud_local.sql', ..., '0004_device_proof_truth.sql']
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`migrate` still takes its directory explicitly, because the same runner also
|
|
149
|
+
applies `deploy/sql/` directly for this repository's deploy script and test
|
|
150
|
+
suites. `migrationsDir()` is the answer for anyone who installed the package and
|
|
151
|
+
has no checkout in reach. The release pack compares the two — filename set and
|
|
152
|
+
per-file sha256, in both directions — so a migration that fails to reach the
|
|
153
|
+
tarball fails the release instead of a deployment.
|
|
154
|
+
|
|
155
|
+
The runner:
|
|
156
|
+
|
|
157
|
+
- takes a session-level `pg_advisory_lock`, so two deploy jobs starting together
|
|
158
|
+
cannot both apply the same file;
|
|
159
|
+
- bootstraps its own `byok_schema_migration(version, checksum, applied_at)`
|
|
160
|
+
ledger — the only DDL in this package, since everything else belongs to a file
|
|
161
|
+
under `deploy/sql/`;
|
|
162
|
+
- applies each file and its ledger row in **one transaction**, so a crash leaves
|
|
163
|
+
a migration entirely applied or entirely absent;
|
|
164
|
+
- verifies the sha256 of every already-applied file against the ledger and
|
|
165
|
+
**stops** on a mismatch. Published migrations are immutable: fix a mistake with
|
|
166
|
+
a new file, never by editing an old one;
|
|
167
|
+
- has **no down path**. Rollback is "revert the application, leave the tables".
|
|
168
|
+
|
|
169
|
+
A consequence of per-file transactions: a statement that cannot run inside one
|
|
170
|
+
(`CREATE INDEX CONCURRENTLY`) cannot appear in a migration file.
|
|
171
|
+
|
|
172
|
+
## Pool
|
|
173
|
+
|
|
174
|
+
`createByokPool` exists to configure one thing that matters: `int8` columns
|
|
175
|
+
decode to `bigint`, not to strings. Every byte-count contract in `@byok-sdk/core` is
|
|
176
|
+
`bigint`, and a default-configured `pg` pool would hand the stores strings —
|
|
177
|
+
turning `usage.reservedBytes > limit` into a lexicographic comparison that
|
|
178
|
+
answers a different question without throwing. The parser is installed on the
|
|
179
|
+
pool config, never on the process-wide `pg.types` registry, so composing this
|
|
180
|
+
SDK cannot change how a host's own database code decodes results.
|
|
181
|
+
|
|
182
|
+
## Testing
|
|
183
|
+
|
|
184
|
+
The suites need a real Postgres, and get one from the repository's
|
|
185
|
+
`docker-compose.test.yml`:
|
|
186
|
+
|
|
187
|
+
```sh
|
|
188
|
+
docker compose -f docker-compose.test.yml up -d --wait
|
|
189
|
+
export BYOK_TEST_POSTGRES_URL=postgres://byok:byok@127.0.0.1:5433/byok_test
|
|
190
|
+
export BYOK_TEST_S3_ENDPOINT=http://127.0.0.1:9100
|
|
191
|
+
pnpm --filter @byok-sdk/cloud-dataplane test
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Both variables, one gate: the compose file starts Postgres and MinIO together,
|
|
195
|
+
and the blob port writes a manifest row and signs against the object store in
|
|
196
|
+
the same call. Without either, the database-backed suites skip and say so. CI's
|
|
197
|
+
`dataplane` job sets `BYOK_REQUIRE_DATAPLANE=1`, which turns that absence into a
|
|
198
|
+
hard failure — the skip path cannot be how CI stays green.
|
|
199
|
+
|
|
200
|
+
Every case migrates a fresh schema from empty through the real runner over the
|
|
201
|
+
real `deploy/sql/` files, so "fresh install + migrate-up" is a property of each
|
|
202
|
+
test rather than a step someone remembers. What runs:
|
|
203
|
+
|
|
204
|
+
- `runCloudConformance('postgres', ...)` and `runCoreConformance('postgres', ...)`
|
|
205
|
+
— the same assertion source `@byok-sdk/conformance` runs against the in-memory
|
|
206
|
+
compositions, with that package zero-diff. An assertion that needed a
|
|
207
|
+
composition-specific branch would be a port-contract bug to escalate, not a
|
|
208
|
+
test to adjust.
|
|
209
|
+
- `tests/sql/control_plane_invariants.sql`, executed post-migration. It asserts
|
|
210
|
+
that every unique index on a tenant-owned table leads with `tenant_id`, with a
|
|
211
|
+
two-entry whitelist. Operators run the identical file against a live database
|
|
212
|
+
with `psql -f`; the TypeScript side only runs it and checks it did not raise.
|
|
213
|
+
- The migrate runner's fault suite, and the reservation-admission concurrency
|
|
214
|
+
test that pins `reserve`'s no-oversell property against real contention.
|
|
215
|
+
- The object suite, against the compose MinIO. Seven of its nine assertions are
|
|
216
|
+
about what a presigned URL binds to, and a binding asserted against our own
|
|
217
|
+
verifier is self-certifying — so MinIO adjudicates them as an independent
|
|
218
|
+
SigV4 implementation, and nothing stubs a signature check. The two that are
|
|
219
|
+
about retry semantics go through a fault injector wrapped around `fetch`,
|
|
220
|
+
which replaces individual attempts and never answers a request itself.
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
MIT. Node.js 22.19.0 or newer.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { type Clock, type MailboxMessage, type TenantId } from '@byok-sdk/core';
|
|
2
|
+
import type { Pool } from 'pg';
|
|
3
|
+
import { type R2BlobStoreOptions, type R2ObjectMaintenance } from './stores/r2-blobs';
|
|
4
|
+
export declare const CLOUD_CLEANUP_ERROR_CODES: {
|
|
5
|
+
readonly cleanup_invalid_input: 'cleanup_invalid_input';
|
|
6
|
+
readonly cleanup_policy_missing: 'cleanup_policy_missing';
|
|
7
|
+
readonly cleanup_job_running: 'cleanup_job_running';
|
|
8
|
+
readonly cleanup_dead_letter_not_found: 'cleanup_dead_letter_not_found';
|
|
9
|
+
readonly cleanup_accounting_drift: 'cleanup_accounting_drift';
|
|
10
|
+
};
|
|
11
|
+
export type CloudCleanupErrorCode = (typeof CLOUD_CLEANUP_ERROR_CODES)[keyof typeof CLOUD_CLEANUP_ERROR_CODES];
|
|
12
|
+
export declare class CloudCleanupError extends Error {
|
|
13
|
+
readonly code: CloudCleanupErrorCode;
|
|
14
|
+
constructor(code: CloudCleanupErrorCode, message: string, options?: ErrorOptions);
|
|
15
|
+
}
|
|
16
|
+
export interface TenantRetentionPolicyInput {
|
|
17
|
+
readonly policyId: string;
|
|
18
|
+
readonly mailboxAckedRetentionMs: bigint;
|
|
19
|
+
readonly mailboxUnackedRetentionMs: bigint;
|
|
20
|
+
readonly requestReceiptRetentionMs: bigint;
|
|
21
|
+
readonly objectOrphanGraceMs: bigint;
|
|
22
|
+
}
|
|
23
|
+
export interface TenantRetentionPolicy extends TenantRetentionPolicyInput {
|
|
24
|
+
readonly tenantId: TenantId;
|
|
25
|
+
readonly updatedAt: string;
|
|
26
|
+
}
|
|
27
|
+
export type CleanupJobState = 'running' | 'completed' | 'completed_with_errors' | 'failed';
|
|
28
|
+
export interface CloudCleanupResult {
|
|
29
|
+
readonly tenantId: TenantId;
|
|
30
|
+
readonly jobId: string;
|
|
31
|
+
readonly state: CleanupJobState;
|
|
32
|
+
readonly startedAt: string;
|
|
33
|
+
readonly finishedAt?: string;
|
|
34
|
+
readonly mailboxDeletedCount: bigint;
|
|
35
|
+
readonly mailboxExpiredCount: bigint;
|
|
36
|
+
readonly mailboxReleasedBytes: bigint;
|
|
37
|
+
readonly reservationsExpired: bigint;
|
|
38
|
+
readonly ttlRowsDeleted: bigint;
|
|
39
|
+
readonly objectsTombstoned: bigint;
|
|
40
|
+
readonly objectsDeleted: bigint;
|
|
41
|
+
readonly objectReleasedBytes: bigint;
|
|
42
|
+
readonly orphanWitnessesCreated: bigint;
|
|
43
|
+
readonly missingObjects: bigint;
|
|
44
|
+
readonly shapeDrift: bigint;
|
|
45
|
+
readonly invalidObjectKeys: bigint;
|
|
46
|
+
readonly operationErrors: bigint;
|
|
47
|
+
readonly errorMessage?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface DeadLetterQuery {
|
|
50
|
+
readonly deviceId?: string;
|
|
51
|
+
/** Exclusive composite cursor; use the last message from the prior page. */
|
|
52
|
+
readonly after?: DeadLetterRef;
|
|
53
|
+
readonly limit?: number;
|
|
54
|
+
}
|
|
55
|
+
export interface DeadLetterPage {
|
|
56
|
+
readonly messages: readonly MailboxMessage[];
|
|
57
|
+
readonly hasMore: boolean;
|
|
58
|
+
}
|
|
59
|
+
export interface DeadLetterReplayInput {
|
|
60
|
+
readonly deviceId: string;
|
|
61
|
+
readonly seq: number;
|
|
62
|
+
/** Operator-issued idempotency key for the new delivery. */
|
|
63
|
+
readonly replayMessageId: string;
|
|
64
|
+
}
|
|
65
|
+
export interface DeadLetterRef {
|
|
66
|
+
readonly deviceId: string;
|
|
67
|
+
readonly seq: number;
|
|
68
|
+
}
|
|
69
|
+
export interface ObjectUsageRebuildResult {
|
|
70
|
+
readonly committedObjectBytes: bigint;
|
|
71
|
+
readonly objectCount: bigint;
|
|
72
|
+
readonly updatedAt: string;
|
|
73
|
+
}
|
|
74
|
+
export interface PostgresCloudCleanupOptions {
|
|
75
|
+
readonly pool: Pool;
|
|
76
|
+
readonly clock: Clock;
|
|
77
|
+
readonly objectStorage: R2ObjectMaintenance;
|
|
78
|
+
readonly batchSize?: number;
|
|
79
|
+
}
|
|
80
|
+
export interface PostgresCloudMaintenanceOptions {
|
|
81
|
+
readonly pool: Pool;
|
|
82
|
+
readonly clock: Clock;
|
|
83
|
+
readonly objectStorage: Omit<R2BlobStoreOptions, 'objects'>;
|
|
84
|
+
readonly batchSize?: number;
|
|
85
|
+
}
|
|
86
|
+
export declare class PostgresCloudCleanup {
|
|
87
|
+
#private;
|
|
88
|
+
constructor(options: PostgresCloudCleanupOptions);
|
|
89
|
+
writeRetentionPolicy(tenant: TenantId, input: TenantRetentionPolicyInput): Promise<TenantRetentionPolicy>;
|
|
90
|
+
readRetentionPolicy(tenant: TenantId): Promise<TenantRetentionPolicy>;
|
|
91
|
+
/** Run one bounded tenant maintenance cycle. Completed job ids are replay-safe. */
|
|
92
|
+
runTenant(tenant: TenantId, jobId: string): Promise<CloudCleanupResult>;
|
|
93
|
+
listDeadLetters(tenant: TenantId, query?: DeadLetterQuery): Promise<DeadLetterPage>;
|
|
94
|
+
/** Clone an expired row to a new monotonic seq. The original remains evidence. */
|
|
95
|
+
replayDeadLetter(tenant: TenantId, input: DeadLetterReplayInput): Promise<MailboxMessage>;
|
|
96
|
+
/** Explicit operator discard. Automatic retention never deletes dead letters. */
|
|
97
|
+
discardDeadLetter(tenant: TenantId, ref: DeadLetterRef): Promise<MailboxMessage>;
|
|
98
|
+
/**
|
|
99
|
+
* Explicit recovery operation: rebuild object accounting from committed
|
|
100
|
+
* Postgres manifests. Reconciliation must run first; R2 LIST is never used as
|
|
101
|
+
* billing authority and inline/mailbox usage is left untouched.
|
|
102
|
+
*/
|
|
103
|
+
rebuildObjectUsage(tenant: TenantId): Promise<ObjectUsageRebuildResult>;
|
|
104
|
+
}
|
|
105
|
+
/** Build the maintenance composition against the same Postgres/R2 authority. */
|
|
106
|
+
export declare function createPostgresCloudMaintenance(options: PostgresCloudMaintenanceOptions): PostgresCloudCleanup;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@byok-sdk/cloud-dataplane` — the durable data plane.
|
|
3
|
+
*
|
|
4
|
+
* `cloud-dataplane → core + cloud + protocol + pg`, and never the reverse. The two
|
|
5
|
+
* platform-neutral packages stay loadable on Workers precisely because the
|
|
6
|
+
* database driver lives here (design §4): `@byok-sdk/cloud` is a stateless handler
|
|
7
|
+
* package, and a `hono` user must not be made to install `pg` to use it.
|
|
8
|
+
*
|
|
9
|
+
* The package name follows its hosted role rather than leaking one storage
|
|
10
|
+
* technology into the public identity. Postgres remains the transaction
|
|
11
|
+
* authority and R2/S3-compatible storage remains the byte plane; a future
|
|
12
|
+
* alternative composition must use a distinct package name rather than making
|
|
13
|
+
* this authority conditional at runtime.
|
|
14
|
+
*/
|
|
15
|
+
export { createByokPool } from './pool';
|
|
16
|
+
export type { ByokPoolOptions } from './pool';
|
|
17
|
+
export { MigrationChecksumMismatchError, MigrationFilenameError, migrate, readMigrationFiles } from './migrate';
|
|
18
|
+
export type { MigrationFile, MigrationResult } from './migrate';
|
|
19
|
+
export { migrationsDir } from './migrations-dir';
|
|
20
|
+
export { PostgresDeviceDirectory, PostgresInboundDedupStore, PostgresNonceStore, PostgresPairingCodeStore, PostgresRequestReceiptStore, PostgresTaskAttemptStore, createPostgresCloudStores, } from './stores/index';
|
|
21
|
+
export type { PostgresCloudStoreOptions, PostgresCloudStores, PostgresObjectStorageOptions, } from './stores/index';
|
|
22
|
+
export { DEFAULT_MAX_ATTEMPTS, DEFAULT_PRESIGN_TTL_SECONDS, DEFAULT_RETRY_DELAY_MS, MAX_PRESIGN_TTL_SECONDS, MIN_PRESIGN_TTL_SECONDS, ObjectStoreRequestError, R2_BLOB_ERROR_CODES, R2BlobStoreError, R2CloudBlobStore, R2ObjectMaintenanceStore, } from './stores/index';
|
|
23
|
+
export type { ObjectStoreFetch, R2BlobErrorCode, R2BlobStoreOptions } from './stores/index';
|
|
24
|
+
export type { R2DeleteResult, R2ListedObject, R2ObjectMaintenance, R2ObjectMaintenanceOptions, R2ObjectPage, } from './stores/index';
|
|
25
|
+
export { PostgresActivityStore, PostgresBoardStore, PostgresMailboxStore, PostgresObjectStore, PostgresPresenceStore, PostgresQuotaStore, PostgresTruthStore, createPostgresCoreStores, } from './stores/core/index';
|
|
26
|
+
export type { PostgresCoreStoreOptions } from './stores/core/index';
|
|
27
|
+
export { CLOUD_CLEANUP_ERROR_CODES, CloudCleanupError, PostgresCloudCleanup, createPostgresCloudMaintenance, } from './cleanup';
|
|
28
|
+
export { PostgresTruthCommitter } from './truth-committer';
|
|
29
|
+
export type { PostgresTruthCommitterOptions } from './truth-committer';
|
|
30
|
+
export type { CleanupJobState, CloudCleanupErrorCode, CloudCleanupResult, DeadLetterPage, DeadLetterQuery, DeadLetterRef, DeadLetterReplayInput, ObjectUsageRebuildResult, PostgresCloudCleanupOptions, PostgresCloudMaintenanceOptions, TenantRetentionPolicy, TenantRetentionPolicyInput, } from './cleanup';
|