@classytic/repo-core 0.18.0 → 0.19.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/CHANGELOG.md +10 -0
- package/LICENSE +1 -1
- package/README.md +7 -0
- package/dist/filter/coerce-dates.d.mts +65 -0
- package/dist/filter/coerce-dates.mjs +125 -0
- package/dist/filter/index.d.mts +2 -1
- package/dist/filter/index.mjs +2 -1
- package/dist/query-parser/coerce.mjs +8 -1
- package/package.json +178 -178
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ All notable changes to `@classytic/repo-core` are documented here.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.19.0] - 2026-07-29
|
|
8
|
+
|
|
9
|
+
### Added — `coerceFilterDates` + ISO date helpers (`./filter`)
|
|
10
|
+
|
|
11
|
+
- **`coerceFilterDates(filter)`** — walks a record-shape filter (Mongo-dialect or bare bracket-op syntax) and coerces ISO-8601 strings on range operators (`gt`/`gte`/`lt`/`lte` and `$`-prefixed equivalents) to `Date`, recursing through `$and`/`$or`/`$nor`/`$not` logical wrappers. Returns a new object; the input is never mutated. Equality operators are deliberately excluded — a string that happens to look like a date is far more likely a string id than a date equality predicate. Fixes silent empty results on aggregation `$match` stages: MongoDB `$match` (unlike `find`) performs no schema casting, so a string compared to a Date column matches nothing.
|
|
12
|
+
- **`tryCoerceIsoDate(value)`** — coerces one unknown value to a `Date` when it is an unambiguous ISO-8601 string; returns anything else untouched. Safe to apply unconditionally.
|
|
13
|
+
- **`ISO_DATE_PATTERN`** — the single regex source of truth for tight ISO-8601 detection (date-only through millisecond precision + optional timezone). `query-parser/coerce.ts` now imports this instead of keeping a duplicate pattern — one definition means the URL boundary and the compile boundary can never disagree on what "looks like a date".
|
|
14
|
+
|
|
15
|
+
All three are exported from `@classytic/repo-core/filter`. Purely additive.
|
|
16
|
+
|
|
7
17
|
## [0.18.0] - 2026-07-27
|
|
8
18
|
|
|
9
19
|
### Added — `definePurgeStep` builder (`./cleanup`) and `resolveTenantField` (`./tenant`)
|
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -239,3 +239,10 @@ See [docs/data-lifecycle.md](./docs/data-lifecycle.md) for the billion-row runbo
|
|
|
239
239
|
## License
|
|
240
240
|
|
|
241
241
|
MIT — see [LICENSE](./LICENSE).
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
## Trademark
|
|
245
|
+
|
|
246
|
+
The code is MIT-licensed. **"Classytic", "arc", and the logos are trademarks of
|
|
247
|
+
Classytic LLC** and are **not** licensed under MIT — see [TRADEMARK.md](TRADEMARK.md).
|
|
248
|
+
Forks must be renamed; the license covers the code, not the brand.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
//#region src/filter/coerce-dates.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* ISO-date coercion for record-shape filters — the canonical helper every
|
|
4
|
+
* kit shares.
|
|
5
|
+
*
|
|
6
|
+
* WHY THIS EXISTS. A URL carries strings (`?createdAt[gte]=2026-04-01`), but
|
|
7
|
+
* the stored column is a real date. On a `find`-family call Mongoose casts
|
|
8
|
+
* the query against the schema, so a string silently becomes a `Date` and
|
|
9
|
+
* everything works. **An aggregation `$match` stage gets no such casting** —
|
|
10
|
+
* and BSON type ordering makes `Date` (type 9) and `String` (type 2)
|
|
11
|
+
* non-comparable, so `{ createdAt: { $gte: '2026-04-01' } }` matches
|
|
12
|
+
* NOTHING against a Date field. Silent empty result, no error. The same
|
|
13
|
+
* hazard exists for any kit comparing a typed column to a string literal.
|
|
14
|
+
*
|
|
15
|
+
* Coercion therefore has to happen in the shared layer, before a kit emits
|
|
16
|
+
* its native predicate — which is what this module is for. It is
|
|
17
|
+
* DIALECT-PRESERVING (record in, record out): it never converts to the
|
|
18
|
+
* Filter IR, so a caller that hands over Mongo-dialect syntax gets
|
|
19
|
+
* Mongo-dialect syntax back, just with dates typed correctly. That keeps it
|
|
20
|
+
* usable from `compileFilterToMongo`'s already-built-query passthrough
|
|
21
|
+
* branch, where converting to IR would lose operators the IR doesn't model.
|
|
22
|
+
*
|
|
23
|
+
* Two shapes are handled, because both reach the compile boundary:
|
|
24
|
+
* - **bare shorthand** — `{ gte: '…' }`, what Fastify parses out of arc's
|
|
25
|
+
* bracket-syntax URL params, and
|
|
26
|
+
* - **`$`-prefixed** — `{ $gte: '…' }`, already-built Mongo/policy syntax.
|
|
27
|
+
*
|
|
28
|
+
* Logical wrappers (`$and` / `$or` / `$nor` / `$not`) are recursed — the
|
|
29
|
+
* same operator set {@link policyRecordToFilter} walks, kept deliberately in
|
|
30
|
+
* sync. Without recursion a date range nested under `$and` (exactly what a
|
|
31
|
+
* tenant/policy-scope merge produces when it conjoins a policy filter with a
|
|
32
|
+
* caller filter) is never coerced.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* Tight ISO-8601 pattern — date-only through millisecond precision with an
|
|
36
|
+
* optional timezone. Anchored at BOTH ends on purpose: a loose prefix-only
|
|
37
|
+
* match (`/^\d{4}-\d{2}-\d{2}/`) also swallows strings that merely START
|
|
38
|
+
* with something date-shaped (order numbers, slugs, serials), silently
|
|
39
|
+
* rewriting a legitimate string predicate into a Date one.
|
|
40
|
+
*
|
|
41
|
+
* THE single source of truth — `query-parser/coerce.ts` imports it rather
|
|
42
|
+
* than keeping a second copy.
|
|
43
|
+
*/
|
|
44
|
+
declare const ISO_DATE_PATTERN: RegExp;
|
|
45
|
+
/**
|
|
46
|
+
* Coerce one value to a `Date` when it is an unambiguous ISO-8601 string.
|
|
47
|
+
* Anything else — including an unparseable date-shaped string — is returned
|
|
48
|
+
* untouched, so this is always safe to apply.
|
|
49
|
+
*/
|
|
50
|
+
declare function tryCoerceIsoDate(value: unknown): unknown;
|
|
51
|
+
/**
|
|
52
|
+
* Walk a record-shape filter and coerce ISO-date strings on range operators
|
|
53
|
+
* to `Date`, recursing through logical wrappers. Returns a new object;
|
|
54
|
+
* the input is never mutated. Non-range operators, real nested documents,
|
|
55
|
+
* and already-typed values pass through unchanged.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* coerceFilterDates({ $and: [{ createdAt: { $gte: '2026-04-01' } }] })
|
|
60
|
+
* // → { $and: [{ createdAt: { $gte: Date(2026-04-01) } }] }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function coerceFilterDates(filter: Record<string, unknown>): Record<string, unknown>;
|
|
64
|
+
//#endregion
|
|
65
|
+
export { ISO_DATE_PATTERN, coerceFilterDates, tryCoerceIsoDate };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
//#region src/filter/coerce-dates.ts
|
|
2
|
+
/**
|
|
3
|
+
* ISO-date coercion for record-shape filters — the canonical helper every
|
|
4
|
+
* kit shares.
|
|
5
|
+
*
|
|
6
|
+
* WHY THIS EXISTS. A URL carries strings (`?createdAt[gte]=2026-04-01`), but
|
|
7
|
+
* the stored column is a real date. On a `find`-family call Mongoose casts
|
|
8
|
+
* the query against the schema, so a string silently becomes a `Date` and
|
|
9
|
+
* everything works. **An aggregation `$match` stage gets no such casting** —
|
|
10
|
+
* and BSON type ordering makes `Date` (type 9) and `String` (type 2)
|
|
11
|
+
* non-comparable, so `{ createdAt: { $gte: '2026-04-01' } }` matches
|
|
12
|
+
* NOTHING against a Date field. Silent empty result, no error. The same
|
|
13
|
+
* hazard exists for any kit comparing a typed column to a string literal.
|
|
14
|
+
*
|
|
15
|
+
* Coercion therefore has to happen in the shared layer, before a kit emits
|
|
16
|
+
* its native predicate — which is what this module is for. It is
|
|
17
|
+
* DIALECT-PRESERVING (record in, record out): it never converts to the
|
|
18
|
+
* Filter IR, so a caller that hands over Mongo-dialect syntax gets
|
|
19
|
+
* Mongo-dialect syntax back, just with dates typed correctly. That keeps it
|
|
20
|
+
* usable from `compileFilterToMongo`'s already-built-query passthrough
|
|
21
|
+
* branch, where converting to IR would lose operators the IR doesn't model.
|
|
22
|
+
*
|
|
23
|
+
* Two shapes are handled, because both reach the compile boundary:
|
|
24
|
+
* - **bare shorthand** — `{ gte: '…' }`, what Fastify parses out of arc's
|
|
25
|
+
* bracket-syntax URL params, and
|
|
26
|
+
* - **`$`-prefixed** — `{ $gte: '…' }`, already-built Mongo/policy syntax.
|
|
27
|
+
*
|
|
28
|
+
* Logical wrappers (`$and` / `$or` / `$nor` / `$not`) are recursed — the
|
|
29
|
+
* same operator set {@link policyRecordToFilter} walks, kept deliberately in
|
|
30
|
+
* sync. Without recursion a date range nested under `$and` (exactly what a
|
|
31
|
+
* tenant/policy-scope merge produces when it conjoins a policy filter with a
|
|
32
|
+
* caller filter) is never coerced.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* Tight ISO-8601 pattern — date-only through millisecond precision with an
|
|
36
|
+
* optional timezone. Anchored at BOTH ends on purpose: a loose prefix-only
|
|
37
|
+
* match (`/^\d{4}-\d{2}-\d{2}/`) also swallows strings that merely START
|
|
38
|
+
* with something date-shaped (order numbers, slugs, serials), silently
|
|
39
|
+
* rewriting a legitimate string predicate into a Date one.
|
|
40
|
+
*
|
|
41
|
+
* THE single source of truth — `query-parser/coerce.ts` imports it rather
|
|
42
|
+
* than keeping a second copy.
|
|
43
|
+
*/
|
|
44
|
+
const ISO_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
45
|
+
/**
|
|
46
|
+
* Coerce one value to a `Date` when it is an unambiguous ISO-8601 string.
|
|
47
|
+
* Anything else — including an unparseable date-shaped string — is returned
|
|
48
|
+
* untouched, so this is always safe to apply.
|
|
49
|
+
*/
|
|
50
|
+
function tryCoerceIsoDate(value) {
|
|
51
|
+
if (typeof value !== "string" || !ISO_DATE_PATTERN.test(value)) return value;
|
|
52
|
+
const parsed = new Date(value);
|
|
53
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Range operators whose operand is compared by BSON/SQL type and therefore
|
|
57
|
+
* must be a real date, not a string. Equality is deliberately EXCLUDED:
|
|
58
|
+
* `{ status: '2026-01-01' }` is far more likely a string id than a date, and
|
|
59
|
+
* an `eq` against a Date column is the one case a caller can express exactly
|
|
60
|
+
* by passing a `Date`. Both bare and `$`-prefixed spellings are listed.
|
|
61
|
+
*/
|
|
62
|
+
const RANGE_OPS = /* @__PURE__ */ new Set([
|
|
63
|
+
"gt",
|
|
64
|
+
"gte",
|
|
65
|
+
"lt",
|
|
66
|
+
"lte",
|
|
67
|
+
"$gt",
|
|
68
|
+
"$gte",
|
|
69
|
+
"$lt",
|
|
70
|
+
"$lte"
|
|
71
|
+
]);
|
|
72
|
+
/** Logical operators whose operand is an ARRAY of sub-filters. */
|
|
73
|
+
const LOGICAL_ARRAY_OPS = /* @__PURE__ */ new Set([
|
|
74
|
+
"$and",
|
|
75
|
+
"$or",
|
|
76
|
+
"$nor",
|
|
77
|
+
"and",
|
|
78
|
+
"or",
|
|
79
|
+
"nor"
|
|
80
|
+
]);
|
|
81
|
+
/** Logical operators whose operand is a SINGLE nested sub-filter. */
|
|
82
|
+
const LOGICAL_OBJECT_OPS = /* @__PURE__ */ new Set(["$not", "not"]);
|
|
83
|
+
function isPlainObject(value) {
|
|
84
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Walk a record-shape filter and coerce ISO-date strings on range operators
|
|
88
|
+
* to `Date`, recursing through logical wrappers. Returns a new object;
|
|
89
|
+
* the input is never mutated. Non-range operators, real nested documents,
|
|
90
|
+
* and already-typed values pass through unchanged.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* coerceFilterDates({ $and: [{ createdAt: { $gte: '2026-04-01' } }] })
|
|
95
|
+
* // → { $and: [{ createdAt: { $gte: Date(2026-04-01) } }] }
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
function coerceFilterDates(filter) {
|
|
99
|
+
const out = {};
|
|
100
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
101
|
+
if (LOGICAL_ARRAY_OPS.has(key) && Array.isArray(value)) {
|
|
102
|
+
out[key] = value.map((entry) => isPlainObject(entry) ? coerceFilterDates(entry) : entry);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (LOGICAL_OBJECT_OPS.has(key) && isPlainObject(value)) {
|
|
106
|
+
out[key] = coerceFilterDates(value);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (isPlainObject(value)) {
|
|
110
|
+
let changed = false;
|
|
111
|
+
const coerced = {};
|
|
112
|
+
for (const [op, operand] of Object.entries(value)) if (RANGE_OPS.has(op)) {
|
|
113
|
+
const next = tryCoerceIsoDate(operand);
|
|
114
|
+
coerced[op] = next;
|
|
115
|
+
if (next !== operand) changed = true;
|
|
116
|
+
} else coerced[op] = operand;
|
|
117
|
+
out[key] = changed ? coerced : value;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
out[key] = value;
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
export { ISO_DATE_PATTERN, coerceFilterDates, tryCoerceIsoDate };
|
package/dist/filter/index.d.mts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Filter, FilterAnd, FilterEq, FilterExists, FilterFalse, FilterGt, FilterGte, FilterIn, FilterLike, FilterLt, FilterLte, FilterNe, FilterNin, FilterNot, FilterOp, FilterOr, FilterRaw, FilterRegex, FilterTrue } from "./types.mjs";
|
|
2
2
|
import { FALSE, TRUE, and, anyOf as in_, between, contains, endsWith, eq, exists, gt, gte, iEq, invert as not, isNotNull, isNull, like, lt, lte, ne, nin, or, raw, regex, startsWith } from "./builders.mjs";
|
|
3
|
+
import { ISO_DATE_PATTERN, coerceFilterDates, tryCoerceIsoDate } from "./coerce-dates.mjs";
|
|
3
4
|
import { recordToFilter } from "./from-record.mjs";
|
|
4
5
|
import { isFilter } from "./guard.mjs";
|
|
5
6
|
import { asPredicate, matchFilter } from "./match.mjs";
|
|
6
7
|
import { matchesRecordFilter, policyRecordToFilter } from "./match-record.mjs";
|
|
7
8
|
import { SCOPE_ANY, buildTenantScope, mergeScope } from "./scope.mjs";
|
|
8
9
|
import { collectFields, mapFilter, walkFilter } from "./walk.mjs";
|
|
9
|
-
export { FALSE, type Filter, type FilterAnd, type FilterEq, type FilterExists, type FilterFalse, type FilterGt, type FilterGte, type FilterIn, type FilterLike, type FilterLt, type FilterLte, type FilterNe, type FilterNin, type FilterNot, type FilterOp, type FilterOr, type FilterRaw, type FilterRegex, type FilterTrue, SCOPE_ANY, TRUE, and, in_ as anyOf, asPredicate, between, buildTenantScope, collectFields, contains, endsWith, eq, exists, gt, gte, iEq, in_, not as invert, isFilter, isNotNull, isNull, like, lt, lte, mapFilter, matchFilter, matchesRecordFilter, mergeScope, ne, nin, nin as noneOf, not, or, policyRecordToFilter, raw, recordToFilter, regex, startsWith, walkFilter };
|
|
10
|
+
export { FALSE, type Filter, type FilterAnd, type FilterEq, type FilterExists, type FilterFalse, type FilterGt, type FilterGte, type FilterIn, type FilterLike, type FilterLt, type FilterLte, type FilterNe, type FilterNin, type FilterNot, type FilterOp, type FilterOr, type FilterRaw, type FilterRegex, type FilterTrue, ISO_DATE_PATTERN, SCOPE_ANY, TRUE, and, in_ as anyOf, asPredicate, between, buildTenantScope, coerceFilterDates, collectFields, contains, endsWith, eq, exists, gt, gte, iEq, in_, not as invert, isFilter, isNotNull, isNull, like, lt, lte, mapFilter, matchFilter, matchesRecordFilter, mergeScope, ne, nin, nin as noneOf, not, or, policyRecordToFilter, raw, recordToFilter, regex, startsWith, tryCoerceIsoDate, walkFilter };
|
package/dist/filter/index.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { isFilter } from "./guard.mjs";
|
|
2
2
|
import { collectFields, mapFilter, walkFilter } from "./walk.mjs";
|
|
3
3
|
import { FALSE, TRUE, and, anyOf as in_, between, contains, endsWith, eq, exists, gt, gte, iEq, invert as not, isNotNull, isNull, like, lt, lte, ne, nin, or, raw, regex, startsWith } from "./builders.mjs";
|
|
4
|
+
import { ISO_DATE_PATTERN, coerceFilterDates, tryCoerceIsoDate } from "./coerce-dates.mjs";
|
|
4
5
|
import { recordToFilter } from "./from-record.mjs";
|
|
5
6
|
import { asPredicate, matchFilter } from "./match.mjs";
|
|
6
7
|
import { matchesRecordFilter, policyRecordToFilter } from "./match-record.mjs";
|
|
7
8
|
import { SCOPE_ANY, buildTenantScope, mergeScope } from "./scope.mjs";
|
|
8
|
-
export { FALSE, SCOPE_ANY, TRUE, and, in_ as anyOf, asPredicate, between, buildTenantScope, collectFields, contains, endsWith, eq, exists, gt, gte, iEq, in_, not as invert, isFilter, isNotNull, isNull, like, lt, lte, mapFilter, matchFilter, matchesRecordFilter, mergeScope, ne, nin, nin as noneOf, not, or, policyRecordToFilter, raw, recordToFilter, regex, startsWith, walkFilter };
|
|
9
|
+
export { FALSE, ISO_DATE_PATTERN, SCOPE_ANY, TRUE, and, in_ as anyOf, asPredicate, between, buildTenantScope, coerceFilterDates, collectFields, contains, endsWith, eq, exists, gt, gte, iEq, in_, not as invert, isFilter, isNotNull, isNull, like, lt, lte, mapFilter, matchFilter, matchesRecordFilter, mergeScope, ne, nin, nin as noneOf, not, or, policyRecordToFilter, raw, recordToFilter, regex, startsWith, tryCoerceIsoDate, walkFilter };
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
import { ISO_DATE_PATTERN } from "../filter/coerce-dates.mjs";
|
|
1
2
|
//#region src/query-parser/coerce.ts
|
|
3
|
+
/**
|
|
4
|
+
* Scalar coercion. URLs are strings; filters compare against typed fields.
|
|
5
|
+
* The parser uses `fieldTypes` hints when provided, otherwise a safe
|
|
6
|
+
* heuristic that avoids the classic footguns (string SKUs becoming
|
|
7
|
+
* numbers, numeric-looking strings becoming Dates).
|
|
8
|
+
*/
|
|
2
9
|
const BOOLEAN_STRINGS = /* @__PURE__ */ new Set([
|
|
3
10
|
"true",
|
|
4
11
|
"1",
|
|
@@ -11,7 +18,7 @@ const FALSEY_STRINGS = /* @__PURE__ */ new Set([
|
|
|
11
18
|
"no",
|
|
12
19
|
"off"
|
|
13
20
|
]);
|
|
14
|
-
const ISO_DATE_RE =
|
|
21
|
+
const ISO_DATE_RE = ISO_DATE_PATTERN;
|
|
15
22
|
/**
|
|
16
23
|
* Coerce a single URL value to its field-declared type, or to a best-guess
|
|
17
24
|
* scalar when no hint exists. Always returns `string`, `number`, `boolean`,
|
package/package.json
CHANGED
|
@@ -1,180 +1,180 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
2
|
+
"name": "@classytic/repo-core",
|
|
3
|
+
"version": "0.19.0",
|
|
4
|
+
"description": "Driver-agnostic repository primitives: hooks, Filter IR, operations, pagination, cache contract. Foundation for mongokit, sqlitekit, pgkit, and prismakit. Lean by design — no plugins ship here; each kit owns its own.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE",
|
|
11
|
+
"CHANGELOG.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=22"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
"./hooks": {
|
|
18
|
+
"types": "./dist/hooks/index.d.mts",
|
|
19
|
+
"default": "./dist/hooks/index.mjs"
|
|
20
|
+
},
|
|
21
|
+
"./operations": {
|
|
22
|
+
"types": "./dist/operations/index.d.mts",
|
|
23
|
+
"default": "./dist/operations/index.mjs"
|
|
24
|
+
},
|
|
25
|
+
"./errors": {
|
|
26
|
+
"types": "./dist/errors/index.d.mts",
|
|
27
|
+
"default": "./dist/errors/index.mjs"
|
|
28
|
+
},
|
|
29
|
+
"./pagination": {
|
|
30
|
+
"types": "./dist/pagination/index.d.mts",
|
|
31
|
+
"default": "./dist/pagination/index.mjs"
|
|
32
|
+
},
|
|
33
|
+
"./repository": {
|
|
34
|
+
"types": "./dist/repository/index.d.mts",
|
|
35
|
+
"default": "./dist/repository/index.mjs"
|
|
36
|
+
},
|
|
37
|
+
"./filter": {
|
|
38
|
+
"types": "./dist/filter/index.d.mts",
|
|
39
|
+
"default": "./dist/filter/index.mjs"
|
|
40
|
+
},
|
|
41
|
+
"./update": {
|
|
42
|
+
"types": "./dist/update/index.d.mts",
|
|
43
|
+
"default": "./dist/update/index.mjs"
|
|
44
|
+
},
|
|
45
|
+
"./query-parser": {
|
|
46
|
+
"types": "./dist/query-parser/index.d.mts",
|
|
47
|
+
"default": "./dist/query-parser/index.mjs"
|
|
48
|
+
},
|
|
49
|
+
"./context": {
|
|
50
|
+
"types": "./dist/context/index.d.mts",
|
|
51
|
+
"default": "./dist/context/index.mjs"
|
|
52
|
+
},
|
|
53
|
+
"./cache": {
|
|
54
|
+
"types": "./dist/cache/index.d.mts",
|
|
55
|
+
"default": "./dist/cache/index.mjs"
|
|
56
|
+
},
|
|
57
|
+
"./events": {
|
|
58
|
+
"types": "./dist/events/index.d.mts",
|
|
59
|
+
"default": "./dist/events/index.mjs"
|
|
60
|
+
},
|
|
61
|
+
"./schema": {
|
|
62
|
+
"types": "./dist/schema/index.d.mts",
|
|
63
|
+
"default": "./dist/schema/index.mjs"
|
|
64
|
+
},
|
|
65
|
+
"./testing": {
|
|
66
|
+
"types": "./dist/testing/index.d.mts",
|
|
67
|
+
"default": "./dist/testing/index.mjs"
|
|
68
|
+
},
|
|
69
|
+
"./tenant": {
|
|
70
|
+
"types": "./dist/tenant/index.d.mts",
|
|
71
|
+
"default": "./dist/tenant/index.mjs"
|
|
72
|
+
},
|
|
73
|
+
"./lookup": {
|
|
74
|
+
"types": "./dist/lookup/index.d.mts",
|
|
75
|
+
"default": "./dist/lookup/index.mjs"
|
|
76
|
+
},
|
|
77
|
+
"./adapter": {
|
|
78
|
+
"types": "./dist/adapter/index.d.mts",
|
|
79
|
+
"default": "./dist/adapter/index.mjs"
|
|
80
|
+
},
|
|
81
|
+
"./better-auth": {
|
|
82
|
+
"types": "./dist/better-auth/index.d.mts",
|
|
83
|
+
"default": "./dist/better-auth/index.mjs"
|
|
84
|
+
},
|
|
85
|
+
"./aggregate": {
|
|
86
|
+
"types": "./dist/aggregate/index.d.mts",
|
|
87
|
+
"default": "./dist/aggregate/index.mjs"
|
|
88
|
+
},
|
|
89
|
+
"./plugins": {
|
|
90
|
+
"types": "./dist/plugins/index.d.mts",
|
|
91
|
+
"default": "./dist/plugins/index.mjs"
|
|
92
|
+
},
|
|
93
|
+
"./lock": {
|
|
94
|
+
"types": "./dist/lock/index.d.mts",
|
|
95
|
+
"default": "./dist/lock/index.mjs"
|
|
96
|
+
},
|
|
97
|
+
"./usage": {
|
|
98
|
+
"types": "./dist/usage/index.d.mts",
|
|
99
|
+
"default": "./dist/usage/index.mjs"
|
|
100
|
+
},
|
|
101
|
+
"./package.json": "./package.json",
|
|
102
|
+
"./sync": {
|
|
103
|
+
"types": "./dist/sync/index.d.mts",
|
|
104
|
+
"default": "./dist/sync/index.mjs"
|
|
105
|
+
},
|
|
106
|
+
"./cleanup": {
|
|
107
|
+
"types": "./dist/cleanup/index.d.mts",
|
|
108
|
+
"default": "./dist/cleanup/index.mjs"
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
"keywords": [
|
|
112
|
+
"repository",
|
|
113
|
+
"repository-pattern",
|
|
114
|
+
"data-access",
|
|
115
|
+
"hooks",
|
|
116
|
+
"filter-ir",
|
|
117
|
+
"pagination",
|
|
118
|
+
"cursor-pagination",
|
|
119
|
+
"keyset-pagination",
|
|
120
|
+
"plugin-based",
|
|
121
|
+
"driver-agnostic",
|
|
122
|
+
"typescript",
|
|
123
|
+
"esm"
|
|
124
|
+
],
|
|
125
|
+
"author": "Classytic <classytic.dev@gmail.com> (https://github.com/classytic)",
|
|
126
|
+
"license": "MIT",
|
|
127
|
+
"repository": {
|
|
128
|
+
"type": "git",
|
|
129
|
+
"url": "git+https://github.com/classytic/repo-core.git"
|
|
130
|
+
},
|
|
131
|
+
"bugs": {
|
|
132
|
+
"url": "https://github.com/classytic/repo-core/issues"
|
|
133
|
+
},
|
|
134
|
+
"homepage": "https://github.com/classytic/repo-core#readme",
|
|
135
|
+
"scripts": {
|
|
136
|
+
"build": "tsdown",
|
|
137
|
+
"dev": "tsdown --watch",
|
|
138
|
+
"test": "vitest run --project unit --project integration",
|
|
139
|
+
"test:unit": "vitest run --project unit",
|
|
140
|
+
"test:integration": "vitest run --project integration",
|
|
141
|
+
"test:e2e": "vitest run --project e2e",
|
|
142
|
+
"test:all": "vitest run",
|
|
143
|
+
"test:watch": "vitest --project unit --project integration",
|
|
144
|
+
"bench": "vitest bench --run --project bench",
|
|
145
|
+
"test:coverage": "vitest run --coverage",
|
|
146
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
|
|
147
|
+
"lint": "biome check src tests",
|
|
148
|
+
"lint:fix": "biome check src tests --write",
|
|
149
|
+
"format": "biome format src tests --write",
|
|
150
|
+
"check": "biome ci src tests --diagnostic-level=error",
|
|
151
|
+
"knip": "knip",
|
|
152
|
+
"push": "classytic-push",
|
|
153
|
+
"prepublishOnly": "npm run check && npm run build && npm run typecheck && npm test",
|
|
154
|
+
"release:tag": "node -e \"require('child_process').execSync('npm run push -- v'+require('./package.json').version,{stdio:'inherit'})\"",
|
|
155
|
+
"release": "npm run push -- main && npm run release:tag && npm publish",
|
|
156
|
+
"publish:dry": "npm publish --dry-run --access public",
|
|
157
|
+
"publish:npm": "npm publish --access public"
|
|
158
|
+
},
|
|
159
|
+
"devDependencies": {
|
|
160
|
+
"@arethetypeswrong/cli": "^0.18.2",
|
|
161
|
+
"@biomejs/biome": "^2.4.12",
|
|
162
|
+
"@classytic/dev-tools": "^0.2.0",
|
|
163
|
+
"@types/node": "^22.0.0",
|
|
164
|
+
"@vitest/coverage-v8": "^4.1.4",
|
|
165
|
+
"fast-check": "^4.7.0",
|
|
166
|
+
"knip": "^6.3.0",
|
|
167
|
+
"publint": "^0.3.18",
|
|
168
|
+
"tsdown": "^0.22.5",
|
|
169
|
+
"typescript": "^7.0.2",
|
|
170
|
+
"vitest": "^4.1.4"
|
|
171
|
+
},
|
|
172
|
+
"peerDependencies": {
|
|
173
|
+
"vitest": "^3.0.0 || ^4.0.0"
|
|
174
|
+
},
|
|
175
|
+
"peerDependenciesMeta": {
|
|
176
|
+
"vitest": {
|
|
177
|
+
"optional": true
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
180
|
}
|