latticesql 1.13.10 → 1.15.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/README.md CHANGED
@@ -804,6 +804,16 @@ await db.seed({
804
804
  });
805
805
  ```
806
806
 
807
+ A junction link whose target row doesn't resolve is **never silently dropped**. `SeedResult.unresolvedLinks` lists every such link (source record, field, target name, junction). Pass `onUnresolvedLink: 'throw'` to abort with a `SeedReconciliationError` instead — for pipelines that must never leave a record citing a relationship that has no link in the graph:
808
+
809
+ ```typescript
810
+ const result = await db.seed({ ...config, onUnresolvedLink: 'collect' });
811
+ if (result.unresolvedLinks.length) {
812
+ // create the missing targets, then re-seed
813
+ console.warn('unresolved links:', result.unresolvedLinks);
814
+ }
815
+ ```
816
+
807
817
  ### `buildReport()` (v0.14+)
808
818
 
809
819
  ```typescript
@@ -2091,12 +2101,14 @@ The convergence means you don't need to duplicate entity-context definitions in
2091
2101
 
2092
2102
  **Dashboard renders every entity (v1.13.3+).** Previously the dashboard cards filtered through a hardcoded entity list (`meetings`, `people`, `messages`, `projects`, `repositories`, `files`). Installs whose YAML declared different names saw a blank dashboard. Now every first-class entity gets a card; the hardcoded list survives as an ordering preference only.
2093
2103
 
