@axinom/mosaic-db-common 0.10.0 → 0.11.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.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('../dist/cli').run();
@@ -0,0 +1,14 @@
1
+ import { CommandModule } from 'yargs';
2
+ /**
3
+ * CLI command options object.
4
+ */
5
+ interface GenerateSnippetsOptions {
6
+ includeMultitenancySnippets: boolean;
7
+ snippetsFileNameWithoutExtension: string;
8
+ }
9
+ /**
10
+ * Yargs command module definition for `generate-vscode-sql-snippets` CLI script.
11
+ */
12
+ export declare const generateVscodeSqlSnippets: CommandModule<unknown, GenerateSnippetsOptions>;
13
+ export {};
14
+ //# sourceMappingURL=generate-vscode-sql-snippets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-vscode-sql-snippets.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/generate-vscode-sql-snippets.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEtC;;GAEG;AACH,UAAU,uBAAuB;IAC/B,2BAA2B,EAAE,OAAO,CAAC;IACrC,gCAAgC,EAAE,MAAM,CAAC;CAC1C;AAqMD;;GAEG;AACH,eAAO,MAAM,yBAAyB,EAAE,aAAa,CACnD,OAAO,EACP,uBAAuB,CAsBxB,CAAC"}
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
3
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
4
+ var m = o[Symbol.asyncIterator], i;
5
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
6
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
7
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.generateVscodeSqlSnippets = void 0;
11
+ /* eslint-disable no-console */
12
+ const fs_1 = require("fs");
13
+ const path_1 = require("path");
14
+ const readdir = require("readdirp");
15
+ /**
16
+ * Colors the CLI text in green.
17
+ */
18
+ const green = (text) => `\u001b[32m${text}\u001b[39m`;
19
+ /**
20
+ * Colors the CLI text in red.
21
+ */
22
+ const red = (text) => `\u001b[31m${text}\u001b[39m`;
23
+ /**
24
+ * Returns a readable timestamp in current locale time, e.g. `[2021-05-13 13:02:12.685]`
25
+ */
26
+ const getTimestamp = () => {
27
+ const offset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds
28
+ const localISOTime = new Date(Date.now() - offset)
29
+ .toISOString()
30
+ .slice(0, -1)
31
+ .replace('T', ' ');
32
+ return `[${localISOTime}]`;
33
+ };
34
+ /**
35
+ * Writes generated snippets object to .vscode folder. Creates a folder if it does not exist.
36
+ * Returns a success message.
37
+ */
38
+ const writeSourceFile = (snippetsFileName, contents) => {
39
+ const outDir = path_1.join(process.env.PROJECT_CWD || process.cwd(), '.vscode');
40
+ if (!fs_1.existsSync(outDir)) {
41
+ fs_1.mkdirSync(outDir, { recursive: true });
42
+ }
43
+ const filePath = path_1.join(outDir, `${snippetsFileName}.code-snippets`);
44
+ fs_1.writeFileSync(filePath, JSON.stringify(contents, null, 2), 'utf-8');
45
+ return `Success! Snippets file path: ${filePath}`;
46
+ };
47
+ /**
48
+ * Counter for potential unnamed snippets to generate temporary unique names.
49
+ */
50
+ let unparsedSnippetIndex = 1;
51
+ /**
52
+ * Extracts snippet name and prefix from a line that defines a function.
53
+ * In case it's impossible to extract - placeholder values are used and logged (which is expected to be fixed).
54
+ * @example `CREATE OR REPLACE FUNCTION ax_utils.raise_error(` -> `{"prefix": "ax-raise-error", "snippetName": "Raise Error (Ax Utils)"}
55
+ */
56
+ const parseFunctionName = (line, snippetJson) => {
57
+ var _a;
58
+ const match = (_a = new RegExp(/CREATE OR REPLACE FUNCTION (.*)\(/i).exec(line)) === null || _a === void 0 ? void 0 : _a[1];
59
+ let prefix = `unnamed-snippet-${unparsedSnippetIndex}`;
60
+ let snippetName = `Unnamed Snippet ${unparsedSnippetIndex}`;
61
+ if (!match) {
62
+ console.log(red(`Unable to parse the name for the following snippet. Naming the snippet ${prefix} instead.`));
63
+ console.log(snippetJson);
64
+ unparsedSnippetIndex++;
65
+ }
66
+ else {
67
+ prefix = match;
68
+ const [schemaName, functionName] = match.split('.');
69
+ const format = (text) => text
70
+ .replace(/_/g, ' ')
71
+ .replace(/(^\w{1})|(\s+\w{1})/g, (letter) => letter.toUpperCase());
72
+ snippetName = `${format(functionName)} (${format(schemaName)})`;
73
+ prefix = `ax-${functionName.replace(/_/g, '-')}`;
74
+ }
75
+ return { prefix, snippetName };
76
+ };
77
+ /**
78
+ * Reads a file to memory as an array, where each element is a new line.
79
+ * Goes through the array and generates sql snippet objects based on file contents.
80
+ * Depends on comment conventions to work properly.
81
+ */
82
+ const populateSnippets = async (filePath, snippetsObject) => {
83
+ const contents = fs_1.readFileSync(filePath, 'utf-8').split('\n').filter(Boolean);
84
+ const startTag = '/*-snippet';
85
+ const endTag = 'snippet-*/';
86
+ const funcLine = 'create or replace function';
87
+ let readingSnippet = false;
88
+ let snippetStringContents = '';
89
+ let snippetJson = undefined;
90
+ for (const line of contents) {
91
+ if (line.startsWith(startTag)) {
92
+ readingSnippet = true;
93
+ continue;
94
+ }
95
+ if (line.startsWith(endTag)) {
96
+ snippetJson = JSON.parse(snippetStringContents);
97
+ snippetStringContents = '';
98
+ readingSnippet = false;
99
+ continue;
100
+ }
101
+ if (snippetJson && line.toLowerCase().startsWith(funcLine)) {
102
+ const { prefix, snippetName } = parseFunctionName(line, snippetJson);
103
+ snippetJson = Object.assign({ prefix }, snippetJson); // Adds a property to the first position
104
+ snippetJson['scope'] = 'sql';
105
+ snippetsObject[snippetName] = snippetJson;
106
+ snippetJson = undefined;
107
+ continue;
108
+ }
109
+ if (readingSnippet) {
110
+ snippetStringContents += line;
111
+ }
112
+ }
113
+ return snippetsObject;
114
+ };
115
+ /**
116
+ * Reads all `.sql` files in `migrations` folder (and sub-folders), extracts snippet json objects from them, and returns a combined object with all snippets.
117
+ */
118
+ const getParsedSnippets = async (dirPath, includeMultitenancySnippets) => {
119
+ var e_1, _a;
120
+ const parsedSnippets = {};
121
+ try {
122
+ for (var _b = __asyncValues(readdir(dirPath, {
123
+ fileFilter: '*.sql',
124
+ })), _c; _c = await _b.next(), !_c.done;) {
125
+ const { fullPath, path } = _c.value;
126
+ if (!includeMultitenancySnippets && path.includes('multitenancy')) {
127
+ continue;
128
+ }
129
+ console.log(getTimestamp(), `Parsing ${path}`);
130
+ await populateSnippets(fullPath, parsedSnippets);
131
+ }
132
+ }
133
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
134
+ finally {
135
+ try {
136
+ if (_c && !_c.done && (_a = _b.return)) await _a.call(_b);
137
+ }
138
+ finally { if (e_1) throw e_1.error; }
139
+ }
140
+ return parsedSnippets;
141
+ };
142
+ /**
143
+ * Reads all `.code-snippets` files in `migrations` folder (and sub-folders) and returns a combined object with all snippets.
144
+ * These files contain custom snippets that are not tied to any one utils or define sql function.
145
+ */
146
+ const getCustomSnippets = async (dirPath, includeMultitenancySnippets) => {
147
+ var e_2, _a;
148
+ let customSnippets = {};
149
+ try {
150
+ for (var _b = __asyncValues(readdir(dirPath, {
151
+ fileFilter: '*.code-snippets',
152
+ })), _c; _c = await _b.next(), !_c.done;) {
153
+ const { fullPath, path } = _c.value;
154
+ if (!includeMultitenancySnippets && path.includes('multitenancy')) {
155
+ continue;
156
+ }
157
+ console.log(getTimestamp(), `Reading ${path}`);
158
+ const fileContents = JSON.parse(fs_1.readFileSync(fullPath, 'utf-8'));
159
+ customSnippets = Object.assign(Object.assign({}, customSnippets), fileContents);
160
+ }
161
+ }
162
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
163
+ finally {
164
+ try {
165
+ if (_c && !_c.done && (_a = _b.return)) await _a.call(_b);
166
+ }
167
+ finally { if (e_2) throw e_2.error; }
168
+ }
169
+ return customSnippets;
170
+ };
171
+ /**
172
+ * Iterates over all .sql files inside of the `migrations` folder.
173
+ * Parses each file based on comments and function definition conventions.
174
+ * Reads all .code-snippets files inside of the `migrations` folder and adds these snippets to parsed snippets.
175
+ * Generates or updates a VScode snippets file.
176
+ */
177
+ const generate = async ({ includeMultitenancySnippets, snippetsFileNameWithoutExtension, }) => {
178
+ console.log(getTimestamp(), green(`Starting snippets generation!`));
179
+ const dirPath = path_1.resolve(__dirname, '../../../migrations');
180
+ const parsedSnippets = await getParsedSnippets(dirPath, includeMultitenancySnippets);
181
+ const customSnippets = await getCustomSnippets(dirPath, includeMultitenancySnippets);
182
+ const writeResult = writeSourceFile(snippetsFileNameWithoutExtension, Object.assign(Object.assign({}, parsedSnippets), customSnippets));
183
+ console.log(getTimestamp(), green(writeResult));
184
+ };
185
+ /**
186
+ * Yargs command module definition for `generate-vscode-sql-snippets` CLI script.
187
+ */
188
+ exports.generateVscodeSqlSnippets = {
189
+ command: 'generate-vscode-sql-snippets',
190
+ describe: 'Creates or updates a mosaic sql snippets file, providing an easier way to generate database migrations.',
191
+ builder: (yargs) => yargs
192
+ .option('includeMultitenancySnippets', {
193
+ alias: 'm',
194
+ describe: 'If set to true, would also include snippets for multitenancy define functions.',
195
+ default: false,
196
+ boolean: true,
197
+ })
198
+ .option('snippetsFileNameWithoutExtension', {
199
+ alias: 'f',
200
+ describe: 'Overrides default snippets file name with a custom value. Extension `code-snippets` will be attached to this value.',
201
+ default: 'mosaic-sql-migrations',
202
+ string: true,
203
+ }),
204
+ handler: generate,
205
+ };
206
+ //# sourceMappingURL=generate-vscode-sql-snippets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-vscode-sql-snippets.js","sourceRoot":"","sources":["../../../src/cli/commands/generate-vscode-sql-snippets.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,+BAA+B;AAC/B,2BAAwE;AACxE,+BAAqC;AACrC,oCAAoC;AAWpC;;GAEG;AACH,MAAM,KAAK,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,aAAa,IAAI,YAAY,CAAC;AAEtE;;GAEG;AACH,MAAM,GAAG,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,aAAa,IAAI,YAAY,CAAC;AAEpE;;GAEG;AACH,MAAM,YAAY,GAAG,GAAW,EAAE;IAChC,MAAM,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC,iBAAiB,EAAE,GAAG,KAAK,CAAC,CAAC,wBAAwB;IAC/E,MAAM,YAAY,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC;SAC/C,WAAW,EAAE;SACb,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;SACZ,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACrB,OAAO,IAAI,YAAY,GAAG,CAAC;AAC7B,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,eAAe,GAAG,CACtB,gBAAwB,EACxB,QAAiC,EACzB,EAAE;IACV,MAAM,MAAM,GAAG,WAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC;IACzE,IAAI,CAAC,eAAU,CAAC,MAAM,CAAC,EAAE;QACvB,cAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;KACxC;IACD,MAAM,QAAQ,GAAG,WAAI,CAAC,MAAM,EAAE,GAAG,gBAAgB,gBAAgB,CAAC,CAAC;IACnE,kBAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACpE,OAAO,gCAAgC,QAAQ,EAAE,CAAC;AACpD,CAAC,CAAC;AAEF;;GAEG;AACH,IAAI,oBAAoB,GAAG,CAAC,CAAC;AAC7B;;;;GAIG;AACH,MAAM,iBAAiB,GAAG,CACxB,IAAY,EACZ,WAAoC,EACK,EAAE;;IAC3C,MAAM,KAAK,GAAG,MAAA,IAAI,MAAM,CAAC,oCAAoC,CAAC,CAAC,IAAI,CACjE,IAAI,CACL,0CAAG,CAAC,CAAC,CAAC;IACP,IAAI,MAAM,GAAG,mBAAmB,oBAAoB,EAAE,CAAC;IACvD,IAAI,WAAW,GAAG,mBAAmB,oBAAoB,EAAE,CAAC;IAC5D,IAAI,CAAC,KAAK,EAAE;QACV,OAAO,CAAC,GAAG,CACT,GAAG,CACD,0EAA0E,MAAM,WAAW,CAC5F,CACF,CAAC;QACF,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACzB,oBAAoB,EAAE,CAAC;KACxB;SAAM;QACL,MAAM,GAAG,KAAK,CAAC;QACf,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACpD,MAAM,MAAM,GAAG,CAAC,IAAY,EAAU,EAAE,CACtC,IAAI;aACD,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC;aAClB,OAAO,CAAC,sBAAsB,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;QACvE,WAAW,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,KAAK,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC;QAChE,MAAM,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;KAClD;IAED,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;AACjC,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,gBAAgB,GAAG,KAAK,EAC5B,QAAgB,EAChB,cAAuC,EACL,EAAE;IACpC,MAAM,QAAQ,GAAG,iBAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7E,MAAM,QAAQ,GAAG,YAAY,CAAC;IAC9B,MAAM,MAAM,GAAG,YAAY,CAAC;IAC5B,MAAM,QAAQ,GAAG,4BAA4B,CAAC;IAC9C,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,IAAI,qBAAqB,GAAG,EAAE,CAAC;IAC/B,IAAI,WAAW,GAAwC,SAAS,CAAC;IACjE,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE;QAC3B,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;YAC7B,cAAc,GAAG,IAAI,CAAC;YACtB,SAAS;SACV;QAED,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE;YAC3B,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;YAChD,qBAAqB,GAAG,EAAE,CAAC;YAC3B,cAAc,GAAG,KAAK,CAAC;YACvB,SAAS;SACV;QAED,IAAI,WAAW,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;YAC1D,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,iBAAiB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;YACrE,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC,wCAAwC;YAC9F,WAAW,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC;YAC7B,cAAc,CAAC,WAAW,CAAC,GAAG,WAAW,CAAC;YAC1C,WAAW,GAAG,SAAS,CAAC;YACxB,SAAS;SACV;QAED,IAAI,cAAc,EAAE;YAClB,qBAAqB,IAAI,IAAI,CAAC;SAC/B;KACF;IACD,OAAO,cAAc,CAAC;AACxB,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,iBAAiB,GAAG,KAAK,EAC7B,OAAe,EACf,2BAAoC,EACF,EAAE;;IACpC,MAAM,cAAc,GAA4B,EAAE,CAAC;;QACnD,KAAuC,IAAA,KAAA,cAAA,OAAO,CAAC,OAAO,EAAE;YACtD,UAAU,EAAE,OAAO;SACpB,CAAC,CAAA,IAAA;YAFS,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAA,CAAA;YAGjC,IAAI,CAAC,2BAA2B,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE;gBACjE,SAAS;aACV;YACD,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,EAAE,WAAW,IAAI,EAAE,CAAC,CAAC;YAC/C,MAAM,gBAAgB,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;SAClD;;;;;;;;;IAED,OAAO,cAAc,CAAC;AACxB,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,iBAAiB,GAAG,KAAK,EAC7B,OAAe,EACf,2BAAoC,EACF,EAAE;;IACpC,IAAI,cAAc,GAA4B,EAAE,CAAC;;QACjD,KAAuC,IAAA,KAAA,cAAA,OAAO,CAAC,OAAO,EAAE;YACtD,UAAU,EAAE,iBAAiB;SAC9B,CAAC,CAAA,IAAA;YAFS,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAA,CAAA;YAGjC,IAAI,CAAC,2BAA2B,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE;gBACjE,SAAS;aACV;YACD,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,EAAE,WAAW,IAAI,EAAE,CAAC,CAAC;YAC/C,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;YACjE,cAAc,mCAAQ,cAAc,GAAK,YAAY,CAAE,CAAC;SACzD;;;;;;;;;IAED,OAAO,cAAc,CAAC;AACxB,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,QAAQ,GAAG,KAAK,EAAE,EACtB,2BAA2B,EAC3B,gCAAgC,GACR,EAAiB,EAAE;IAC3C,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,EAAE,KAAK,CAAC,+BAA+B,CAAC,CAAC,CAAC;IACpE,MAAM,OAAO,GAAG,cAAO,CAAC,SAAS,EAAE,qBAAqB,CAAC,CAAC;IAC1D,MAAM,cAAc,GAAG,MAAM,iBAAiB,CAC5C,OAAO,EACP,2BAA2B,CAC5B,CAAC;IACF,MAAM,cAAc,GAAG,MAAM,iBAAiB,CAC5C,OAAO,EACP,2BAA2B,CAC5B,CAAC;IACF,MAAM,WAAW,GAAG,eAAe,CAAC,gCAAgC,kCAC/D,cAAc,GACd,cAAc,EACjB,CAAC;IACH,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;AAClD,CAAC,CAAC;AAEF;;GAEG;AACU,QAAA,yBAAyB,GAGlC;IACF,OAAO,EAAE,8BAA8B;IACvC,QAAQ,EACN,yGAAyG;IAC3G,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CACjB,KAAK;SACF,MAAM,CAAC,6BAA6B,EAAE;QACrC,KAAK,EAAE,GAAG;QACV,QAAQ,EACN,gFAAgF;QAClF,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,IAAI;KACd,CAAC;SACD,MAAM,CAAC,kCAAkC,EAAE;QAC1C,KAAK,EAAE,GAAG;QACV,QAAQ,EACN,qHAAqH;QACvH,OAAO,EAAE,uBAAuB;QAChC,MAAM,EAAE,IAAI;KACb,CAAC;IACN,OAAO,EAAE,QAAQ;CAClB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from './generate-vscode-sql-snippets';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/index.ts"],"names":[],"mappings":"AAAA,cAAc,gCAAgC,CAAC"}
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
10
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ __exportStar(require("./generate-vscode-sql-snippets"), exports);
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli/commands/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,iEAA+C"}
@@ -0,0 +1,2 @@
1
+ export declare const run: () => void;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,GAAG,QAAO,IAOtB,CAAC"}
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.run = void 0;
4
+ const yargs = require("yargs");
5
+ const commands_1 = require("./commands");
6
+ const run = () => {
7
+ yargs
8
+ .scriptName('mosaic-db')
9
+ .command(commands_1.generateVscodeSqlSnippets)
10
+ .demandCommand()
11
+ .help()
12
+ .epilog(`For more information, visit https://portal.axinom.com.`).argv;
13
+ };
14
+ exports.run = run;
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";;;AAAA,+BAA+B;AAC/B,yCAAuD;AAEhD,MAAM,GAAG,GAAG,GAAS,EAAE;IAC5B,KAAK;SACF,UAAU,CAAC,WAAW,CAAC;SACvB,OAAO,CAAC,oCAAyB,CAAC;SAClC,aAAa,EAAE;SACf,IAAI,EAAE;SACN,MAAM,CAAC,wDAAwD,CAAC,CAAC,IAAI,CAAC;AAC3E,CAAC,CAAC;AAPW,QAAA,GAAG,OAOd"}
@@ -43,3 +43,233 @@ They reside in their own SQL schema to which the GraphQL role does not have
43
43
  access.
