@spaethtech/svelte-ui 0.7.0 → 0.7.1-dev.37.ab40d6b
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.
|
@@ -265,6 +265,14 @@
|
|
|
265
265
|
</tbody>
|
|
266
266
|
</table>
|
|
267
267
|
</div>
|
|
268
|
+
<div>
|
|
269
|
+
<h4 class="mb-1 font-semibold">Dynamic dates</h4>
|
|
270
|
+
<p class="mb-1 text-xs [color:color-mix(in_srgb,var(--ui-color-text)_70%,transparent)]">
|
|
271
|
+
<code class="font-mono">now()</code> is the current time; add a signed offset
|
|
272
|
+
<code class="font-mono">now()±<n><unit></code> — units <code>s m h d w</code>
|
|
273
|
+
(e.g. <code>now()-7d</code>, <code>now()+1h</code>). Resolved when the query runs.
|
|
274
|
+
</p>
|
|
275
|
+
</div>
|
|
268
276
|
<div>
|
|
269
277
|
<h4 class="mb-1 font-semibold">Examples</h4>
|
|
270
278
|
<pre
|
|
@@ -272,6 +280,7 @@
|
|
|
272
280
|
>$status == active
|
|
273
281
|
$email ~= /@gmail\.com$/i
|
|
274
282
|
$price |= [20..50) && $role |= [admin, ops]
|
|
283
|
+
$created >= now()-7d && $status == open
|
|
275
284
|
$ip|inet |= [10.0.0.0|inet..10.255.255.255|inet]
|
|
276
285
|
"lemon balm" || $featured == true</code
|
|
277
286
|
></pre>
|
package/dist/data/query/ast.d.ts
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
export type CompareOp = "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "contains" | "ncontains" | "starts" | "ends";
|
|
2
2
|
export type Scalar = string | number | boolean;
|
|
3
|
+
/** Relative-time unit for a `now()` offset: seconds/minutes/hours/days/weeks. */
|
|
4
|
+
export type TimeUnit = "s" | "m" | "h" | "d" | "w";
|
|
5
|
+
/** A dynamic `now()` value — the current instant, optionally shifted by a signed offset
|
|
6
|
+
* (`now()-7d`, `now()+1h`). Resolved at EVALUATION time, not parse time (so it's always "now" when
|
|
7
|
+
* the query runs, and the server can push it down to the DB's own `now()`). Carried in the AST as a
|
|
8
|
+
* marker; `resolveNow` turns it into a `Date`, or the consumer's `toConditions` emits DB SQL. */
|
|
9
|
+
export interface NowValue {
|
|
10
|
+
now: true;
|
|
11
|
+
/** Signed amount + unit, e.g. `{ n: -7, unit: "d" }` for `now()-7d`. Absent = the current instant. */
|
|
12
|
+
offset?: {
|
|
13
|
+
n: number;
|
|
14
|
+
unit: TimeUnit;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** A comparison operand: a literal scalar or a dynamic value (`now()`). */
|
|
18
|
+
export type Value = Scalar | NowValue;
|
|
19
|
+
/** Narrow a `Value` to the dynamic `now()` marker. */
|
|
20
|
+
export declare function isNow(v: Value): v is NowValue;
|
|
21
|
+
/** Resolve a `NowValue` to a concrete `Date` (defaults to the real current instant). For client-side
|
|
22
|
+
* evaluation; server code typically maps the marker to the DB's `now()` in `toConditions` instead. */
|
|
23
|
+
export declare function resolveNow(v: NowValue, at?: Date): Date;
|
|
3
24
|
/** Explicit value/column cast on a comparison, e.g. `$ip |inet >= 10.0.0.0 |inet`. Storage-agnostic:
|
|
4
25
|
* the query layer only carries the marker; the consumer (`toConditions`) decides the SQL (e.g.
|
|
5
26
|
* Postgres `::inet`). `number`/`string`/`bool` also retype the literal at parse time. */
|
|
@@ -8,7 +29,7 @@ export type Cast = "number" | "string" | "bool" | "date" | "inet";
|
|
|
8
29
|
export type MemberMode = "any" | "all";
|
|
9
30
|
/** One end of an interval. `inclusive` reflects the bracket: `[`/`]` inclusive, `(`/`)` exclusive. */
|
|
10
31
|
export type Bound = {
|
|
11
|
-
value:
|
|
32
|
+
value: Value;
|
|
12
33
|
inclusive: boolean;
|
|
13
34
|
};
|
|
14
35
|
/** `null` bound = open/unbounded on that side (e.g. `[20..)` -> upper is null). */
|
|
@@ -29,7 +50,7 @@ export type QueryNode = {
|
|
|
29
50
|
kind: "cmp";
|
|
30
51
|
col: string;
|
|
31
52
|
op: CompareOp;
|
|
32
|
-
value:
|
|
53
|
+
value: Value;
|
|
33
54
|
cast?: Cast;
|
|
34
55
|
} | {
|
|
35
56
|
kind: "match";
|
|
@@ -41,7 +62,7 @@ export type QueryNode = {
|
|
|
41
62
|
kind: "in";
|
|
42
63
|
col: string;
|
|
43
64
|
mode: MemberMode;
|
|
44
|
-
values:
|
|
65
|
+
values: Value[];
|
|
45
66
|
cast?: Cast;
|
|
46
67
|
} | {
|
|
47
68
|
kind: "range";
|
package/dist/data/query/ast.js
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
// AST for the data-table query language. Framework-agnostic — no Svelte/Drizzle/SvelteKit deps,
|
|
2
2
|
// so it runs in the browser (Query input), on the server (toConditions), and in unit tests.
|
|
3
|
+
/** Narrow a `Value` to the dynamic `now()` marker. */
|
|
4
|
+
export function isNow(v) {
|
|
5
|
+
return typeof v === "object" && v !== null && v.now === true;
|
|
6
|
+
}
|
|
7
|
+
const UNIT_MS = {
|
|
8
|
+
s: 1_000,
|
|
9
|
+
m: 60_000,
|
|
10
|
+
h: 3_600_000,
|
|
11
|
+
d: 86_400_000,
|
|
12
|
+
w: 604_800_000,
|
|
13
|
+
};
|
|
14
|
+
/** Resolve a `NowValue` to a concrete `Date` (defaults to the real current instant). For client-side
|
|
15
|
+
* evaluation; server code typically maps the marker to the DB's `now()` in `toConditions` instead. */
|
|
16
|
+
export function resolveNow(v, at = new Date()) {
|
|
17
|
+
const base = at.getTime();
|
|
18
|
+
return new Date(v.offset ? base + v.offset.n * UNIT_MS[v.offset.unit] : base);
|
|
19
|
+
}
|
|
3
20
|
export class QueryError extends Error {
|
|
4
21
|
position;
|
|
5
22
|
constructor(message,
|
|
@@ -159,7 +159,35 @@ class Parser {
|
|
|
159
159
|
}
|
|
160
160
|
throw new QueryError("Expected an operator after the column", opTok.start);
|
|
161
161
|
}
|
|
162
|
+
/** `now()` (+ optional signed offset like `-7d`, `+1h`) → a dynamic value. `now` alone (no parens)
|
|
163
|
+
* is an ordinary literal. Returns undefined if the cursor isn't on a `now()`. */
|
|
164
|
+
tryNow() {
|
|
165
|
+
const t = this.peek();
|
|
166
|
+
if (t.type !== "word" ||
|
|
167
|
+
t.value !== "now" ||
|
|
168
|
+
this.tokens[this.pos + 1]?.type !== "lparen" ||
|
|
169
|
+
this.tokens[this.pos + 2]?.type !== "rparen")
|
|
170
|
+
return undefined;
|
|
171
|
+
this.next(); // now
|
|
172
|
+
this.next(); // (
|
|
173
|
+
this.next(); // )
|
|
174
|
+
let offset;
|
|
175
|
+
const nx = this.peek();
|
|
176
|
+
if (nx.type === "word") {
|
|
177
|
+
const m = /^([+-])(\d+)([smhdw])$/.exec(nx.value);
|
|
178
|
+
if (m) {
|
|
179
|
+
this.next();
|
|
180
|
+
offset = { n: (m[1] === "-" ? -1 : 1) * parseInt(m[2], 10), unit: m[3] };
|
|
181
|
+
}
|
|
182
|
+
// a following word that ISN'T an offset is left for the outer parser (adjacent-terms error).
|
|
183
|
+
}
|
|
184
|
+
this.castAfter(); // a redundant `|date` is allowed and recorded, but not required
|
|
185
|
+
return { now: true, offset };
|
|
186
|
+
}
|
|
162
187
|
parseScalar() {
|
|
188
|
+
const now = this.tryNow();
|
|
189
|
+
if (now)
|
|
190
|
+
return now;
|
|
163
191
|
const t = this.next();
|
|
164
192
|
let raw;
|
|
165
193
|
let value;
|