@orthacms/utils-server 0.0.0-reserve.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/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/lib/clamp-int.d.ts +8 -0
- package/dist/lib/clamp-int.d.ts.map +1 -0
- package/dist/lib/clamp-int.js +15 -0
- package/dist/lib/filters/filter-exceptions.d.ts +92 -0
- package/dist/lib/filters/filter-exceptions.d.ts.map +1 -0
- package/dist/lib/filters/filter-exceptions.js +117 -0
- package/dist/lib/filters/negation.d.ts +33 -0
- package/dist/lib/filters/negation.d.ts.map +1 -0
- package/dist/lib/filters/negation.js +59 -0
- package/dist/lib/filters/operator-support.d.ts +25 -0
- package/dist/lib/filters/operator-support.d.ts.map +1 -0
- package/dist/lib/filters/operator-support.js +88 -0
- package/dist/lib/filters/own-property.d.ts +28 -0
- package/dist/lib/filters/own-property.d.ts.map +1 -0
- package/dist/lib/filters/own-property.js +34 -0
- package/dist/lib/filters/parse-filter-tree.d.ts +17 -0
- package/dist/lib/filters/parse-filter-tree.d.ts.map +1 -0
- package/dist/lib/filters/parse-filter-tree.js +107 -0
- package/dist/lib/filters/relation-exists.d.ts +35 -0
- package/dist/lib/filters/relation-exists.d.ts.map +1 -0
- package/dist/lib/filters/relation-exists.js +119 -0
- package/dist/lib/filters/resolve-leaf.d.ts +8 -0
- package/dist/lib/filters/resolve-leaf.d.ts.map +1 -0
- package/dist/lib/filters/resolve-leaf.js +170 -0
- package/dist/lib/filters/scalar-op.d.ts +14 -0
- package/dist/lib/filters/scalar-op.d.ts.map +1 -0
- package/dist/lib/filters/scalar-op.js +66 -0
- package/dist/lib/filters/table-helpers.d.ts +67 -0
- package/dist/lib/filters/table-helpers.d.ts.map +1 -0
- package/dist/lib/filters/table-helpers.js +68 -0
- package/dist/lib/filters/tree-to-drizzle.d.ts +35 -0
- package/dist/lib/filters/tree-to-drizzle.d.ts.map +1 -0
- package/dist/lib/filters/tree-to-drizzle.js +76 -0
- package/dist/lib/filters/types.d.ts +217 -0
- package/dist/lib/filters/types.d.ts.map +1 -0
- package/dist/lib/filters/types.js +35 -0
- package/dist/lib/pg-errors.d.ts +43 -0
- package/dist/lib/pg-errors.d.ts.map +1 -0
- package/dist/lib/pg-errors.js +82 -0
- package/package.json +35 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.columnOf = columnOf;
|
|
4
|
+
exports.rebind = rebind;
|
|
5
|
+
exports.primaryKey = primaryKey;
|
|
6
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
7
|
+
const filter_exceptions_1 = require("./filter-exceptions");
|
|
8
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a column on a table by name, or throw if absent.
|
|
11
|
+
*
|
|
12
|
+
* The lookup is `Object.hasOwn`-guarded: drizzle's column map is a plain
|
|
13
|
+
* object, so `cols['constructor']` would otherwise return `Object` itself and
|
|
14
|
+
* this function would hand a `Function` to drizzle as a `Column`. The parser's
|
|
15
|
+
* whitelist is the primary guard, but this is the site where an escaped name
|
|
16
|
+
* turns into malformed SQL rather than an error, so it guards itself too.
|
|
17
|
+
*/
|
|
18
|
+
function columnOf(table, name) {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
const cols = (0, drizzle_orm_1.getTableColumns)(table);
|
|
21
|
+
const col = Object.hasOwn(cols, name) ? cols[name] : undefined;
|
|
22
|
+
if (!col)
|
|
23
|
+
throw new filter_exceptions_1.FilterSchemaException(`column "${name}" not on table`);
|
|
24
|
+
return col;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Re-resolve a prebuilt column against the table the translator is actually
|
|
28
|
+
* querying.
|
|
29
|
+
*
|
|
30
|
+
* A `RelationSchema`'s parent-side columns (`fk` on a `many-to-one` /
|
|
31
|
+
* `self-referential`, and every `parentKey`) are built once, against the
|
|
32
|
+
* **physical** parent table. That is wrong the moment the parent is an
|
|
33
|
+
* *alias* — which happens for every relation nested under a
|
|
34
|
+
* `self-referential` hop, whose subquery is `FROM t AS qb_x`. An unaliased
|
|
35
|
+
* `t.col` inside that subquery is still resolvable from the OUTERMOST
|
|
36
|
+
* query's `FROM t`, so Postgres silently binds the correlation to the root
|
|
37
|
+
* row instead of the aliased one: `parent.author.name` would filter on the
|
|
38
|
+
* ROOT row's author, and `parent.parent.name` would collapse to
|
|
39
|
+
* `parent.name`. Valid SQL, wrong rows, no error.
|
|
40
|
+
*
|
|
41
|
+
* Rebinding by the column's own DB name against the queried table fixes the
|
|
42
|
+
* correlation. It is a no-op when the parent is the physical table (the
|
|
43
|
+
* lookup finds the identical column), so unaliased paths are unaffected.
|
|
44
|
+
* Only ever call this for columns that live on the parent **by
|
|
45
|
+
* construction** — never for a target-side `fk` (`one-to-many`), where the
|
|
46
|
+
* name could collide with an unrelated parent column.
|
|
47
|
+
*/
|
|
48
|
+
function rebind(column, table) {
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
const cols = (0, drizzle_orm_1.getTableColumns)(table);
|
|
51
|
+
for (const candidate of Object.values(cols)) {
|
|
52
|
+
if (candidate.name === column.name)
|
|
53
|
+
return candidate;
|
|
54
|
+
}
|
|
55
|
+
return column;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve a table's primary key (defaults to an `id` column).
|
|
59
|
+
* Callers can override with an explicit `parentKey`/`targetKey` in the
|
|
60
|
+
* relation schema when the table's primary key is named differently.
|
|
61
|
+
*/
|
|
62
|
+
function primaryKey(table) {
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
64
|
+
const cols = (0, drizzle_orm_1.getTableColumns)(table);
|
|
65
|
+
if (Object.hasOwn(cols, 'id'))
|
|
66
|
+
return cols['id'];
|
|
67
|
+
throw new filter_exceptions_1.FilterSchemaException('table has no `id` column — declare parentKey/targetKey explicitly');
|
|
68
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type SQL } from 'drizzle-orm';
|
|
2
|
+
import { type DbLike, type TableLike } from './table-helpers';
|
|
3
|
+
import type { FilterSchema, ParsedNode, ParsedRule } from './types';
|
|
4
|
+
export type { DbLike, TableLike } from './table-helpers';
|
|
5
|
+
/**
|
|
6
|
+
* Hook a host can supply to handle filter rules whose field is in
|
|
7
|
+
* {@link FilterSchema.extensionFields}. Returns a Drizzle SQL fragment
|
|
8
|
+
* that gets spliced into the WHERE tree at the leaf's position, so
|
|
9
|
+
* extension rules compose with native ones inside AND/OR groups.
|
|
10
|
+
*
|
|
11
|
+
* The translator awaits each call sequentially during the walk; if
|
|
12
|
+
* an extension performs a DB roundtrip its latency adds linearly per
|
|
13
|
+
* leaf — extensions that need expensive lookups should prepare data
|
|
14
|
+
* once per request and capture it in the closure.
|
|
15
|
+
*/
|
|
16
|
+
export type FilterExtensionResolver = (rule: ParsedRule) => Promise<SQL>;
|
|
17
|
+
/** Optional translator hooks — currently just the extension resolver. */
|
|
18
|
+
export interface ApplyFilterTreeOptions {
|
|
19
|
+
/** Called for any leaf whose `path[0]` is in `schema.extensionFields`. */
|
|
20
|
+
resolveExtension?: FilterExtensionResolver;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Translate a parsed filter tree into a single Drizzle SQL fragment.
|
|
24
|
+
* Returns `undefined` when the tree is empty (or every group collapses
|
|
25
|
+
* to nothing) so callers can skip the WHERE clause without a special
|
|
26
|
+
* case. Group nodes emit `and(...)` / `or(...)`; rule nodes share the
|
|
27
|
+
* same scalar / relation translation.
|
|
28
|
+
*
|
|
29
|
+
* Async because extension resolvers may be async (e.g. a role filter
|
|
30
|
+
* runs a tiny `SELECT slug FROM roles` to validate enum membership).
|
|
31
|
+
* Non-extension trees stay synchronous in spirit — every native branch
|
|
32
|
+
* resolves immediately, no awaits hit the wire.
|
|
33
|
+
*/
|
|
34
|
+
export declare function applyFilterTree(tree: ParsedNode | null | undefined, schema: FilterSchema, rootTable: TableLike, db: DbLike, options?: ApplyFilterTreeOptions): Promise<SQL | undefined>;
|
|
35
|
+
//# sourceMappingURL=tree-to-drizzle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-to-drizzle.d.ts","sourceRoot":"","sources":["../../../src/lib/filters/tree-to-drizzle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,GAAG,EAAE,MAAM,aAAa,CAAC;AAMrD,OAAO,EAAY,KAAK,MAAM,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEpE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEzD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;AAEzE,yEAAyE;AACzE,MAAM,WAAW,sBAAsB;IACnC,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;CAC9C;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CACjC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,EACnC,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,SAAS,EACpB,EAAE,EAAE,MAAM,EACV,OAAO,GAAE,sBAA2B,GACrC,OAAO,CAAC,GAAG,GAAG,SAAS,CAAC,CAG1B"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyFilterTree = applyFilterTree;
|
|
4
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
5
|
+
const filter_exceptions_1 = require("./filter-exceptions");
|
|
6
|
+
const negation_1 = require("./negation");
|
|
7
|
+
const own_property_1 = require("./own-property");
|
|
8
|
+
const relation_exists_1 = require("./relation-exists");
|
|
9
|
+
const scalar_op_1 = require("./scalar-op");
|
|
10
|
+
const table_helpers_1 = require("./table-helpers");
|
|
11
|
+
/**
|
|
12
|
+
* Translate a parsed filter tree into a single Drizzle SQL fragment.
|
|
13
|
+
* Returns `undefined` when the tree is empty (or every group collapses
|
|
14
|
+
* to nothing) so callers can skip the WHERE clause without a special
|
|
15
|
+
* case. Group nodes emit `and(...)` / `or(...)`; rule nodes share the
|
|
16
|
+
* same scalar / relation translation.
|
|
17
|
+
*
|
|
18
|
+
* Async because extension resolvers may be async (e.g. a role filter
|
|
19
|
+
* runs a tiny `SELECT slug FROM roles` to validate enum membership).
|
|
20
|
+
* Non-extension trees stay synchronous in spirit — every native branch
|
|
21
|
+
* resolves immediately, no awaits hit the wire.
|
|
22
|
+
*/
|
|
23
|
+
async function applyFilterTree(tree, schema, rootTable, db, options = {}) {
|
|
24
|
+
if (!tree)
|
|
25
|
+
return undefined;
|
|
26
|
+
return walk(tree, schema, rootTable, db, options);
|
|
27
|
+
}
|
|
28
|
+
async function walk(node, schema, parent, db, options) {
|
|
29
|
+
if (node.kind === 'rule') {
|
|
30
|
+
return translateRule(node, schema, parent, db, options);
|
|
31
|
+
}
|
|
32
|
+
const parts = [];
|
|
33
|
+
for (const child of node.children) {
|
|
34
|
+
const piece = await walk(child, schema, parent, db, options);
|
|
35
|
+
if (piece !== undefined)
|
|
36
|
+
parts.push(piece);
|
|
37
|
+
}
|
|
38
|
+
if (parts.length === 0)
|
|
39
|
+
return undefined;
|
|
40
|
+
if (parts.length === 1)
|
|
41
|
+
return parts[0];
|
|
42
|
+
return node.combinator === 'or' ? (0, drizzle_orm_1.or)(...parts) : (0, drizzle_orm_1.and)(...parts);
|
|
43
|
+
}
|
|
44
|
+
async function translateRule(rule, schema, parent, db, options) {
|
|
45
|
+
const head = rule.path[0];
|
|
46
|
+
if (schema.extensionFields?.has(head)) {
|
|
47
|
+
if (!options.resolveExtension) {
|
|
48
|
+
throw new filter_exceptions_1.FilterException(filter_exceptions_1.FilterErrorCode.InvalidNode, `extension field "${head}" referenced but no resolver provided`, { field: head });
|
|
49
|
+
}
|
|
50
|
+
return options.resolveExtension(rule);
|
|
51
|
+
}
|
|
52
|
+
if (rule.path.length === 1) {
|
|
53
|
+
return (0, scalar_op_1.scalar)((0, table_helpers_1.columnOf)(parent, rule.path[0]), rule.op, rule.value);
|
|
54
|
+
}
|
|
55
|
+
const key = rule.path[0];
|
|
56
|
+
// `Object.hasOwn`-guarded: `relations['valueOf']` on a plain object map
|
|
57
|
+
// returns `Object.prototype.valueOf`, which passes this truthiness check
|
|
58
|
+
// and then falls out of `relationExists`'s `switch (rel.kind)` as
|
|
59
|
+
// `undefined` — the predicate the user asked for is silently dropped and
|
|
60
|
+
// the endpoint answers 200 unfiltered.
|
|
61
|
+
const rel = (0, own_property_1.own)(schema.relations, key);
|
|
62
|
+
if (!rel) {
|
|
63
|
+
throw new filter_exceptions_1.FilterException(filter_exceptions_1.FilterErrorCode.UnknownRelation, `unknown relation "${key}"`, { relation: key });
|
|
64
|
+
}
|
|
65
|
+
const leaf = { path: rule.path.slice(1), op: rule.op, value: rule.value };
|
|
66
|
+
// A negating rule on a relation path asks for the ABSENCE of a matching
|
|
67
|
+
// related row, which is `NOT EXISTS(… positive …)` — NOT the naive
|
|
68
|
+
// `EXISTS(… negated …)`, which on a to-many relation asserts the
|
|
69
|
+
// opposite of what the user wrote. The negation wraps the OUTERMOST
|
|
70
|
+
// hop, so a multi-hop path negates the whole chain ("no (author,
|
|
71
|
+
// company) pair matches") rather than just its last segment.
|
|
72
|
+
if ((0, negation_1.isNegatingLeaf)(rule.op, rule.value)) {
|
|
73
|
+
return (0, drizzle_orm_1.not)((0, relation_exists_1.relationExists)(rel, parent, (0, negation_1.positiveLeaf)(leaf), db));
|
|
74
|
+
}
|
|
75
|
+
return (0, relation_exists_1.relationExists)(rel, parent, leaf, db);
|
|
76
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import type { AnyColumn, SQL, Table } from 'drizzle-orm';
|
|
2
|
+
import type { TableLike } from './table-helpers';
|
|
3
|
+
/**
|
|
4
|
+
* Builds a relation's `scope` predicate — the workspace + soft-delete
|
|
5
|
+
* guard ANDed inside its EXISTS subquery. It is a function, not a
|
|
6
|
+
* prebuilt `SQL`, because the table it must reference is not always the
|
|
7
|
+
* physical target: a `self-referential` relation aliases the target, and
|
|
8
|
+
* the predicate has to bind to that alias, not the outer table of the
|
|
9
|
+
* same name. The translator passes whichever table it actually queries
|
|
10
|
+
* (aliased or not), so the host resolves columns from that — e.g. via
|
|
11
|
+
* `getTableColumns(target)`. Return `undefined` to add nothing.
|
|
12
|
+
*/
|
|
13
|
+
export type RelationScope = (target: TableLike) => SQL | undefined;
|
|
14
|
+
/** Named constants for `FilterOperator` — use in switches and comparisons. */
|
|
15
|
+
export declare const FilterOperator: {
|
|
16
|
+
readonly Eq: "eq";
|
|
17
|
+
readonly Ne: "ne";
|
|
18
|
+
readonly Gt: "gt";
|
|
19
|
+
readonly Gte: "gte";
|
|
20
|
+
readonly Lt: "lt";
|
|
21
|
+
readonly Lte: "lte";
|
|
22
|
+
readonly In: "in";
|
|
23
|
+
readonly Nin: "nin";
|
|
24
|
+
readonly Like: "like";
|
|
25
|
+
readonly Ilike: "ilike";
|
|
26
|
+
readonly Nilike: "nilike";
|
|
27
|
+
readonly Null: "null";
|
|
28
|
+
};
|
|
29
|
+
/** Standard REST operator names. Translator maps each to a Drizzle helper. */
|
|
30
|
+
export type FilterOperator = (typeof FilterOperator)[keyof typeof FilterOperator];
|
|
31
|
+
/** Named constants for `ScalarFieldType` — use in switches. */
|
|
32
|
+
export declare const ScalarFieldType: {
|
|
33
|
+
readonly String: "string";
|
|
34
|
+
readonly Number: "number";
|
|
35
|
+
readonly Boolean: "boolean";
|
|
36
|
+
readonly Uuid: "uuid";
|
|
37
|
+
readonly Date: "date";
|
|
38
|
+
readonly Enum: "enum";
|
|
39
|
+
};
|
|
40
|
+
/** How the parser coerces raw URL string values. */
|
|
41
|
+
export type ScalarFieldType = (typeof ScalarFieldType)[keyof typeof ScalarFieldType];
|
|
42
|
+
/** Named constants for `RelationSchema['kind']` — use in switches. */
|
|
43
|
+
export declare const RelationKind: {
|
|
44
|
+
readonly OneToOne: "one-to-one";
|
|
45
|
+
readonly OneToMany: "one-to-many";
|
|
46
|
+
readonly ManyToOne: "many-to-one";
|
|
47
|
+
readonly ManyToMany: "many-to-many";
|
|
48
|
+
readonly SelfReferential: "self-referential";
|
|
49
|
+
};
|
|
50
|
+
/** Relation cardinality discriminants. */
|
|
51
|
+
export type RelationKind = (typeof RelationKind)[keyof typeof RelationKind];
|
|
52
|
+
/**
|
|
53
|
+
* Coercion rules for one column.
|
|
54
|
+
*
|
|
55
|
+
* The declared `type` decides two things, not one: how an incoming string is
|
|
56
|
+
* coerced, and **which operators the field may be asked** (`operator-support.ts`
|
|
57
|
+
* holds the table). A field does not accept every operator — the pattern family
|
|
58
|
+
* (`like`/`ilike`/`nilike`) is text-only, because Postgres defines `~~` for text
|
|
59
|
+
* and nothing else.
|
|
60
|
+
*/
|
|
61
|
+
export interface ScalarFieldSchema {
|
|
62
|
+
/** How incoming strings are coerced before hitting Drizzle. */
|
|
63
|
+
type: ScalarFieldType;
|
|
64
|
+
/** Required when `type === 'enum'`. */
|
|
65
|
+
enumValues?: readonly string[];
|
|
66
|
+
}
|
|
67
|
+
/** Columns exposed on one table. */
|
|
68
|
+
export type FieldSchema = Record<string, ScalarFieldSchema>;
|
|
69
|
+
/**
|
|
70
|
+
* Relation descriptor. `kind` discriminates the SQL shape the translator
|
|
71
|
+
* emits (EXISTS, EXISTS + INNER JOIN, aliased self-join).
|
|
72
|
+
*/
|
|
73
|
+
export type RelationSchema = {
|
|
74
|
+
/** Target FK references parent — one row (1:1) or many (1:N). */
|
|
75
|
+
kind: 'one-to-one' | 'one-to-many';
|
|
76
|
+
/** Target table containing the FK. */
|
|
77
|
+
table: Table;
|
|
78
|
+
/** Column on `table` referencing parent's primary key. */
|
|
79
|
+
fk: AnyColumn;
|
|
80
|
+
/** Parent primary key — defaults to `parent.id`. */
|
|
81
|
+
parentKey?: AnyColumn;
|
|
82
|
+
/**
|
|
83
|
+
* Extra predicate ANDed inside the EXISTS subquery, over the
|
|
84
|
+
* target table's own columns — e.g. a workspace boundary and a
|
|
85
|
+
* soft-delete guard. Without it a relation filter traverses rows
|
|
86
|
+
* the root query itself excludes (a soft-deleted or foreign
|
|
87
|
+
* target still matches `relation.field`). See {@link RelationScope}.
|
|
88
|
+
*/
|
|
89
|
+
scope?: RelationScope;
|
|
90
|
+
fields?: FieldSchema;
|
|
91
|
+
relations?: Record<string, RelationSchema>;
|
|
92
|
+
} | {
|
|
93
|
+
/** Parent FK references target. */
|
|
94
|
+
kind: 'many-to-one';
|
|
95
|
+
/** Target table. */
|
|
96
|
+
table: Table;
|
|
97
|
+
/** Column on the parent table referencing target's primary key. */
|
|
98
|
+
fk: AnyColumn;
|
|
99
|
+
/** Target primary key — defaults to `target.id`. */
|
|
100
|
+
targetKey?: AnyColumn;
|
|
101
|
+
/** Workspace + soft-delete guard. See {@link RelationScope}. */
|
|
102
|
+
scope?: RelationScope;
|
|
103
|
+
fields?: FieldSchema;
|
|
104
|
+
relations?: Record<string, RelationSchema>;
|
|
105
|
+
} | {
|
|
106
|
+
/** Many rows link parent to target through a junction table. */
|
|
107
|
+
kind: 'many-to-many';
|
|
108
|
+
/** Junction (through) table. */
|
|
109
|
+
through: Table;
|
|
110
|
+
/** Junction column referencing parent's primary key. */
|
|
111
|
+
fk: AnyColumn;
|
|
112
|
+
/** Junction column referencing target's primary key. */
|
|
113
|
+
targetFk: AnyColumn;
|
|
114
|
+
/** Target table — required when filtering on target fields. */
|
|
115
|
+
table?: Table;
|
|
116
|
+
parentKey?: AnyColumn;
|
|
117
|
+
targetKey?: AnyColumn;
|
|
118
|
+
/**
|
|
119
|
+
* Workspace + soft-delete guard. Because a scope lives on the
|
|
120
|
+
* target table, it can only be applied through the join to
|
|
121
|
+
* `table` — so when it is present the junction-only fast path
|
|
122
|
+
* (filtering on the target FK alone) must yield to the full join.
|
|
123
|
+
* See {@link RelationScope}.
|
|
124
|
+
*/
|
|
125
|
+
scope?: RelationScope;
|
|
126
|
+
fields?: FieldSchema;
|
|
127
|
+
relations?: Record<string, RelationSchema>;
|
|
128
|
+
} | {
|
|
129
|
+
/** Parent references another row in the same table. */
|
|
130
|
+
kind: 'self-referential';
|
|
131
|
+
/** Same physical table as the parent. */
|
|
132
|
+
table: Table;
|
|
133
|
+
/** Column on the parent referencing its own primary key. */
|
|
134
|
+
fk: AnyColumn;
|
|
135
|
+
/**
|
|
136
|
+
* Alias used for the self-joined table inside the subquery. Must
|
|
137
|
+
* be unique per occurrence in the filter tree, not per relation:
|
|
138
|
+
* two rules on the same self-relation would otherwise share a
|
|
139
|
+
* correlation name and collide.
|
|
140
|
+
*/
|
|
141
|
+
alias: string;
|
|
142
|
+
/**
|
|
143
|
+
* Workspace + soft-delete guard, applied against the aliased
|
|
144
|
+
* self-join (the translator passes the alias). See
|
|
145
|
+
* {@link RelationScope}.
|
|
146
|
+
*/
|
|
147
|
+
scope?: RelationScope;
|
|
148
|
+
fields?: FieldSchema;
|
|
149
|
+
relations?: Record<string, RelationSchema>;
|
|
150
|
+
};
|
|
151
|
+
/** Public filter surface for one endpoint. */
|
|
152
|
+
export interface FilterSchema {
|
|
153
|
+
/** Scalar columns on the root table. */
|
|
154
|
+
fields?: FieldSchema;
|
|
155
|
+
/** Relations traversable from the root table. */
|
|
156
|
+
relations?: Record<string, RelationSchema>;
|
|
157
|
+
/**
|
|
158
|
+
* Field names that should be routed through a host-supplied
|
|
159
|
+
* `resolveExtension` hook instead of the standard scalar / relation
|
|
160
|
+
* translator. Lets a downstream plugin contribute a "virtual"
|
|
161
|
+
* field — e.g. `role` on the user filter — that resolves to a
|
|
162
|
+
* subquery the host stitches into the WHERE tree.
|
|
163
|
+
*
|
|
164
|
+
* Each entry MUST also have a corresponding declaration under
|
|
165
|
+
* {@link fields} so the parser can validate the leaf's value
|
|
166
|
+
* against a {@link ScalarFieldSchema}. The set is consulted only
|
|
167
|
+
* by the translator, not by `parseFilterTree`.
|
|
168
|
+
*/
|
|
169
|
+
extensionFields?: ReadonlySet<string>;
|
|
170
|
+
/** Maximum dotted-path depth. Defaults to 3. */
|
|
171
|
+
maxDepth?: number;
|
|
172
|
+
/**
|
|
173
|
+
* Maximum total number of rules + groups in a tree-shaped filter.
|
|
174
|
+
* Defaults to 50. Guards against pathological JSON payloads that
|
|
175
|
+
* would otherwise translate into very large SQL trees.
|
|
176
|
+
*/
|
|
177
|
+
maxNodes?: number;
|
|
178
|
+
/**
|
|
179
|
+
* Maximum nesting depth of `and`/`or` groups in a tree-shaped filter.
|
|
180
|
+
* Defaults to 5. Independent of {@link maxDepth} (which limits
|
|
181
|
+
* dotted-path depth on a single rule).
|
|
182
|
+
*/
|
|
183
|
+
maxGroupDepth?: number;
|
|
184
|
+
/**
|
|
185
|
+
* Maximum number of elements in a single `in`/`nin` value list.
|
|
186
|
+
* Defaults to 100. Bounds the size of the generated `IN (...)` clause
|
|
187
|
+
* independently of {@link maxNodes} (which counts whole rules, not the
|
|
188
|
+
* elements inside one rule's value list).
|
|
189
|
+
*/
|
|
190
|
+
maxInListLength?: number;
|
|
191
|
+
}
|
|
192
|
+
/** Parser output, one node per URL filter entry. */
|
|
193
|
+
export interface ParsedFilter {
|
|
194
|
+
/** Dotted path split into segments, e.g. `['workspaces','name']`. */
|
|
195
|
+
path: string[];
|
|
196
|
+
/** Requested operator. */
|
|
197
|
+
op: FilterOperator;
|
|
198
|
+
/** Coerced to the declared type; array for `in`/`nin`, boolean for `null`. */
|
|
199
|
+
value: unknown;
|
|
200
|
+
}
|
|
201
|
+
/** Tree-shaped parser output — leaf rule, type-tagged for the union. */
|
|
202
|
+
export interface ParsedRule extends ParsedFilter {
|
|
203
|
+
kind: 'rule';
|
|
204
|
+
}
|
|
205
|
+
/** Tree-shaped parser output — group node combining children with AND or OR. */
|
|
206
|
+
export interface ParsedGroup {
|
|
207
|
+
kind: 'group';
|
|
208
|
+
combinator: 'and' | 'or';
|
|
209
|
+
children: ParsedNode[];
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Tree-shaped parser output. `parseFilterTree` returns a `ParsedGroup`
|
|
213
|
+
* root (or null when the input is empty); the translator walks this
|
|
214
|
+
* tree directly into Drizzle SQL.
|
|
215
|
+
*/
|
|
216
|
+
export type ParsedNode = ParsedRule | ParsedGroup;
|
|
217
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/filters/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,GAAG,GAAG,SAAS,CAAC;AAEnE,8EAA8E;AAC9E,eAAO,MAAM,cAAc;;;;;;;;;;;;;CAajB,CAAC;AAEX,8EAA8E;AAC9E,MAAM,MAAM,cAAc,GACtB,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,CAAC;AAEzD,+DAA+D;AAC/D,eAAO,MAAM,eAAe;;;;;;;CAOlB,CAAC;AAEX,oDAAoD;AACpD,MAAM,MAAM,eAAe,GACvB,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,CAAC;AAE3D,sEAAsE;AACtE,eAAO,MAAM,YAAY;;;;;;CAMf,CAAC;AAEX,0CAA0C;AAC1C,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AAE5E;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAC9B,+DAA+D;IAC/D,IAAI,EAAE,eAAe,CAAC;IACtB,uCAAuC;IACvC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED,oCAAoC;AACpC,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;AAE5D;;;GAGG;AACH,MAAM,MAAM,cAAc,GACpB;IACI,iEAAiE;IACjE,IAAI,EAAE,YAAY,GAAG,aAAa,CAAC;IACnC,sCAAsC;IACtC,KAAK,EAAE,KAAK,CAAC;IACb,0DAA0D;IAC1D,EAAE,EAAE,SAAS,CAAC;IACd,oDAAoD;IACpD,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CAC9C,GACD;IACI,mCAAmC;IACnC,IAAI,EAAE,aAAa,CAAC;IACpB,oBAAoB;IACpB,KAAK,EAAE,KAAK,CAAC;IACb,mEAAmE;IACnE,EAAE,EAAE,SAAS,CAAC;IACd,oDAAoD;IACpD,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,gEAAgE;IAChE,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CAC9C,GACD;IACI,gEAAgE;IAChE,IAAI,EAAE,cAAc,CAAC;IACrB,gCAAgC;IAChC,OAAO,EAAE,KAAK,CAAC;IACf,wDAAwD;IACxD,EAAE,EAAE,SAAS,CAAC;IACd,wDAAwD;IACxD,QAAQ,EAAE,SAAS,CAAC;IACpB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CAC9C,GACD;IACI,uDAAuD;IACvD,IAAI,EAAE,kBAAkB,CAAC;IACzB,yCAAyC;IACzC,KAAK,EAAE,KAAK,CAAC;IACb,4DAA4D;IAC5D,EAAE,EAAE,SAAS,CAAC;IACd;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CAC9C,CAAC;AAER,8CAA8C;AAC9C,MAAM,WAAW,YAAY;IACzB,wCAAwC;IACxC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC3C;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,oDAAoD;AACpD,MAAM,WAAW,YAAY;IACzB,qEAAqE;IACrE,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,0BAA0B;IAC1B,EAAE,EAAE,cAAc,CAAC;IACnB,8EAA8E;IAC9E,KAAK,EAAE,OAAO,CAAC;CAClB;AAED,wEAAwE;AACxE,MAAM,WAAW,UAAW,SAAQ,YAAY;IAC5C,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,gFAAgF;AAChF,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,KAAK,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,UAAU,EAAE,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,WAAW,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RelationKind = exports.ScalarFieldType = exports.FilterOperator = void 0;
|
|
4
|
+
/** Named constants for `FilterOperator` — use in switches and comparisons. */
|
|
5
|
+
exports.FilterOperator = {
|
|
6
|
+
Eq: 'eq',
|
|
7
|
+
Ne: 'ne',
|
|
8
|
+
Gt: 'gt',
|
|
9
|
+
Gte: 'gte',
|
|
10
|
+
Lt: 'lt',
|
|
11
|
+
Lte: 'lte',
|
|
12
|
+
In: 'in',
|
|
13
|
+
Nin: 'nin',
|
|
14
|
+
Like: 'like',
|
|
15
|
+
Ilike: 'ilike',
|
|
16
|
+
Nilike: 'nilike',
|
|
17
|
+
Null: 'null'
|
|
18
|
+
};
|
|
19
|
+
/** Named constants for `ScalarFieldType` — use in switches. */
|
|
20
|
+
exports.ScalarFieldType = {
|
|
21
|
+
String: 'string',
|
|
22
|
+
Number: 'number',
|
|
23
|
+
Boolean: 'boolean',
|
|
24
|
+
Uuid: 'uuid',
|
|
25
|
+
Date: 'date',
|
|
26
|
+
Enum: 'enum'
|
|
27
|
+
};
|
|
28
|
+
/** Named constants for `RelationSchema['kind']` — use in switches. */
|
|
29
|
+
exports.RelationKind = {
|
|
30
|
+
OneToOne: 'one-to-one',
|
|
31
|
+
OneToMany: 'one-to-many',
|
|
32
|
+
ManyToOne: 'many-to-one',
|
|
33
|
+
ManyToMany: 'many-to-many',
|
|
34
|
+
SelfReferential: 'self-referential'
|
|
35
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postgres error introspection shared by services that map constraint
|
|
3
|
+
* violations to clean HTTP errors (e.g. a unique-index race settled by the
|
|
4
|
+
* constraint → 409) instead of leaking a 500.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Whether an error (or its `cause`, where a driver/ORM wraps it) is a
|
|
8
|
+
* Postgres unique violation (`23505`). Drivers surface the SQLSTATE on a
|
|
9
|
+
* `code` property; the walk is defensive because Drizzle has wrapped driver
|
|
10
|
+
* errors differently across versions.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isUniqueViolation(error: unknown): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The **name of the index/constraint** a unique violation (`23505`) tripped, or
|
|
15
|
+
* `undefined` when the error is not one.
|
|
16
|
+
*
|
|
17
|
+
* A table can carry several unique indexes, and "which one" is the difference
|
|
18
|
+
* between two entirely different messages to the caller — a localized content
|
|
19
|
+
* table has both a `(locale_group_id, locale)` pair and a per-locale one-to-one
|
|
20
|
+
* relation index, and reporting either as the other tells the user to fix
|
|
21
|
+
* something that is not wrong. Postgres names the offender on `constraint`;
|
|
22
|
+
* the same defensive `cause` walk as {@link isUniqueViolation} finds it through
|
|
23
|
+
* whatever the driver/ORM wrapped it in.
|
|
24
|
+
*
|
|
25
|
+
* Returns `''` for a violation whose constraint the driver did not name, so a
|
|
26
|
+
* caller can still distinguish "a unique violation, unattributed" from "not a
|
|
27
|
+
* unique violation" without a second call.
|
|
28
|
+
*/
|
|
29
|
+
export declare function violatedConstraint(error: unknown): string | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Whether an error (or its `cause`, where a driver/ORM wraps it) is a Postgres
|
|
32
|
+
* **foreign-key** violation (`23503`).
|
|
33
|
+
*
|
|
34
|
+
* The counterpart of {@link isUniqueViolation} for the other constraint a write
|
|
35
|
+
* can lose to. A row that something else still references under
|
|
36
|
+
* `ON DELETE RESTRICT` is a legitimate refusal the caller can act on ("detach
|
|
37
|
+
* the referring records first") — leaking it as a 500 tells them the server
|
|
38
|
+
* broke instead. Deliberately a boolean and not a constraint name: unlike the
|
|
39
|
+
* several unique indexes on one localized content table, the answer here does
|
|
40
|
+
* not vary by which FK tripped.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isForeignKeyViolation(error: unknown): boolean;
|
|
43
|
+
//# sourceMappingURL=pg-errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pg-errors.d.ts","sourceRoot":"","sources":["../../src/lib/pg-errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEzD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAiBrE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAgB7D"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Postgres error introspection shared by services that map constraint
|
|
4
|
+
* violations to clean HTTP errors (e.g. a unique-index race settled by the
|
|
5
|
+
* constraint → 409) instead of leaking a 500.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.isUniqueViolation = isUniqueViolation;
|
|
9
|
+
exports.violatedConstraint = violatedConstraint;
|
|
10
|
+
exports.isForeignKeyViolation = isForeignKeyViolation;
|
|
11
|
+
/** Postgres error code for a unique constraint/index violation. */
|
|
12
|
+
const UNIQUE_VIOLATION = '23505';
|
|
13
|
+
/** Postgres error code for a foreign-key constraint violation. */
|
|
14
|
+
const FOREIGN_KEY_VIOLATION = '23503';
|
|
15
|
+
/**
|
|
16
|
+
* Whether an error (or its `cause`, where a driver/ORM wraps it) is a
|
|
17
|
+
* Postgres unique violation (`23505`). Drivers surface the SQLSTATE on a
|
|
18
|
+
* `code` property; the walk is defensive because Drizzle has wrapped driver
|
|
19
|
+
* errors differently across versions.
|
|
20
|
+
*/
|
|
21
|
+
function isUniqueViolation(error) {
|
|
22
|
+
return violatedConstraint(error) !== undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The **name of the index/constraint** a unique violation (`23505`) tripped, or
|
|
26
|
+
* `undefined` when the error is not one.
|
|
27
|
+
*
|
|
28
|
+
* A table can carry several unique indexes, and "which one" is the difference
|
|
29
|
+
* between two entirely different messages to the caller — a localized content
|
|
30
|
+
* table has both a `(locale_group_id, locale)` pair and a per-locale one-to-one
|
|
31
|
+
* relation index, and reporting either as the other tells the user to fix
|
|
32
|
+
* something that is not wrong. Postgres names the offender on `constraint`;
|
|
33
|
+
* the same defensive `cause` walk as {@link isUniqueViolation} finds it through
|
|
34
|
+
* whatever the driver/ORM wrapped it in.
|
|
35
|
+
*
|
|
36
|
+
* Returns `''` for a violation whose constraint the driver did not name, so a
|
|
37
|
+
* caller can still distinguish "a unique violation, unattributed" from "not a
|
|
38
|
+
* unique violation" without a second call.
|
|
39
|
+
*/
|
|
40
|
+
function violatedConstraint(error) {
|
|
41
|
+
let current = error;
|
|
42
|
+
for (let depth = 0; current && depth < 5; depth += 1) {
|
|
43
|
+
if (typeof current === 'object' &&
|
|
44
|
+
'code' in current &&
|
|
45
|
+
current.code === UNIQUE_VIOLATION) {
|
|
46
|
+
const name = current.constraint;
|
|
47
|
+
return typeof name === 'string' ? name : '';
|
|
48
|
+
}
|
|
49
|
+
current =
|
|
50
|
+
typeof current === 'object' && 'cause' in current
|
|
51
|
+
? current.cause
|
|
52
|
+
: undefined;
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Whether an error (or its `cause`, where a driver/ORM wraps it) is a Postgres
|
|
58
|
+
* **foreign-key** violation (`23503`).
|
|
59
|
+
*
|
|
60
|
+
* The counterpart of {@link isUniqueViolation} for the other constraint a write
|
|
61
|
+
* can lose to. A row that something else still references under
|
|
62
|
+
* `ON DELETE RESTRICT` is a legitimate refusal the caller can act on ("detach
|
|
63
|
+
* the referring records first") — leaking it as a 500 tells them the server
|
|
64
|
+
* broke instead. Deliberately a boolean and not a constraint name: unlike the
|
|
65
|
+
* several unique indexes on one localized content table, the answer here does
|
|
66
|
+
* not vary by which FK tripped.
|
|
67
|
+
*/
|
|
68
|
+
function isForeignKeyViolation(error) {
|
|
69
|
+
let current = error;
|
|
70
|
+
for (let depth = 0; current && depth < 5; depth += 1) {
|
|
71
|
+
if (typeof current === 'object' &&
|
|
72
|
+
'code' in current &&
|
|
73
|
+
current.code === FOREIGN_KEY_VIOLATION) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
current =
|
|
77
|
+
typeof current === 'object' && 'cause' in current
|
|
78
|
+
? current.cause
|
|
79
|
+
: undefined;
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@orthacms/utils-server",
|
|
3
|
+
"version": "0.0.0-reserve.0",
|
|
4
|
+
"description": "@orthacms/utils-server — part of Ortha CMS.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/ortha-source/ortha-cms/tree/main/packages/utils/server",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/ortha-source/ortha-cms.git",
|
|
10
|
+
"directory": "packages/utils/server"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/ortha-source/ortha-cms/issues"
|
|
14
|
+
},
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@nestjs/common": "^11.0.0",
|
|
29
|
+
"drizzle-orm": "^0.45.0",
|
|
30
|
+
"tslib": "^2.3.0"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
}
|
|
35
|
+
}
|