2104
+ **Approximate row counts on Postgres (v1.14.1+).** The dashboard / entity-list view reads row counts from `pg_class.reltuples` (the planner statistic maintained by `ANALYZE` / autovacuum) so that a single query covers every table. Older versions issued one `COUNT(*)` per table in parallel, which exhausted small connection pools (e.g. a 95-table database against Supabase's 15-slot session pooler). The trade is that list-view counts are approximate and include soft-deleted rows for tables that have a `deleted_at` column; per-table drill-in still shows exact filtered counts. SQLite-backed installs are unaffected and continue to show exact, soft-delete-aware counts (no pool to exhaust).
2105
+
2094
2106
  **Views**
2095
2107
 
2096
2108
  - **Dashboard** (`#/`) — one card per first-class entity with live row counts.
2097
2109
  - **Table view** (`#/objects/<entity>`) — intrinsic columns, `belongsTo` chips, and a column per junction this entity participates in.
2098
2110
  - **Detail view** (`#/objects/<entity>/<id>`) — read mode by default; `Edit` flips cells into inputs (`Save` PATCHes, `Cancel` reverts).
2099
- - **Data Model** (`#/settings/data-model`) — entity-level graph plus a side panel for adding / removing junction-table links between rows.
2111
+ - **Data Model** (inside **Database Settings**, v1.14+) — entity-level graph including the native `files`/`secrets` objects, with a per-entity editor. On a team cloud each table you own carries a **Share with team / Unshare** toggle. (Pre-1.14 this was a separate `#/settings/data-model` nav item; that hash still resolves for back-compat.)
2100
2112
 
2101
2113
  **Internal tables added on first open**
2102
2114
 
@@ -2112,18 +2124,22 @@ These tables are prefixed with `_lattice_gui_` and are hidden from `/api/entitie
2112
2124
 
2113
2125
  **HTTP surface** (all routes scoped to `http://127.0.0.1:<port>/api`):
2114
2126
 
2115
- | Route | Method | Lattice call |
2116
- | -------------------------- | ------ | ----------------------------- |
2117
- | `/project` | GET | (config + manifest summary) |
2118
- | `/entities` | GET | tables + `db.count` per table |
2119
- | `/graph` | GET | (schema graph for Data Model) |
2120
- | `/tables/:table/rows` | GET | `db.query(table, …)` |
2121
- | `/tables/:table/rows` | POST | `db.insert(table, body)` |
2122
- | `/tables/:table/rows/:id` | GET | `db.get(table, id)` |
2123
- | `/tables/:table/rows/:id` | PATCH | `db.update(table, id, body)` |
2124
- | `/tables/:table/rows/:id` | DELETE | `db.delete(table, id)` |
2125
- | `/tables/:junction/link` | POST | `db.link(junction, body)` |
2126
- | `/tables/:junction/unlink` | POST | `db.unlink(junction, body)` |
2127
+ | Route | Method | Lattice call |
2128
+ | ------------------------------ | ------ | --------------------------------------------------------------- |
2129
+ | `/project` | GET | (config + manifest summary) |
2130
+ | `/entities` | GET | tables + `db.count` per table |
2131
+ | `/graph` | GET | (schema graph for Data Model) |
2132
+ | `/tables/:table/rows` | GET | `db.query(table, …)` |
2133
+ | `/tables/:table/rows` | POST | `db.insert(table, body)` |
2134
+ | `/tables/:table/rows/:id` | GET | `db.get(table, id)` |
2135
+ | `/tables/:table/rows/:id` | PATCH | `db.update(table, id, body)` |
2136
+ | `/tables/:table/rows/:id` | DELETE | `db.delete(table, id)` |
2137
+ | `/tables/:junction/link` | POST | `db.link(junction, body)` |
2138
+ | `/tables/:junction/unlink` | POST | `db.unlink(junction, body)` |
2139
+ | `/schema/entities` | POST | create a new entity/table |
2140
+ | `/schema/entities/:name/share` | POST | share/unshare a table you own with the team (cloud, owner-only) |
2141
+
2142
+ On a team cloud, `/entities` and `/graph` (and the queryable `/tables/*` allowlist) are filtered to the tables you own plus tables shared to the team — so the API surface matches exactly what the GUI shows; a table you can't see is not reachable. `/entities` rows carry `shared` / `ownedByMe` flags in that mode.
2127
2143
 
2128
2144
  The server only binds to `127.0.0.1` and has no authentication. See [SECURITY.md](./SECURITY.md) for the threat model — do not expose this port to a non-loopback interface.
2129
2145
 
@@ -2150,6 +2166,18 @@ registerNativeEntities(db);
2150
2166
  await db.init();
2151
2167
  ```
2152
2168
 
2169
+ `isNativeEntity(name)` / `NATIVE_ENTITY_NAMES` are the single source of truth for "is this table a framework-shipped native object?" — adding a key to `NATIVE_ENTITY_DEFS` flows everywhere automatically (table creation, GUI surfacing, recognition).
2170
+
2171
+ **Adopting an existing `files`/`secrets` table (v1.14+).** If a database already has its own `files` or `secrets` table — possibly with a different/legacy column shape — `adoptNativeEntities(db)` (run after `init()`) labels that physical table as THE native object instead of duplicating it: it merges the native column superset non-destructively (`CREATE TABLE IF NOT EXISTS` + `ADD COLUMN IF NOT EXISTS`, never dropping data) and records the binding in an internal `__lattice_native_entities` registry. Legacy plaintext `secrets.value` rows stay readable (decrypt passes non-`enc:` values through) and new writes encrypt. `listNativeBindings(db)` reads the bindings. The GUI runs this automatically on every open and exposes the bindings at `GET /api/native-entities`.
2172
+
2173
+ ```ts
2174
+ import { adoptNativeEntities, listNativeBindings } from 'latticesql';
2175
+
2176
+ await db.init();
2177
+ await adoptNativeEntities(db); // merge + label existing files/secrets as native
2178
+ const bindings = await listNativeBindings(db); // [{ entity, tableName, origin }]
2179
+ ```
2180
+
2153
2181
  **Machine-local user config at `~/.lattice/` (v1.12+).** A small set of files outside any Lattice DB so a user's identity, encrypted master key, saved cloud-DB credentials, and per-team bearer tokens survive switching projects:
2154
2182
 
2155
2183
  | File | Purpose |
@@ -2220,9 +2248,19 @@ lattice teams join \
2220
2248
 
2221
2249
  The cloud rejects redemption if the caller's claimed email doesn't match the invitation's `invitee_email` (case-insensitive). Sharing an invite token in a public channel is therefore safe — only the addressee can redeem it.
2222
2250
 
2223
- **Other subcommands** (`lattice teams help` for the full list): `list`, `members`, `leave`, `destroy`, `share`, `unshare`, `shared`, `sync`, `link`, `unlink`, `pull`, `push`, `status`.
2251
+ **Other subcommands** (`lattice teams help` for the full list): `list`, `members`, `leave`, `destroy`, `share`, `unshare`, `shared`, `sync`, `link`, `unlink`, `pull`, `push`, `status`, `dlq`.
2252
+
2253
+ **Dead-letter queue (v1.15+).** A pulled change envelope that fails to apply (e.g. it arrived before the row/table it depends on), and any non-owner-overwrite divergence notice, lands in `__lattice_team_dlq`. Inspect and recover it instead of losing it behind the pull cursor:
2254
+
2255
+ ```bash
2256
+ lattice teams dlq list --team <name> # show entries (op, target, error)
2257
+ lattice teams dlq retry --team <name> [--id <id>] # replay; a late dependency now applies cleanly
2258
+ lattice teams dlq purge --team <name> [--id <id>] # discard without applying
2259
+ ```
2224
2260
 
2225
- **Same flows from the GUI.** The local `lattice gui` Project Config view drives the entire teams lifecycle create / join, invite by email, share tables, link rows, see sync status. Identity (display name + email) comes from `~/.lattice/identity.json` and is prefilled in every modal.
2261
+ **Per-table ownership + opt-in sharing (v1.14+).** Team members share one physical Postgres, so visibility is enforced at the app layer via a `__lattice_object_owners` table: each table records its creator, and a user sees only the tables they own plus tables explicitly shared to the team. The native `files`/`secrets` objects are owned by the database creator and private by default. Sharing is an explicit, owner-only action (not a side effect of creating a table). The filter gates API access, not just the display.
2262
+
2263
+ **Same flows from the GUI (v1.14+).** The local `lattice gui` drives the entire teams lifecycle from **Database Settings**: rename (owner-only), invite by email (owner-only), the inline Members list (the owner is always shown as `creator`; your own row offers Leave/Destroy; non-owners can't kick), share/unshare from the Data Model, and sync status. Member admin is resolved from `GET /api/dbconfig` against the active cloud DB, so it works even when the team cloud itself is the active database. Identity (display name + email) comes from `~/.lattice/identity.json` and is locked in the Join modal. Leaving a team removes the local config + credential and switches you to another database.
2226
2264
 
2227
2265
  **Joining via the GUI is one click (v1.13.7+).** When you click "Join via invite" and the redeem succeeds, the team's cloud URL is automatically saved as a switchable database credential and a sibling YAML config is written to your project directory. The new entry shows up in the database dropdown as `<team-name>.config`. Clicking it opens the SPA with the team's shared tables already populated — no YAML editing, no `db.define()` calls.
2228
2266
 
@@ -2593,14 +2631,40 @@ interface AutoUpdateResult {
2593
2631
 
2594
2632
  ## Telemetry
2595
2633
 
2596
- `latticesql` installs and runs with **zero telemetry network calls**. No postinstall pings, no runtime beacons, no anonymous-ID files written to your home directory. The only outbound requests the package ever makes are the explicit, caller-invoked `checkForUpdate()` / `autoUpdate()` calls to `registry.npmjs.org` — and you only get those if you call them.
2634
+ `latticesql` includes [Scarf](https://scarf.sh) install analytics so we can understand how the package is used in the wild what versions are running, on what platforms, at roughly what scale. This signal is what lets us prioritize fixes, deprecations, and new features against real usage instead of guesswork.
2635
+
2636
+ **What is sent — once, at `npm install` time, by the `@scarf/scarf` postinstall hook:**
2597
2637
 
2598
- To understand who's using the package we rely on two passive signals that require no instrumentation in your install or your runtime:
2638
+ - Package name + version (e.g. `latticesql@1.13.6`)
2639
+ - Node.js version, OS, CPU architecture
2640
+ - A coarse, non-identifying hash derived from the install host (Scarf's default — used for deduplication, not identification)
2641
+ - The public IP of the install request (visible to any HTTPS endpoint; not stored long-term by Scarf)
2599
2642
 
2600
- - **A 1×1 tracking pixel** embedded at the bottom of this README, served by [Scarf](https://scarf.sh). It fires when this README is rendered (e.g. on the npmjs.com package page). It sees only what any HTTPS image request sees — the requester's user-agent and IP, which Scarf de-identifies into coarse geo/company aggregates. Block it with any standard ad-blocker, or use a privacy-focused npm UI that doesn't render images, and Scarf sees nothing.
2601
- - **Public npm download counts**, queried by us from npm's own [downloads API](https://api.npmjs.org/downloads/range/last-month/latticesql). These are the same counts npmjs.com itself publishes — no per-user data, just aggregate package downloads.
2643
+ **What is NOT sent:**
2644
+
2645
+ - No data from your application code, schemas, rows, or query strings
2646
+ - No environment variables, file paths, hostnames, or usernames
2647
+ - No runtime telemetry — `latticesql` makes zero outbound telemetry calls after install. The only network requests it makes at runtime are the explicit `checkForUpdate()` / `autoUpdate()` calls to `registry.npmjs.org`, which you opt into by calling them.
2648
+
2649
+ **How to opt out** — any one of these suppresses the install ping:
2650
+
2651
+ ```bash
2652
+ # Per-install (recommended for CI):
2653
+ SCARF_ANALYTICS=false npm install latticesql
2602
2654
 
2603
- Neither signal touches your code, your data, your environment, or your install pipeline. If your network blocks `static.scarf.sh`, the README still renders (image alt text is empty); installs and runtime behavior are identical.
2655
+ # Or, project-wide (add to .npmrc):
2656
+ scarf-analytics=false
2657
+
2658
+ # Or, the cross-tool standard:
2659
+ DO_NOT_TRACK=1 npm install latticesql
2660
+
2661
+ # Or, disable all postinstall scripts entirely:
2662
+ npm install latticesql --ignore-scripts
2663
+ ```
2664
+
2665
+ Opting out has no effect on functionality — the package works identically. The Scarf postinstall is a fire-and-forget HTTPS ping with a short timeout; even when enabled it cannot fail your install.
2666
+
2667
+ See Scarf's own [privacy documentation](https://docs.scarf.sh) for the upstream policy.
2604
2668
 
2605
2669
  ---
2606
2670
 
@@ -2619,7 +2683,3 @@ See [CHANGELOG.md](./CHANGELOG.md) for the full history.
2619
2683
  ## License
2620
2684
 
2621
2685
  [Apache 2.0](./LICENSE) — includes explicit patent grant (Section 3).
2622
-
2623
- <!-- Scarf README pixel — see § Telemetry above for what it does and how to block it. -->
2624
-
2625
- ![](https://static.scarf.sh/a.png?x-pxid=bcbfdaa1-ef11-455a-bcc8-3ec215709da4)