@lotics/app-sdk 0.102.0 → 0.102.1

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.
Files changed (2) hide show
  1. package/docs/queries.md +17 -38
  2. package/package.json +1 -1
package/docs/queries.md CHANGED
@@ -234,7 +234,7 @@ supports the full §5 operator matrix **except**: `traversal` nodes, `locked`, a
234
234
  `record_id` works on any row-level derived query (rejected over a `group`, which has no row
235
235
  identity). When a `filter` directly wraps a `union` of `project(from_table)` arms and every
236
236
  condition targets bare passthrough columns, the engine pushes the predicate into each arm's
237
- source filter automatically (making it index-servable); otherwise it evaluates post-union.
237
+ source filter automatically, where it runs once per record; otherwise it evaluates post-union.
238
238
 
239
239
  ### `join` — combine two row sets
240
240
 
@@ -545,9 +545,9 @@ Negation-shaped inner operators (`has_none_of`, `not_equals`, `is_none_of`,
545
545
 
546
546
  Traversals are **source-layer only** (rejected on derived columns — push them into
547
547
  `from_table.filter`). Each hop reaches the linked rows by primary key from the ids in the link
548
- cell. Under an OR beside column conditions a traversal runs once per row and takes the column
549
- conditions off their indexes with it; give it a `union` arm of its own when the other arms must
550
- stay index-served. Two access classes:
548
+ cell. Under an OR beside column conditions a traversal runs once per row and takes a membership
549
+ condition off its index with it; give it a `union` arm of its own when the other arms must keep
550
+ theirs. Two access classes:
551
551
 
552
552
  - **Self-scoped** — inner operator `is_current_member` / `is_not_current_member`: tests only
553
553
  the viewer's own membership on the linked row, leaks nothing, and is exempt from the linked
@@ -871,7 +871,7 @@ ordering and scoping into the template or its params.
871
871
  Any other execution failure returns a generic `query execution failed` (the real error — which
872
872
  may embed SQL — is server-logged only). Bind-time and validation errors are always specific.
873
873
 
874
- ### Index reality, in plain terms
874
+ ### What a query reads, in plain terms
875
875
 
876
876
  Records are stored partitioned by workspace, so a query reads only its own workspace's slice; a
877
877
  `from_table` narrows that slice to the table's rows, and within them:
@@ -879,39 +879,18 @@ Records are stored partitioned by workspace, so a query reads only its own works
879
879
  - **GIN-served (fast at any size):** *positive* exact-membership filters at the **source
880
880
  layer** — `select`/`select_member`/`select_record_link` `has_any_of` / `has_all_of`, and
881
881
  `is_current_member`. These compile to containment the JSONB GIN index serves.
882
- - **Trigram-served:** `from_table.search` (§7).
883
- - **B-tree-served (automatic for bound queries):** text `equals`, number and date
884
- comparisons (exact and range), `from_table.sort` fields, and a one-key `sort` node over a
885
- number or date field the rows pass unchanged — for fields referenced in a **bound named
886
- query's template**. The platform provisions a partial expression index per referenced
887
- field automatically: built online whenever `set_app_query` or `set_app_queries` binds a query, re-synced daily,
888
- capped at 8 per table (fields past the cap fall back to the scan tier, with a server WARN).
889
- A `{{params.…}}` value hole doesn't change this — the field key is static in the template,
890
- so it still gets its index. Index-seek speed at any table size once provisioned.
891
- - **Table scan (linear in table size):** everything else — text `contains`, negations
892
- (`has_none_of`, `is_none_of`, `not_*`), emptiness, files predicates, and predicates/sorts on
893
- fields that appear **only** in the runtime `filter`/`sort` options rather than the bound
894
- template. Fine on thousands of rows; on very large tables these dominate latency and are the
895
- usual timeout cause.
896
-
897
- **Filter shape drives latency.** Equality/range/sort predicates in the bound template are
898
- index-served; lead with those or a GIN-served membership filter / `search`, and let scan-shaped
899
- predicates refine the already-narrowed set. Derived-layer filters run over the subquery result
900
- (no index), so **filter at the source layer whenever the field exists there** — the runtime
901
- filter is for caller-driven refinement, not for the main cut (a runtime-only field gets no
902
- managed index). (The engine pushes eligible filter-over-union predicates down automatically,
903
- but don't rely on that for other shapes.)
904
-
905
- **A sorted page reads in its sort index — declare the order it is read in.** A page sorted by one
906
- column that passes a number or date field unchanged (a bare `project` source with no `type`
907
- override, an `unpivot` passthrough, or a row column that reads the same field in every row) reads
908
- the table in that field's sort index and stops at the page, instead of reading every row and
909
- sorting. Over a `union`, each arm is paged this way on its own and the arms are merged, so a
910
- register over several tables or `unpivot` sides is fast at any size — don't hand-split it into
911
- per-table queries you merge client-side. The index exists when a bound template sorts that field
912
- in the same direction and blank position: give the query its default order as a top-level `sort`,
913
- and a page the app sorts the same way is served. A text column, a computed, literal or cast column,
914
- a sort on more than one key, and a cursor (`keyset`) page sort every row.
882
+ - **Trigram-served:** `from_table.search` (§7), within the one table.
883
+ - **Every row of the table:** everything else — text equality and `contains`, number and date
884
+ comparisons, sorts, negations, emptiness, files predicates. Fine on thousands of rows; the cost
885
+ grows with the table, and on very large tables these dominate latency and are the usual
886
+ timeout cause.
887
+
888
+ **Filter shape drives latency.** Lead with a GIN-served membership filter or `search`, which
889
+ narrow the table before its rows are read, and let the other conditions refine that set.
890
+ Derived-layer filters run over the subquery result, after any `unpivot` fan-out, so **filter at
891
+ the source layer whenever the field exists there** — the runtime filter is for caller-driven
892
+ refinement, not for the main cut. (The engine moves eligible filter-over-union predicates into
893
+ each arm's source automatically, but don't rely on that for other shapes.)
915
894
 
916
895
  **An aggregate arm you cannot filter costs its whole table, every execution.** A `join` whose right
917
896
  side is a `group` over entire tables, keyed on a value the LEFT side supplies at run time, has no
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/app-sdk",
3
- "version": "0.102.0",
3
+ "version": "0.102.1",
4
4
  "description": "The SDK a Lotics custom-code app reads and writes through \u2014 typed hooks over the host bridge, cell readers, mount() and AppRouter",
5
5
  "type": "module",
6
6
  "exports": {