@jarenjs/linq 0.43.3 → 0.46.4

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
@@ -52,6 +52,12 @@ played by the provider seam below.
52
52
  spatial measurements are `geoArea`/`geoLength` because `length` on
53
53
  this surface is already `$string-length` — the mapping table says so
54
54
  in its own row.
55
+ - **Meaning is spellable too.** `m.embedding.similarity(query)` emits
56
+ §8.15's `$similarity`, and k-nearest is the chain it already looks
57
+ like — `.orderByDescending(..., { empty: 'least' }).thenBy(m => m.id)
58
+ .take(10)` — because ordering and windowing are stages, not a `knn`
59
+ method. `.params({ query })` binds the query vector at call time, so
60
+ one compiled document serves every question.
55
61
  - **The provider contract.** Any object with
56
62
  `execute(queryDocument, { externals })` is a provider.
57
63
  `@jarenjs/db` implements it — a chain over a SQLite-backed
@@ -116,6 +116,7 @@ is part of THIS design.
116
116
  | `Zip` | — no positional co-iteration in the grammar | unsupported (`JL0006`) | — |
117
117
  | expression methods | `eq ne lt le gt ge` → `$eq…$ge`; `and or not`; `add sub mul div idiv mod neg`; `startsWith endsWith contains matches upper lower length concat substring replace` → §8.7; `count sum avg min max` → §8.8 (aggregates as expressions, e.g. over a group); `year month day epoch` → §8.13; `exists isEmpty`; `at all get` | native | on `Expr<…>`, per the typed-surface order |
118
118
  | spatial family (§8.14) | `bbox geoArea geoLength centroid` → `$bbox $area $length $centroid`; `distance within bboxIntersects` → `$distance $within $bbox-intersects`; `geohash(precision?)` → `$geohash` (optional arity, like `substring`); `geoParse geoText geohashBounds geohashNeighbours` → the conversion family; `geoSimplify(tolerance)` → `$geo-simplify`. A plain JSON polygon embeds as a literal (`p.at.within(poly)`); `.params({ region })` makes it an external instead | native | on `Expr<…>`, per the typed-surface order |
119
+ | vector family (§8.15) | `similarity(other)` → `$similarity`. The other operand is an array of numbers: a captured one embeds as a literal, `.params({ query })` binds it at call time. There is no `knn` method — k-nearest is `orderByDescending(...).take(k)`, which is the composition the emitted document already is | native | on `Expr<…>`, per the typed-surface order |
119
120
 
120
121
  Two spatial names are deliberately not the obvious ones, and the reason
121
122
  is the same one that made §8.14's `$length` and §8.7's `$string-length`
@@ -130,7 +131,31 @@ names the family the way `geoParse`/`geoText` already do.
130
131
  Every method name shadows a data member of the same name — that is what
131
132
  the null prototype on the method table is for, and what `get(name)`
132
133
  escapes. A position stored as `at` is the case that bites: `p.at` is the
133
- index method, so it reads `p.get('at').within(region)`.
134
+ index method, so it reads `p.get('at').within(region)`. A stored score
135
+ named `similarity` is the same bite with a worse error — `r.similarity`
136
+ is the *method*, so calling it as a member yields a `TypeError` about a
137
+ function rather than a coded build error, because the surface never sees
138
+ a member access at all. `r.get('similarity')` reads the data.
139
+
140
+ **k-nearest is a chain, not a method.** `similarity()` is one operator
141
+ and the ordering and the window are stages that already exist, so the
142
+ top k reads as what it is:
143
+
144
+ ```js
145
+ from(memories)
146
+ .params({ query })
147
+ .orderByDescending((m, p) => m.embedding.similarity(p.query), { empty: 'least' })
148
+ .thenBy((m) => m.id)
149
+ .take(10)
150
+ .select((m) => m.text)
151
+ ```
152
+
153
+ `{ empty: 'least' }` under a descending sort puts the rows whose key is
154
+ empty — no vector, or one of the wrong width — **last**, and `thenBy` on
155
+ the identity breaks ties, so the chain answers the same rows in the same
156
+ order every time it runs. `.params({ query })` rather than a captured
157
+ array is what makes the emitted document one query for every question,
158
+ which is the shape a provider can push down.
134
159
 
135
160
  ## 5. Deferred execution and re-enumeration
136
161
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jarenjs/linq",
3
3
  "private": false,
4
- "version": "0.43.3",
4
+ "version": "0.46.4",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "types": "./types/index.d.ts",
@@ -47,7 +47,7 @@
47
47
  "prepack": "npm run build:types"
48
48
  },
49
49
  "dependencies": {
50
- "@jarenjs/core": "^0.43.3",
51
- "@jarenjs/json": "^0.43.3"
50
+ "@jarenjs/core": "^0.46.4",
51
+ "@jarenjs/json": "^0.46.4"
52
52
  }
53
53
  }
package/src/expression.js CHANGED
@@ -231,6 +231,11 @@ const METHODS = {
231
231
  geohashBounds: unary('$geohash-bounds'),
232
232
  geohashNeighbours: unary('$geohash-neighbours'),
233
233
  geoSimplify: binary('$geo-simplify'),
234
+ // §8.15 vectors. One method for one operator: the metric is cosine
235
+ // and there is no distance spelling to choose between. A member
236
+ // literally named `similarity` is read with `get('similarity')`, the
237
+ // same escape every method name needs.
238
+ similarity: binary('$similarity'),
234
239
  geohash(record, precision) {
235
240
  const args = precision === undefined
236
241
  ? record.doc