@storyshelf/db-postgres 0.0.0-stage → 0.5.3
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 +18 -2
- package/dist/ddl.d.mts +8 -0
- package/dist/ddl.mjs +321 -0
- package/dist/ddl.mjs.map +1 -0
- package/dist/drizzle-factory-pg.mjs +77 -0
- package/dist/drizzle-factory-pg.mjs.map +1 -0
- package/dist/drizzle-factory.mjs +42 -0
- package/dist/drizzle-factory.mjs.map +1 -0
- package/dist/index.d.mts +50 -0
- package/dist/index.mjs +51 -0
- package/dist/index.mjs.map +1 -0
- package/dist/migrate.mjs +91 -0
- package/dist/migrate.mjs.map +1 -0
- package/dist/neon-http.d.mts +27 -0
- package/dist/neon-http.mjs +66 -0
- package/dist/neon-http.mjs.map +1 -0
- package/dist/neon.d.mts +29 -0
- package/dist/neon.mjs +59 -0
- package/dist/neon.mjs.map +1 -0
- package/dist/pg.d.mts +33 -0
- package/dist/pg.mjs +56 -0
- package/dist/pg.mjs.map +1 -0
- package/dist/pglite.d.mts +28 -0
- package/dist/pglite.mjs +54 -0
- package/dist/pglite.mjs.map +1 -0
- package/dist/schema/baseline.d.mts +197 -0
- package/dist/schema/baseline.mjs +29 -0
- package/dist/schema/baseline.mjs.map +1 -0
- package/dist/schema/build.d.mts +380 -0
- package/dist/schema/build.mjs +39 -0
- package/dist/schema/build.mjs.map +1 -0
- package/dist/schema/capture-attempt.d.mts +445 -0
- package/dist/schema/capture-attempt.mjs +58 -0
- package/dist/schema/capture-attempt.mjs.map +1 -0
- package/dist/schema/comment.d.mts +197 -0
- package/dist/schema/comment.mjs +32 -0
- package/dist/schema/comment.mjs.map +1 -0
- package/dist/schema/content-refs.d.mts +89 -0
- package/dist/schema/content-refs.mjs +22 -0
- package/dist/schema/content-refs.mjs.map +1 -0
- package/dist/schema/label.d.mts +262 -0
- package/dist/schema/label.mjs +36 -0
- package/dist/schema/label.mjs.map +1 -0
- package/dist/schema/member.d.mts +129 -0
- package/dist/schema/member.mjs +23 -0
- package/dist/schema/member.mjs.map +1 -0
- package/dist/schema/notification.d.mts +316 -0
- package/dist/schema/notification.mjs +42 -0
- package/dist/schema/notification.mjs.map +1 -0
- package/dist/schema/project-group-mapping.d.mts +110 -0
- package/dist/schema/project-group-mapping.mjs +21 -0
- package/dist/schema/project-group-mapping.mjs.map +1 -0
- package/dist/schema/project.d.mts +337 -0
- package/dist/schema/project.mjs +35 -0
- package/dist/schema/project.mjs.map +1 -0
- package/dist/schema/snapshot.d.mts +452 -0
- package/dist/schema/snapshot.mjs +47 -0
- package/dist/schema/snapshot.mjs.map +1 -0
- package/dist/schema/status-config.d.mts +143 -0
- package/dist/schema/status-config.mjs +26 -0
- package/dist/schema/status-config.mjs.map +1 -0
- package/dist/schema/token.d.mts +143 -0
- package/dist/schema/token.mjs +27 -0
- package/dist/schema/token.mjs.map +1 -0
- package/dist/schema/user-invite.d.mts +125 -0
- package/dist/schema/user-invite.mjs +28 -0
- package/dist/schema/user-invite.mjs.map +1 -0
- package/dist/schema/user.d.mts +146 -0
- package/dist/schema/user.mjs +25 -0
- package/dist/schema/user.mjs.map +1 -0
- package/dist/schema/webhook.d.mts +143 -0
- package/dist/schema/webhook.mjs +26 -0
- package/dist/schema/webhook.mjs.map +1 -0
- package/dist/schema.d.mts +47 -0
- package/dist/schema.mjs +46 -0
- package/dist/schema.mjs.map +1 -0
- package/dist/vercel.d.mts +26 -0
- package/dist/vercel.mjs +48 -0
- package/dist/vercel.mjs.map +1 -0
- package/package.json +134 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Siddhant Gupta
|
|
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
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @storyshelf/db-postgres
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Postgres database adapter for StoryShelf (postgres.js + Drizzle). Connects to AWS RDS, GCP Cloud SQL, Supabase, Neon, Azure Database, or self-hosted Postgres. See [Deployment](../../apps/website/src/content/docs/guides/deployment.md) for provider recipes.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
nub add @storyshelf/db-postgres
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start (self-hosted)
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createPostgresDatabase } from "@storyshelf/db-postgres";
|
|
15
|
+
|
|
16
|
+
const db = createPostgresDatabase({ url: "postgres://user:pass@localhost:5432/shelf" });
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
For managed providers, pass `ssl`, `prepare`, and pool options via `options` or inject a preconfigured `client` (see `PostgresDatabaseOptions`).
|
package/dist/ddl.d.mts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
//#region src/ddl.d.ts
|
|
4
|
+
/** Canonical Postgres DDL for all StoryShelf tables and indexes. */
|
|
5
|
+
declare const DDL = "\nCREATE TABLE IF NOT EXISTS projects (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n slug TEXT NOT NULL UNIQUE,\n git_repository TEXT,\n git_default_branch TEXT NOT NULL DEFAULT 'main',\n pixel_threshold DOUBLE PRECISION NOT NULL DEFAULT 0.1,\n max_diff_ratio DOUBLE PRECISION NOT NULL DEFAULT 0.01,\n public_branch_regex TEXT,\n storybook_meta TEXT,\n execute_play BOOLEAN NOT NULL DEFAULT false,\n play_timeout_ms INTEGER NOT NULL DEFAULT 10000,\n run_a11y BOOLEAN NOT NULL DEFAULT false,\n browser TEXT NOT NULL DEFAULT 'chromium',\n viewports TEXT,\n automigrate BOOLEAN NOT NULL DEFAULT false,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE TABLE IF NOT EXISTS project_status_configs (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n provider TEXT NOT NULL,\n config TEXT NOT NULL,\n token_encrypted TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE TABLE IF NOT EXISTS builds (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n git_sha TEXT NOT NULL,\n git_branch TEXT NOT NULL,\n is_default BOOLEAN NOT NULL DEFAULT false,\n author_email TEXT,\n author_name TEXT,\n message TEXT,\n public BOOLEAN NOT NULL DEFAULT false,\n status TEXT NOT NULL DEFAULT 'pending',\n snapshot_count INTEGER NOT NULL DEFAULT 0,\n changed_count INTEGER NOT NULL DEFAULT 0,\n approved_count INTEGER NOT NULL DEFAULT 0,\n rejected_count INTEGER NOT NULL DEFAULT 0,\n affected_only BOOLEAN NOT NULL DEFAULT true,\n baseline_sha TEXT,\n changed_files TEXT,\n affected_import_paths TEXT,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE INDEX IF NOT EXISTS builds_project_gitsha_idx ON builds (project_id, git_sha);\nCREATE INDEX IF NOT EXISTS builds_git_branch_idx ON builds (git_branch);\nCREATE TABLE IF NOT EXISTS capture_attempts (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,\n attempt_no INTEGER NOT NULL,\n status TEXT NOT NULL DEFAULT 'queued',\n error TEXT,\n req_id TEXT,\n story_count INTEGER NOT NULL DEFAULT 0,\n failed_count INTEGER NOT NULL DEFAULT 0,\n queued_at TIMESTAMPTZ NOT NULL,\n started_at TIMESTAMPTZ,\n finished_at TIMESTAMPTZ,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS capture_attempts_build_no_idx ON capture_attempts (build_id, attempt_no);\nCREATE INDEX IF NOT EXISTS capture_attempts_build_id_idx ON capture_attempts (build_id);\nCREATE TABLE IF NOT EXISTS capture_logs (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,\n attempt_id TEXT NOT NULL REFERENCES capture_attempts(id) ON DELETE CASCADE,\n seq INTEGER NOT NULL,\n level TEXT NOT NULL DEFAULT 'info',\n message TEXT NOT NULL,\n fields TEXT,\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE INDEX IF NOT EXISTS capture_logs_attempt_seq_idx ON capture_logs (attempt_id, seq);\nCREATE TABLE IF NOT EXISTS snapshots (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,\n story_id TEXT NOT NULL,\n story_name TEXT NOT NULL,\n story_title TEXT NOT NULL,\n story_import_path TEXT,\n viewport_name TEXT NOT NULL DEFAULT 'desktop',\n viewport_width INTEGER NOT NULL DEFAULT 1280,\n viewport_height INTEGER NOT NULL DEFAULT 720,\n screenshot_path TEXT NOT NULL,\n diff_path TEXT,\n diff_pixels INTEGER,\n diff_ratio DOUBLE PRECISION,\n diff_passed BOOLEAN,\n status TEXT NOT NULL DEFAULT 'pending',\n reviewed_by TEXT,\n reviewed_at TIMESTAMPTZ,\n infra_hash TEXT,\n inherited BOOLEAN NOT NULL DEFAULT false,\n baseline_id TEXT,\n baseline_version TEXT,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS snapshots_build_story_viewport_idx ON snapshots (build_id, story_id, viewport_name);\nCREATE INDEX IF NOT EXISTS snapshots_build_id_idx ON snapshots (build_id);\nCREATE TABLE IF NOT EXISTS baselines (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n story_id TEXT NOT NULL,\n viewport_name TEXT NOT NULL DEFAULT 'desktop',\n branch TEXT NOT NULL,\n snapshot_id TEXT,\n screenshot_path TEXT NOT NULL,\n infra_hash TEXT,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS baselines_project_story_viewport_branch_idx ON baselines (project_id, story_id, viewport_name, branch);\nCREATE INDEX IF NOT EXISTS baselines_project_story_idx ON baselines (project_id, story_id);\nCREATE TABLE IF NOT EXISTS users (\n id TEXT PRIMARY KEY,\n email TEXT NOT NULL UNIQUE,\n name TEXT NOT NULL,\n avatar_url TEXT,\n role TEXT NOT NULL DEFAULT 'member',\n last_login_at TIMESTAMPTZ,\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE TABLE IF NOT EXISTS comments (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,\n snapshot_id TEXT REFERENCES snapshots(id) ON DELETE CASCADE,\n user_id TEXT REFERENCES users(id) ON DELETE CASCADE,\n body TEXT NOT NULL,\n parent_id TEXT,\n resolved BOOLEAN NOT NULL DEFAULT false,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE INDEX IF NOT EXISTS comments_build_id_idx ON comments (build_id);\nCREATE TABLE IF NOT EXISTS label_types (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n key TEXT NOT NULL,\n name TEXT NOT NULL,\n link_template TEXT,\n color TEXT,\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS label_types_project_key_idx ON label_types (project_id, key);\nCREATE TABLE IF NOT EXISTS build_labels (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,\n type_key TEXT NOT NULL,\n value TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS build_labels_build_type_value_idx ON build_labels (build_id, type_key, value);\nCREATE TABLE IF NOT EXISTS tokens (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n name TEXT NOT NULL,\n hash TEXT NOT NULL UNIQUE,\n user_id TEXT REFERENCES users(id) ON DELETE CASCADE,\n last_used_at TIMESTAMPTZ,\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE TABLE IF NOT EXISTS webhooks (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n url TEXT NOT NULL,\n secret_encrypted TEXT NOT NULL,\n events TEXT,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE TABLE IF NOT EXISTS notification_channels (\n id TEXT PRIMARY KEY,\n project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,\n provider TEXT NOT NULL,\n config TEXT NOT NULL,\n secret_encrypted TEXT,\n events TEXT,\n enabled BOOLEAN NOT NULL DEFAULT TRUE,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE INDEX IF NOT EXISTS notification_channels_project_id_idx ON notification_channels (project_id);\nCREATE TABLE IF NOT EXISTS notification_subscriptions (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n events TEXT,\n via TEXT,\n enabled BOOLEAN NOT NULL DEFAULT TRUE,\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS notification_subscriptions_project_user_idx ON notification_subscriptions (project_id, user_id);\nCREATE TABLE IF NOT EXISTS user_invite_tokens (\n id TEXT PRIMARY KEY,\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n token_hash TEXT NOT NULL UNIQUE,\n expires_at TIMESTAMPTZ NOT NULL,\n used_at TIMESTAMPTZ,\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE TABLE IF NOT EXISTS project_members (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n role TEXT NOT NULL DEFAULT 'viewer',\n source TEXT NOT NULL DEFAULT 'manual',\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS project_members_project_user_idx ON project_members (project_id, user_id);\nCREATE TABLE IF NOT EXISTS project_group_mappings (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n group_name TEXT NOT NULL,\n role TEXT NOT NULL DEFAULT 'viewer',\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS project_group_mappings_project_group_idx ON project_group_mappings (project_id, group_name);\nCREATE TABLE IF NOT EXISTS content_refs (\n hash TEXT PRIMARY KEY,\n ref_count INTEGER NOT NULL DEFAULT 1,\n last_seen_at TIMESTAMPTZ NOT NULL,\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE TABLE IF NOT EXISTS \"user\" (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n email TEXT NOT NULL UNIQUE,\n email_verified BOOLEAN NOT NULL DEFAULT false,\n image TEXT,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE TABLE IF NOT EXISTS session (\n id TEXT PRIMARY KEY,\n user_id TEXT NOT NULL REFERENCES \"user\"(id) ON DELETE CASCADE,\n token TEXT NOT NULL UNIQUE,\n expires_at TIMESTAMPTZ NOT NULL,\n ip_address TEXT,\n user_agent TEXT,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE INDEX IF NOT EXISTS session_token_idx ON session (token);\nCREATE INDEX IF NOT EXISTS session_user_id_idx ON session (user_id);\nCREATE TABLE IF NOT EXISTS account (\n id TEXT PRIMARY KEY,\n user_id TEXT NOT NULL REFERENCES \"user\"(id) ON DELETE CASCADE,\n account_id TEXT NOT NULL,\n provider_id TEXT NOT NULL,\n access_token TEXT,\n refresh_token TEXT,\n id_token TEXT,\n access_token_expires_at TIMESTAMPTZ,\n refresh_token_expires_at TIMESTAMPTZ,\n scope TEXT,\n expires_at TIMESTAMPTZ,\n password TEXT,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE INDEX IF NOT EXISTS account_user_id_idx ON account (user_id);\nCREATE TABLE IF NOT EXISTS verification (\n id TEXT PRIMARY KEY,\n identifier TEXT NOT NULL,\n value TEXT NOT NULL,\n expires_at TIMESTAMPTZ NOT NULL,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE INDEX IF NOT EXISTS verification_identifier_idx ON verification (identifier);\nCREATE TABLE IF NOT EXISTS passkey (\n id TEXT PRIMARY KEY,\n name TEXT,\n public_key TEXT NOT NULL,\n user_id TEXT NOT NULL REFERENCES \"user\"(id) ON DELETE CASCADE,\n credential_id TEXT NOT NULL,\n counter INTEGER NOT NULL,\n device_type TEXT NOT NULL,\n backed_up BOOLEAN NOT NULL DEFAULT false,\n transports TEXT,\n aaguid TEXT,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ\n);\nCREATE INDEX IF NOT EXISTS passkey_user_id_idx ON passkey (user_id);\nCREATE INDEX IF NOT EXISTS passkey_credential_id_idx ON passkey (credential_id);\nCREATE TABLE IF NOT EXISTS \"ssoProvider\" (\n id TEXT PRIMARY KEY,\n issuer TEXT NOT NULL,\n oidc_config TEXT,\n saml_config TEXT,\n user_id TEXT NOT NULL REFERENCES \"user\"(id) ON DELETE CASCADE,\n provider_id TEXT NOT NULL UNIQUE,\n organization_id TEXT,\n domain TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS ssoprovider_domain_idx ON \"ssoProvider\" (domain);\n";
|
|
6
|
+
//#endregion
|
|
7
|
+
export { DDL };
|
|
8
|
+
//# sourceMappingURL=ddl.d.mts.map
|
package/dist/ddl.mjs
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
//#region src/ddl.ts
|
|
5
|
+
/** Canonical Postgres DDL for all StoryShelf tables and indexes. */
|
|
6
|
+
const DDL = `
|
|
7
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
8
|
+
id TEXT PRIMARY KEY,
|
|
9
|
+
name TEXT NOT NULL,
|
|
10
|
+
slug TEXT NOT NULL UNIQUE,
|
|
11
|
+
git_repository TEXT,
|
|
12
|
+
git_default_branch TEXT NOT NULL DEFAULT 'main',
|
|
13
|
+
pixel_threshold DOUBLE PRECISION NOT NULL DEFAULT 0.1,
|
|
14
|
+
max_diff_ratio DOUBLE PRECISION NOT NULL DEFAULT 0.01,
|
|
15
|
+
public_branch_regex TEXT,
|
|
16
|
+
storybook_meta TEXT,
|
|
17
|
+
execute_play BOOLEAN NOT NULL DEFAULT false,
|
|
18
|
+
play_timeout_ms INTEGER NOT NULL DEFAULT 10000,
|
|
19
|
+
run_a11y BOOLEAN NOT NULL DEFAULT false,
|
|
20
|
+
browser TEXT NOT NULL DEFAULT 'chromium',
|
|
21
|
+
viewports TEXT,
|
|
22
|
+
automigrate BOOLEAN NOT NULL DEFAULT false,
|
|
23
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
24
|
+
updated_at TIMESTAMPTZ NOT NULL
|
|
25
|
+
);
|
|
26
|
+
CREATE TABLE IF NOT EXISTS project_status_configs (
|
|
27
|
+
id TEXT PRIMARY KEY,
|
|
28
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
29
|
+
provider TEXT NOT NULL,
|
|
30
|
+
config TEXT NOT NULL,
|
|
31
|
+
token_encrypted TEXT NOT NULL,
|
|
32
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
33
|
+
updated_at TIMESTAMPTZ NOT NULL
|
|
34
|
+
);
|
|
35
|
+
CREATE TABLE IF NOT EXISTS builds (
|
|
36
|
+
id TEXT PRIMARY KEY,
|
|
37
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
38
|
+
git_sha TEXT NOT NULL,
|
|
39
|
+
git_branch TEXT NOT NULL,
|
|
40
|
+
is_default BOOLEAN NOT NULL DEFAULT false,
|
|
41
|
+
author_email TEXT,
|
|
42
|
+
author_name TEXT,
|
|
43
|
+
message TEXT,
|
|
44
|
+
public BOOLEAN NOT NULL DEFAULT false,
|
|
45
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
46
|
+
snapshot_count INTEGER NOT NULL DEFAULT 0,
|
|
47
|
+
changed_count INTEGER NOT NULL DEFAULT 0,
|
|
48
|
+
approved_count INTEGER NOT NULL DEFAULT 0,
|
|
49
|
+
rejected_count INTEGER NOT NULL DEFAULT 0,
|
|
50
|
+
affected_only BOOLEAN NOT NULL DEFAULT true,
|
|
51
|
+
baseline_sha TEXT,
|
|
52
|
+
changed_files TEXT,
|
|
53
|
+
affected_import_paths TEXT,
|
|
54
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
55
|
+
updated_at TIMESTAMPTZ NOT NULL
|
|
56
|
+
);
|
|
57
|
+
CREATE INDEX IF NOT EXISTS builds_project_gitsha_idx ON builds (project_id, git_sha);
|
|
58
|
+
CREATE INDEX IF NOT EXISTS builds_git_branch_idx ON builds (git_branch);
|
|
59
|
+
CREATE TABLE IF NOT EXISTS capture_attempts (
|
|
60
|
+
id TEXT PRIMARY KEY,
|
|
61
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
62
|
+
build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,
|
|
63
|
+
attempt_no INTEGER NOT NULL,
|
|
64
|
+
status TEXT NOT NULL DEFAULT 'queued',
|
|
65
|
+
error TEXT,
|
|
66
|
+
req_id TEXT,
|
|
67
|
+
story_count INTEGER NOT NULL DEFAULT 0,
|
|
68
|
+
failed_count INTEGER NOT NULL DEFAULT 0,
|
|
69
|
+
queued_at TIMESTAMPTZ NOT NULL,
|
|
70
|
+
started_at TIMESTAMPTZ,
|
|
71
|
+
finished_at TIMESTAMPTZ,
|
|
72
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
73
|
+
updated_at TIMESTAMPTZ NOT NULL
|
|
74
|
+
);
|
|
75
|
+
CREATE UNIQUE INDEX IF NOT EXISTS capture_attempts_build_no_idx ON capture_attempts (build_id, attempt_no);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS capture_attempts_build_id_idx ON capture_attempts (build_id);
|
|
77
|
+
CREATE TABLE IF NOT EXISTS capture_logs (
|
|
78
|
+
id TEXT PRIMARY KEY,
|
|
79
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
80
|
+
build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,
|
|
81
|
+
attempt_id TEXT NOT NULL REFERENCES capture_attempts(id) ON DELETE CASCADE,
|
|
82
|
+
seq INTEGER NOT NULL,
|
|
83
|
+
level TEXT NOT NULL DEFAULT 'info',
|
|
84
|
+
message TEXT NOT NULL,
|
|
85
|
+
fields TEXT,
|
|
86
|
+
created_at TIMESTAMPTZ NOT NULL
|
|
87
|
+
);
|
|
88
|
+
CREATE INDEX IF NOT EXISTS capture_logs_attempt_seq_idx ON capture_logs (attempt_id, seq);
|
|
89
|
+
CREATE TABLE IF NOT EXISTS snapshots (
|
|
90
|
+
id TEXT PRIMARY KEY,
|
|
91
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
92
|
+
build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,
|
|
93
|
+
story_id TEXT NOT NULL,
|
|
94
|
+
story_name TEXT NOT NULL,
|
|
95
|
+
story_title TEXT NOT NULL,
|
|
96
|
+
story_import_path TEXT,
|
|
97
|
+
viewport_name TEXT NOT NULL DEFAULT 'desktop',
|
|
98
|
+
viewport_width INTEGER NOT NULL DEFAULT 1280,
|
|
99
|
+
viewport_height INTEGER NOT NULL DEFAULT 720,
|
|
100
|
+
screenshot_path TEXT NOT NULL,
|
|
101
|
+
diff_path TEXT,
|
|
102
|
+
diff_pixels INTEGER,
|
|
103
|
+
diff_ratio DOUBLE PRECISION,
|
|
104
|
+
diff_passed BOOLEAN,
|
|
105
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
106
|
+
reviewed_by TEXT,
|
|
107
|
+
reviewed_at TIMESTAMPTZ,
|
|
108
|
+
infra_hash TEXT,
|
|
109
|
+
inherited BOOLEAN NOT NULL DEFAULT false,
|
|
110
|
+
baseline_id TEXT,
|
|
111
|
+
baseline_version TEXT,
|
|
112
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
113
|
+
updated_at TIMESTAMPTZ NOT NULL
|
|
114
|
+
);
|
|
115
|
+
CREATE UNIQUE INDEX IF NOT EXISTS snapshots_build_story_viewport_idx ON snapshots (build_id, story_id, viewport_name);
|
|
116
|
+
CREATE INDEX IF NOT EXISTS snapshots_build_id_idx ON snapshots (build_id);
|
|
117
|
+
CREATE TABLE IF NOT EXISTS baselines (
|
|
118
|
+
id TEXT PRIMARY KEY,
|
|
119
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
120
|
+
story_id TEXT NOT NULL,
|
|
121
|
+
viewport_name TEXT NOT NULL DEFAULT 'desktop',
|
|
122
|
+
branch TEXT NOT NULL,
|
|
123
|
+
snapshot_id TEXT,
|
|
124
|
+
screenshot_path TEXT NOT NULL,
|
|
125
|
+
infra_hash TEXT,
|
|
126
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
127
|
+
updated_at TIMESTAMPTZ NOT NULL
|
|
128
|
+
);
|
|
129
|
+
CREATE UNIQUE INDEX IF NOT EXISTS baselines_project_story_viewport_branch_idx ON baselines (project_id, story_id, viewport_name, branch);
|
|
130
|
+
CREATE INDEX IF NOT EXISTS baselines_project_story_idx ON baselines (project_id, story_id);
|
|
131
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
132
|
+
id TEXT PRIMARY KEY,
|
|
133
|
+
email TEXT NOT NULL UNIQUE,
|
|
134
|
+
name TEXT NOT NULL,
|
|
135
|
+
avatar_url TEXT,
|
|
136
|
+
role TEXT NOT NULL DEFAULT 'member',
|
|
137
|
+
last_login_at TIMESTAMPTZ,
|
|
138
|
+
created_at TIMESTAMPTZ NOT NULL
|
|
139
|
+
);
|
|
140
|
+
CREATE TABLE IF NOT EXISTS comments (
|
|
141
|
+
id TEXT PRIMARY KEY,
|
|
142
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
143
|
+
build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,
|
|
144
|
+
snapshot_id TEXT REFERENCES snapshots(id) ON DELETE CASCADE,
|
|
145
|
+
user_id TEXT REFERENCES users(id) ON DELETE CASCADE,
|
|
146
|
+
body TEXT NOT NULL,
|
|
147
|
+
parent_id TEXT,
|
|
148
|
+
resolved BOOLEAN NOT NULL DEFAULT false,
|
|
149
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
150
|
+
updated_at TIMESTAMPTZ NOT NULL
|
|
151
|
+
);
|
|
152
|
+
CREATE INDEX IF NOT EXISTS comments_build_id_idx ON comments (build_id);
|
|
153
|
+
CREATE TABLE IF NOT EXISTS label_types (
|
|
154
|
+
id TEXT PRIMARY KEY,
|
|
155
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
156
|
+
key TEXT NOT NULL,
|
|
157
|
+
name TEXT NOT NULL,
|
|
158
|
+
link_template TEXT,
|
|
159
|
+
color TEXT,
|
|
160
|
+
created_at TIMESTAMPTZ NOT NULL
|
|
161
|
+
);
|
|
162
|
+
CREATE UNIQUE INDEX IF NOT EXISTS label_types_project_key_idx ON label_types (project_id, key);
|
|
163
|
+
CREATE TABLE IF NOT EXISTS build_labels (
|
|
164
|
+
id TEXT PRIMARY KEY,
|
|
165
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
166
|
+
build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,
|
|
167
|
+
type_key TEXT NOT NULL,
|
|
168
|
+
value TEXT NOT NULL,
|
|
169
|
+
created_at TIMESTAMPTZ NOT NULL
|
|
170
|
+
);
|
|
171
|
+
CREATE UNIQUE INDEX IF NOT EXISTS build_labels_build_type_value_idx ON build_labels (build_id, type_key, value);
|
|
172
|
+
CREATE TABLE IF NOT EXISTS tokens (
|
|
173
|
+
id TEXT PRIMARY KEY,
|
|
174
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
175
|
+
name TEXT NOT NULL,
|
|
176
|
+
hash TEXT NOT NULL UNIQUE,
|
|
177
|
+
user_id TEXT REFERENCES users(id) ON DELETE CASCADE,
|
|
178
|
+
last_used_at TIMESTAMPTZ,
|
|
179
|
+
created_at TIMESTAMPTZ NOT NULL
|
|
180
|
+
);
|
|
181
|
+
CREATE TABLE IF NOT EXISTS webhooks (
|
|
182
|
+
id TEXT PRIMARY KEY,
|
|
183
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
184
|
+
url TEXT NOT NULL,
|
|
185
|
+
secret_encrypted TEXT NOT NULL,
|
|
186
|
+
events TEXT,
|
|
187
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
188
|
+
updated_at TIMESTAMPTZ NOT NULL
|
|
189
|
+
);
|
|
190
|
+
CREATE TABLE IF NOT EXISTS notification_channels (
|
|
191
|
+
id TEXT PRIMARY KEY,
|
|
192
|
+
project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
|
|
193
|
+
provider TEXT NOT NULL,
|
|
194
|
+
config TEXT NOT NULL,
|
|
195
|
+
secret_encrypted TEXT,
|
|
196
|
+
events TEXT,
|
|
197
|
+
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
198
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
199
|
+
updated_at TIMESTAMPTZ NOT NULL
|
|
200
|
+
);
|
|
201
|
+
CREATE INDEX IF NOT EXISTS notification_channels_project_id_idx ON notification_channels (project_id);
|
|
202
|
+
CREATE TABLE IF NOT EXISTS notification_subscriptions (
|
|
203
|
+
id TEXT PRIMARY KEY,
|
|
204
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
205
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
206
|
+
events TEXT,
|
|
207
|
+
via TEXT,
|
|
208
|
+
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
209
|
+
created_at TIMESTAMPTZ NOT NULL
|
|
210
|
+
);
|
|
211
|
+
CREATE UNIQUE INDEX IF NOT EXISTS notification_subscriptions_project_user_idx ON notification_subscriptions (project_id, user_id);
|
|
212
|
+
CREATE TABLE IF NOT EXISTS user_invite_tokens (
|
|
213
|
+
id TEXT PRIMARY KEY,
|
|
214
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
215
|
+
token_hash TEXT NOT NULL UNIQUE,
|
|
216
|
+
expires_at TIMESTAMPTZ NOT NULL,
|
|
217
|
+
used_at TIMESTAMPTZ,
|
|
218
|
+
created_at TIMESTAMPTZ NOT NULL
|
|
219
|
+
);
|
|
220
|
+
CREATE TABLE IF NOT EXISTS project_members (
|
|
221
|
+
id TEXT PRIMARY KEY,
|
|
222
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
223
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
224
|
+
role TEXT NOT NULL DEFAULT 'viewer',
|
|
225
|
+
source TEXT NOT NULL DEFAULT 'manual',
|
|
226
|
+
created_at TIMESTAMPTZ NOT NULL
|
|
227
|
+
);
|
|
228
|
+
CREATE UNIQUE INDEX IF NOT EXISTS project_members_project_user_idx ON project_members (project_id, user_id);
|
|
229
|
+
CREATE TABLE IF NOT EXISTS project_group_mappings (
|
|
230
|
+
id TEXT PRIMARY KEY,
|
|
231
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
232
|
+
group_name TEXT NOT NULL,
|
|
233
|
+
role TEXT NOT NULL DEFAULT 'viewer',
|
|
234
|
+
created_at TIMESTAMPTZ NOT NULL
|
|
235
|
+
);
|
|
236
|
+
CREATE UNIQUE INDEX IF NOT EXISTS project_group_mappings_project_group_idx ON project_group_mappings (project_id, group_name);
|
|
237
|
+
CREATE TABLE IF NOT EXISTS content_refs (
|
|
238
|
+
hash TEXT PRIMARY KEY,
|
|
239
|
+
ref_count INTEGER NOT NULL DEFAULT 1,
|
|
240
|
+
last_seen_at TIMESTAMPTZ NOT NULL,
|
|
241
|
+
created_at TIMESTAMPTZ NOT NULL
|
|
242
|
+
);
|
|
243
|
+
CREATE TABLE IF NOT EXISTS "user" (
|
|
244
|
+
id TEXT PRIMARY KEY,
|
|
245
|
+
name TEXT NOT NULL,
|
|
246
|
+
email TEXT NOT NULL UNIQUE,
|
|
247
|
+
email_verified BOOLEAN NOT NULL DEFAULT false,
|
|
248
|
+
image TEXT,
|
|
249
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
250
|
+
updated_at TIMESTAMPTZ NOT NULL
|
|
251
|
+
);
|
|
252
|
+
CREATE TABLE IF NOT EXISTS session (
|
|
253
|
+
id TEXT PRIMARY KEY,
|
|
254
|
+
user_id TEXT NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
|
|
255
|
+
token TEXT NOT NULL UNIQUE,
|
|
256
|
+
expires_at TIMESTAMPTZ NOT NULL,
|
|
257
|
+
ip_address TEXT,
|
|
258
|
+
user_agent TEXT,
|
|
259
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
260
|
+
updated_at TIMESTAMPTZ NOT NULL
|
|
261
|
+
);
|
|
262
|
+
CREATE INDEX IF NOT EXISTS session_token_idx ON session (token);
|
|
263
|
+
CREATE INDEX IF NOT EXISTS session_user_id_idx ON session (user_id);
|
|
264
|
+
CREATE TABLE IF NOT EXISTS account (
|
|
265
|
+
id TEXT PRIMARY KEY,
|
|
266
|
+
user_id TEXT NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
|
|
267
|
+
account_id TEXT NOT NULL,
|
|
268
|
+
provider_id TEXT NOT NULL,
|
|
269
|
+
access_token TEXT,
|
|
270
|
+
refresh_token TEXT,
|
|
271
|
+
id_token TEXT,
|
|
272
|
+
access_token_expires_at TIMESTAMPTZ,
|
|
273
|
+
refresh_token_expires_at TIMESTAMPTZ,
|
|
274
|
+
scope TEXT,
|
|
275
|
+
expires_at TIMESTAMPTZ,
|
|
276
|
+
password TEXT,
|
|
277
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
278
|
+
updated_at TIMESTAMPTZ NOT NULL
|
|
279
|
+
);
|
|
280
|
+
CREATE INDEX IF NOT EXISTS account_user_id_idx ON account (user_id);
|
|
281
|
+
CREATE TABLE IF NOT EXISTS verification (
|
|
282
|
+
id TEXT PRIMARY KEY,
|
|
283
|
+
identifier TEXT NOT NULL,
|
|
284
|
+
value TEXT NOT NULL,
|
|
285
|
+
expires_at TIMESTAMPTZ NOT NULL,
|
|
286
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
287
|
+
updated_at TIMESTAMPTZ NOT NULL
|
|
288
|
+
);
|
|
289
|
+
CREATE INDEX IF NOT EXISTS verification_identifier_idx ON verification (identifier);
|
|
290
|
+
CREATE TABLE IF NOT EXISTS passkey (
|
|
291
|
+
id TEXT PRIMARY KEY,
|
|
292
|
+
name TEXT,
|
|
293
|
+
public_key TEXT NOT NULL,
|
|
294
|
+
user_id TEXT NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
|
|
295
|
+
credential_id TEXT NOT NULL,
|
|
296
|
+
counter INTEGER NOT NULL,
|
|
297
|
+
device_type TEXT NOT NULL,
|
|
298
|
+
backed_up BOOLEAN NOT NULL DEFAULT false,
|
|
299
|
+
transports TEXT,
|
|
300
|
+
aaguid TEXT,
|
|
301
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
302
|
+
updated_at TIMESTAMPTZ
|
|
303
|
+
);
|
|
304
|
+
CREATE INDEX IF NOT EXISTS passkey_user_id_idx ON passkey (user_id);
|
|
305
|
+
CREATE INDEX IF NOT EXISTS passkey_credential_id_idx ON passkey (credential_id);
|
|
306
|
+
CREATE TABLE IF NOT EXISTS "ssoProvider" (
|
|
307
|
+
id TEXT PRIMARY KEY,
|
|
308
|
+
issuer TEXT NOT NULL,
|
|
309
|
+
oidc_config TEXT,
|
|
310
|
+
saml_config TEXT,
|
|
311
|
+
user_id TEXT NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
|
|
312
|
+
provider_id TEXT NOT NULL UNIQUE,
|
|
313
|
+
organization_id TEXT,
|
|
314
|
+
domain TEXT NOT NULL
|
|
315
|
+
);
|
|
316
|
+
CREATE INDEX IF NOT EXISTS ssoprovider_domain_idx ON "ssoProvider" (domain);
|
|
317
|
+
`;
|
|
318
|
+
//#endregion
|
|
319
|
+
export { DDL };
|
|
320
|
+
|
|
321
|
+
//# sourceMappingURL=ddl.mjs.map
|
package/dist/ddl.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ddl.mjs","names":[],"sources":["../src/ddl.ts"],"sourcesContent":["/** Canonical Postgres DDL for all StoryShelf tables and indexes. */\nexport const DDL = `\nCREATE TABLE IF NOT EXISTS projects (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n slug TEXT NOT NULL UNIQUE,\n git_repository TEXT,\n git_default_branch TEXT NOT NULL DEFAULT 'main',\n pixel_threshold DOUBLE PRECISION NOT NULL DEFAULT 0.1,\n max_diff_ratio DOUBLE PRECISION NOT NULL DEFAULT 0.01,\n public_branch_regex TEXT,\n storybook_meta TEXT,\n execute_play BOOLEAN NOT NULL DEFAULT false,\n play_timeout_ms INTEGER NOT NULL DEFAULT 10000,\n run_a11y BOOLEAN NOT NULL DEFAULT false,\n browser TEXT NOT NULL DEFAULT 'chromium',\n viewports TEXT,\n automigrate BOOLEAN NOT NULL DEFAULT false,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE TABLE IF NOT EXISTS project_status_configs (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n provider TEXT NOT NULL,\n config TEXT NOT NULL,\n token_encrypted TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE TABLE IF NOT EXISTS builds (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n git_sha TEXT NOT NULL,\n git_branch TEXT NOT NULL,\n is_default BOOLEAN NOT NULL DEFAULT false,\n author_email TEXT,\n author_name TEXT,\n message TEXT,\n public BOOLEAN NOT NULL DEFAULT false,\n status TEXT NOT NULL DEFAULT 'pending',\n snapshot_count INTEGER NOT NULL DEFAULT 0,\n changed_count INTEGER NOT NULL DEFAULT 0,\n approved_count INTEGER NOT NULL DEFAULT 0,\n rejected_count INTEGER NOT NULL DEFAULT 0,\n affected_only BOOLEAN NOT NULL DEFAULT true,\n baseline_sha TEXT,\n changed_files TEXT,\n affected_import_paths TEXT,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE INDEX IF NOT EXISTS builds_project_gitsha_idx ON builds (project_id, git_sha);\nCREATE INDEX IF NOT EXISTS builds_git_branch_idx ON builds (git_branch);\nCREATE TABLE IF NOT EXISTS capture_attempts (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,\n attempt_no INTEGER NOT NULL,\n status TEXT NOT NULL DEFAULT 'queued',\n error TEXT,\n req_id TEXT,\n story_count INTEGER NOT NULL DEFAULT 0,\n failed_count INTEGER NOT NULL DEFAULT 0,\n queued_at TIMESTAMPTZ NOT NULL,\n started_at TIMESTAMPTZ,\n finished_at TIMESTAMPTZ,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS capture_attempts_build_no_idx ON capture_attempts (build_id, attempt_no);\nCREATE INDEX IF NOT EXISTS capture_attempts_build_id_idx ON capture_attempts (build_id);\nCREATE TABLE IF NOT EXISTS capture_logs (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,\n attempt_id TEXT NOT NULL REFERENCES capture_attempts(id) ON DELETE CASCADE,\n seq INTEGER NOT NULL,\n level TEXT NOT NULL DEFAULT 'info',\n message TEXT NOT NULL,\n fields TEXT,\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE INDEX IF NOT EXISTS capture_logs_attempt_seq_idx ON capture_logs (attempt_id, seq);\nCREATE TABLE IF NOT EXISTS snapshots (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,\n story_id TEXT NOT NULL,\n story_name TEXT NOT NULL,\n story_title TEXT NOT NULL,\n story_import_path TEXT,\n viewport_name TEXT NOT NULL DEFAULT 'desktop',\n viewport_width INTEGER NOT NULL DEFAULT 1280,\n viewport_height INTEGER NOT NULL DEFAULT 720,\n screenshot_path TEXT NOT NULL,\n diff_path TEXT,\n diff_pixels INTEGER,\n diff_ratio DOUBLE PRECISION,\n diff_passed BOOLEAN,\n status TEXT NOT NULL DEFAULT 'pending',\n reviewed_by TEXT,\n reviewed_at TIMESTAMPTZ,\n infra_hash TEXT,\n inherited BOOLEAN NOT NULL DEFAULT false,\n baseline_id TEXT,\n baseline_version TEXT,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS snapshots_build_story_viewport_idx ON snapshots (build_id, story_id, viewport_name);\nCREATE INDEX IF NOT EXISTS snapshots_build_id_idx ON snapshots (build_id);\nCREATE TABLE IF NOT EXISTS baselines (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n story_id TEXT NOT NULL,\n viewport_name TEXT NOT NULL DEFAULT 'desktop',\n branch TEXT NOT NULL,\n snapshot_id TEXT,\n screenshot_path TEXT NOT NULL,\n infra_hash TEXT,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS baselines_project_story_viewport_branch_idx ON baselines (project_id, story_id, viewport_name, branch);\nCREATE INDEX IF NOT EXISTS baselines_project_story_idx ON baselines (project_id, story_id);\nCREATE TABLE IF NOT EXISTS users (\n id TEXT PRIMARY KEY,\n email TEXT NOT NULL UNIQUE,\n name TEXT NOT NULL,\n avatar_url TEXT,\n role TEXT NOT NULL DEFAULT 'member',\n last_login_at TIMESTAMPTZ,\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE TABLE IF NOT EXISTS comments (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,\n snapshot_id TEXT REFERENCES snapshots(id) ON DELETE CASCADE,\n user_id TEXT REFERENCES users(id) ON DELETE CASCADE,\n body TEXT NOT NULL,\n parent_id TEXT,\n resolved BOOLEAN NOT NULL DEFAULT false,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE INDEX IF NOT EXISTS comments_build_id_idx ON comments (build_id);\nCREATE TABLE IF NOT EXISTS label_types (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n key TEXT NOT NULL,\n name TEXT NOT NULL,\n link_template TEXT,\n color TEXT,\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS label_types_project_key_idx ON label_types (project_id, key);\nCREATE TABLE IF NOT EXISTS build_labels (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,\n type_key TEXT NOT NULL,\n value TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS build_labels_build_type_value_idx ON build_labels (build_id, type_key, value);\nCREATE TABLE IF NOT EXISTS tokens (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n name TEXT NOT NULL,\n hash TEXT NOT NULL UNIQUE,\n user_id TEXT REFERENCES users(id) ON DELETE CASCADE,\n last_used_at TIMESTAMPTZ,\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE TABLE IF NOT EXISTS webhooks (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n url TEXT NOT NULL,\n secret_encrypted TEXT NOT NULL,\n events TEXT,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE TABLE IF NOT EXISTS notification_channels (\n id TEXT PRIMARY KEY,\n project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,\n provider TEXT NOT NULL,\n config TEXT NOT NULL,\n secret_encrypted TEXT,\n events TEXT,\n enabled BOOLEAN NOT NULL DEFAULT TRUE,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE INDEX IF NOT EXISTS notification_channels_project_id_idx ON notification_channels (project_id);\nCREATE TABLE IF NOT EXISTS notification_subscriptions (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n events TEXT,\n via TEXT,\n enabled BOOLEAN NOT NULL DEFAULT TRUE,\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS notification_subscriptions_project_user_idx ON notification_subscriptions (project_id, user_id);\nCREATE TABLE IF NOT EXISTS user_invite_tokens (\n id TEXT PRIMARY KEY,\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n token_hash TEXT NOT NULL UNIQUE,\n expires_at TIMESTAMPTZ NOT NULL,\n used_at TIMESTAMPTZ,\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE TABLE IF NOT EXISTS project_members (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n role TEXT NOT NULL DEFAULT 'viewer',\n source TEXT NOT NULL DEFAULT 'manual',\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS project_members_project_user_idx ON project_members (project_id, user_id);\nCREATE TABLE IF NOT EXISTS project_group_mappings (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n group_name TEXT NOT NULL,\n role TEXT NOT NULL DEFAULT 'viewer',\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE UNIQUE INDEX IF NOT EXISTS project_group_mappings_project_group_idx ON project_group_mappings (project_id, group_name);\nCREATE TABLE IF NOT EXISTS content_refs (\n hash TEXT PRIMARY KEY,\n ref_count INTEGER NOT NULL DEFAULT 1,\n last_seen_at TIMESTAMPTZ NOT NULL,\n created_at TIMESTAMPTZ NOT NULL\n);\nCREATE TABLE IF NOT EXISTS \"user\" (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n email TEXT NOT NULL UNIQUE,\n email_verified BOOLEAN NOT NULL DEFAULT false,\n image TEXT,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE TABLE IF NOT EXISTS session (\n id TEXT PRIMARY KEY,\n user_id TEXT NOT NULL REFERENCES \"user\"(id) ON DELETE CASCADE,\n token TEXT NOT NULL UNIQUE,\n expires_at TIMESTAMPTZ NOT NULL,\n ip_address TEXT,\n user_agent TEXT,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE INDEX IF NOT EXISTS session_token_idx ON session (token);\nCREATE INDEX IF NOT EXISTS session_user_id_idx ON session (user_id);\nCREATE TABLE IF NOT EXISTS account (\n id TEXT PRIMARY KEY,\n user_id TEXT NOT NULL REFERENCES \"user\"(id) ON DELETE CASCADE,\n account_id TEXT NOT NULL,\n provider_id TEXT NOT NULL,\n access_token TEXT,\n refresh_token TEXT,\n id_token TEXT,\n access_token_expires_at TIMESTAMPTZ,\n refresh_token_expires_at TIMESTAMPTZ,\n scope TEXT,\n expires_at TIMESTAMPTZ,\n password TEXT,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE INDEX IF NOT EXISTS account_user_id_idx ON account (user_id);\nCREATE TABLE IF NOT EXISTS verification (\n id TEXT PRIMARY KEY,\n identifier TEXT NOT NULL,\n value TEXT NOT NULL,\n expires_at TIMESTAMPTZ NOT NULL,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n);\nCREATE INDEX IF NOT EXISTS verification_identifier_idx ON verification (identifier);\nCREATE TABLE IF NOT EXISTS passkey (\n id TEXT PRIMARY KEY,\n name TEXT,\n public_key TEXT NOT NULL,\n user_id TEXT NOT NULL REFERENCES \"user\"(id) ON DELETE CASCADE,\n credential_id TEXT NOT NULL,\n counter INTEGER NOT NULL,\n device_type TEXT NOT NULL,\n backed_up BOOLEAN NOT NULL DEFAULT false,\n transports TEXT,\n aaguid TEXT,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ\n);\nCREATE INDEX IF NOT EXISTS passkey_user_id_idx ON passkey (user_id);\nCREATE INDEX IF NOT EXISTS passkey_credential_id_idx ON passkey (credential_id);\nCREATE TABLE IF NOT EXISTS \"ssoProvider\" (\n id TEXT PRIMARY KEY,\n issuer TEXT NOT NULL,\n oidc_config TEXT,\n saml_config TEXT,\n user_id TEXT NOT NULL REFERENCES \"user\"(id) ON DELETE CASCADE,\n provider_id TEXT NOT NULL UNIQUE,\n organization_id TEXT,\n domain TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS ssoprovider_domain_idx ON \"ssoProvider\" (domain);\n`;\n"],"mappings":";;;;;AACA,MAAa,MAAM"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { applyListOptions, buildLifecycle } from "./drizzle-factory.mjs";
|
|
5
|
+
import { eq, getTableColumns } from "drizzle-orm";
|
|
6
|
+
//#region src/drizzle-factory-pg.ts
|
|
7
|
+
function idColumn(table) {
|
|
8
|
+
const id = getTableColumns(table)["id"];
|
|
9
|
+
if (!id) throw new Error("Table has no id column");
|
|
10
|
+
return id;
|
|
11
|
+
}
|
|
12
|
+
function firstRow(rows, what) {
|
|
13
|
+
const [row] = rows;
|
|
14
|
+
if (row === void 0) throw new Error(`${what} returned no rows`);
|
|
15
|
+
return row;
|
|
16
|
+
}
|
|
17
|
+
async function insertOne(drizzle, table, values) {
|
|
18
|
+
return firstRow(await drizzle.insert(table).values(values).returning(), "Insert");
|
|
19
|
+
}
|
|
20
|
+
async function updateOne(drizzle, table, id, values) {
|
|
21
|
+
const where = eq(idColumn(table), id);
|
|
22
|
+
return firstRow(await drizzle.update(table).set(values).where(where).returning(), "Update");
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Build a {@link DatabaseAdapter} over any Postgres-compatible Drizzle
|
|
26
|
+
* dialect. Mirrors `createDrizzleAdapter` with Postgres terminal shapes
|
|
27
|
+
* (chains await directly; `returning()` yields arrays).
|
|
28
|
+
*/
|
|
29
|
+
function createDrizzlePgAdapter(db, options) {
|
|
30
|
+
const drizzle = db;
|
|
31
|
+
const lifecycle = buildLifecycle(options);
|
|
32
|
+
return {
|
|
33
|
+
metadata: options.metadata,
|
|
34
|
+
tables: options.tables,
|
|
35
|
+
lifecycle,
|
|
36
|
+
insert: async (table, values) => await insertOne(drizzle, table, values),
|
|
37
|
+
update: async (table, id, values) => await updateOne(drizzle, table, id, values),
|
|
38
|
+
get: async (table, id) => {
|
|
39
|
+
const where = eq(idColumn(table), id);
|
|
40
|
+
return (await drizzle.select().from(table).where(where).limit(1))[0] ?? null;
|
|
41
|
+
},
|
|
42
|
+
remove: async (table, id) => {
|
|
43
|
+
const where = eq(idColumn(table), id);
|
|
44
|
+
await drizzle.delete(table).where(where);
|
|
45
|
+
},
|
|
46
|
+
list: async (table, opts = {}) => {
|
|
47
|
+
return await applyListOptions(drizzle.select().from(table), opts);
|
|
48
|
+
},
|
|
49
|
+
count: async (table, where) => await drizzle.$count(table, where),
|
|
50
|
+
all: async (query) => await drizzle.execute(query),
|
|
51
|
+
transact: async (fn) => await drizzle.transaction(async (tx) => await fn(buildTxStore(tx)))
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** CRUD subset bound to a Postgres transaction handle. */
|
|
55
|
+
function buildTxStore(drizzle) {
|
|
56
|
+
return {
|
|
57
|
+
insert: async (table, values) => await insertOne(drizzle, table, values),
|
|
58
|
+
update: async (table, id, values) => await updateOne(drizzle, table, id, values),
|
|
59
|
+
get: async (table, id) => await getOne(drizzle, table, id),
|
|
60
|
+
remove: async (table, id) => {
|
|
61
|
+
const where = eq(idColumn(table), id);
|
|
62
|
+
await drizzle.delete(table).where(where);
|
|
63
|
+
},
|
|
64
|
+
list: async (table, opts = {}) => {
|
|
65
|
+
return await applyListOptions(drizzle.select().from(table), opts);
|
|
66
|
+
},
|
|
67
|
+
count: async (table, where) => await drizzle.$count(table, where)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async function getOne(drizzle, table, id) {
|
|
71
|
+
const where = eq(idColumn(table), id);
|
|
72
|
+
return (await drizzle.select().from(table).where(where).limit(1))[0] ?? null;
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
export { createDrizzlePgAdapter };
|
|
76
|
+
|
|
77
|
+
//# sourceMappingURL=drizzle-factory-pg.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drizzle-factory-pg.mjs","names":[],"sources":["../src/drizzle-factory-pg.ts"],"sourcesContent":["import type {\n DatabaseAdapter,\n DrizzleAdapterOptions,\n ListOptions,\n TxStore,\n} from \"@storyshelf/core/adapter/database\";\nimport type { AdapterLifecycle } from \"@storyshelf/core/adapter/metadata\";\nimport type { SQL } from \"drizzle-orm\";\nimport type { SQLWrapper } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport { eq, getTableColumns } from \"drizzle-orm\";\nimport { applyListOptions, buildLifecycle } from \"./drizzle-factory.ts\";\n\n/** Chainable select surface of the Postgres Drizzle dialect (directly awaitable). */\ninterface DrizzlePgSelectChain extends Promise<unknown[]> {\n where(where: SQL): DrizzlePgSelectChain;\n orderBy(order: SQL): DrizzlePgSelectChain;\n limit(n: number): DrizzlePgSelectChain;\n offset(n: number): DrizzlePgSelectChain;\n}\n\n/**\n * Minimal Postgres Drizzle surface consumed by {@link createDrizzlePgAdapter}.\n * Drivers pass their dialect instance as `unknown`; the cast lives here, once.\n */\ninterface DrizzlePgLike {\n insert(table: Table): {\n values(values: unknown): { returning(): Promise<unknown[]> };\n };\n update(table: Table): {\n set(values: unknown): { where(where: SQL): { returning(): Promise<unknown[]> } };\n };\n select(): { from(table: Table): DrizzlePgSelectChain };\n delete(table: Table): { where(where: SQL): Promise<unknown> };\n $count(table: Table, where?: SQL): Promise<number>;\n execute<T>(query: SQL): Promise<T>;\n transaction<R>(fn: (tx: unknown) => Promise<R>): Promise<R>;\n}\n\nfunction idColumn(table: Table): SQLWrapper {\n const id = getTableColumns(table)[\"id\"];\n if (!id) {\n throw new Error(\"Table has no id column\");\n }\n return id;\n}\n\nfunction firstRow(rows: unknown[], what: string): unknown {\n const [row] = rows;\n if (row === undefined) {\n throw new Error(`${what} returned no rows`);\n }\n return row;\n}\n\nasync function insertOne<T extends Table>(\n drizzle: DrizzlePgLike,\n table: T,\n values: T[\"$inferInsert\"],\n): Promise<T[\"$inferSelect\"]> {\n const rows = await drizzle.insert(table).values(values).returning();\n return firstRow(rows, \"Insert\") as T[\"$inferSelect\"];\n}\n\nasync function updateOne<T extends Table>(\n drizzle: DrizzlePgLike,\n table: T,\n id: string,\n values: Partial<T[\"$inferInsert\"]>,\n): Promise<T[\"$inferSelect\"]> {\n const where = eq(idColumn(table), id);\n const rows = await drizzle.update(table).set(values).where(where).returning();\n return firstRow(rows, \"Update\") as T[\"$inferSelect\"];\n}\n\n/**\n * Build a {@link DatabaseAdapter} over any Postgres-compatible Drizzle\n * dialect. Mirrors `createDrizzleAdapter` with Postgres terminal shapes\n * (chains await directly; `returning()` yields arrays).\n */\nexport function createDrizzlePgAdapter(\n db: unknown,\n options: DrizzleAdapterOptions,\n): DatabaseAdapter {\n const drizzle = db as DrizzlePgLike;\n const lifecycle: AdapterLifecycle = buildLifecycle(options);\n return {\n metadata: options.metadata,\n tables: options.tables,\n lifecycle,\n insert: async <T extends Table>(\n table: T,\n values: T[\"$inferInsert\"],\n ): Promise<T[\"$inferSelect\"]> => await insertOne(drizzle, table, values),\n update: async <T extends Table>(\n table: T,\n id: string,\n values: Partial<T[\"$inferInsert\"]>,\n ): Promise<T[\"$inferSelect\"]> => await updateOne(drizzle, table, id, values),\n get: async <T extends Table>(table: T, id: string): Promise<T[\"$inferSelect\"] | null> => {\n const where = eq(idColumn(table), id);\n const rows = (await drizzle\n .select()\n .from(table)\n .where(where)\n .limit(1)) as T[\"$inferSelect\"][];\n return rows[0] ?? null;\n },\n remove: async (table: Table, id: string): Promise<void> => {\n const where = eq(idColumn(table), id);\n await drizzle.delete(table).where(where);\n },\n list: async <T extends Table>(\n table: T,\n opts: ListOptions = {},\n ): Promise<T[\"$inferSelect\"][]> => {\n const rows: unknown = await applyListOptions(drizzle.select().from(table), opts);\n return rows as T[\"$inferSelect\"][];\n },\n count: async (table: Table, where?: SQL): Promise<number> => await drizzle.$count(table, where),\n all: async <T>(query: SQL): Promise<T[]> => await drizzle.execute<T[]>(query),\n transact: async <R>(fn: (tx: TxStore) => Promise<R>): Promise<R> =>\n await drizzle.transaction(async (tx) => await fn(buildTxStore(tx as DrizzlePgLike))),\n };\n}\n\n/** CRUD subset bound to a Postgres transaction handle. */\nfunction buildTxStore(drizzle: DrizzlePgLike): TxStore {\n return {\n insert: async (table, values) => await insertOne(drizzle, table, values as never),\n update: async (table, id, values) => await updateOne(drizzle, table, id, values as never),\n get: async (table, id) => await getOne(drizzle, table, id),\n remove: async (table, id) => {\n const where = eq(idColumn(table), id);\n await drizzle.delete(table).where(where);\n },\n list: async (table, opts: ListOptions = {}) => {\n const rows: unknown = await applyListOptions(drizzle.select().from(table), opts);\n return rows as never[];\n },\n count: async (table, where) => await drizzle.$count(table, where),\n };\n}\n\nasync function getOne<T extends Table>(\n drizzle: DrizzlePgLike,\n table: T,\n id: string,\n): Promise<T[\"$inferSelect\"] | null> {\n const where = eq(idColumn(table), id);\n const rows = (await drizzle.select().from(table).where(where).limit(1)) as T[\"$inferSelect\"][];\n return rows[0] ?? null;\n}\n"],"mappings":";;;;;;AAuCA,SAAS,SAAS,OAA0B;CAC1C,MAAM,KAAK,gBAAgB,KAAK,CAAC,CAAC;CAClC,IAAI,CAAC,IACH,MAAM,IAAI,MAAM,wBAAwB;CAE1C,OAAO;AACT;AAEA,SAAS,SAAS,MAAiB,MAAuB;CACxD,MAAM,CAAC,OAAO;CACd,IAAI,QAAQ,KAAA,GACV,MAAM,IAAI,MAAM,GAAG,KAAK,kBAAkB;CAE5C,OAAO;AACT;AAEA,eAAe,UACb,SACA,OACA,QAC4B;CAE5B,OAAO,SAAS,MADG,QAAQ,OAAO,KAAK,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,UAAU,GAC5C,QAAQ;AAChC;AAEA,eAAe,UACb,SACA,OACA,IACA,QAC4B;CAC5B,MAAM,QAAQ,GAAG,SAAS,KAAK,GAAG,EAAE;CAEpC,OAAO,SAAS,MADG,QAAQ,OAAO,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,UAAU,GACtD,QAAQ;AAChC;;;;;;AAOA,SAAgB,uBACd,IACA,SACiB;CACjB,MAAM,UAAU;CAChB,MAAM,YAA8B,eAAe,OAAO;CAC1D,OAAO;EACL,UAAU,QAAQ;EAClB,QAAQ,QAAQ;EAChB;EACA,QAAQ,OACN,OACA,WAC+B,MAAM,UAAU,SAAS,OAAO,MAAM;EACvE,QAAQ,OACN,OACA,IACA,WAC+B,MAAM,UAAU,SAAS,OAAO,IAAI,MAAM;EAC3E,KAAK,OAAwB,OAAU,OAAkD;GACvF,MAAM,QAAQ,GAAG,SAAS,KAAK,GAAG,EAAE;GAMpC,QAAO,MALa,QACjB,OAAO,CAAC,CACR,KAAK,KAAK,CAAC,CACX,MAAM,KAAK,CAAC,CACZ,MAAM,CAAC,EAAA,CACE,MAAM;EACpB;EACA,QAAQ,OAAO,OAAc,OAA8B;GACzD,MAAM,QAAQ,GAAG,SAAS,KAAK,GAAG,EAAE;GACpC,MAAM,QAAQ,OAAO,KAAK,CAAC,CAAC,MAAM,KAAK;EACzC;EACA,MAAM,OACJ,OACA,OAAoB,CAAC,MACY;GAEjC,OAAO,MADqB,iBAAiB,QAAQ,OAAO,CAAC,CAAC,KAAK,KAAK,GAAG,IAAI;EAEjF;EACA,OAAO,OAAO,OAAc,UAAiC,MAAM,QAAQ,OAAO,OAAO,KAAK;EAC9F,KAAK,OAAU,UAA6B,MAAM,QAAQ,QAAa,KAAK;EAC5E,UAAU,OAAU,OAClB,MAAM,QAAQ,YAAY,OAAO,OAAO,MAAM,GAAG,aAAa,EAAmB,CAAC,CAAC;CACvF;AACF;;AAGA,SAAS,aAAa,SAAiC;CACrD,OAAO;EACL,QAAQ,OAAO,OAAO,WAAW,MAAM,UAAU,SAAS,OAAO,MAAe;EAChF,QAAQ,OAAO,OAAO,IAAI,WAAW,MAAM,UAAU,SAAS,OAAO,IAAI,MAAe;EACxF,KAAK,OAAO,OAAO,OAAO,MAAM,OAAO,SAAS,OAAO,EAAE;EACzD,QAAQ,OAAO,OAAO,OAAO;GAC3B,MAAM,QAAQ,GAAG,SAAS,KAAK,GAAG,EAAE;GACpC,MAAM,QAAQ,OAAO,KAAK,CAAC,CAAC,MAAM,KAAK;EACzC;EACA,MAAM,OAAO,OAAO,OAAoB,CAAC,MAAM;GAE7C,OAAO,MADqB,iBAAiB,QAAQ,OAAO,CAAC,CAAC,KAAK,KAAK,GAAG,IAAI;EAEjF;EACA,OAAO,OAAO,OAAO,UAAU,MAAM,QAAQ,OAAO,OAAO,KAAK;CAClE;AACF;AAEA,eAAe,OACb,SACA,OACA,IACmC;CACnC,MAAM,QAAQ,GAAG,SAAS,KAAK,GAAG,EAAE;CAEpC,QAAO,MADa,QAAQ,OAAO,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,CAAC,EAAA,CACzD,MAAM;AACpB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
//#region src/drizzle-factory.ts
|
|
5
|
+
function applyListOptions(query, opts) {
|
|
6
|
+
let current = query;
|
|
7
|
+
if (opts.where) current = current.where(opts.where);
|
|
8
|
+
if (opts.orderBy) current = current.orderBy(opts.orderBy);
|
|
9
|
+
if (opts.limit !== void 0) current = current.limit(opts.limit);
|
|
10
|
+
if (opts.offset !== void 0) current = current.offset(opts.offset);
|
|
11
|
+
return current;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Build the database lifecycle: migrations run in `setup`, the connection
|
|
15
|
+
* is destroyed in `teardown`, `health` pings when the driver provides one.
|
|
16
|
+
* Teardown tolerates repeated calls (drivers may not).
|
|
17
|
+
*/
|
|
18
|
+
function buildLifecycle(options) {
|
|
19
|
+
let closed = false;
|
|
20
|
+
return {
|
|
21
|
+
setup: async () => {
|
|
22
|
+
await options.migrate();
|
|
23
|
+
},
|
|
24
|
+
teardown: async () => {
|
|
25
|
+
if (closed) return;
|
|
26
|
+
closed = true;
|
|
27
|
+
await options.close();
|
|
28
|
+
},
|
|
29
|
+
health: async () => {
|
|
30
|
+
if (closed) return {
|
|
31
|
+
ok: false,
|
|
32
|
+
detail: "database closed"
|
|
33
|
+
};
|
|
34
|
+
if (options.ping) await options.ping();
|
|
35
|
+
return { ok: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { applyListOptions, buildLifecycle };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=drizzle-factory.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drizzle-factory.mjs","names":[],"sources":["../src/drizzle-factory.ts"],"sourcesContent":["import type { DrizzleAdapterOptions, ListOptions } from \"@storyshelf/core/adapter/database\";\nimport type { AdapterLifecycle } from \"@storyshelf/core/adapter/metadata\";\nimport type { SQL } from \"drizzle-orm\";\n\n/** Chainable filter surface shared by dialect select chains. */\nexport interface ChainFilter<C> {\n where(where: SQL): C;\n orderBy(order: SQL): C;\n limit(n: number): C;\n offset(n: number): C;\n}\n\nexport function applyListOptions<C extends ChainFilter<C>>(query: C, opts: ListOptions): C {\n let current = query;\n if (opts.where) {\n current = current.where(opts.where);\n }\n if (opts.orderBy) {\n current = current.orderBy(opts.orderBy);\n }\n if (opts.limit !== undefined) {\n current = current.limit(opts.limit);\n }\n if (opts.offset !== undefined) {\n current = current.offset(opts.offset);\n }\n return current;\n}\n\n/**\n * Build the database lifecycle: migrations run in `setup`, the connection\n * is destroyed in `teardown`, `health` pings when the driver provides one.\n * Teardown tolerates repeated calls (drivers may not).\n */\nexport function buildLifecycle(options: DrizzleAdapterOptions): AdapterLifecycle {\n let closed = false;\n return {\n setup: async () => {\n await options.migrate();\n },\n teardown: async () => {\n if (closed) {\n return;\n }\n closed = true;\n await options.close();\n },\n health: async () => {\n if (closed) {\n return { ok: false, detail: \"database closed\" };\n }\n if (options.ping) {\n await options.ping();\n }\n return { ok: true };\n },\n };\n}\n"],"mappings":";;;;AAYA,SAAgB,iBAA2C,OAAU,MAAsB;CACzF,IAAI,UAAU;CACd,IAAI,KAAK,OACP,UAAU,QAAQ,MAAM,KAAK,KAAK;CAEpC,IAAI,KAAK,SACP,UAAU,QAAQ,QAAQ,KAAK,OAAO;CAExC,IAAI,KAAK,UAAU,KAAA,GACjB,UAAU,QAAQ,MAAM,KAAK,KAAK;CAEpC,IAAI,KAAK,WAAW,KAAA,GAClB,UAAU,QAAQ,OAAO,KAAK,MAAM;CAEtC,OAAO;AACT;;;;;;AAOA,SAAgB,eAAe,SAAkD;CAC/E,IAAI,SAAS;CACb,OAAO;EACL,OAAO,YAAY;GACjB,MAAM,QAAQ,QAAQ;EACxB;EACA,UAAU,YAAY;GACpB,IAAI,QACF;GAEF,SAAS;GACT,MAAM,QAAQ,MAAM;EACtB;EACA,QAAQ,YAAY;GAClB,IAAI,QACF,OAAO;IAAE,IAAI;IAAO,QAAQ;GAAkB;GAEhD,IAAI,QAAQ,MACV,MAAM,QAAQ,KAAK;GAErB,OAAO,EAAE,IAAI,KAAK;EACpB;CACF;AACF"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import postgres from "postgres";
|
|
4
|
+
import { DatabaseAdapter } from "@storyshelf/core/adapter/database";
|
|
5
|
+
//#region src/index.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* TLS options for Postgres connections. When `true`, the driver negotiates
|
|
8
|
+
* TLS with the server's default CA. When an object, `ca`/`cert`/`key` are
|
|
9
|
+
* forwarded to `postgres.js` for mutual TLS (e.g. GCP Cloud SQL) or custom
|
|
10
|
+
* RDS bundles.
|
|
11
|
+
*/
|
|
12
|
+
type PostgresSslOptions = boolean | {
|
|
13
|
+
ca?: string;
|
|
14
|
+
cert?: string;
|
|
15
|
+
key?: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Options for creating a Postgres-backed DatabaseAdapter.
|
|
19
|
+
*
|
|
20
|
+
* `url` (or `connectionString`) is the Postgres connection string
|
|
21
|
+
* (`postgres://user:pass@host:5432/db`). For managed providers, see the
|
|
22
|
+
* deployment guide for per-provider recipes (RDS CA bundle, Cloud SQL
|
|
23
|
+
* mutual TLS, Supabase pooler port `6543` with `prepare: false`, etc.).
|
|
24
|
+
*
|
|
25
|
+
* When `client` is supplied, all other connection options are ignored and
|
|
26
|
+
* the caller owns the lifecycle of the client. This is the hook for
|
|
27
|
+
* advanced setups (IAM token refresh, pre-configured TLS, etc.) and for
|
|
28
|
+
* hermetic tests.
|
|
29
|
+
*/
|
|
30
|
+
interface PostgresDatabaseOptions {
|
|
31
|
+
url?: string;
|
|
32
|
+
connectionString?: string;
|
|
33
|
+
ssl?: PostgresSslOptions;
|
|
34
|
+
prepare?: boolean;
|
|
35
|
+
max?: number;
|
|
36
|
+
idleTimeout?: number;
|
|
37
|
+
connectTimeout?: number;
|
|
38
|
+
client?: ReturnType<typeof postgres>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Create a Postgres-backed DatabaseAdapter using postgres.js and Drizzle ORM.
|
|
42
|
+
*
|
|
43
|
+
* Works with self-hosted Postgres, AWS RDS, GCP Cloud SQL, Supabase, Neon,
|
|
44
|
+
* and Azure Database for PostgreSQL — all speak the standard wire protocol.
|
|
45
|
+
* For provider-specific TLS and pooling notes, see the deployment guide.
|
|
46
|
+
*/
|
|
47
|
+
declare function createPostgresDatabase(options: PostgresDatabaseOptions): DatabaseAdapter;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { PostgresDatabaseOptions, PostgresSslOptions, createPostgresDatabase };
|
|
50
|
+
//# sourceMappingURL=index.d.mts.map
|