@axinom/mosaic-cli 0.14.2-rc.8 → 0.15.0-rc.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/package.json +6 -5
- package/src/cli/README.md +60 -0
- package/src/cli/index.ts +47 -0
- package/src/commands/apply-templates/apply-templates.spec.ts +623 -0
- package/src/commands/apply-templates/apply-templates.ts +494 -0
- package/src/commands/apply-templates/bitwarden-vault.ts +130 -0
- package/src/commands/apply-templates/index.ts +1 -0
- package/src/commands/create-extension-config/create-extension-config.ts +92 -0
- package/src/commands/create-extension-config/index.ts +23 -0
- package/src/commands/get-access-token/get-access-token-options.ts +9 -0
- package/src/commands/get-access-token/get-dev-access-token.ts +32 -0
- package/src/commands/get-access-token/index.ts +66 -0
- package/src/commands/graphql-diff.ts +143 -0
- package/src/commands/msg-codegen/codegen.ts +891 -0
- package/src/commands/msg-codegen/index.ts +48 -0
- package/src/commands/msg-codegen/lint.ts +84 -0
- package/src/commands/msg-codegen/message-codegen-options.ts +7 -0
- package/src/commands/msg-diff/asyncapi-override.ts +31 -0
- package/src/commands/msg-diff/git-checkout-tmp.ts +73 -0
- package/src/commands/msg-diff/index.ts +53 -0
- package/src/commands/msg-diff/message-diff-options.ts +7 -0
- package/src/commands/msg-diff/msg-diff.spec.ts +412 -0
- package/src/commands/msg-diff/msg-diff.ts +364 -0
- package/src/commands/msg-diff/test-resources/0/1-asyncapi.yml +38 -0
- package/src/commands/msg-diff/test-resources/0/2-asyncapi.yml +36 -0
- package/src/commands/msg-diff/test-resources/0/command.json +74 -0
- package/src/commands/msg-diff/test-resources/0/event.json +25 -0
- package/src/commands/msg-diff/test-resources/1/1-asyncapi.yml +25 -0
- package/src/commands/msg-diff/test-resources/1/moved-event.json +25 -0
- package/src/commands/msg-diff/test-resources/common.json +20 -0
- package/src/commands/pg-dump/README.md +21 -0
- package/src/commands/pg-dump/generate.ts +146 -0
- package/src/commands/pg-dump/index.ts +39 -0
- package/src/commands/pg-dump/pg-dump-options.ts +6 -0
- package/src/commands/publish-schema-to-db/README.md +130 -0
- package/src/commands/publish-schema-to-db/abstractions/base-smart-tags.ts +6 -0
- package/src/commands/publish-schema-to-db/abstractions/index.ts +5 -0
- package/src/commands/publish-schema-to-db/abstractions/pg-column.ts +31 -0
- package/src/commands/publish-schema-to-db/abstractions/pg-fk-column.ts +6 -0
- package/src/commands/publish-schema-to-db/abstractions/pg-table.ts +55 -0
- package/src/commands/publish-schema-to-db/abstractions/pg-type.ts +8 -0
- package/src/commands/publish-schema-to-db/content-entity-model.ts +93 -0
- package/src/commands/publish-schema-to-db/generate.ts +82 -0
- package/src/commands/publish-schema-to-db/index.ts +49 -0
- package/src/commands/publish-schema-to-db/jest.config.js +9 -0
- package/src/commands/publish-schema-to-db/pg-models/columns/fk-column.spec.ts +42 -0
- package/src/commands/publish-schema-to-db/pg-models/columns/fk-column.ts +41 -0
- package/src/commands/publish-schema-to-db/pg-models/columns/index.ts +4 -0
- package/src/commands/publish-schema-to-db/pg-models/columns/pk-column.spec.ts +47 -0
- package/src/commands/publish-schema-to-db/pg-models/columns/pk-column.ts +34 -0
- package/src/commands/publish-schema-to-db/pg-models/columns/primitive-column.spec.ts +65 -0
- package/src/commands/publish-schema-to-db/pg-models/columns/primitive-column.ts +62 -0
- package/src/commands/publish-schema-to-db/pg-models/columns/virtual-fk-column.spec.ts +24 -0
- package/src/commands/publish-schema-to-db/pg-models/columns/virtual-fk-column.ts +34 -0
- package/src/commands/publish-schema-to-db/pg-models/json-schema-parse-utils.spec.ts +182 -0
- package/src/commands/publish-schema-to-db/pg-models/json-schema-parse-utils.ts +166 -0
- package/src/commands/publish-schema-to-db/pg-models/pg-sql-gen-utils.spec.ts +19 -0
- package/src/commands/publish-schema-to-db/pg-models/pg-sql-gen-utils.ts +237 -0
- package/src/commands/publish-schema-to-db/pg-models/pgl-utils.spec.ts +19 -0
- package/src/commands/publish-schema-to-db/pg-models/pgl-utils.ts +115 -0
- package/src/commands/publish-schema-to-db/pg-models/tables/content-entity-table.ts +104 -0
- package/src/commands/publish-schema-to-db/pg-models/tables/index.ts +3 -0
- package/src/commands/publish-schema-to-db/pg-models/tables/object-property-table.ts +113 -0
- package/src/commands/publish-schema-to-db/pg-models/tables/relations-table.ts +115 -0
- package/src/commands/publish-schema-to-db/postprocessors/collection-postprocessor.ts +33 -0
- package/src/commands/publish-schema-to-db/postprocessors/content-entity-model-postprocessor.ts +13 -0
- package/src/commands/publish-schema-to-db/postprocessors/episode-postprocessor.ts +37 -0
- package/src/commands/publish-schema-to-db/postprocessors/index.ts +6 -0
- package/src/commands/publish-schema-to-db/postprocessors/movie-postprocessor.ts +30 -0
- package/src/commands/publish-schema-to-db/postprocessors/postprocessing-utils.ts +21 -0
- package/src/commands/publish-schema-to-db/postprocessors/season-postprocessor.ts +37 -0
- package/src/commands/publish-schema-to-db/postprocessors/tvshow-postprocessor.ts +30 -0
- package/src/commands/publish-schema-to-db/publish-schema-to-db-options.ts +15 -0
- package/src/commands/publish-schema-to-db/types/sql-formatter.d.ts +10 -0
- package/src/exports.ts +2 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { glob } from 'glob';
|
|
2
|
+
import { JSONSchema4 } from 'json-schema';
|
|
3
|
+
import { dereference } from 'json-schema-ref-parser';
|
|
4
|
+
import { PgType } from '../abstractions';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns true if a JSON schema property element is a primitive property.
|
|
8
|
+
* @remarks
|
|
9
|
+
* A primitive property is considered to be a property whose type is either
|
|
10
|
+
* a primitive type or an array of primitive type.
|
|
11
|
+
* @param schema - Schema element to analyze.
|
|
12
|
+
*/
|
|
13
|
+
export function isPrimitiveProperty(schema: JSONSchema4): boolean {
|
|
14
|
+
if (schema.type === 'array') {
|
|
15
|
+
return isPrimitiveType((schema.items as JSONSchema4)?.type);
|
|
16
|
+
}
|
|
17
|
+
return isPrimitiveType(schema.type);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns true if a JSON schema property element is an object property.
|
|
22
|
+
* @remarks
|
|
23
|
+
* An object property is considered to be a property whose type is either
|
|
24
|
+
* of type object or an array of type object.
|
|
25
|
+
* @param schema - Schema element to analyze.
|
|
26
|
+
*/
|
|
27
|
+
export function isObjectProperty(schema: JSONSchema4): boolean {
|
|
28
|
+
if (schema.type === 'array') {
|
|
29
|
+
return (schema.items as JSONSchema4)?.type === 'object';
|
|
30
|
+
}
|
|
31
|
+
return schema.type === 'object';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Returns true if schema property type is a primitive.
|
|
36
|
+
* @param typeName - Type name to check.
|
|
37
|
+
*/
|
|
38
|
+
export function isPrimitiveType(typeName: unknown): boolean {
|
|
39
|
+
return (
|
|
40
|
+
typeName === 'integer' ||
|
|
41
|
+
typeName === 'number' ||
|
|
42
|
+
typeName === 'string' ||
|
|
43
|
+
typeName === 'boolean'
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Returns a property (defined in 'properties') schema by name from JSON schema.
|
|
49
|
+
* @param schema - JSON schema where the property exists.
|
|
50
|
+
* @param propertyName - Name of the property to extract.
|
|
51
|
+
*/
|
|
52
|
+
export function getPropertySchema(
|
|
53
|
+
schema: JSONSchema4,
|
|
54
|
+
propertyName: string,
|
|
55
|
+
): JSONSchema4 {
|
|
56
|
+
if (schema.properties !== undefined) {
|
|
57
|
+
return schema.properties[propertyName];
|
|
58
|
+
}
|
|
59
|
+
throw new Error(`No properties are defined in the schema.`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Maps JSON schema's property type to a Postgres type.
|
|
64
|
+
* @param schema - Property schema.
|
|
65
|
+
* @throws If the schema property type is not supported.
|
|
66
|
+
*/
|
|
67
|
+
export function mapToPgType(schema: JSONSchema4): PgType {
|
|
68
|
+
const isArray = schema.type === 'array';
|
|
69
|
+
schema = isArray ? (schema?.items as JSONSchema4) : schema;
|
|
70
|
+
|
|
71
|
+
if (schema.type === 'string') {
|
|
72
|
+
return mapString(schema, isArray);
|
|
73
|
+
}
|
|
74
|
+
if (schema.type === 'integer') {
|
|
75
|
+
return isArray ? 'INTEGER[]' : 'INTEGER';
|
|
76
|
+
}
|
|
77
|
+
if (schema.type === 'boolean') {
|
|
78
|
+
return 'BOOLEAN';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
throw new TypeError(`Unknown primitive type ${schema.type}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function mapString(schema: JSONSchema4, isArray: boolean): PgType {
|
|
85
|
+
if (schema.format === 'date-time') {
|
|
86
|
+
return 'TIMESTAMPTZ';
|
|
87
|
+
}
|
|
88
|
+
if (isArray) {
|
|
89
|
+
return 'TEXT[]';
|
|
90
|
+
}
|
|
91
|
+
return 'TEXT';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns an array of all defined content metadata schemas (movie, collection etc.).
|
|
96
|
+
* @param schema - Publish message schema.
|
|
97
|
+
*/
|
|
98
|
+
export async function getContentMetadataSchemas(
|
|
99
|
+
schemaGlob: string,
|
|
100
|
+
): Promise<JSONSchema4[]> {
|
|
101
|
+
const files = glob.sync(schemaGlob);
|
|
102
|
+
const schemas: JSONSchema4[] = [];
|
|
103
|
+
for (const file of files) {
|
|
104
|
+
const schema = (await dereference(file)) as JSONSchema4;
|
|
105
|
+
schemas.push(schema);
|
|
106
|
+
}
|
|
107
|
+
return schemas;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Separates primitive properties from object properties.
|
|
112
|
+
* @param schema - Content metadata schema.
|
|
113
|
+
* @param ignoredProperties - Array of ignored properties.
|
|
114
|
+
* @returns Tuple of separated properties: first element will be primitive properties, second object properties.
|
|
115
|
+
*/
|
|
116
|
+
export function separateProperties(
|
|
117
|
+
schema: JSONSchema4,
|
|
118
|
+
ignoredProperties: string[],
|
|
119
|
+
): [{ [p: string]: JSONSchema4 }, { [p: string]: JSONSchema4 }] {
|
|
120
|
+
const primitiveProperties: { [p: string]: JSONSchema4 } = {};
|
|
121
|
+
const objectProperties: { [p: string]: JSONSchema4 } = {};
|
|
122
|
+
|
|
123
|
+
// 1. Extract primitive properties and create a ContentEntityTable
|
|
124
|
+
for (const key in schema.properties) {
|
|
125
|
+
if (ignoredProperties.includes(key)) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const property = schema.properties[key] as JSONSchema4;
|
|
130
|
+
if (isPrimitiveProperty(property)) {
|
|
131
|
+
primitiveProperties[key] = property;
|
|
132
|
+
} else if (isObjectProperty(property)) {
|
|
133
|
+
objectProperties[key] = property;
|
|
134
|
+
} else {
|
|
135
|
+
throw new TypeError(`Unsupported property in asset schema: ${property}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return [primitiveProperties, objectProperties];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Filters out properties by name from a dictionary of JSON schema properties.
|
|
144
|
+
* @param schemas - Dictionary of schemas.
|
|
145
|
+
* @param excluded - Array of excluded names.
|
|
146
|
+
*/
|
|
147
|
+
export function filterProperties(
|
|
148
|
+
schemas: { [p: string]: JSONSchema4 },
|
|
149
|
+
excluded: string[],
|
|
150
|
+
): { [p: string]: JSONSchema4 } {
|
|
151
|
+
const included = Object.keys(schemas).filter((k) => excluded.indexOf(k) < 0);
|
|
152
|
+
|
|
153
|
+
return included.reduce((obj, key) => ({ ...obj, [key]: schemas[key] }), {});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Returns schema's title, throws if it is undefined.
|
|
158
|
+
* @param schema - JSON schema
|
|
159
|
+
*/
|
|
160
|
+
export function getSchemaTitle(schema: JSONSchema4): string {
|
|
161
|
+
if (schema.title) {
|
|
162
|
+
return schema.title;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
throw new Error('Schema title not set.');
|
|
166
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { buildName } from './pg-sql-gen-utils';
|
|
2
|
+
|
|
3
|
+
describe('buildName', () => {
|
|
4
|
+
test('single part', () => {
|
|
5
|
+
// Arrange & Act
|
|
6
|
+
const name = buildName('name');
|
|
7
|
+
|
|
8
|
+
// Assert
|
|
9
|
+
expect(name).toBe('name');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('multiple parts', () => {
|
|
13
|
+
// Arrange & Act
|
|
14
|
+
const name = buildName('new', 'name');
|
|
15
|
+
|
|
16
|
+
// Assert
|
|
17
|
+
expect(name).toBe('new_name');
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { format } from 'sql-formatter';
|
|
5
|
+
import { PgColumn, PgTable } from '../abstractions';
|
|
6
|
+
import { ContentEntityModel } from '../content-entity-model';
|
|
7
|
+
|
|
8
|
+
export function buildFullTableName(tableName: string, schema?: string): string {
|
|
9
|
+
return schema ? `${schema}.${tableName}` : tableName;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Builds a DROP TABLE IF EXISTS statement.
|
|
14
|
+
* @param table
|
|
15
|
+
*/
|
|
16
|
+
export function buildDropTableIfExists(table: PgTable): string {
|
|
17
|
+
return `DROP TABLE IF EXISTS ${table.buildFullName()} CASCADE`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Builds a CREATE TABLE statement.
|
|
22
|
+
* @param table - PgTable instance to build the CREATE TABLE statement for.
|
|
23
|
+
*/
|
|
24
|
+
export function buildCreateTable(table: PgTable): string {
|
|
25
|
+
const columns = [
|
|
26
|
+
table.pk,
|
|
27
|
+
...table.fks,
|
|
28
|
+
...table.virtualFks,
|
|
29
|
+
...table.columns,
|
|
30
|
+
];
|
|
31
|
+
return `CREATE TABLE ${table.buildFullName()}(
|
|
32
|
+
${columns.map((c) => c.buildExpression()).join(',\n')}
|
|
33
|
+
)`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Builds a GRANT statement for INSERTs and UPDATEs.
|
|
38
|
+
* @param table - PgTable instance to build the GRANT statement for.
|
|
39
|
+
*/
|
|
40
|
+
export function buildInsertUpdateGrants(table: PgTable): string {
|
|
41
|
+
const columns = [...table.fks, ...table.virtualFks, ...table.columns];
|
|
42
|
+
return `GRANT INSERT, UPDATE (${columns.map((c) => c.name).join(',')})
|
|
43
|
+
ON ${table.buildFullName()} TO ":DATABASE_GQL_ROLE";`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Builds a GRANT statement for SELECTs and DELETEs.
|
|
48
|
+
* @param table - PgTable instance to build the GRANT statement for.
|
|
49
|
+
*/
|
|
50
|
+
export function buildSelectDeleteGrants(table: PgTable): string {
|
|
51
|
+
return `GRANT SELECT, DELETE ON ${table.buildFullName()} TO ":DATABASE_GQL_ROLE";`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Builds additional statements relevant for table creation
|
|
56
|
+
* that should be executed after CRATE TABLE e.g. adding indexes or comments.
|
|
57
|
+
* @param table - PgTable instance to build extra statements for.
|
|
58
|
+
*/
|
|
59
|
+
export function buildAdditionalTableStatements(table: PgTable): string[] {
|
|
60
|
+
const statements: string[] = [];
|
|
61
|
+
const columns = [
|
|
62
|
+
table.pk,
|
|
63
|
+
...table.fks,
|
|
64
|
+
...table.virtualFks,
|
|
65
|
+
...table.columns,
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const additionalStatements = ([] as (string | undefined)[])
|
|
69
|
+
.concat(...columns.map((p) => p.buildAdditionalStatements()))
|
|
70
|
+
.filter((s: string | undefined): s is string => s !== undefined);
|
|
71
|
+
|
|
72
|
+
statements.push(...additionalStatements);
|
|
73
|
+
|
|
74
|
+
return statements;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Formats an SQL string using pg-formatter.
|
|
79
|
+
* @param sql - Input SQL string.
|
|
80
|
+
*/
|
|
81
|
+
export function formatSql(sql: string): string {
|
|
82
|
+
return format(sql, { language: 'sql' });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Generates a migration for graphile-migrate.
|
|
87
|
+
* @param statements - Array of SQL statements.
|
|
88
|
+
* @param outPath - Path where the dump is created.
|
|
89
|
+
*/
|
|
90
|
+
export async function generateMigration(
|
|
91
|
+
statements: string[],
|
|
92
|
+
outPath: string,
|
|
93
|
+
): Promise<void> {
|
|
94
|
+
const dirPath = path.dirname(outPath);
|
|
95
|
+
if (!fs.existsSync(dirPath)) {
|
|
96
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log(`Writing migration to ${outPath}.`);
|
|
100
|
+
const preamble = `
|
|
101
|
+
--! Message: added content types
|
|
102
|
+
-- Statements below are generated from publish format definitions.`;
|
|
103
|
+
statements.unshift(preamble);
|
|
104
|
+
|
|
105
|
+
fs.writeFile(outPath, formatSql(statements.join(';\n')), (err) => {
|
|
106
|
+
if (err) {
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Combines `nameParts` into a single snake case string.
|
|
114
|
+
* @param nameParts - Name parts to combine.
|
|
115
|
+
*/
|
|
116
|
+
export function buildName(...nameParts: string[]): string {
|
|
117
|
+
return nameParts.join('_');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function renameDbItem(
|
|
121
|
+
item: PgTable | PgColumn,
|
|
122
|
+
newName: string,
|
|
123
|
+
displayName?: string,
|
|
124
|
+
): void {
|
|
125
|
+
item.name = newName;
|
|
126
|
+
if (displayName) {
|
|
127
|
+
item.displayName = displayName;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface NameOverride {
|
|
132
|
+
[name: string]: [string, string?];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function applyNameOverrides(
|
|
136
|
+
model: ContentEntityModel,
|
|
137
|
+
overrides: NameOverride,
|
|
138
|
+
): void {
|
|
139
|
+
const mainModelTables = [model.contentEntity, ...model.relatedObjects];
|
|
140
|
+
|
|
141
|
+
for (const table of mainModelTables) {
|
|
142
|
+
let rename = overrides[table.name];
|
|
143
|
+
if (rename) {
|
|
144
|
+
renameDbItem(table, ...rename);
|
|
145
|
+
}
|
|
146
|
+
for (const column of table.columns) {
|
|
147
|
+
rename = overrides[column.name];
|
|
148
|
+
if (rename) {
|
|
149
|
+
renameDbItem(column, ...rename);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function isReservedPgWord(s: string): boolean {
|
|
156
|
+
return PgReservedWords.includes(s.toUpperCase());
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const PgReservedWords = [
|
|
160
|
+
'ALL',
|
|
161
|
+
'ANALYSE',
|
|
162
|
+
'ANALYZE',
|
|
163
|
+
'AND',
|
|
164
|
+
'ANY',
|
|
165
|
+
'ARRAY',
|
|
166
|
+
'AS',
|
|
167
|
+
'ASC',
|
|
168
|
+
'ASYMMETRIC',
|
|
169
|
+
'BOTH',
|
|
170
|
+
'CASE',
|
|
171
|
+
'CAST',
|
|
172
|
+
'CHECK',
|
|
173
|
+
'COLLATE',
|
|
174
|
+
'COLUMN',
|
|
175
|
+
'CONSTRAINT',
|
|
176
|
+
'CREATE',
|
|
177
|
+
'CURRENT_CATALOG',
|
|
178
|
+
'CURRENT_DATE',
|
|
179
|
+
'CURRENT_ROLE',
|
|
180
|
+
'CURRENT_TIME',
|
|
181
|
+
'CURRENT_TIMESTAMP',
|
|
182
|
+
'CURRENT_USER',
|
|
183
|
+
'DEFAULT',
|
|
184
|
+
'DEFERRABLE',
|
|
185
|
+
'DESC',
|
|
186
|
+
'DISTINCT',
|
|
187
|
+
'DO',
|
|
188
|
+
'ELSE',
|
|
189
|
+
'END',
|
|
190
|
+
'EXCEPT',
|
|
191
|
+
'FALSE',
|
|
192
|
+
'FETCH',
|
|
193
|
+
'FOR',
|
|
194
|
+
'FOREIGN',
|
|
195
|
+
'FROM',
|
|
196
|
+
'GRANT',
|
|
197
|
+
'GROUP',
|
|
198
|
+
'HAVING',
|
|
199
|
+
'IN',
|
|
200
|
+
'INITIALLY',
|
|
201
|
+
'INTERSECT',
|
|
202
|
+
'INTO',
|
|
203
|
+
'LATERAL',
|
|
204
|
+
'LEADING',
|
|
205
|
+
'LIMIT',
|
|
206
|
+
'LOCALTIME',
|
|
207
|
+
'LOCALTIMESTAMP',
|
|
208
|
+
'NOT',
|
|
209
|
+
'NULL',
|
|
210
|
+
'OFFSET',
|
|
211
|
+
'ON',
|
|
212
|
+
'ONLY',
|
|
213
|
+
'OR',
|
|
214
|
+
'ORDER',
|
|
215
|
+
'PLACING',
|
|
216
|
+
'PRIMARY',
|
|
217
|
+
'REFERENCES',
|
|
218
|
+
'RETURNING',
|
|
219
|
+
'SELECT',
|
|
220
|
+
'SESSION_USER',
|
|
221
|
+
'SOME',
|
|
222
|
+
'SYMMETRIC',
|
|
223
|
+
'TABLE',
|
|
224
|
+
'THEN',
|
|
225
|
+
'TO',
|
|
226
|
+
'TRAILING',
|
|
227
|
+
'TRUE',
|
|
228
|
+
'UNION',
|
|
229
|
+
'UNIQUE',
|
|
230
|
+
'USER',
|
|
231
|
+
'USING',
|
|
232
|
+
'VARIADIC',
|
|
233
|
+
'WHEN',
|
|
234
|
+
'WHERE',
|
|
235
|
+
'WINDOW',
|
|
236
|
+
'WITH',
|
|
237
|
+
];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { snakeCaseToCamelCase } from './pgl-utils';
|
|
2
|
+
|
|
3
|
+
describe('snakeCaseToCamelCase', () => {
|
|
4
|
+
test('single part', () => {
|
|
5
|
+
// Arrange & Act
|
|
6
|
+
const name = snakeCaseToCamelCase('name');
|
|
7
|
+
|
|
8
|
+
// Assert
|
|
9
|
+
expect(name).toBe('name');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('multiple parts', () => {
|
|
13
|
+
// Arrange & Act
|
|
14
|
+
const name = snakeCaseToCamelCase('new_name_it_is');
|
|
15
|
+
|
|
16
|
+
// Assert
|
|
17
|
+
expect(name).toBe('newNameItIs');
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import endent from 'endent';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { JSONPgSmartTags } from 'graphile-utils';
|
|
4
|
+
import * as JSON5 from 'json5';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import {
|
|
7
|
+
BaseSmartTags,
|
|
8
|
+
PgColumn,
|
|
9
|
+
PgFkColumn,
|
|
10
|
+
PgTable,
|
|
11
|
+
TableSmartTags,
|
|
12
|
+
} from '../abstractions';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Builds smart tags for FK constraints.
|
|
16
|
+
* @param columns - Array of FK columns.
|
|
17
|
+
*/
|
|
18
|
+
export function buildFkConstraintSmartTags(
|
|
19
|
+
columns: PgFkColumn[],
|
|
20
|
+
): { [name: string]: BaseSmartTags } {
|
|
21
|
+
return columns.reduce((acc, col) => {
|
|
22
|
+
if (col.fkName) {
|
|
23
|
+
acc[col.fkName] = {
|
|
24
|
+
tags: {
|
|
25
|
+
foreignFieldName: `${col.table.displayName ?? col.table.name}`,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return acc;
|
|
30
|
+
}, {} as { [name: string]: BaseSmartTags });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Builds class (table) level smart tags.
|
|
35
|
+
* @param tables - Array of tables.
|
|
36
|
+
*/
|
|
37
|
+
export function buildClassSmartTags(
|
|
38
|
+
tables: PgTable[],
|
|
39
|
+
): { [name: string]: TableSmartTags } {
|
|
40
|
+
const tags: { [name: string]: TableSmartTags } = {};
|
|
41
|
+
|
|
42
|
+
tables.forEach((t) => {
|
|
43
|
+
tags[t.name] = t.buildSmartTags();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return tags;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Builds smart tags for virtual FK columns.
|
|
51
|
+
* @param virtualFks - Array of virtual FK columns.
|
|
52
|
+
*/
|
|
53
|
+
export function buildVirtualFkSmartTags(virtualFks: PgFkColumn[]): string[] {
|
|
54
|
+
return virtualFks.map((vfk) => {
|
|
55
|
+
return `(${vfk.name}) references ${vfk.targetPk.table.name}|@omit many`;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Builds attribute (column) level smart tags.
|
|
61
|
+
* @param columns - Array of PgColumns.
|
|
62
|
+
*/
|
|
63
|
+
export function buildAttributeSmartTags(
|
|
64
|
+
columns: PgColumn[],
|
|
65
|
+
): { [name: string]: BaseSmartTags } {
|
|
66
|
+
return columns.reduce((acc, col) => {
|
|
67
|
+
acc[col.name] = col.buildSmartTags();
|
|
68
|
+
return acc;
|
|
69
|
+
}, {} as { [name: string]: BaseSmartTags });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// TODO: Replace with camelCase from graphile-build.
|
|
73
|
+
export function snakeCaseToCamelCase(input: string): string {
|
|
74
|
+
return input
|
|
75
|
+
.split('_')
|
|
76
|
+
.reduce(
|
|
77
|
+
(res, word, i) =>
|
|
78
|
+
i === 0
|
|
79
|
+
? word.toLowerCase()
|
|
80
|
+
: `${res}${word.charAt(0).toUpperCase()}${word
|
|
81
|
+
.substr(1)
|
|
82
|
+
.toLowerCase()}`,
|
|
83
|
+
'',
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Generates a smart tags plugin file.
|
|
89
|
+
* @param tags - Smart tags configuration.
|
|
90
|
+
* @param outPath - Output path where the plugin will be written.
|
|
91
|
+
*/
|
|
92
|
+
export function generateSmartTagsPlugin(
|
|
93
|
+
tags: JSONPgSmartTags,
|
|
94
|
+
outPath: string,
|
|
95
|
+
): void {
|
|
96
|
+
const dirPath = path.dirname(outPath);
|
|
97
|
+
if (!fs.existsSync(dirPath)) {
|
|
98
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.log(`Writing smart tags plugn to to ${outPath}.`);
|
|
102
|
+
|
|
103
|
+
const pluginSrc = endent`
|
|
104
|
+
import { makeJSONPgSmartTagsPlugin } from 'graphile-utils';
|
|
105
|
+
/**
|
|
106
|
+
* Smart tags to adjust and enhance the generated GraphQL API.
|
|
107
|
+
*/
|
|
108
|
+
export const SmartTagsPlugin = makeJSONPgSmartTagsPlugin(${endent.pretty(
|
|
109
|
+
// Use JSON5 to generate keys without quotes to make the generated code already compatible with prettier.
|
|
110
|
+
JSON5.stringify(tags, null, 2),
|
|
111
|
+
)});
|
|
112
|
+
`;
|
|
113
|
+
|
|
114
|
+
fs.writeFileSync(outPath, pluginSrc);
|
|
115
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { JSONSchema4 } from 'json-schema';
|
|
2
|
+
import {
|
|
3
|
+
PgColumn,
|
|
4
|
+
PgFkColumn,
|
|
5
|
+
PgTable,
|
|
6
|
+
TableSmartTags,
|
|
7
|
+
} from '../../abstractions';
|
|
8
|
+
import { PublishSchemaToDbOptions } from '../../publish-schema-to-db-options';
|
|
9
|
+
import { PkColumn, PrimitiveColumn } from '../columns';
|
|
10
|
+
import {
|
|
11
|
+
filterProperties,
|
|
12
|
+
isPrimitiveProperty,
|
|
13
|
+
mapToPgType,
|
|
14
|
+
} from '../json-schema-parse-utils';
|
|
15
|
+
import {
|
|
16
|
+
buildAdditionalTableStatements,
|
|
17
|
+
buildCreateTable,
|
|
18
|
+
buildDropTableIfExists,
|
|
19
|
+
buildFullTableName,
|
|
20
|
+
buildInsertUpdateGrants,
|
|
21
|
+
buildSelectDeleteGrants,
|
|
22
|
+
} from '../pg-sql-gen-utils';
|
|
23
|
+
import { buildAttributeSmartTags } from '../pgl-utils';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Table model for storing primitive properties of a root entity (movie, collection etc.).
|
|
27
|
+
*/
|
|
28
|
+
export class ContentEntityTable implements PgTable {
|
|
29
|
+
name: string;
|
|
30
|
+
displayName?: string;
|
|
31
|
+
readonly description?: string;
|
|
32
|
+
readonly pk: PgColumn;
|
|
33
|
+
readonly fks: PgFkColumn[] = [];
|
|
34
|
+
readonly virtualFks: PgFkColumn[] = [];
|
|
35
|
+
readonly columns: PgColumn[] = [];
|
|
36
|
+
private options: PublishSchemaToDbOptions;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Constructor for creating ContentEntityTable.
|
|
40
|
+
* @param name - Name of the table.
|
|
41
|
+
* @param schema - Name of the database schema.
|
|
42
|
+
* @param jsonSchemas - Schemas of primitive properties.
|
|
43
|
+
*/
|
|
44
|
+
constructor(
|
|
45
|
+
options: PublishSchemaToDbOptions,
|
|
46
|
+
name: string,
|
|
47
|
+
jsonSchemas: { [p: string]: JSONSchema4 },
|
|
48
|
+
description?: string,
|
|
49
|
+
) {
|
|
50
|
+
this.options = options;
|
|
51
|
+
this.name = name;
|
|
52
|
+
this.description = description;
|
|
53
|
+
|
|
54
|
+
if (jsonSchemas[options.idKey] === undefined) {
|
|
55
|
+
throw Error(`Could not find the expected PK property "${options.idKey}"`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 1. Create the PK.
|
|
59
|
+
this.pk = new PkColumn(this, mapToPgType(jsonSchemas[options.idKey]));
|
|
60
|
+
|
|
61
|
+
// 2. Iterate over (already separated) primitive properties.
|
|
62
|
+
const excluded = [...options.ignoredProperties, options.idKey];
|
|
63
|
+
for (const propertyName in filterProperties(jsonSchemas, excluded)) {
|
|
64
|
+
const schema = jsonSchemas[propertyName] as JSONSchema4;
|
|
65
|
+
|
|
66
|
+
if (!isPrimitiveProperty(schema)) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
'Non-primitive properties are not supported in content entities',
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 3. Create regular 'data' columns.
|
|
73
|
+
this.columns.push(
|
|
74
|
+
new PrimitiveColumn(propertyName, mapToPgType(schema), this, {
|
|
75
|
+
description: schema.description,
|
|
76
|
+
indexable: propertyName === 'order_no', // Somewhat stinky but it could also be a part of convention.
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
buildFullName(): string {
|
|
82
|
+
return buildFullTableName(this.name, this.options.dbSchema);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
buildStatements(): string[] {
|
|
86
|
+
return [
|
|
87
|
+
buildDropTableIfExists(this),
|
|
88
|
+
buildCreateTable(this),
|
|
89
|
+
...buildAdditionalTableStatements(this),
|
|
90
|
+
buildSelectDeleteGrants(this),
|
|
91
|
+
buildInsertUpdateGrants(this),
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
buildSmartTags(): TableSmartTags {
|
|
96
|
+
return {
|
|
97
|
+
description: this.description,
|
|
98
|
+
attribute: buildAttributeSmartTags(this.columns),
|
|
99
|
+
tags: {
|
|
100
|
+
omit: 'create,update,delete',
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|