@omgbase/oqx 0.8.0 → 0.10.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
@@ -143,9 +143,32 @@ oqx`employee: $value from ${people} where name == "Bob"`; // [{ employee
143
143
  ```
144
144
 
145
145
  Inside a nested block `$value` is the inner item; the enclosing row is `^$value`
146
- (§3). At the root scope (before any row) it is absent. Plain objects are **not**
147
- iterable through `$value` — a record stays a record; converting one to a
148
- collection of entries is a separate, explicit step (`entries()`, planned).
146
+ (§3). At the root scope (before any row) it is absent.
147
+
148
+ **`entries(x)` and `$key` — records to collections, explicitly.** A plain object
149
+ is **not** iterable: `from ${obj}` is one row (the object). `entries(obj)`
150
+ converts it into a collection of entries, and inside such a scope the current
151
+ item is the property's **value** — `$value` and bare names read it — while
152
+ **`$key`** is the property's key:
153
+
154
+ ```js
155
+ const settings = { theme: "dark", fontSize: 14, autosave: true };
156
+ oqx`key: $key, value: $value from entries(${settings})`;
157
+ // [{ key: "theme", value: "dark" }, { key: "fontSize", value: 14 }, { key: "autosave", value: true }]
158
+ oqx`$key values from entries(${settings}) where $value != "dark"`; // ["fontSize", "autosave"]
159
+
160
+ const flags = { beta: { on: true }, legacy: { on: false } };
161
+ oqx`$key values from entries(${flags}) where on`; // ["beta"] (bare `on` reads the value)
162
+
163
+ oqx`name, on: entries(prefs) collect { $key values where $value } from ${users}`; // as a nested receiver
164
+ oqx`name from ${users} where entries(prefs) exists { where $key == "dark" && $value }`;
165
+ ```
166
+
167
+ `entries(array)` yields numeric index keys, a `Map` yields its entries, and
168
+ absence/scalars yield nothing. `$key` exists **only** in an entry scope — an
169
+ ordinary row or array element has no implicit index; `entries(arr)` is how you
170
+ ask for one. As a plain value (not a source) `entries(x)` is an array of
171
+ `{ key, value }` records.
149
172
 
150
173
  ### 3. Predicates (where)
151
174
 
@@ -257,7 +280,7 @@ like a row property.
257
280
 
258
281
  Methods on a value: `contains`, `startsWith`, `endsWith`, `matches` (regex),
259
282
  `size`, `lower`, `upper`. Free functions: `list(x)` (coerce to an array), `size(x)`,
260
- `has(x)`.
283
+ `has(x)`, `range(s)` (§3), `entries(x)` (§2).
261
284
 
262
285
  ```js
263
286
  oqx`name from ${people} where title.startsWith("Eng")`; // → Bob
@@ -266,12 +289,13 @@ oqx`name from ${people} where title.lower() == "director"`; // → Alice
266
289
 
267
290
  ### 5. Consumers
268
291
 
269
- A consumer shapes a result set. There are five:
292
+ A consumer shapes a result set. There are six:
270
293
 
271
294
  | Consumer | Returns |
272
295
  | --------- | ------------------------------ |
273
296
  | `collect` | an array (the default) |
274
- | `exists` | a boolean |
297
+ | `exists` | a boolean — one or more rows |
298
+ | `none` | a boolean — zero rows (the complement of `exists`) |
275
299
  | `count` | a number |
276
300
  | `first` | one record, or `null` |
277
301
  | `single` | one record, or `null`; throws if more than one matches |
@@ -301,8 +325,14 @@ cardinality:
301
325
  ```js
302
326
  oqx`name from ${people} where jobs exists { !end }`; // has a current job → Bob, Carol
303
327
  oqx`name from ${people} where jobs count {} >= 2`; // ≥2 jobs → Bob, Alice
328
+ oqx`name from ${people} where jobs none { where end }`; // no past job → Carol
304
329
  ```
305
330
 
