New:Socket for Asana Is Now Available.Learn more
Get Started

@ultimat3/query

Package Overview
Dependencies
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ultimat3/query - npm Package Compare versions

Comparing version
9.0.0
to
10.0.0
+6
-6
package.json
{
"name": "@ultimat3/query",
"version": "9.0.0",
"version": "10.0.0",
"description": "The query primitive: a policy-checked read, optionally live, with cursor pagination and an incremental matcher",

@@ -34,8 +34,8 @@ "license": "MIT",

"dependencies": {
"@ultimat3/cache": "9.0.0",
"@ultimat3/core": "9.0.0",
"@ultimat3/http": "9.0.0",
"@ultimat3/policy": "9.0.0",
"@ultimat3/schema": "9.0.0"
"@ultimat3/cache": "10.0.0",
"@ultimat3/core": "10.0.0",
"@ultimat3/http": "10.0.0",
"@ultimat3/policy": "10.0.0",
"@ultimat3/schema": "10.0.0"
}
}

@@ -5,3 +5,8 @@ /** Every failure @ultimat3/query can produce, one subclass per stable code. */

const docs = (code: string): string => `https://ultimate.dev/errors/${code}`;
// No `docs:` on the classes below, with one exception noted at `QueryRequestFailedError`.
// `UltimateError` fills it from `describeErrorCode(code).docs`, which is `@ultimat3/core`'s
// `ERROR_DOCS_URL` — one page for every code, never one per code, because `wiki/` is the
// framework's only public documentation surface and a code lives there in a TABLE ROW, which has
// no anchor. The `https://ultimate.dev/errors/<code>` links this file built until 9.x answered
// 404, host included, on every read this package has ever refused.

@@ -80,3 +85,2 @@ /** One class, one code: core owns the cursor codec, so core owns `X_CURSOR_INVALID`. */

fix: `x policy explain ${query} --json # shows which clause decided and why`,
docs: docs(code),
});

@@ -94,3 +98,2 @@ this.denial = denial;

fix: "call registerQueries(await import('./live')) at boot, before serving reads",
docs: docs('X_QUERY_UNREGISTERED'),
});

@@ -112,3 +115,2 @@ }

fix: "declare it as `export const name = query({ input, policy, sql })` from '@ultimat3/query'",
docs: docs('X_QUERY_FOREIGN'),
});

@@ -132,3 +134,2 @@ }

fix: 'flatten the key into scalar arguments (status: t.string, limit: t.number), spell an absent value as `.optional()` rather than `t.nullable(...)`, or declare it as an action() if it really needs a JSON body',
docs: docs('X_QUERY_INPUT_UNENCODABLE'),
});

@@ -156,3 +157,2 @@ }

fix: 'set `cache: { ttlMs: 60_000 }` to a positive whole number of milliseconds, or drop ttlMs to take the read cache default',
docs: docs('X_QUERY_CACHE_TTL_INVALID'),
meta: { ttlMs },

@@ -169,3 +169,2 @@ });

fix: 'rename one export — query names are globally unique: x queries list --json',
docs: docs('X_QUERY_DUPLICATE'),
});

@@ -189,3 +188,2 @@ }

fix: `add \`policy: can('<resource>:<verb>')\` to the query() that exports "${name}" — a permission your definePermissions() call declares, never the query's own name — or \`allow('<resource>:<verb>')\` to state that the read is public`,
docs: docs('X_QUERY_POLICY_MISSING'),
});

@@ -207,3 +205,2 @@ }

fix: `edit \`deprecated: { ${field}: … }\` on ${name} to an ISO-8601 instant — e.g. '2026-12-31T23:59:59Z'`,
docs: docs('X_QUERY_DEPRECATION_INVALID'),
meta: { query: name, field, value },

@@ -226,3 +223,2 @@ });

fix: `return the primary key from the query's sql: db.${entity ?? 'rows'}.select({ id: true, … })`,
docs: docs('X_QUERY_NOT_PAGEABLE'),
});

@@ -250,3 +246,2 @@ }

fix: 'order by a scalar column — .orderBy("createdAt") or .orderBy("id") — and project the composite value into the row instead',
docs: docs('X_CURSOR_VALUE_UNSUPPORTED'),
});

@@ -263,3 +258,2 @@ }

fix: `set \`live: false\` and poll, or reshape the query to equality filters + orderBy + limit`,
docs: docs('X_MATCHER_UNSUPPORTED'),
});

@@ -275,3 +269,2 @@ }

fix: `x queries describe ${name} --json # prints the expected input schema`,
docs: docs('X_INPUT_INVALID'),
});

@@ -299,2 +292,3 @@ }

const code = text(problem.code) ?? 'X_RPC_FAILED';
const served = text(problem.docs);
super({

@@ -306,3 +300,7 @@ code,

`check the gateway in front of the app, then: x queries describe ${name} --json`,
docs: text(problem.docs) ?? docs(code),
// The one place a `docs` is passed: the SERVER's, re-thrown verbatim beside the cause and
// fix it came with. An app that documents its own codes somewhere else is entitled to say
// so, and overwriting it with this framework's page would bury the answer. Absent, the
// constructor resolves `ERROR_DOCS_URL` like every other error here.
...(served === undefined ? {} : { docs: served }),
});

@@ -309,0 +307,0 @@ }

@@ -18,3 +18,3 @@ /**

import type { QueryShape, SeekKey } from './shape';
import { seekKeyOf } from './shape';
import { compareRows, seekKeyOf, totalOrder } from './shape';
import type { SqlSource } from './source';

@@ -74,3 +74,3 @@ import { isAfterKey } from './source';

const executed = await source.execute();
const scoped = base.seek === undefined ? sliceAfter(executed, after, shape) : executed;
const scoped = base.seek === undefined ? inTotalOrder(executed, after, shape) : executed;
// The source came from this query's own `sql()`, so its rows are TRow.

@@ -97,4 +97,16 @@ const rows = scoped.slice(0, args.first) as unknown as readonly TRow[];

* silent restart, which is the failure keyset pagination exists to make impossible.
*
* **It SORTS first, and that half was missing.** `isAfterKey` breaks a tie on the declared keys by
* `id` — it has to, or the cut is not a position at all — while a foreign `SqlSource` ordered its
* rows by the DECLARED keys alone. So a tie group arrived in an order the cut does not describe,
* and the cut fell in the middle of it: page one served `a(10), d(20)`, the cursor named `(20, d)`,
* and rows `b(20)` and `c(20)` matched no page in the listing. Rows VANISH — silently, and only
* where two rows share a sort key.
*
* `Builder` has always done this: `execute()` sorts by `servedOrder()`, which appends `id` once a
* read asked to be seekable. This is that rule applied to the path a `Builder` does not take, and
* it is the ordering the cursor arithmetic already assumes on both sides. Reachable only for a
* hand-written `SqlSource` with no `seek()` — the branch this whole function exists for.
*/
function sliceAfter(
function inTotalOrder(
rows: readonly object[],

@@ -104,4 +116,6 @@ after: SeekKey | null,

): readonly object[] {
if (after === null) return rows;
return rows.filter((row) => isAfterKey(row, after, shape.orderBy));
const keys = totalOrder(shape.orderBy);
const ordered = [...rows].sort((left, right) => compareRows(left, right, keys));
if (after === null) return ordered;
return ordered.filter((row) => isAfterKey(row, after, shape.orderBy));
}