arrowbase 0.1.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.
@@ -0,0 +1,279 @@
1
+ import { R as RowValue, C as Collection } from './collection-DGlKgOGi.js';
2
+
3
+ /**
4
+ * Column-op filter DSL. A vectorizable alternative to `.where(fn)`.
5
+ *
6
+ * The surface is deliberately tiny — eq/neq/gt/gte/lt/lte for scalars,
7
+ * and/or/not for composition, plus a column-ref helper. Predicates built
8
+ * here are compiled to a `RowTest` closure that reads directly from the
9
+ * column typed-arrays, skipping proxy allocation and per-row row-object
10
+ * materialization. Anything outside this surface still works via
11
+ * `.where(fn)` at normal (proxy) speed.
12
+ *
13
+ * Example:
14
+ * import { query, f, eq, and, gt } from 'arrowbase';
15
+ * query(c).filter(and(eq(f('tag'), 'asphalt'), gt(f('speed'), 40)));
16
+ */
17
+ type Scalar = number | bigint | boolean | string | null;
18
+ /** A reference to a column by name. */
19
+ interface ColumnRef {
20
+ readonly kind: 'col';
21
+ readonly name: string;
22
+ }
23
+ interface LiteralNode {
24
+ readonly kind: 'lit';
25
+ readonly value: Scalar;
26
+ }
27
+ type Operand = ColumnRef | LiteralNode;
28
+ type CmpOp = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte';
29
+ interface CompareNode {
30
+ readonly kind: 'cmp';
31
+ readonly op: CmpOp;
32
+ readonly left: Operand;
33
+ readonly right: Operand;
34
+ }
35
+ interface AndNode {
36
+ readonly kind: 'and';
37
+ readonly args: readonly FilterExpr[];
38
+ }
39
+ interface OrNode {
40
+ readonly kind: 'or';
41
+ readonly args: readonly FilterExpr[];
42
+ }
43
+ interface NotNode {
44
+ readonly kind: 'not';
45
+ readonly arg: FilterExpr;
46
+ }
47
+ interface IsNullNode {
48
+ readonly kind: 'isNull';
49
+ readonly col: ColumnRef;
50
+ readonly negated: boolean;
51
+ }
52
+ interface InNode {
53
+ readonly kind: 'in';
54
+ readonly col: ColumnRef;
55
+ readonly values: readonly Scalar[];
56
+ }
57
+ /**
58
+ * String predicate. Covers the common text-matching cases with a
59
+ * vectorizable shape so the kernel can pre-compute (e.g., the matching
60
+ * set of dict codes) instead of walking characters per row.
61
+ *
62
+ * `op`:
63
+ * - `contains` / `startsWith` / `endsWith`: plain substring match.
64
+ * - `like`: SQL LIKE, with `%` = any sequence, `_` = any single char,
65
+ * and `\` as an escape for literal `%` / `_`.
66
+ * - `matches`: raw RegExp pattern (string + optional flags) run with
67
+ * `RegExp.prototype.test`. Use sparingly; engines inline the other
68
+ * ops much more aggressively.
69
+ *
70
+ * `caseInsensitive` performs ASCII-fold case folding (fast). For
71
+ * locale-aware folding (German ß, Turkish dotted-I, etc.) use
72
+ * `matches` with an explicit RegExp containing the `i` flag, which the
73
+ * engine delegates to the platform.
74
+ */
75
+ type StringOp = 'contains' | 'startsWith' | 'endsWith' | 'like' | 'matches';
76
+ interface StringOpNode {
77
+ readonly kind: 'str';
78
+ readonly op: StringOp;
79
+ readonly col: ColumnRef;
80
+ /** Pattern or substring. For `matches`, the regex source. */
81
+ readonly value: string;
82
+ /** Flags (for `matches` only). Ignored for other ops. */
83
+ readonly flags?: string;
84
+ /** ASCII-fold case-insensitive compare. Ignored by `matches`. */
85
+ readonly caseInsensitive?: boolean;
86
+ /** If true, the match result is inverted (NOT contains/like/...). */
87
+ readonly negated?: boolean;
88
+ }
89
+ type FilterExpr = CompareNode | AndNode | OrNode | NotNode | IsNullNode | InNode | StringOpNode | ColumnRef;
90
+ /** Build a column ref. `f('speed')` reads shorter at the call site. */
91
+ declare function f(name: string): ColumnRef;
92
+ declare function eq(a: Operand | Scalar, b: Operand | Scalar): CompareNode;
93
+ declare function neq(a: Operand | Scalar, b: Operand | Scalar): CompareNode;
94
+ declare function gt(a: Operand | Scalar, b: Operand | Scalar): CompareNode;
95
+ declare function gte(a: Operand | Scalar, b: Operand | Scalar): CompareNode;
96
+ declare function lt(a: Operand | Scalar, b: Operand | Scalar): CompareNode;
97
+ declare function lte(a: Operand | Scalar, b: Operand | Scalar): CompareNode;
98
+ declare function and(...args: FilterExpr[]): AndNode;
99
+ declare function or(...args: FilterExpr[]): OrNode;
100
+ declare function not(arg: FilterExpr): NotNode;
101
+ declare function isNull(col: ColumnRef): IsNullNode;
102
+ declare function isNotNull(col: ColumnRef): IsNullNode;
103
+ declare function inArray(col: ColumnRef, values: readonly Scalar[]): InNode;
104
+ interface StringOpOptions {
105
+ /** ASCII-fold case-insensitive. Ignored by `matches`. */
106
+ caseInsensitive?: boolean;
107
+ /** Invert match. */
108
+ negated?: boolean;
109
+ }
110
+ declare function contains(col: ColumnRef, value: string, opts?: StringOpOptions): StringOpNode;
111
+ declare function startsWith(col: ColumnRef, value: string, opts?: StringOpOptions): StringOpNode;
112
+ declare function endsWith(col: ColumnRef, value: string, opts?: StringOpOptions): StringOpNode;
113
+ declare function like(col: ColumnRef, pattern: string, opts?: StringOpOptions): StringOpNode;
114
+ declare function matches(col: ColumnRef, pattern: string | RegExp, opts?: StringOpOptions): StringOpNode;
115
+
116
+ type Predicate = (row: RowValue) => boolean;
117
+ type SortDir = 'asc' | 'desc';
118
+ interface JoinSpecLike {
119
+ readonly on: {
120
+ readonly left: string;
121
+ readonly right: string;
122
+ };
123
+ readonly as: string;
124
+ readonly type?: 'inner';
125
+ }
126
+ type JoinQueryLike = any;
127
+ interface OrderSpec {
128
+ readonly column: string;
129
+ readonly dir: SortDir;
130
+ }
131
+ /**
132
+ * Immutable query spec. Each builder method returns a new Query. Deps are
133
+ * either `'all'` (everything) or a concrete set of column names. When a
134
+ * predicate is present and no explicit `.deps(...)` override was applied,
135
+ * deps are inferred on first evaluation via the tracking proxy and grow
136
+ * monotonically over time.
137
+ */
138
+ declare class Query<TRow extends RowValue = RowValue> {
139
+ readonly collection: Collection;
140
+ readonly predicate: Predicate | null;
141
+ /** Column-op filter DSL expression (vectorizable). */
142
+ readonly filterExpr: FilterExpr | null;
143
+ readonly selection: readonly string[] | null;
144
+ readonly order: readonly OrderSpec[];
145
+ readonly limitN: number | null;
146
+ readonly offsetN: number;
147
+ /** Explicit deps override. If null, deps are inferred. */
148
+ readonly explicitDeps: ReadonlySet<string> | null;
149
+ constructor(collection: Collection, predicate?: Predicate | null, selection?: readonly string[] | null, order?: readonly OrderSpec[], limitN?: number | null, offsetN?: number, explicitDeps?: ReadonlySet<string> | null, filterExpr?: FilterExpr | null);
150
+ where(predicate: Predicate): Query<TRow>;
151
+ /**
152
+ * Add a vectorizable column-op filter. Composes with prior `filter()`
153
+ * calls via AND. Anything that fits the `FilterExpr` DSL takes the
154
+ * vectorized fast path in `QueryExecutor`; combine with `.where(fn)`
155
+ * when you need an escape hatch for logic outside the DSL — the fast
156
+ * path still runs first for the DSL portion.
157
+ */
158
+ filter(expr: FilterExpr): Query<TRow>;
159
+ select(columns: readonly string[]): Query<TRow>;
160
+ orderBy(column: string, dir?: SortDir): Query<TRow>;
161
+ limit(n: number): Query<TRow>;
162
+ offset(n: number): Query<TRow>;
163
+ /**
164
+ * Equijoin against another collection. Returns a `JoinQuery` that
165
+ * exposes the post-join builder surface (where / select / orderBy /
166
+ * limit / offset).
167
+ *
168
+ * ```ts
169
+ * query(orders)
170
+ * .filter(f('amount').gt(100))
171
+ * .join(users, { on: { left: 'userId', right: '__id' }, as: 'user' })
172
+ * .orderBy('user.name')
173
+ * .select(['id', 'amount', 'user.name']);
174
+ * ```
175
+ *
176
+ * Implementation: to avoid a static cycle between query.ts and
177
+ * join.ts, join.ts registers its factory via `registerJoinFactory`
178
+ * at module-load time. Importing `arrowbase` (or
179
+ * `arrowbase/query/join`) before calling `.join()` is sufficient.
180
+ */
181
+ join(right: Collection, spec: JoinSpecLike): JoinQueryLike;
182
+ /**
183
+ * Explicit dep override. Use this when:
184
+ * - your predicate reads columns conditionally and you want stable deps
185
+ * - you want to force reactivity on a superset of columns
186
+ */
187
+ deps(columns: readonly string[]): Query<TRow>;
188
+ /** Columns that must be materialized for this query (deps + filter + selection + order). */
189
+ requiredColumns(inferredDeps: ReadonlySet<string>): ReadonlySet<string>;
190
+ private requireKnownColumn;
191
+ }
192
+ /**
193
+ * Snapshot = result of evaluating a Query at a specific global version.
194
+ * Rows are plain JS objects shaped by `selection` (or full rows if null).
195
+ */
196
+ interface QuerySnapshot<TRow extends RowValue = RowValue> {
197
+ readonly rows: readonly TRow[];
198
+ /** Collection.globalVersion at the moment of the snapshot. */
199
+ readonly version: number;
200
+ }
201
+ /**
202
+ * Executor = caches the last snapshot + dep set for a Query so we can skip
203
+ * recomputation when only untouched columns changed.
204
+ *
205
+ * Executors are created by `run(query)` and held by callers (including the
206
+ * subscribe wrapper below). A single Query instance may have multiple
207
+ * executors in flight; each owns its own cache.
208
+ */
209
+ declare class QueryExecutor<TRow extends RowValue = RowValue> {
210
+ readonly query: Query<TRow>;
211
+ private deps;
212
+ private cachedSnapshot;
213
+ /** Per-column version seen at the time `cachedSnapshot` was produced. */
214
+ private cachedVersions;
215
+ /**
216
+ * When the predicate trips (throws) during the tracking dry-run, we
217
+ * fall back to `deps = all`. We still try inference again after the
218
+ * predicate changes, but a single Query instance keeps this bit sticky.
219
+ */
220
+ private depsAreAll;
221
+ /**
222
+ * Inserts, deletes, and rollbacks invalidate any cached snapshot because
223
+ * they may change the live row set even when no observed value column
224
+ * moves (soft-delete notwithstanding). We flip this flag from a passive
225
+ * change listener and clear it on recompute.
226
+ */
227
+ private staleFromInsertOrDelete;
228
+ private readonly unsubscribePassive;
229
+ constructor(query: Query<TRow>);
230
+ /** Release the passive change listener. */
231
+ dispose(): void;
232
+ /** True if the cached snapshot is still valid given current column versions. */
233
+ private cacheIsFresh;
234
+ /**
235
+ * Columns this query observes for invalidation purposes. Includes:
236
+ * - inferred or explicit deps (from predicate / .deps())
237
+ * - selection columns (stale projection otherwise)
238
+ * - order-by columns (stale sort otherwise)
239
+ */
240
+ private observedColumns;
241
+ /** Lazily compile the filterExpr. Cached for executor lifetime. */
242
+ private compiledFilter;
243
+ private compiledFilterExpr;
244
+ private compiledFilterError;
245
+ private getCompiledFilter;
246
+ /** Recompute the snapshot, updating cache. */
247
+ private recompute;
248
+ /** Legacy scan: materialize + run fn predicate with dep tracking. Returns ordinals of survivors. */
249
+ private legacyScan;
250
+ /**
251
+ * Map the sequential index returned by `rows()` to the live row
252
+ * ordinal. `rows()` yields in the same order as `_internalLiveOrdinals()`,
253
+ * so we lazily keep a parallel ordinal buffer.
254
+ */
255
+ private cachedOrdinalBuffer;
256
+ private cachedOrdinalVersion;
257
+ private getOrdinalFor;
258
+ /** Apply the fn predicate to a candidate ordinal set (post-kernel filter). */
259
+ private applyFnPredicate;
260
+ private materializeFullRow;
261
+ /** Return a snapshot, reusing the cached one if all observed columns are unchanged. */
262
+ snapshot(): QuerySnapshot<TRow>;
263
+ /**
264
+ * Subscribe to reactive updates. `listener` fires once with the initial
265
+ * snapshot and then again after every mutation that touches an observed
266
+ * column. Returns an unsubscribe function.
267
+ */
268
+ subscribe(listener: (snap: QuerySnapshot<TRow>) => void): () => void;
269
+ /** Visible for tests: introspect current inferred deps. */
270
+ get inferredDeps(): ReadonlySet<string>;
271
+ get isDepsAll(): boolean;
272
+ private shouldReact;
273
+ }
274
+ /** Shorthand entry point: `query(collection).where(...).select(...)`. */
275
+ declare function query<TRow extends RowValue = RowValue>(collection: Collection): Query<TRow>;
276
+ /** Build an executor for a Query. */
277
+ declare function run<TRow extends RowValue = RowValue>(q: Query<TRow>): QueryExecutor<TRow>;
278
+
279
+ export { type ColumnRef as C, type FilterExpr as F, type InNode as I, type OrderSpec as O, type Predicate as P, Query as Q, type SortDir as S, type Scalar as a, QueryExecutor as b, type QuerySnapshot as c, type StringOp as d, type StringOpNode as e, type StringOpOptions as f, f as g, and as h, contains as i, endsWith as j, eq as k, gt as l, gte as m, inArray as n, isNotNull as o, isNull as p, like as q, lt as r, lte as s, matches as t, neq as u, not as v, or as w, startsWith as x, query as y, run as z };
@@ -0,0 +1,51 @@
1
+ import { R as RowValue, C as Collection } from './collection-DGlKgOGi.js';
2
+ import { c as QuerySnapshot, Q as Query } from './query-CzV9E57Y.js';
3
+
4
+ /**
5
+ * Subscribe a React component to a Query. Returns the latest snapshot
6
+ * (`{ rows, version }`); the component re-renders when the query's
7
+ * observed columns change.
8
+ *
9
+ * The executor is created on first mount and cached for the lifetime of
10
+ * the owning component (not the Query instance). When the query
11
+ * identity changes between renders, a fresh executor is created and
12
+ * the previous one is disposed.
13
+ *
14
+ * ```tsx
15
+ * const q = useMemo(() => query(users).filter(fEq(f('active'), true)), [users]);
16
+ * const { rows } = useLiveQuery(q);
17
+ * return <ul>{rows.map(r => <li key={r.__id}>{r.name}</li>)}</ul>;
18
+ * ```
19
+ *
20
+ * The snapshot `rows` reference is stable across renders when the
21
+ * executor determined nothing changed (internal caching), so users can
22
+ * pass it directly into `React.memo`'d children without extra memo.
23
+ */
24
+ declare function useLiveQuery<TRow extends RowValue>(query: Query<TRow>, options?: UseLiveQueryOptions<TRow>): QuerySnapshot<TRow>;
25
+ interface UseLiveQueryOptions<TRow extends RowValue> {
26
+ /** Value returned during SSR. Defaults to an empty-rows snapshot. */
27
+ readonly ssrSnapshot?: QuerySnapshot<TRow>;
28
+ }
29
+ /**
30
+ * Builder-closure variant. Accepts a function that composes a Query
31
+ * (typically a `.filter().orderBy()...` chain) and a dependency array
32
+ * for when to rebuild. This avoids making callers memoize the Query
33
+ * object themselves.
34
+ *
35
+ * ```tsx
36
+ * const { rows } = useLiveQueryResult(
37
+ * users,
38
+ * (q) => q.filter(fEq(f('team'), team)).orderBy('name'),
39
+ * [team],
40
+ * );
41
+ * ```
42
+ */
43
+ declare function useLiveQueryResult<TRow extends RowValue>(collection: Collection, build: (q: Query<TRow>) => Query<TRow>, deps?: readonly unknown[], options?: UseLiveQueryOptions<TRow>): QuerySnapshot<TRow>;
44
+ /**
45
+ * Subscribe to a collection's `globalVersion`. Returns a monotonically
46
+ * increasing number that bumps on every mutation. Useful as a cache-bust
47
+ * key or a dependency to other hooks.
48
+ */
49
+ declare function useCollectionVersion(collection: Collection): number;
50
+
51
+ export { type UseLiveQueryOptions, useCollectionVersion, useLiveQuery, useLiveQueryResult };