44
44
 
45
45
  The define SQL functions depend on the `ax_utils` functions.
46
+
47
+ ## VSCode Snippets, Documentation, and Maintainability
48
+
49
+ Both Utility and Define SQL functions are there to simplify creation of database
50
+ migrations. But because they are provided as part of the library and there is no
51
+ intellisense support in sql files - we need to also make these functions
52
+ discoverable and provide documentation and examples to make it easier to use
53
+ them.
54
+
55
+ To help with that - we provide a way to generate a file that contains VSCode
56
+ snippets for all relevant functions, which adds a possibility to scaffold code
57
+ pieces in migration files and easily fill them with relevant data. There are 2
58
+ perspectives that should be kept in mind to make this work: Library Consumer and
59
+ Library Maintainer.
60
+
61
+ For more info on custom VSCode snippets - check here:
62
+ https://code.visualstudio.com/docs/editor/userdefinedsnippets
63
+
64
+ ### Library Consumer Perspective
65
+
66
+ From this perspective, the following steps can be performed by the developer:
67
+
68
+ - install or update `@axinom/mosaic-db-common`
69
+ - run the cli command: `yarn mosaic-db generate-vscode-sql-snippets`
70
+ - if it's a multitenant service - run
71
+ `yarn mosaic-db generate-vscode-sql-snippets -m` to include multitenancy
72
+ snippets
73
+ - This generates or updates a `.vscode/mosaic-sql-migrations.code-snippets`
74
+ file, which contains all sql migration code snippets that are supported by the
75
+ currently installed `@axinom/mosaic-db-common` version.
76
+ - This file shall be kept under source control.
77
+ - There is no need to look into the contents of this file unless you are just
78
+ interested.
79
+ - go to `migrations/current.sql` or any other `.sql` file.
80
+ - Start typing `ax-` and intellisense will show a dropdown of all available
81
+ snippets.
82
+ - Use the Arrow Up and Down keys to select different options.
83
+ - Documentation window will be visible with text explanation and preview of
84
+ generated code.
85
+ - If this window is not able to be viewed fully and some content are
86
+ hidden - its size can be changed by dragging the right-bottom corner of
87
+ the window.
88
+ - typing after `ax-` would narrow down the selection. e.g. `ax-auth` would
89
+ find multiple authentication-related snippets, e.g.
90
+ `ax-define-authentication`
91
+ - When fitting snippet is selected - clicking `Enter` would expand it into the
92
+ actual migration code and highlight properties for easier editing
93
+ - It is recommended to edit highlighted values and clicking `Tab` to switch
94
+ to the next highlighted value. Some snippets are constructed in a way to
95
+ modify multiple values at once in such a way, making creating the
96
+ resulting migrations code easier.
97
+
98
+ With this, user is provided with an option to browse available snippets,
99
+ familiarize with them using IDE intellisense capabilities, and simplify the
100
+ process of writing migrations.
101
+
102
+ ### Library Maintainer Perspective
103
+
104
+ To provide the user experience from the section above - files in this
105
+ `migrations` folder must be maintained in a certain way and it's good to know
106
+ how things work.
107
+
108
+ The resulting `.vscode/mosaic-sql-migrations.code-snippets` file is generated
109
+ based on 2 different sources (combining contents of both):
110
+
111
+ - from all `.sql` files inside of the `migrations` folders and sub-folders
112
+ (including `before-migration` and `define`)
113
+ - from all `.code-snippets` files inside of the `migrations` folders and
114
+ sub-folders, which contains ready-to-use snippets that are not related to any
115
+ single `ax_utils` or `ax_define` sql function. (can be related to no functions
116
+ or to multiple functions)
117
+
118
+ To be able to generate SQL snippets from sql files - certain conventions must be
119
+ followed when creating/modifying sql functions.
120
+
121
+ In it's simplified form, the function example can look like this:
122
+
123
+ ```SQL
124
+ /*-snippet
125
+ {
126
+ "body": [
127
+ "SELECT ax_define.function_name('${1:column_name}');"
128
+ ],
129
+ "description": [
130
+ "Description of function that will be visible in SQL snippet"
131
+ ]
132
+ }
133
+ snippet-*/
134
+ CREATE OR REPLACE FUNCTION ax_define.function_name(column_name text)
135
+ RETURNS void LANGUAGE plpgsql AS $$
136
+ BEGIN
137
+ -- Function logic which is not important in this particular case
138
+ END;
139
+ $$;
140
+ ```
141
+
142
+ The logic that generates the SQL snippet performs the following steps:
143
+
144
+ - Parses the SQL file into memory and read it line by line
145
+ - if line starts with `/*-snippet` - starts recording of lines after it
146
+ - if line starts with `snippet-*/` - recording of lines is stopped.
147
+ - Content that is recorder is passed to `JSON.Parse` and converted from string
148
+ to json object
149
+ - Reading of lines continues, until a line is found that starts with
150
+ `CREATE OR REPLACE FUNCTION`
151
+ - Logic reads the function schema and name and generates a json object name
152
+ and snippet name. For the example above, it would be
153
+ `Function Name (Ax Define)` and `ax-function-name`
154
+ - Because it's theoretically possible to have a function with same name in
155
+ multiple database schemas, (and we can potentially have
156
+ `ax_utils.function_name` - we preserve schema name in form of `(Ax Define)`
157
+ suffix, since json function name is the one that must be unique, snippet
158
+ names can have duplicates)
159
+ - Once these steps are performed - a json snippet object is saved for this
160
+ function and parsing continues anew. The resulting snippet would look like
161
+ this:
162
+ - scope is always `sql` and is attached at the end of the object
163
+
164
+ ```JSON
165
+ "Function Name (Ax Define)": {
166
+ "prefix": "ax-function-name",
167
+ "body": [
168
+ "SELECT ax_define.function_name('${1:column_name}');"
169
+ ],
170
+ "description": [
171
+ "Description of function that will be visible in SQL snippet"
172
+ ],
173
+ "scope": "sql"
174
+ },
175
+ ```
176
+
177
+ As for `migrations/snippets` files - objects like the one above must be defined
178
+ there and files are read as is and merged into resulting
179
+ `.vscode/mosaic-sql-migrations.code-snippets` file.
180
+
181
+ ### Development Tips
182
+
183
+ The way to develop and test the snippets code would look like this:
184
+
185
+ - If you modify typescript parts of `db-common` - run `yarn dev` for continuous
186
+ updates that will trigger on changes.
187
+ - If only sql or code-snippets files are modified - no need to keep the lib
188
+ running.
189
+ - Once adjustments are made - save them and run
190
+ `yarn mosaic-db generate-vscode-sql-snippets -m` in some backend service in
191
+ Navy repository (that has `@axinom/mosaic-db-common` dependency) or from Navy
192
+ repository root.
193
+ - `.vscode/mosaic-sql-migrations.code-snippets` will be updated
194
+ - go to any `.sql` file in monorepo (preferably `migrations/current.sql` in one
195
+ of the backend services) and test the added/updated snippet there.
196
+
197
+ When maintaining comment snippet sections of sql functions, it's good to keep in
198
+ mind, that they are fed into `JSON.Parse` as is, and therefore must be valid
199
+ json objects. This means that if
200
+ `yarn mosaic-db generate-vscode-sql-snippets -m` fails - chances are - json
201
+ object is not valid.
202
+
203
+ For example:
204
+
205
+ - `SyntaxError: Unexpected token ] in JSON at position 1035` might signify that
206
+ there is an extra `,` added at the end of the last string of the array which
207
+ must be removed.
208
+ - Similar error with other token value could signify that some character, for
209
+ example `"` must be escaped like `\"`.
210
+ - You might have a regex inside of the snippet that end with `*/`, which will be
211
+ recognized as a closing tag for the comment.
212
+ - In such situation - either modify the regex or move the whole snippet into
213
+ one of the `migrations/snippets` files.
214
+
215
+ Another thing to keep in mind, is that since `ax_utils` are updated on the fly
216
+ in projects whenever a new migration is applied - extra care must be done when
217
+ doing changes in `before-migration` files.
218
+
219
+ - Do not rename parameters of `ax_utils` functions, even if function name stays
220
+ the same. PostgreSQL recognizes that as a breaking change.
221
+ - Acceptable is to rename the column and to add something like
222
+ `DROP FUNCTION IF EXISTS ax_utils.function_name(TEXT, TEXT, TEXT);` before
223
+ definition of affected function. This way, old function will be dropped and
224
+ new property name would be applied. (but keep the number and order of
225
+ parameters the same)
226
+ - Do not just delete `ax_utils` functions. Because they can already be used by
227
+ some projects, they must stay, even if they are not used.
228
+ - Removing the snippet comment would make it harder to discover them, so that
229
+ might be a valid option.
230
+ - But modifying the snippet to indicate that ax_utils function is
231
+ deprecated/obsolete is a better option.
232
+ - The only exception to this is early stages of development, where we do not
233
+ have non-axinom projects and are 100% that `ax_utils` function is not used
234
+ in any project.
235
+ - And even in this case, if dropped function was already released - an
236
+ explicit drop statement must be added, e.g.
237
+
238
+ ```SQL
239
+ -- TODO: Remove these after 01.01.2023
240
+ DROP FUNCTION IF EXISTS ax_utils.function_name(TEXT, TEXT, TEXT);
241
+ ```
242
+
243
+ The TODO comment is added to signify when this drop statement can be removed.
244
+ Assumption is that one year is enough for for a library to be updated at least
245
+ once for this statement to be executed in consuming services.
246
+
247
+ Worse case scenario:
248
+
249
+ - Someone uses the function that will be dropped in the future.
250
+ - That someone updates the library to the latest version where this function no
251
+ longer exists and drop statement is present.
252
+ - A bug is introduced and (hopefully) reported to us.
253
+ - We provide the code version of dropped function, which can be be explicitly
254
+ added as a script to run before each migration is applied in affected project.
255
+ - example -
256
+ `services/video/service/migrations/after-reset/initial-define-functions.sql`,
257
+ but instead - use it before migrations.
258
+
259
+ Also, some notes about the contents of snippets comment section:
260
+
261
+ - Both body and description are string arrays to make it possible to expand both
262
+ sections with more text, while keeping it all on the screen.
263
+ - If text contents are short - both can be changed from arrays of strings to
264
+ just strings.
265
+ - Snippets will be displayed differently depending on existance of `\n` inside
266
+ of the `description`.
267
+ - If it exists - line break will be added, but it is possible that text might
268
+ not be visible, curring it at the border of the window with `...`. In this
269
+ case - window must be manually resized for contents to be fully read.
270
+ - If `\n` is not specified - intellisense window will try to wrap the text no
271
+ matter the size and always keep it readable, but it might not look very
272
+ good.
273
+ - Formatting capabilities of snippets documentation window is quite limited, so
274
+ it makes sense to always preview it after changes to make sure it is
275
+ acceptable. (perhaps adding a few `\n` line breaks would make it better)
@@ -1,6 +1,24 @@
1
+ /*-snippet
2
+ {
3
+ "body": [
4
+ "PERFORM ax_utils.raise_error('${1:Enter your error message here.}', '${2:UNDEF}');"
5
+ ],
6
+ "description": [
7
+ "Raises an error with specified message, code, and optional placeholder parameter(s) for the message.",
8
+ "Code must be an exactly 5-character value (letters or numbers), as per PostgreSQL convention.",
9
+ "If you want to use custom code values, consult PostgreSQL documentation for a list of existing error codes to prevent collisions:",
10
+ "https://www.postgresql.org/docs/13/errcodes-appendix.html\n",
11
+ "'PERFORM' is used because usually, you would want to throw an error inside of the function or condition block.",
12
+ "If you just want to throw an error in the migration file (e.g. to test this snippet) - replace 'PERFORM' with 'SELECT'.\n",
13
+ "Examples:",
14
+ "ax_utils.raise_error('Sample error message without parameters', 'NPERR');",
15
+ "ax_utils.raise_error('Sample error %s with one parameters', 'OPERR', 'message');",
16
+ "ax_utils.raise_error('Sample error %s with %s parameters', 'TPERR', 'message', 'two');"
17
+ ]
18
+ }
19
+ snippet-*/
1
20
  CREATE OR REPLACE FUNCTION ax_utils.raise_error(
2
21
  error_message text,
3
- -- error code must be exact 5 characters long (letters or numbers)
4
22
  error_code text,
5
23
  VARIADIC placeholder_values text[] DEFAULT '{}'
6
24
  ) RETURNS void