331
+ `none { … }` is exactly `!… exists { … }`, kept as its own word because the
332
+ cardinality is the point. It is also how you say "every": *all* members active is
333
+ `members none { where !active }` — there is deliberately no `all { … }`, whose
334
+ block would have to mean something different from every other consumer's.
335
+
306
336
  In a projection, `collect` yields a nested array; `first` / `single` yield one
307
337
  nested record:
308
338
 
@@ -383,6 +413,26 @@ oqx`name from ${people} where city == "NYC" order by age desc`;
383
413
  // [{ name: "Bob" }, { name: "Carol" }]
384
414
  ```
385
415
 
416
+ ### 8b. Bounding: `limit` / `offset`
417
+
418
+ `limit N` and `offset N` bound the row set **after** `where`, `order by`, and
419
+ `distinct`, and **before** the consumer reduces it — so they mean the same thing
420
+ under every consumer: `count { … limit 5 }` is at most 5, `first { … offset 1 }`
421
+ is the second row, `exists { offset 2 }` asks for a third. They work at the top
422
+ level and inside any block, and `N` may be a literal, a `${…}` binding, or an
423
+ outer reference — `limit ^n` reads the enclosing row's `n`, as `^` does everywhere
424
+ inside `{ … }`; it must be a non-negative integer.
425
+
426
+ ```js
427
+ oqx`name values from ${people} order by age desc limit 2`; // ["Alice", "Bob"]
428
+ oqx`name values from ${people} order by age desc offset 1 limit 1`; // ["Bob"]
429
+ oqx`name, latest: jobs collect { employer values order by start desc limit 1 } from ${people}`;
430
+ oqx`name from ${people} where jobs exists { offset 1 }`; // has a second job
431
+ ```
432
+
433
+ A storage adapter that pushes the whole query may translate them to SQL
434
+ `LIMIT`/`OFFSET`; the shipped `SqliteTable` leaves them to the residual.
435
+
386
436
  ### 9. Recursion: `follow`
387
437
 
388
438
  `follow <relation>` turns a query into a bounded recursive traversal: the `where`
@@ -435,17 +485,21 @@ your relation returns fresh objects rather than shared references.
435
485
  name, alias: expr, nested: rel collect { … } projection (select optional)
436
486
  from ${source} source collection
437
487
  where a == b && rel exists { … } || !c predicate tree + nested ops
488
+ where rel none { … } zero rows (≡ !rel exists { … }; "all" = none over the complement)
438
489
  where x in lo..hi / lo...hi / ..hi / lo.. range membership (incl. / excl. / open-ended)
439
490
  where x in range(field) coerce a string field to a range, then test coverage
440
491
  name the CURRENT row's field only (never climbs)
441
492
  $value the current item itself (a scalar row, or the whole object)
493
+ from entries(obj) … $key / $value a record's properties as a collection (key + value; bare names read the value)
442
494
  <expr> values scalar projection: the value, not a { name: value } record
443
495
  ^name / ^^name read an enclosing row's field (exactly N scopes out); ^$value = the enclosing row
444
496
  ^name: expr / ^^name: expr lift/export a value N scopes out (flatten-append)
445
497
  ^rel collect { … } / ^^root exists { … } nested consumer over an enclosing row's relation / a named root
498
+ entries(rel) exists { … } a free-function call may be a receiver
446
499
  order by expr desc, expr2 ordering
500
+ limit n / offset n bound the row set (after where/order/distinct, before the consumer)
447
501
  follow rel { where … frontier … depth n by … } recursion ($depth/$stop/$leaf/$frontier)
448
- ${source} <collect|exists|count|first|single> { … } whole-query consumer
502
+ ${source} <collect|exists|none|count|first|single> { … } whole-query consumer
449
503
  ```
450
504
 
