typeorm-hasura 0.0.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.
- package/.env.example +9 -0
- package/LICENSE +21 -0
- package/dev-playground/UserRole.ts +5 -0
- package/dev-playground/action/currencyConverter.ts +60 -0
- package/dev-playground/data-source.ts +21 -0
- package/dev-playground/entity/Org.ts +52 -0
- package/dev-playground/entity/Product.ts +74 -0
- package/dev-playground/entity/User.ts +51 -0
- package/dev-playground/entity/index.ts +3 -0
- package/dev-playground/hasura.ts +35 -0
- package/dev-playground/index.ts +43 -0
- package/dev-playground/migration/1679166386871-next.ts +29 -0
- package/jest.config.js +6 -0
- package/package.json +70 -0
- package/random-notes.md +8 -0
- package/rollup.config.mjs +56 -0
- package/src/builders/Action.ts +27 -0
- package/src/builders/Metadata.ts +96 -0
- package/src/builders/index.ts +2 -0
- package/src/decorators/Column.ts +12 -0
- package/src/decorators/Entity.ts +12 -0
- package/src/decorators/index.ts +2 -0
- package/src/index.ts +5 -0
- package/src/internalStorage.ts +23 -0
- package/src/mappers/databaseUrl.spec.ts +33 -0
- package/src/mappers/databaseUrl.ts +19 -0
- package/src/mappers/graphql.spec.ts +124 -0
- package/src/mappers/graphql.ts +61 -0
- package/src/mappers/hasuraKind.spec.ts +12 -0
- package/src/mappers/hasuraKind.ts +11 -0
- package/src/mappers/index.ts +3 -0
- package/src/mappers/permissions.spec.ts +143 -0
- package/src/mappers/permissions.ts +84 -0
- package/src/mappers/relationships.ts +65 -0
- package/src/mappers/source.ts +29 -0
- package/src/mappers/table.ts +27 -0
- package/src/mappers/tableConfiguration.ts +45 -0
- package/src/mappers/whereClause.spec.ts +85 -0
- package/src/mappers/whereClause.ts +41 -0
- package/src/types/Action.ts +27 -0
- package/src/types/Column.ts +23 -0
- package/src/types/DataSourceOptions.ts +22 -0
- package/src/types/Entity.ts +53 -0
- package/src/types/base.ts +2 -0
- package/src/types/index.ts +7 -0
- package/src/types/permissions.ts +15 -0
- package/src/types/whereClause.ts +75 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as TypeORM from "typeorm";
|
|
2
|
+
import type * as Hasura from "hasura-metadata-types";
|
|
3
|
+
|
|
4
|
+
type RelationshipKind = 'object_relationships' | 'array_relationships'
|
|
5
|
+
|
|
6
|
+
export function generateRelationship(relation: TypeORM.EntityMetadata['relations'][number]): {
|
|
7
|
+
kind: RelationshipKind,
|
|
8
|
+
relationship: Hasura.LocalTableObjectRelationship | Hasura.SameTableObjectRelationship
|
|
9
|
+
} {
|
|
10
|
+
const kind = relation.relationType.endsWith('-to-one') ? 'object_relationships' : 'array_relationships';
|
|
11
|
+
|
|
12
|
+
const owningRelation = relation.isOwning ? relation : relation.inverseRelation;
|
|
13
|
+
|
|
14
|
+
if (!owningRelation)
|
|
15
|
+
throw new Error('Does not support many-to-many relations yet, so we will skip this specific relation. ' +
|
|
16
|
+
'Also its possible that you have missed to set inverse side of the relation.');
|
|
17
|
+
|
|
18
|
+
const columns = owningRelation.joinColumns.map(column => column.propertyName);
|
|
19
|
+
|
|
20
|
+
// todo: does not work?
|
|
21
|
+
// const schema = owningRelation.entityMetadata.schema;
|
|
22
|
+
// @ts-ignore is that okay?
|
|
23
|
+
const schema = owningRelation.target.dataSource.options.schema || 'public';
|
|
24
|
+
|
|
25
|
+
const relationship: Hasura.LocalTableObjectRelationship | Hasura.SameTableObjectRelationship =
|
|
26
|
+
relation.isOwning ? {
|
|
27
|
+
name: relation.propertyName,
|
|
28
|
+
using: {
|
|
29
|
+
foreign_key_constraint_on: columns
|
|
30
|
+
}
|
|
31
|
+
} : {
|
|
32
|
+
name: relation.propertyName,
|
|
33
|
+
using: {
|
|
34
|
+
foreign_key_constraint_on: {
|
|
35
|
+
columns,
|
|
36
|
+
table: {
|
|
37
|
+
name: owningRelation.entityMetadata.tableName,
|
|
38
|
+
schema,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { kind, relationship };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function generateRelationships(relations: TypeORM.EntityMetadata['relations']):
|
|
48
|
+
Pick<Hasura.MetadataTable, RelationshipKind> {
|
|
49
|
+
const result: Required<Pick<Hasura.MetadataTable, RelationshipKind>> = {
|
|
50
|
+
object_relationships: [],
|
|
51
|
+
array_relationships: [],
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const relation of relations) {
|
|
55
|
+
try {
|
|
56
|
+
const { kind, relationship } = generateRelationship(relation);
|
|
57
|
+
// @ts-ignore dont want to play with types for now
|
|
58
|
+
result[kind].push(relationship);
|
|
59
|
+
} catch (e) {
|
|
60
|
+
console.warn(e);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type * as Hasura from "hasura-metadata-types";
|
|
2
|
+
import { DataSourceOptions } from "../types";
|
|
3
|
+
import { getHasuraKind } from "./hasuraKind";
|
|
4
|
+
import { getDatabaseUrl } from "./databaseUrl";
|
|
5
|
+
import { generateTable } from "./table";
|
|
6
|
+
|
|
7
|
+
export function generateSource(dataSourceOptions: DataSourceOptions): Hasura.Source {
|
|
8
|
+
let { name, dataSource, customizationNative: customization } = dataSourceOptions;
|
|
9
|
+
|
|
10
|
+
// customization ??= {}
|
|
11
|
+
// customization.naming_convention ??= 'graphql-default'
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
name,
|
|
15
|
+
kind: getHasuraKind(dataSource.options.type),
|
|
16
|
+
tables: [...dataSource.entityMetadatas]
|
|
17
|
+
.reverse()
|
|
18
|
+
.map(table => generateTable(dataSourceOptions, table)),
|
|
19
|
+
customization,
|
|
20
|
+
configuration: {
|
|
21
|
+
"connection_info": {
|
|
22
|
+
"database_url": getDatabaseUrl(dataSourceOptions),
|
|
23
|
+
"isolation_level": "read-committed",
|
|
24
|
+
"use_prepared_statements": false
|
|
25
|
+
},
|
|
26
|
+
"extensions_schema": "extensions_schema_test"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type * as Hasura from "hasura-metadata-types";
|
|
2
|
+
import * as TypeORM from "typeorm";
|
|
3
|
+
import { generateRelationships } from "./relationships";
|
|
4
|
+
import { generateTableConfiguration } from "./tableConfiguration";
|
|
5
|
+
import { DataSourceOptions } from "../types";
|
|
6
|
+
import { generatePermissions } from "./permissions";
|
|
7
|
+
import { internalStorage } from "../internalStorage";
|
|
8
|
+
|
|
9
|
+
export function generateTable<Entity extends Object>(
|
|
10
|
+
dataSourceOptions: DataSourceOptions,
|
|
11
|
+
table: TypeORM.EntityMetadata
|
|
12
|
+
): Hasura.MetadataTable {
|
|
13
|
+
const entityOptions = internalStorage.getEntityOptions<Entity>(table.target);
|
|
14
|
+
const columnMetadata = internalStorage.getEntityColumnsOptionsList(table.target);
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
table: {
|
|
18
|
+
name: table.tableName,
|
|
19
|
+
// todo: does not work?
|
|
20
|
+
// schema: table.schema,
|
|
21
|
+
schema: 'schema' in table.connection.options && table.connection.options.schema || 'public',
|
|
22
|
+
},
|
|
23
|
+
configuration: generateTableConfiguration(table, entityOptions, columnMetadata),
|
|
24
|
+
...generateRelationships(table.relations),
|
|
25
|
+
...generatePermissions(dataSourceOptions, entityOptions, columnMetadata),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as TypeORM from "typeorm";
|
|
2
|
+
import type * as Hasura from "hasura-metadata-types";
|
|
3
|
+
import { ColumnMetadata, EntityOptions, EntityRootField } from "../types";
|
|
4
|
+
import snakeCase from 'lodash.snakecase'
|
|
5
|
+
|
|
6
|
+
export function generateTableConfiguration<Entity extends Object>(table: TypeORM.EntityMetadata,
|
|
7
|
+
entityOptions: EntityOptions<Entity> | undefined,
|
|
8
|
+
columnMetadata: ColumnMetadata[]
|
|
9
|
+
): Hasura.MetadataTableConfig {
|
|
10
|
+
|
|
11
|
+
let custom_name: string | undefined = entityOptions && entityOptions.customName || table.tableName;
|
|
12
|
+
|
|
13
|
+
const columnHasuraEntries: [string, Hasura.MetadataTableColumnConfig][] = [];
|
|
14
|
+
for (const column of columnMetadata) {
|
|
15
|
+
// looks like its only possible to set custom_name for columns here
|
|
16
|
+
if (column.options?.customName) {
|
|
17
|
+
columnHasuraEntries.push([column.propertyName, {
|
|
18
|
+
custom_name: column.options.customName,
|
|
19
|
+
}])
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const column_config = columnHasuraEntries.length ? Object.fromEntries(columnHasuraEntries) : undefined;
|
|
23
|
+
|
|
24
|
+
const custom_root_fields: Record<string, EntityRootField> = {};
|
|
25
|
+
if (entityOptions?.customRootFields) {
|
|
26
|
+
// iterate over all possible root fields and push them to custom_root_fields
|
|
27
|
+
for (const rootField in entityOptions.customRootFields) {
|
|
28
|
+
let rootFieldConfig = entityOptions.customRootFields[rootField as keyof typeof entityOptions.customRootFields];
|
|
29
|
+
if (rootFieldConfig) {
|
|
30
|
+
if (typeof rootFieldConfig === 'string') {
|
|
31
|
+
rootFieldConfig = {
|
|
32
|
+
name: rootFieldConfig,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
custom_root_fields[snakeCase(rootField)] = rootFieldConfig;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
custom_name,
|
|
42
|
+
column_config,
|
|
43
|
+
custom_root_fields,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Equal, Not, MoreThan, LessThan, MoreThanOrEqual, LessThanOrEqual, Like, ILike, In, BaseEntity } from "typeorm"
|
|
2
|
+
|
|
3
|
+
import { convertWhereClause } from "./whereClause"
|
|
4
|
+
import { User, Org, Product } from "../../dev-playground/entity";
|
|
5
|
+
import { Where, Filter } from "../types";
|
|
6
|
+
|
|
7
|
+
type Case<T extends BaseEntity> = {
|
|
8
|
+
input: Where<T>;
|
|
9
|
+
output: Filter<T>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const cases: Case<User>[] = [
|
|
13
|
+
{
|
|
14
|
+
input: {
|
|
15
|
+
id: "1",
|
|
16
|
+
},
|
|
17
|
+
output: {
|
|
18
|
+
id: { _eq: "1" }
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
input: {
|
|
23
|
+
id: "1",
|
|
24
|
+
name: "test"
|
|
25
|
+
},
|
|
26
|
+
output: {
|
|
27
|
+
_and: [
|
|
28
|
+
{ id: { _eq: "1" } },
|
|
29
|
+
{ name: { _eq: "test" } }
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
input: [
|
|
35
|
+
{ id: "1", name: "test" },
|
|
36
|
+
{ id: "2", name: "test" }
|
|
37
|
+
],
|
|
38
|
+
output: {
|
|
39
|
+
_or: [
|
|
40
|
+
{ _and: [{ id: { _eq: "1" } }, { name: { _eq: "test" } }] },
|
|
41
|
+
{ _and: [{ id: { _eq: "2" } }, { name: { _eq: "test" } }] }
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
input: {
|
|
47
|
+
id: Not("1")
|
|
48
|
+
},
|
|
49
|
+
output: {
|
|
50
|
+
id: { _neq: "1" }
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
input: [
|
|
55
|
+
{ id: Not("1"), name: "test" },
|
|
56
|
+
{ id: "2", name: In(["test"]) }
|
|
57
|
+
],
|
|
58
|
+
output: {
|
|
59
|
+
_or: [
|
|
60
|
+
{ _and: [{ id: { _neq: "1" } }, { name: { _eq: "test" } }] },
|
|
61
|
+
{ _and: [{ id: { _eq: "2" } }, { name: { _in: ["test"] } }] }
|
|
62
|
+
|
|
63
|
+
],
|
|
64
|
+
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
input: {
|
|
69
|
+
products: {
|
|
70
|
+
id: "1"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
output: {
|
|
74
|
+
products: {
|
|
75
|
+
id: { _eq: "1" },
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
describe("convert whereTypeorm to hasuraObj", () => {
|
|
82
|
+
cases.forEach(({ input, output }) =>
|
|
83
|
+
it("input to Equal output", () => expect(convertWhereClause(input)).toEqual(output))
|
|
84
|
+
)
|
|
85
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { FindOptionsWhere, InstanceChecker, And } from "typeorm";
|
|
2
|
+
import { Where, Filter, Operators, } from "../types";
|
|
3
|
+
|
|
4
|
+
export function convertWhereClause<Entity extends Object>(...wheres: (Where<Entity> | undefined)[]): Filter<Entity> {
|
|
5
|
+
wheres = wheres.filter(Boolean)
|
|
6
|
+
if (!wheres.length) return {}
|
|
7
|
+
if (wheres.length > 1) {
|
|
8
|
+
return {
|
|
9
|
+
_and: wheres.map(where => convertWhereClause(where))
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const [where] = wheres
|
|
13
|
+
|
|
14
|
+
if (!where) return {}
|
|
15
|
+
if (Array.isArray(where)) {
|
|
16
|
+
return { _or: where.map(i => parseParameters(i)) }
|
|
17
|
+
}
|
|
18
|
+
return parseParameters(where)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseParameters<Entity extends Object>(object: FindOptionsWhere<Entity>): Filter<Entity> {
|
|
22
|
+
let conditions: Filter<Entity>[] = []
|
|
23
|
+
for (let key in object) {
|
|
24
|
+
const parameterValue = object[key]
|
|
25
|
+
if (InstanceChecker.isFindOperator(parameterValue)) {
|
|
26
|
+
const operator = Operators[parameterValue.type]
|
|
27
|
+
if (operator) {
|
|
28
|
+
conditions.push({ [key]: { [operator]: parameterValue.value } })
|
|
29
|
+
} else
|
|
30
|
+
throw new Error("this operator is not supported in this time");
|
|
31
|
+
} else if (["string", "number", "boolean"].includes(typeof parameterValue)) {
|
|
32
|
+
conditions.push({ [key]: { "_eq": parameterValue } })
|
|
33
|
+
} else if (typeof parameterValue === "object" && !Array.isArray(parameterValue) && parameterValue !== null) {
|
|
34
|
+
conditions.push({ [key]: parseParameters(parameterValue) })
|
|
35
|
+
} else
|
|
36
|
+
throw new Error("this parameter is not supported in this time");
|
|
37
|
+
}
|
|
38
|
+
return conditions.length == 0 ? {} :
|
|
39
|
+
conditions.length == 1 ? conditions[0] :
|
|
40
|
+
{ _and: conditions }
|
|
41
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DocumentNode } from "graphql"
|
|
2
|
+
import type * as Hasura from "hasura-metadata-types";
|
|
3
|
+
import { UserRoleName } from "./base";
|
|
4
|
+
|
|
5
|
+
export type GraphQlMetadataForAction = {
|
|
6
|
+
baseActions: (Omit<Hasura.Action, 'definition'> & {
|
|
7
|
+
definition: Pick<Hasura.ActionDefinition, 'type' | 'output_type' | 'arguments'>;
|
|
8
|
+
})[],
|
|
9
|
+
custom_types: Hasura.CustomTypes,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ActionCustomMetadataV1 = {
|
|
13
|
+
comment?: string,
|
|
14
|
+
/**
|
|
15
|
+
* The action's webhook URL
|
|
16
|
+
*/
|
|
17
|
+
handler: string,
|
|
18
|
+
definitionType: DocumentNode,
|
|
19
|
+
/**
|
|
20
|
+
* If set to true the client headers are forwarded to the webhook handler (default: false)
|
|
21
|
+
*/
|
|
22
|
+
forwardClientHeaders?: boolean
|
|
23
|
+
nativeDefinition: Partial<Hasura.ActionDefinition>,
|
|
24
|
+
permissions?: { role: UserRoleName }[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type ActionBuildResult = Required<Pick<Hasura.Metadata['metadata'], 'actions' | 'custom_types'>>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { UserActionType, UserRoleName } from "./base";
|
|
2
|
+
import { EntityTarget } from "./Entity";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export interface ColumnOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Column name which will be show up in hasura.
|
|
8
|
+
*/
|
|
9
|
+
customName?: string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Column type which will be allowed to do actions.
|
|
13
|
+
*/
|
|
14
|
+
permissions?: {
|
|
15
|
+
[role: UserRoleName]: UserActionType[] | UserActionType | boolean
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ColumnMetadata {
|
|
20
|
+
object: EntityTarget,
|
|
21
|
+
propertyName: string,
|
|
22
|
+
options: ColumnOptions | undefined,
|
|
23
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as TypeORM from "typeorm";
|
|
2
|
+
import type * as Hasura from "hasura-metadata-types";
|
|
3
|
+
|
|
4
|
+
export interface DataSourceOptions {
|
|
5
|
+
name: string;
|
|
6
|
+
dataSource: TypeORM.DataSource;
|
|
7
|
+
customizationNative?: Hasura.SourceCustomization;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* override database url instead of url from data source
|
|
11
|
+
*
|
|
12
|
+
* @example `postgres://username:password@host:5432/database`
|
|
13
|
+
* @deprecated please rely on the `dataSource` parameter instead
|
|
14
|
+
*/
|
|
15
|
+
databaseUrl?: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Set limit on number of rows fetched per request by default for all entities
|
|
19
|
+
* @default undefined
|
|
20
|
+
*/
|
|
21
|
+
defaultSelectPermissionLimit?: number;
|
|
22
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { UserActionType, UserRoleName } from "./base";
|
|
2
|
+
import { BasePermissionRule, SelectPermissionRule } from "./permissions";
|
|
3
|
+
import { Where } from "./whereClause";
|
|
4
|
+
|
|
5
|
+
export type EntityTarget = any;
|
|
6
|
+
|
|
7
|
+
export interface EntityRootField {
|
|
8
|
+
/**
|
|
9
|
+
* name for root field
|
|
10
|
+
*/
|
|
11
|
+
name?: string;
|
|
12
|
+
/**
|
|
13
|
+
* user visible comment for root field
|
|
14
|
+
*/
|
|
15
|
+
comment?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type Permissions<Entity extends Object> = {
|
|
19
|
+
[role: UserRoleName]: {
|
|
20
|
+
/**
|
|
21
|
+
* append where clause to all actions
|
|
22
|
+
*/
|
|
23
|
+
where?: Where<Entity>;
|
|
24
|
+
select?: SelectPermissionRule<Entity> | boolean;
|
|
25
|
+
} & {
|
|
26
|
+
[action in Exclude<UserActionType, 'select'>]?: BasePermissionRule<Entity> | boolean;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface EntityOptions<Entity extends Object = Object> {
|
|
31
|
+
/**
|
|
32
|
+
* Table name which will be show up in hasura.
|
|
33
|
+
*/
|
|
34
|
+
customName?: string;
|
|
35
|
+
|
|
36
|
+
customRootFields?:
|
|
37
|
+
Partial<Record<
|
|
38
|
+
| 'select'
|
|
39
|
+
| 'selectByPk'
|
|
40
|
+
| 'selectAggregate'
|
|
41
|
+
| 'selectStream'
|
|
42
|
+
| 'insert'
|
|
43
|
+
| 'insertOne'
|
|
44
|
+
| 'update'
|
|
45
|
+
| 'updateByPk'
|
|
46
|
+
| 'delete'
|
|
47
|
+
| 'deleteByPk'
|
|
48
|
+
| 'updateMany'
|
|
49
|
+
, EntityRootField | string>>,
|
|
50
|
+
|
|
51
|
+
permissions?: Permissions<Entity>
|
|
52
|
+
}
|
|
53
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Where, Filter } from "./whereClause";
|
|
2
|
+
export interface BasePermissionRule<Entity extends Object> {
|
|
3
|
+
where?: Where<Entity>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface SelectPermissionRule<Entity extends Object> extends BasePermissionRule<Entity> {
|
|
7
|
+
/**
|
|
8
|
+
* limit the number of rows returned by the query(select action only)
|
|
9
|
+
*/
|
|
10
|
+
limit?: number;
|
|
11
|
+
/**
|
|
12
|
+
* allow aggregations on the table (select action only)
|
|
13
|
+
*/
|
|
14
|
+
allowAggregations?: boolean;
|
|
15
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { FindOptionsWhere, FindOperatorType, BaseEntity } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export type Where<Entity> = FindOptionsWhere<Entity>[] | FindOptionsWhere<Entity>
|
|
4
|
+
|
|
5
|
+
export type ExclusiveKeys<T extends {}, Key = { [K in keyof T]: { [P in keyof T]?: P extends K ? T[P] : never } }> =
|
|
6
|
+
{ [K in keyof Key]: Key[K] }[keyof Key];
|
|
7
|
+
|
|
8
|
+
type StringParameters =
|
|
9
|
+
| "_eq"
|
|
10
|
+
| "_neq"
|
|
11
|
+
| "_gt"
|
|
12
|
+
| "_lt"
|
|
13
|
+
| "_gte"
|
|
14
|
+
| "_lte"
|
|
15
|
+
| "_like"
|
|
16
|
+
// | "_nlike"
|
|
17
|
+
| "_ilike"
|
|
18
|
+
// | "_nilike"
|
|
19
|
+
// | "_similar"
|
|
20
|
+
// | "_nsimilar"
|
|
21
|
+
// | "_regex"
|
|
22
|
+
// | "_iregex"
|
|
23
|
+
// | "_nregex"
|
|
24
|
+
// | "_niregex"
|
|
25
|
+
|
|
26
|
+
type ArrayParameters =
|
|
27
|
+
| "_in"
|
|
28
|
+
// | "_nin"
|
|
29
|
+
// | "_ceq"
|
|
30
|
+
// | "_cne"
|
|
31
|
+
// | "_cgt"
|
|
32
|
+
// | "_clt"
|
|
33
|
+
// | "_cgte"
|
|
34
|
+
// | "_clte"
|
|
35
|
+
type NullParameters = "_is_null"
|
|
36
|
+
type JsonBParameters = "_contains" | "_contained_in" | "_has_key" | "_has_keys_any" | "_has_keys_all"
|
|
37
|
+
|
|
38
|
+
type ExclusiveParameters = ExclusiveKeys<
|
|
39
|
+
{ [key in StringParameters]?: string; } &
|
|
40
|
+
{ [key in ArrayParameters]?: string[] } &
|
|
41
|
+
{ [key in NullParameters]?: boolean }
|
|
42
|
+
>
|
|
43
|
+
type Subject<Entity extends Object> = {
|
|
44
|
+
[key in keyof Entity]?: ExclusiveParameters
|
|
45
|
+
}
|
|
46
|
+
export type ExclusiveArguments<Entity extends Object, T = {}, EntityParameters extends string | number | symbol = keyof Omit<Entity, keyof BaseEntity>> =
|
|
47
|
+
ExclusiveKeys<{ [key in EntityParameters]?: ExclusiveParameters | Subject<Entity> } & T>
|
|
48
|
+
|
|
49
|
+
export type Filter<EntityParameters extends Object> = ExclusiveKeys<{
|
|
50
|
+
_and?: Filter<EntityParameters>[];
|
|
51
|
+
_or?: Filter<EntityParameters>[];
|
|
52
|
+
_not?: Filter<EntityParameters>
|
|
53
|
+
}> | ExclusiveArguments<EntityParameters>
|
|
54
|
+
|
|
55
|
+
export const Operators: Readonly<
|
|
56
|
+
Partial<Record<FindOperatorType, StringParameters | ArrayParameters>>
|
|
57
|
+
> = {
|
|
58
|
+
// string
|
|
59
|
+
"equal": "_eq",
|
|
60
|
+
"not": "_neq",
|
|
61
|
+
"moreThan": "_gt",
|
|
62
|
+
"lessThan": "_lt",
|
|
63
|
+
"moreThanOrEqual": "_gte",
|
|
64
|
+
"lessThanOrEqual": "_lte",
|
|
65
|
+
"like": "_like",
|
|
66
|
+
"ilike": "_ilike",
|
|
67
|
+
// array
|
|
68
|
+
"in": "_in",
|
|
69
|
+
}
|
|
70
|
+
enum NullParams {
|
|
71
|
+
"isNull" = "_is_null"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// all typeorm operators FindOperatorType
|
|
75
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"strict": true,
|
|
4
|
+
"target": "es6",
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"emitDecoratorMetadata": true,
|
|
9
|
+
"experimentalDecorators": true,
|
|
10
|
+
"allowSyntheticDefaultImports": true,
|
|
11
|
+
"sourceMap": true,
|
|
12
|
+
"types": [
|
|
13
|
+
"node",
|
|
14
|
+
"jest"
|
|
15
|
+
],
|
|
16
|
+
"rootDir": "./src"
|
|
17
|
+
},
|
|
18
|
+
"include": [
|
|
19
|
+
"./src/**/*.ts"
|
|
20
|
+
],
|
|
21
|
+
"exclude": [
|
|
22
|
+
"node_modules",
|
|
23
|
+
"build",
|
|
24
|
+
"**/*.spec.ts"
|
|
25
|
+
]
|
|
26
|
+
}
|