@promptowl/contextnest-community 1.4.0 → 1.6.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/CONFIGURATION.md CHANGED
@@ -45,7 +45,15 @@ The server prints a loud warning at startup when `AUTH_MODE=open` is active.
45
45
  |---|---|---|
46
46
  | `PORT` | `3838` | HTTP port the server listens on. |
47
47
  | `DATA_ROOT` | `./data` (relative to cwd) | Root directory for SQLite DB + nest filesystem vaults. Set to an absolute path in production so it's independent of where you `cd`'d from. |
48
- | `DATABASE_PATH` | `$DATA_ROOT/community.db` | Override the SQLite file location explicitly. |
48
+ | `DATABASE_PATH` | `$DATA_ROOT/community.db` | Override the SQLite file location explicitly. (SQLite backend only.) |
49
+ | `NEST_STORAGE_ROOT` | `$DATA_ROOT/nests` | Root directory for nest vault files (markdown + version history). Override to store nests separately from the DB — e.g. point it at a Cloud Storage (GCS) volume mount on Cloud Run (`/mnt/nests`) while the SQLite DB stays on `DATA_ROOT` off the mount. **Moving existing nests:** the app does not relocate files — copy `$DATA_ROOT/nests/*` to the new root (e.g. `gsutil rsync`) before switching, or existing nests will appear empty (DB rows present, files missing). On a GCS mount, also set `DRIFT_SCAN_INTERVAL_MS=0` to avoid constant FUSE scans, and run a single instance (GCS FUSE has no file locking — concurrent writers lose data). |
50
+ | `DB_DRIVER` | `sqlite` | Database backend: `sqlite` (default) or `postgres`. When unset, the server infers `postgres` if `DATABASE_URL` or `CLOUD_SQL_CONNECTION_NAME` is set, else `sqlite`. Existing deployments need no change — they stay on SQLite. See [Database backends](#database-backends). |
51
+ | `DATABASE_URL` | `""` | PostgreSQL connection string for a TCP connection, e.g. `postgres://user:pass@host:5432/dbname`. Setting it selects the Postgres backend (unless `DB_DRIVER=sqlite`). |
52
+ | `CLOUD_SQL_CONNECTION_NAME` | `""` | Cloud SQL instance connection name (`project:region:instance`). When set, the server connects over the Cloud SQL Auth Proxy unix socket at `/cloudsql/<name>` — the recommended Cloud Run setup. Combine with `DB_USER`, `DB_PASSWORD`, `DB_NAME`. |
53
+ | `DB_HOST` / `DB_PORT` / `DB_USER` / `DB_PASSWORD` / `DB_NAME` | `localhost` / `5432` / `""` / `""` / `""` | Discrete Postgres connection params, used when neither `CLOUD_SQL_CONNECTION_NAME` nor `DATABASE_URL` is set. |
54
+ | `DB_POOL_MAX` | `10` | Max Postgres pool connections. Keep modest — Cloud SQL tiers cap total connections. |
55
+ | `DB_SSL` | `false` | Set `true` to require TLS on a Postgres TCP connection (not needed over the Cloud SQL unix socket, which is already secure). |
56
+ | `DB_SSL_CA` | `""` | Path to a CA certificate (PEM) for verify-ca/verify-full TLS when `DB_SSL=true`. |
49
57
  | `AUTH_MODE` | `key` | `key` or `open`. See above. |
50
58
  | `PROMPTOWL_API_URL` | `https://app.promptowl.ai` | PromptOwl's API origin — used for device auth, license validation, telemetry. Override for air-gapped or test setups. |
51
59
  | `PROMPTOWL_KEY` | `""` | Your PromptOwl Community License key (`pk_...`). Unlicensed instances still run and serve reads, but every write returns `503` until a valid key is installed. Can also be set via the browser License Setup Page, which persists it to `ENV_FILE_PATH`. |
@@ -61,6 +69,56 @@ The server prints a loud warning at startup when `AUTH_MODE=open` is active.
61
69
 
62
70
  ---
63
71
 
72
+ ## Database backends
73
+
74
+ The governance/auth metadata (users, sessions, nests registry, stewards, reviews,
75
+ versions index, comments, telemetry) is stored in a relational database. Two
76
+ backends are supported:
77
+
78
+ - **SQLite (default).** A single file at `DATABASE_PATH`. Zero-config, ideal for
79
+ single-box and local deployments. Nothing changes for existing installs.
80
+ - **PostgreSQL / Cloud SQL.** For deployments where local disk isn't durable
81
+ (e.g. Google Cloud Run, where the container filesystem is ephemeral). Selected
82
+ by setting `DB_DRIVER=postgres`, `DATABASE_URL`, or `CLOUD_SQL_CONNECTION_NAME`.
83
+
84
+ On first connect the server creates its schema automatically (idempotent) — no
85
+ manual migration step. There is no built-in SQLite→Postgres data migration; a new
86
+ Postgres backend starts empty.
87
+
88
+ > ⚠️ **Cloud Run durability — read this.** Cloud SQL persists only the *metadata
89
+ > database*. The actual **document content and version history** live on the
90
+ > filesystem under `$DATA_ROOT/nests/`, which on Cloud Run is **ephemeral** and is
91
+ > lost when an instance is recycled. Using Cloud SQL alone does **not** make a
92
+ > Cloud Run deployment fully durable. You must also put `$DATA_ROOT` on persistent,
93
+ > shared storage — e.g. mount a GCS bucket via Cloud Storage FUSE, or use a
94
+ > persistent volume — otherwise documents will disappear on redeploy/scale-in.
95
+
96
+ ### Cloud Run + Cloud SQL (PostgreSQL)
97
+
98
+ Attach the Cloud SQL instance to the service (`--add-cloudsql-instances`) so the
99
+ Auth Proxy unix socket appears at `/cloudsql/<connection-name>`, then:
100
+
101
+ ```bash
102
+ DB_DRIVER=postgres \
103
+ CLOUD_SQL_CONNECTION_NAME=my-project:us-central1:contextnest \
104
+ DB_USER=contextnest \
105
+ DB_PASSWORD=*** \
106
+ DB_NAME=contextnest \
107
+ DATA_ROOT=/mnt/vault \ # mount GCS/persistent storage here — see warning above
108
+ PROMPTOWL_KEY=pk_... \
109
+ node dist/index.js
110
+ ```
111
+
112
+ Or with a plain TCP connection string (e.g. the Cloud SQL Proxy running on
113
+ localhost, or a private IP):
114
+
115
+ ```bash
116
+ DATABASE_URL=postgres://contextnest:***@127.0.0.1:5432/contextnest \
117
+ DB_DRIVER=postgres \
118
+ DATA_ROOT=/mnt/vault \
119
+ npm start
120
+ ```
121
+
64
122
  ## Typical deployments
65
123
 
66
124
  ### Local dev / single user
@@ -86,7 +144,7 @@ Terminate TLS at the proxy, forward `X-Forwarded-For` and `X-Real-IP` headers (t
86
144
  ### Hosted / commercial SaaS
87
145
 
88
146
  Same as team, plus:
89
- - Run N instances behind a load balancer sharing a network-attached `DATA_ROOT` (note: SQLite + shared filesystem is not a great long-term story — migrate to the MongoDB storage adapter when scaling beyond one box).
147
+ - Run N instances behind a load balancer. SQLite + a shared filesystem is not a great long-term story for the metadata DB when scaling beyond one box switch to the **PostgreSQL / Cloud SQL** backend (see [Database backends](#database-backends)) so all instances share one consistent database. The on-disk nest vault under `$DATA_ROOT/nests/` still needs durable shared storage.
90
148
  - Set `TELEMETRY_ENABLED=true` and a valid `PROMPTOWL_KEY` so usage rolls up to PromptOwl.
91
149
  - Rotate keys regularly via `DELETE /auth/keys/:id` + `POST /auth/keys`.
92
150
 
package/README.md CHANGED
@@ -89,6 +89,8 @@ For redistribution, hosted-service, OEM, or regulated-industry licensing, contac
89
89
  | Feature | Community Edition | Enterprise |
90
90
  |---|:---:|:---:|
91
91
  | Self-hosted context server | ✅ | ✅ |
92
+ | SQLite or PostgreSQL / Cloud SQL backend | ✅ | ✅ |
93
+ | Configurable nest storage (external / GCS volume mount) | ✅ | ✅ |
92
94
  | Markdown + YAML frontmatter vaults | ✅ | ✅ |
93
95
  | Import existing folder / vault | ✅ | ✅ |
94
96
  | Markdown rendering + wiki cross-linking | ✅ | ✅ |
@@ -110,6 +112,17 @@ For redistribution, hosted-service, OEM, or regulated-industry licensing, contac
110
112
 
111
113
  For Enterprise pricing and features, contact **hoot@promptowl.ai** or visit <https://promptowl.ai/contextnest/>.
112
114
 
115
+ ## What's new in 1.6.0
116
+
117
+ - **PostgreSQL / Cloud SQL backend** — run the server on PostgreSQL alongside SQLite via an async DB adapter, so metadata survives on Cloud Run and other deployments where local disk isn't durable. Supports the Cloud SQL Auth Proxy (unix socket) and TCP + TLS. New `DB_*` env vars (`DB_DRIVER`, `DATABASE_URL`, `CLOUD_SQL_CONNECTION_NAME`, `DB_HOST`/`DB_PORT`/`DB_USER`/`DB_PASSWORD`/`DB_NAME`, `DB_POOL_MAX`, `DB_SSL`, `DB_SSL_CA`); SQLite stays the default when nothing is configured. See [CONFIGURATION.md](./CONFIGURATION.md).
118
+ - **Configurable `NEST_STORAGE_ROOT`** — store nest vault files (markdown + version history) outside `DATA_ROOT` — e.g. on a Cloud Storage (GCS) volume mount — while the database stays on `DATA_ROOT`. Defaults to `<DATA_ROOT>/nests`, so existing deployments are unchanged. On a GCS FUSE mount, set `DRIFT_SCAN_INTERVAL_MS=0` and run a single instance.
119
+ - **Security fix** — the admin guard on `/admin/settings` (GET/PATCH) is now correctly awaited; without it a non-admin could read or change server-wide settings in key mode.
120
+ - **AdminLoginModal respects `PROMPTOWL_SIGN_IN_GATE`** — the "Sign in with PromptOwl" affordance follows the configured gate (`open` / `admin-only` / `disabled`).
121
+ - **Drift scanner toggle** — `DRIFT_SCAN_INTERVAL_MS=0` now actually disables the drift scanner (previously reset to the 30s default).
122
+ - **Safer folder sync** — unsynced-folder detection no longer treats a configured nest storage root as a sync candidate, preventing accidental duplication/removal of live nests when `NEST_STORAGE_ROOT` is nested under `DATA_ROOT`.
123
+
124
+ Full history in [CHANGELOG.md](./CHANGELOG.md).
125
+
113
126
  ## What's new in 1.3.0
114
127
 
115
128
  - **Server super-admins** — emails listed in `access.yaml: super_admins` administer every nest (change visibility, manage collaborators and stewards) without being added per-nest. Owner-only operations (delete, transfer) still require the nest owner. See [STEWARDSHIP.md](./STEWARDSHIP.md).
@@ -0,0 +1,71 @@
1
+ // src/db/adapter.postgres.ts
2
+ function translatePlaceholders(sql) {
3
+ let out = "";
4
+ let inStr = false;
5
+ let n = 0;
6
+ for (let i = 0; i < sql.length; i++) {
7
+ const ch = sql[i];
8
+ if (ch === "'") {
9
+ inStr = !inStr;
10
+ out += ch;
11
+ } else if (ch === "?" && !inStr) {
12
+ out += "$" + ++n;
13
+ } else {
14
+ out += ch;
15
+ }
16
+ }
17
+ return out;
18
+ }
19
+ var PgQuerier = class {
20
+ constructor(q) {
21
+ this.q = q;
22
+ }
23
+ q;
24
+ async get(sql, params = []) {
25
+ const res = await this.q.query(translatePlaceholders(sql), params);
26
+ return res.rows[0];
27
+ }
28
+ async all(sql, params = []) {
29
+ const res = await this.q.query(translatePlaceholders(sql), params);
30
+ return res.rows;
31
+ }
32
+ async run(sql, params = []) {
33
+ const res = await this.q.query(translatePlaceholders(sql), params);
34
+ return { changes: res.rowCount ?? 0 };
35
+ }
36
+ async exec(sql) {
37
+ await this.q.query(sql);
38
+ }
39
+ };
40
+ var PostgresAdapter = class extends PgQuerier {
41
+ constructor(pool) {
42
+ super(pool);
43
+ this.pool = pool;
44
+ }
45
+ pool;
46
+ dialect = "postgres";
47
+ async transaction(fn) {
48
+ const client = await this.pool.connect();
49
+ try {
50
+ await client.query("BEGIN");
51
+ const result = await fn(new PgQuerier(client));
52
+ await client.query("COMMIT");
53
+ return result;
54
+ } catch (err) {
55
+ try {
56
+ await client.query("ROLLBACK");
57
+ } catch {
58
+ }
59
+ throw err;
60
+ } finally {
61
+ client.release();
62
+ }
63
+ }
64
+ async close() {
65
+ await this.pool.end();
66
+ }
67
+ };
68
+ export {
69
+ PostgresAdapter,
70
+ translatePlaceholders
71
+ };