451
505
  ## Data context: string queries and named roots
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/adapters/sqlite.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAQ,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGxD,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,6EAA6E;IAC7E,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,+EAA+E;IAC/E,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;CACjD;AAOD,qBAAa,WAAY,YAAW,YAAY;IAC9C,OAAO,CAAC,EAAE,CAAe;IACzB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,IAAI,CAAqB;gBAErB,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB;IAOrE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,GAAG,IAAI;IAsB3D,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,MAAM;CAWf"}
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/adapters/sqlite.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAQ,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGxD,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,6EAA6E;IAC7E,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,+EAA+E;IAC/E,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;CACjD;AAOD,qBAAa,WAAY,YAAW,YAAY;IAC9C,OAAO,CAAC,EAAE,CAAe;IACzB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,IAAI,CAAqB;gBAErB,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB;IAOrE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,GAAG,IAAI;IAwB3D,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,MAAM;CAWf"}
@@ -36,10 +36,12 @@ export class SqliteTable {
36
36
  const { pushed, residual } = partitionPushable(query.where, (e) => this.translatable(e));
37
37
  const sqlParams = [];
38
38
  const whereSql = pushed.map((e) => this.translate(e, params, sqlParams)).join(" AND ");
39
- // A LIMIT is only safe when nothing is left to filter in-memory and the
40
- // result is unordered (first/single are "some row" without an order by).
39
+ // A LIMIT is only safe when nothing is left to filter in-memory, the result
40
+ // is unordered (first/single are "some row" without an order by), and the
41
+ // query carries no limit/offset of its own (the residual applies those, so
42
+ // a SQL LIMIT underneath would starve them).
41
43
  let tail = "";
42
- if (!residual && !query.orderBy) {
44
+ if (!residual && !query.orderBy && !query.limit && !query.offset) {
43
45
  if (query.consumer === "first")
44
46
  tail = " LIMIT 1";
45
47
  else if (query.consumer === "single")
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/adapters/sqlite.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/adapters/sqlite.ts"],"names":[],"mappings":"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"}
package/dist/ast.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- /** Query consumers — how a (sub)query's row set is shaped. */
2
- export type Consumer = "collect" | "exists" | "count" | "first" | "single";
1
+ /** Query consumers — how a (sub)query's row set is shaped. `none` is the
2
+ * zero-cardinality complement of `exists` (true iff the block yields no rows). */
3
+ export type Consumer = "collect" | "exists" | "none" | "count" | "first" | "single";
3
4
  /** Comparison operators usable in a `count { … } <op> <int>` test. */
4
5
  export type RelOp = "==" | "!=" | "<" | "<=" | ">" | ">=";
5
6
  /** Scalar value/predicate expression, evaluated against a row scope + bindings. */
@@ -106,6 +107,14 @@ export interface Subquery {
106
107
  * row's result itself rather than being wrapped in a `{ name: value }` record,
107
108
  * so `name values` yields `["Bob", …]` and `$value values` yields the rows. */
108
109
  values?: boolean;
110
+ /** `limit N` / `offset N` — bound the row set AFTER where/order/distinct and
111
+ * BEFORE the consumer reduces it, so `count { … limit 5 }` is at most 5 and
112
+ * `first { … offset 1 }` is the second row. Each is a value expression
113
+ * (a literal, a `${…}` binding, or an outer reference) read as part of the
114
+ * block — `^n` is the enclosing row's field, as everywhere inside `{ … }` —
115
+ * and must yield a non-negative integer. */
116
+ limit?: Expr;
117
+ offset?: Expr;
109
118
  }
110
119
  /** The where-clause boolean tree: OQX owns &&/||/!/grouping so consumer ops
111
120
  * (invisible to the scalar evaluator) compose with scalar predicates. */
@@ -135,5 +144,8 @@ export interface Query {
135
144
  distinct?: boolean;
136
145
  /** `values` — scalar projection mode (see Subquery). */
137
146
  values?: boolean;
147
+ /** `limit N` / `offset N` (see Subquery); evaluated at the root scope. */
148
+ limit?: Expr;
149
+ offset?: Expr;
138
150
  }
139
151
  //# sourceMappingURL=ast.d.ts.map
package/dist/ast.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"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"}
package/dist/engine.d.ts CHANGED
@@ -7,6 +7,9 @@ export type OqxResult = {
7
7
  } | {
8
8
  consumer: "exists";
9
9
  exists: boolean;
10
+ } | {
11
+ consumer: "none";
12
+ none: boolean;
10
13
  } | {
11
14
  consumer: "count";
12
15
  count: number;
@@ -25,9 +28,11 @@ export declare class InMemoryEngine implements Engine {
25
28
  private ctx;
26
29
  constructor(context?: DataContext);
27
30
  run(query: Query, bindings?: readonly unknown[]): OqxResult;
31
+ private boundOf;
28
32
  private matches;
29
33
  private rowsOf;
30
34
  private child;
35
+ private enter;
31
36
  private runFollow;
32
37
  private dedupByProjection;
33
38
  private followWalk;
@@ -36,6 +41,7 @@ export declare class InMemoryEngine implements Engine {
36
41
  private itemValue;
37
42
  private evalWhere;
38
43
  private evalWhereOp;
44
+ private opRows;
39
45
  private anyMatch;
40
46
  private iterMatchRows;
41
47
  private matchRows;
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,KAAK,EACN,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAOhD,yEAAyE;AACzE,MAAM,MAAM,SAAS,GACjB;IAAE,QAAQ,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,EAAE,CAAA;CAAE,GACxC;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACvC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,GAC1C;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,CAAC;AAEhD,6EAA6E;AAC7E,MAAM,WAAW,MAAM;IACrB,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,CAAC;CAC5D;AAsBD,qBAAa,cAAe,YAAW,MAAM;IAC3C,OAAO,CAAC,GAAG,CAAc;gBAEb,OAAO,GAAE,WAAkC;IAIvD,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,GAAE,SAAS,OAAO,EAAO,GAAG,SAAS;IAsC/D,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,KAAK;IAMb,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,iBAAiB;IA2BzB,OAAO,CAAC,UAAU;IA6DlB,OAAO,CAAC,KAAK;IAcb,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,WAAW;IA6BnB,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAE,aAAa;IAStB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,gBAAgB;IAyBxB,OAAO,CAAC,UAAU;IAwBlB,OAAO,CAAC,QAAQ;IAuDhB,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,QAAQ;CAYjB;AA+ED,8EAA8E;AAC9E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,CAG9F"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,KAAK,EACN,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAOhD,yEAAyE;AACzE,MAAM,MAAM,SAAS,GACjB;IAAE,QAAQ,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,EAAE,CAAA;CAAE,GACxC;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACvC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GACnC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,GAC1C;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,CAAC;AAEhD,6EAA6E;AAC7E,MAAM,WAAW,MAAM;IACrB,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,CAAC;CAC5D;AA4BD,qBAAa,cAAe,YAAW,MAAM;IAC3C,OAAO,CAAC,GAAG,CAAc;gBAEb,OAAO,GAAE,WAAkC;IAIvD,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,GAAE,SAAS,OAAO,EAAO,GAAG,SAAS;IAmD/D,OAAO,CAAC,OAAO;IAef,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,KAAK;IASb,OAAO,CAAC,KAAK;IAab,OAAO,CAAC,SAAS;IAejB,OAAO,CAAC,iBAAiB;IA2BzB,OAAO,CAAC,UAAU;IA6DlB,OAAO,CAAC,KAAK;IAeb,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,WAAW;IAiCnB,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAE,aAAa;IAStB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,gBAAgB;IA2BxB,OAAO,CAAC,UAAU;IAwBlB,OAAO,CAAC,QAAQ;IAwDhB,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,QAAQ;CAYjB;AAyFD,8EAA8E;AAC9E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,CAG9F"}
package/dist/engine.js CHANGED
@@ -13,9 +13,11 @@
13
13
  // fall-through from an inner scope to an outer one — see `resolveIn`.
14
14
  import { DefaultContext } from "./context.js";
15
15
  import { OqxError } from "./errors.js";
16
- import { equals, relate, arith, membership, truthy, toNumber, compareForSort, makeRange, } from "./semantics.js";
16
+ import { equals, relate, arith, membership, truthy, toNumber, compareForSort, makeRange, isEntry, } from "./semantics.js";
17
17
  const RECUR = new Set(["$depth", "$stop", "$leaf", "$frontier", "$ordinal"]);
18
+ const KEY = "$key";
18
19
  const HARD_DEPTH_CAP = 8;
20
+ const UNBOUNDED = { offset: 0, limit: null };
19
21
  export class InMemoryEngine {
20
22
  ctx;
21
23
  constructor(context = new DefaultContext()) {
@@ -27,29 +29,39 @@ export class InMemoryEngine {
27
29
  for (const proj of query.from) {
28
30
  rows = rows.flatMap((r) => this.rowsOf(this.evalExpr(proj, this.child(r, root))));
29
31
  }
32
+ const bound = this.boundOf(query, root);
30
33
  if (query.follow)
31
- return this.runFollow(query, rows, root);
34
+ return this.runFollow(query, rows, root, bound);
32
35
  // Consumer-directed short-circuits (skipped under `distinct`, which must
33
- // materialize + dedup by projection before reducing).
34
- if (!query.distinct && query.consumer === "exists") {
35
- for (const r of rows)
36
- if (this.matches(query.where, r, root))
37
- return { consumer: "exists", exists: true };
38
- return { consumer: "exists", exists: false };
39
- }
40
- if (!query.distinct && query.consumer === "count") {
41
- let count = 0;
42
- for (const r of rows)
43
- if (this.matches(query.where, r, root))
44
- count++;
45
- return { consumer: "count", count };
36
+ // materialize + dedup by projection before reducing). Counting never
37
+ // materializes rows; exists/none stop as soon as the bound is known to be
38
+ // non-empty (the (offset+1)th match — or the first, when unbounded).
39
+ if (!query.distinct && (query.consumer === "exists" || query.consumer === "none" || query.consumer === "count")) {
40
+ const need = query.consumer === "count" ? Infinity : bound.offset + 1;
41
+ let n = 0;
42
+ for (const r of rows) {
43
+ if (this.matches(query.where, r, root)) {
44
+ n++;
45
+ if (n >= need)
46
+ break;
47
+ }
48
+ }
49
+ const m = boundedCount(n, bound);
50
+ if (query.consumer === "count")
51
+ return { consumer: "count", count: m };
52
+ if (query.consumer === "exists")
53
+ return { consumer: "exists", exists: m > 0 };
54
+ return { consumer: "none", none: m === 0 };
46
55
  }
47
- const cap = !query.orderBy && !query.distinct && query.consumer === "first" ? 1
48
- : !query.orderBy && !query.distinct && query.consumer === "single" ? 2
49
- : Infinity;
56
+ // first/single over an unordered, non-distinct set need only the rows up to
57
+ // the bound: offset + 1 (first) / offset + 2 (single, to detect a second).
58
+ const want = query.consumer === "first" ? 1 : query.consumer === "single" ? 2 : Infinity;
59
+ const cap = !query.orderBy && !query.distinct && want !== Infinity
60
+ ? bound.offset + Math.min(want, bound.limit ?? want)
61
+ : Infinity;
50
62
  let kept = [];
51
63
  for (const r of rows) {
52
- const s = { row: r, parent: root, bindings, lifts: {} };
64
+ const s = this.enter(r, root, { lifts: {} });
53
65
  if (!query.where || this.evalWhere(query.where, s)) {
54
66
  kept.push(s);
55
67
  if (kept.length >= cap)
@@ -59,31 +71,70 @@ export class InMemoryEngine {
59
71
  this.sortScopes(kept, query.orderBy);
60
72
  if (query.distinct)
61
73
  kept = this.dedupByProjection(kept, query);
74
+ kept = sliceBound(kept, bound);
62
75
  return this.shape(query.consumer, kept, query);
63
76
  }
77
+ // Evaluate a block's `limit`/`offset`. The bound is part of the block, so it
78
+ // is read in a row-less scope INSIDE it: a bare name is absent (there is no
79
+ // current item yet), `^name` is the enclosing row — exactly as in the block's
80
+ // body — and literals/bindings are themselves. For a top-level query `scope`
81
+ // is the root. Each must be a non-negative integer.
82
+ boundOf(b, enclosing) {
83
+ if (!b.limit && !b.offset)
84
+ return UNBOUNDED;
85
+ const scope = enclosing.parent === null ? enclosing : { row: undefined, parent: enclosing, bindings: enclosing.bindings };
86
+ const read = (e, word) => {
87
+ if (!e)
88
+ return null;
89
+ const v = this.evalExpr(e, scope);
90
+ if (typeof v !== "number" || !Number.isInteger(v) || v < 0) {
91
+ throw new OqxError(`${word} must be a non-negative integer (got ${JSON.stringify(v ?? null)})`, "eval");
92
+ }
93
+ return v;
94
+ };
95
+ return { offset: read(b.offset, "offset") ?? 0, limit: read(b.limit, "limit") };
96
+ }
64
97
  // A where match that needs no lift capture (exists/count fast paths).
65
98
  matches(where, row, parent) {
66
99
  if (!where)
67
100
  return true;
68
- return this.evalWhere(where, { row, parent, bindings: parent.bindings });
101
+ return this.evalWhere(where, this.enter(row, parent));
69
102
  }
70
103
  rowsOf(v) {
71
104
  return Array.from(this.ctx.toRows(v));
72
105
  }
73
106
  child(row, parent) {
74
- return { row, parent, bindings: parent.bindings };
107
+ return this.enter(row, parent);
108
+ }
109
+ // Make the scope for a row. An `entries()` entry is unwrapped here: the scope's
110
+ // row is the property's VALUE (so `$value` and bare names read it) and the key
111
+ // becomes the `$key` intrinsic in `meta`. Every place a row becomes a scope
112
+ // goes through this, so entries behave the same at the top level, in nested
113
+ // blocks, as `from` re-projections, and as follow seeds.
114
+ enter(row, parent, extra = {}) {
115
+ const s = { row, parent, bindings: parent.bindings };
116
+ if (extra.lifts)
117
+ s.lifts = extra.lifts;
118
+ if (extra.meta)
119
+ s.meta = extra.meta;
120
+ if (isEntry(row)) {
121
+ s.row = row.value;
122
+ s.meta = { ...(s.meta ?? {}), [KEY]: row.key };
123
+ }
124
+ return s;
75
125
  }
76
126
  // ---- follow ---------------------------------------------------------------
77
- runFollow(query, rows, root) {
127
+ runFollow(query, rows, root, bound) {
78
128
  const { seed, post } = query.where ? partitionRecur(query.where) : { seed: null, post: null };
79
129
  const seeds = seed ? rows.filter((r) => this.matches(seed, r, root)) : rows;
80
130
  const occ = this.followWalk(seeds, query.follow, root);
81
- let scopes = occ.map((o) => ({ row: o.row, parent: root, bindings: root.bindings, lifts: {}, meta: o.meta }));
131
+ let scopes = occ.map((o) => this.enter(o.row, root, { lifts: {}, meta: o.meta }));
82
132
  if (post)
83
133
  scopes = scopes.filter((s) => this.evalWhere(post, s));
84
134
  this.sortScopes(scopes, query.orderBy);
85
135
  if (query.distinct)
86
136
  scopes = this.dedupByProjection(scopes, query);
137
+ scopes = sliceBound(scopes, bound);
87
138
  return this.shape(query.consumer, scopes, query);
88
139
  }
89
140
  // Dedup scopes by their PROJECTED value (`distinct`): keep the first scope per
@@ -118,7 +169,7 @@ export class InMemoryEngine {
118
169
  // `ctx.identity(row)`.
119
170
  followWalk(seedRows, follow, parent) {
120
171
  const cap = follow.depth ?? HARD_DEPTH_CAP;
121
- const scopeFor = (row) => ({ row, parent, bindings: parent.bindings });
172
+ const scopeFor = (row) => this.enter(row, parent);
122
173
  const keyOf = (row) => String(follow.by ? this.evalExpr(follow.by, scopeFor(row)) : this.ctx.identity(row));
123
174
  const succOf = (row) => {
124
175
  const raw = this.rowsOf(this.evalExpr(follow.receiver, scopeFor(row)));
@@ -179,6 +230,7 @@ export class InMemoryEngine {
179
230
  shape(consumer, scopes, proj) {
180
231
  switch (consumer) {
181
232
  case "exists": return { consumer, exists: scopes.length > 0 };
233
+ case "none": return { consumer, none: scopes.length === 0 };
182
234
  case "count": return { consumer, count: scopes.length };
183
235
  case "collect": return { consumer, rows: scopes.map((s) => this.projectRow(proj, s)) };
184
236
  case "first": return { consumer, row: scopes.length > 0 ? this.projectRow(proj, scopes[0]) : null };
@@ -223,10 +275,15 @@ export class InMemoryEngine {
223
275
  evalWhereOp(op, scope) {
224
276
  if (op.sub.follow)
225
277
  throw new OqxError("`follow` is only valid on a select-position collect { … }, not a where op", "eval");
226
- if (op.op === "exists")
227
- return this.anyMatch(op, scope);
278
+ const bound = this.boundOf(op.sub, scope);
279
+ if (op.op === "exists" || op.op === "none") {
280
+ // Unbounded: stop at the first match (dedup cannot change emptiness).
281
+ // Bounded: the offset/limit decide emptiness, so materialize the set.
282
+ const any = bound === UNBOUNDED ? this.anyMatch(op, scope) : this.opRows(op, scope, bound).length > 0;
283
+ return op.op === "exists" ? any : !any;
284
+ }
228
285
  if (op.op === "collect") {
229
- const matched = this.matchRows(op, scope);
286
+ const matched = this.opRows(op, scope, bound);
230
287
  for (const item of op.sub.select) {
231
288
  if (item.kind !== "field")
232
289
  continue;
@@ -244,13 +301,18 @@ export class InMemoryEngine {
244
301
  return matched.length > 0;
245
302
  }
246
303
  if (op.op === "count") {
247
- let rows = this.matchRows(op, scope);
248
- if (op.distinct)
249
- rows = this.dedupByProjection(rows, op.sub);
250
- const n = rows.length;
304
+ const n = this.opRows(op, scope, bound).length;
251
305
  return op.countCmp ? compareCount(n, op.countCmp) : n > 0;
252
306
  }
253
- return this.matchRows(op, scope).length > 0;
307
+ return this.opRows(op, scope, bound).length > 0;
308
+ }
309
+ // The rows a consumer op reduces: matched → ordered → distinct → bounded.
310
+ opRows(op, scope, bound) {
311
+ let scopes = this.matchRows(op, scope);
312
+ this.sortScopes(scopes, op.sub.orderBy);
313
+ if (op.distinct)
314
+ scopes = this.dedupByProjection(scopes, op.sub);
315
+ return sliceBound(scopes, bound);
254
316
  }
255
317
  // Short-circuiting existence check over a consumer receiver.
256
318
  anyMatch(op, scope) {
@@ -265,7 +327,7 @@ export class InMemoryEngine {
265
327
  for (const proj of op.sub.from)
266
328
  rows = rows.flatMap((r) => this.rowsOf(this.evalExpr(proj, this.child(r, scope))));
267
329
  for (const r of rows) {
268
- const s = { row: r, parent: scope, bindings: scope.bindings, lifts: {} };
330
+ const s = this.enter(r, scope, { lifts: {} });
269
331
  if (!op.sub.where || this.evalWhere(op.sub.where, s))
270
332
  yield s;
271
333
  }
@@ -276,20 +338,22 @@ export class InMemoryEngine {
276
338
  // A select-position collect/first/single, optionally recursive via `follow`.
277
339
  evalCollectValue(op, scope) {
278
340
  const sub = op.sub;
341
+ const bound = this.boundOf(sub, scope);
279
342
  let scopes;
280
343
  if (sub.follow) {
281
344
  let rows = this.rowsOf(this.evalExpr(op.receiver, scope));
282
345
  for (const proj of sub.from)
283
346
  rows = rows.flatMap((r) => this.rowsOf(this.evalExpr(proj, this.child(r, scope))));
284
- const seeds = sub.where ? rows.filter((r) => this.evalWhere(sub.where, { row: r, parent: scope, bindings: scope.bindings, lifts: {} })) : rows;
285
- scopes = this.followWalk(seeds, sub.follow, scope).map((o) => ({ row: o.row, parent: scope, bindings: scope.bindings, meta: o.meta }));
347
+ const seeds = sub.where ? rows.filter((r) => this.evalWhere(sub.where, this.enter(r, scope, { lifts: {} }))) : rows;
348
+ scopes = this.followWalk(seeds, sub.follow, scope).map((o) => this.enter(o.row, scope, { meta: o.meta }));
349
+ this.sortScopes(scopes, sub.orderBy);
350
+ if (op.distinct)
351
+ scopes = this.dedupByProjection(scopes, sub);
352
+ scopes = sliceBound(scopes, bound);
286
353
  }
287
354
  else {
288
- scopes = this.matchRows(op, scope);
355
+ scopes = this.opRows(op, scope, bound);
289
356
  }
290
- this.sortScopes(scopes, sub.orderBy);
291
- if (op.distinct)
292
- scopes = this.dedupByProjection(scopes, sub);
293
357
  switch (op.op) {
294
358
  case "collect": return scopes.map((s) => this.projectRow(sub, s));
295
359
  case "first": return scopes.length > 0 ? this.projectRow(sub, scopes[0]) : null;
@@ -371,9 +435,10 @@ export class InMemoryEngine {
371
435
  // Resolve a name against ONE scope — never its ancestors. A scope provides,
372
436
  // in order: `$value` (the scope's row itself — the current item, whatever its
373
437
  // type, so scalar collections are queryable; absent at the root, which has no
374
- // row); the recursion intrinsics (`$depth`, …) when it is a follow occurrence;
375
- // values lifted into it by `^name:` items; then either the row's own property
376
- // or, for the root scope (no row), the context's named roots.
438
+ // row); `$key` (the property key, for an entry scope only — see `enter`); the
439
+ // recursion intrinsics (`$depth`, …) when it is a follow occurrence; values
440
+ // lifted into it by `^name:` items; then either the row's own property or,
441
+ // for the root scope (no row), the context's named roots.
377
442
  //
378
443
  // A name the scope lacks is simply absent (undefined). It does NOT fall
379
444
  // through to an enclosing scope, so a query's meaning never depends on which
@@ -384,7 +449,7 @@ export class InMemoryEngine {
384
449
  resolveIn(name, scope) {
385
450
  if (name === "$value")
386
451
  return scope.parent === null ? undefined : scope.row;
387
- if (RECUR.has(name))
452
+ if (name === KEY || RECUR.has(name))
388
453
  return scope.meta ? scope.meta[name] : undefined;
389
454
  if (scope.lifts && name in scope.lifts)
390
455
  return scope.lifts[name];
@@ -428,6 +493,16 @@ function stableStringify(v) {
428
493
  function compareCount(n, cmp) {
429
494
  return relate(cmp.op, n, cmp.value);
430
495
  }
496
+ // Apply a bound to an ordered row set / to a match count.
497
+ function sliceBound(rows, b) {
498
+ if (b === UNBOUNDED)
499
+ return rows;
500
+ return rows.slice(b.offset, b.limit == null ? undefined : b.offset + b.limit);
501
+ }
502
+ function boundedCount(n, b) {
503
+ const rest = Math.max(0, n - b.offset);
504
+ return b.limit == null ? rest : Math.min(rest, b.limit);
505
+ }
431
506
  // Order `&&` conjuncts so cheap scalar leaves run before consumer-op leaves.
432
507
  function orderByCost(parts) {
433
508
  return [...parts].sort((a, b) => whereCost(a) - whereCost(b));