@everystack/cli 0.4.71 → 0.4.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ All notable changes to `@everystack/cli` are documented here.
4
4
 
5
5
  Format based on [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
+ ## [0.4.72] - 2026-09-04
8
+
9
+ ### Fixed
10
+
11
+ - db:reconcile defers function-body validation (check_function_bodies=off)
12
+
7
13
  ## [0.4.71] - 2026-09-04
8
14
 
9
15
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@everystack/cli",
3
- "version": "0.4.71",
3
+ "version": "0.4.72",
4
4
  "description": "CLI and OTA updates for Expo apps on everystack",
5
5
  "license": "AGPL-3.0-only",
6
6
  "author": "Scalable Technology, Inc. <licensing@scalable.technology>",
@@ -178,7 +178,7 @@
178
178
  "jest": "29.7.0",
179
179
  "react": "19.2.0",
180
180
  "ts-jest": "29.4.9",
181
- "@everystack/server": "0.4.29"
181
+ "@everystack/server": "0.4.30"
182
182
  },
183
183
  "scripts": {
184
184
  "test": "jest",
@@ -188,6 +188,16 @@ export async function executeReconcile(
188
188
  // as it was. See derivedSearchPath.
189
189
  const setSearchPath = renderSetSearchPath(derivedSearchPath(parsed.objects), atomic);
190
190
 
191
+ // Defer SQL/PLpgSQL function-body validation for the apply window — the pg_dump/pg_restore idiom
192
+ // db:build already uses (db-build.ts). A SQL-language function's body is validated at CREATE, so a
193
+ // function that reads a derived object being dropped-and-recreated in the SAME batch aborts with
194
+ // "relation does not exist" when it is (re)created before that object. The bodies are already
195
+ // validated upstream (db:build against a fresh DB, db:check in CI); the reconcile only needs the
196
+ // objects to EXIST by commit, which they do. Without this, reconcile fails where db:build succeeds
197
+ // on the very same schema — the only difference between them was this SET. LOCAL in the atomic
198
+ // batch (reverts at COMMIT); session-scoped on the unwrapped path (its connection is ephemeral).
199
+ const setCheckBodies = atomic ? 'SET LOCAL check_function_bodies = off' : 'SET check_function_bodies = off';
200
+
191
201
  // A schema populated only by derived objects has no model in it, so the state layer never
192
202
  // CREATE SCHEMAs it — the derived apply ensures its own object schemas (+ their USAGE
193
203
  // grants) before anything is created in them. Empty for all-public apps. See
@@ -221,9 +231,10 @@ export async function executeReconcile(
221
231
 
222
232
  if (rendered.statements.length > 0) {
223
233
  if (atomic) {
224
- // The SET rides the same simple-query batch (and the same transaction) as the DDL,
225
- // so bare body refs resolve to their real schema; SET LOCAL resets it at COMMIT.
226
- await runner([...ensureSchemas, setSearchPath, ...rendered.statements].filter(Boolean).join(';\n'));
234
+ // The SETs ride the same simple-query batch (and the same transaction) as the DDL, so bare
235
+ // body refs resolve to their real schema and function bodies defer validation; SET LOCAL
236
+ // resets both at COMMIT.
237
+ await runner([...ensureSchemas, setSearchPath, setCheckBodies, ...rendered.statements].filter(Boolean).join(';\n'));
227
238
  } else {
228
239
  // Self-committing statements (CONCURRENTLY, VACUUM) refuse ANY transaction block —
229
240
  // including the implicit one a joined multi-statement simple query runs in, so the
@@ -233,7 +244,17 @@ export async function executeReconcile(
233
244
  // create resolves its bare refs.
234
245
  for (const statement of ensureSchemas) await runner(statement);
235
246
  if (setSearchPath) await runner(setSearchPath);
236
- for (const statement of rendered.statements) await runner(statement);
247
+ // No transaction to scope the deferral to (this path exists for statements a transaction
248
+ // forbids), so the session GUC is set bare and RESET in a finally. A leak of THIS GUC is
249
+ // benign, unlike search_path: it only defers a body validation that already ran upstream
250
+ // (db:build against a fresh DB, db:check in CI) — it never changes what a query resolves to
251
+ // or what a fingerprint reads, so it cannot mint a wrong result the way a leaked path could.
252
+ await runner(setCheckBodies);
253
+ try {
254
+ for (const statement of rendered.statements) await runner(statement);
255
+ } finally {
256
+ await runner('RESET check_function_bodies');
257
+ }
237
258
  }
238
259
  }
239
260