@antelopejs/dms 0.13.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.14.0
4
+
5
+ [compare changes](https://github.com/AntelopeJS/dms/compare/v0.13.1...v0.14.0)
6
+
7
+ ### 💅 Refactors
8
+
9
+ - **frontend:** ⚠️ Remove API error migration helpers ([#638](https://github.com/AntelopeJS/dms/pull/638))
10
+
11
+ #### ⚠️ Breaking Changes
12
+
13
+ - **frontend:** ⚠️ Remove API error migration helpers ([#638](https://github.com/AntelopeJS/dms/pull/638))
14
+
15
+ ### ❤️ Contributors
16
+
17
+ - Antony Rizzitelli ([@Upd4ting](http://github.com/Upd4ting))
18
+
3
19
  ## v0.13.1
4
20
 
5
21
  [compare changes](https://github.com/AntelopeJS/dms/compare/v0.13.0...v0.13.1)
@@ -4,7 +4,7 @@ import {
4
4
  type FieldErrorCode,
5
5
  resolveFieldErrors,
6
6
  } from "#dms-core/app/composables/useFieldErrors";
7
- import { resolveApiErrorMessage } from "#dms-core/app/composables/useApiError";
7
+ import { resolveApiErrorMessage } from "#dms-core/app/composables/resolveApiError";
8
8
 
9
9
  export interface AuthFormError {
10
10
  title: string;
@@ -15,7 +15,7 @@ import {
15
15
  useLiveFormErrors,
16
16
  useLocalizedSchema,
17
17
  } from "#dms-core/app/composables/useFormValidation";
18
- import { resolveApiErrorMessage } from "#dms-core/app/composables/useApiError";
18
+ import { resolveApiErrorMessage } from "#dms-core/app/composables/resolveApiError";
19
19
  import { isString } from "#dms-core/app/utils/type-check";
20
20
 
21
21
  const MIN_NAME_LENGTH = 2;
@@ -1,11 +1,5 @@
1
1
  import { isObject, isString } from "../utils/type-check";
2
2
 
3
- interface Options {
4
- context?: string;
5
- title?: string;
6
- description?: string;
7
- }
8
-
9
3
  interface ApiError {
10
4
  statusCode?: number;
11
5
  status?: number;
@@ -68,13 +62,3 @@ export function resolveApiErrorMessage(
68
62
  ): string {
69
63
  return isApiError(error) ? resolveDescription(error) : FALLBACK_ERROR_KEY;
70
64
  }
71
-
72
- /** @deprecated Capture useApiErrorPresenter during setup instead. */
73
- export function useApiError(
74
- error: Parameters<typeof resolveApiErrorTitle>[0],
75
- options?: Options,
76
- ) {
77
- useApiErrorPresenter().presentError(error, {
78
- fallbackKey: options?.description || options?.title || FALLBACK_ERROR_KEY,
79
- });
80
- }
@@ -1,6 +1,6 @@
1
1
  import { nextTick, reactive } from "vue";
2
2
  import { type I18nTranslate } from "./translation/useTranslation";
3
- import { resolveApiErrorMessage } from "./useApiError";
3
+ import { resolveApiErrorMessage } from "./resolveApiError";
4
4
  import { isBoolean, isNumber, isObject, isString } from "../utils/type-check";
5
5
 
6
6
  /**
@@ -25,10 +25,7 @@ export interface ActionTargetsConfig {
25
25
  /** Called once a target changed something (an API call, a closed form). */
26
26
  refreshCallback?: () => void;
27
27
  /** Toasts a failure no dialog shows. */
28
- handleApiError: (
29
- error: Parameters<typeof useApiError>[0],
30
- title?: string,
31
- ) => void;
28
+ handleApiError: (error: unknown, title?: string) => void;
32
29
  /** The rows a drawer or modal opened on a row steps through (J / K). */
33
30
  rowNavigation?: RowNavigationSource;
34
31
  /** Where a `deepLink` action writes the row it has open in the URL. */
@@ -11,12 +11,7 @@ import { tmpdir } from "node:os";
11
11
  import { join } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import ts from "typescript";
14
- import {
15
- analyzeApiErrorScript,
16
- apiErrorScripts,
17
- } from "./check-api-error-presentation.mjs";
18
- import { migrateApiErrorSource } from "./codemods/api-error-presenter.mjs";
19
- import { parse } from "vue/compiler-sfc";
14
+ import { analyzeApiErrorScript } from "./check-api-error-presentation.mjs";
20
15
 
21
16
  const rejected = [
22
17
  `try { await save() } catch (error) { toast.add({ title: t('save.failed'), color: 'error' }) }`,
@@ -49,58 +44,6 @@ for (const source of allowed)
49
44
  source,
50
45
  );
51
46
 
52
- const vue = `<script setup lang="ts">
53
- const toast = useToast();
54
- async function save() {
55
- try { await write() } catch (failure) { toast.add({ title: t("save.failed"), color: "error" }); }
56
- }
57
- </script><template><button @click="save">Save</button></template>`;
58
- const migrated = migrateApiErrorSource("fixture.vue", vue);
59
- assert.ok(migrated.rewritten);
60
- assert.deepEqual(migrated.manual, []);
61
- assert.ok(
62
- migrated.output.includes(
63
- 'presentError(failure, { fallbackKey: "save.failed" })',
64
- ),
65
- );
66
- assert.equal(
67
- migrateApiErrorSource("fixture.vue", migrated.output).rewritten,
68
- false,
69
- );
70
- for (const script of apiErrorScripts("fixture.vue", migrated.output, { parse }))
71
- assert.equal(
72
- analyzeApiErrorScript(script.source, "fixture.vue", ts).length,
73
- 0,
74
- );
75
-
76
- const composable = `export function useSave() { return async () => { try { await save() } catch (error) { toast.add({ title: t('save.failed'), color: 'error' }); } } }`;
77
- const converted = migrateApiErrorSource("fixture.ts", composable);
78
- assert.ok(
79
- converted.output.indexOf("const { presentError }") <
80
- converted.output.indexOf("return async"),
81
- );
82
- assert.ok(converted.output.includes("import { useApiErrorPresenter }"));
83
-
84
- for (const source of [
85
- `async function save() { try {} catch (error) { toast.add({ title: t('save.failed'), color: 'error' }); } }`,
86
- `export function useSave() { try {} catch { toast.add({ title: t('save.failed'), color: 'error' }); } }`,
87
- `export function useSave() { try {} catch (error) { toast.update(id, { title: t('save.failed'), color: 'error' }); } }`,
88
- `export function useSave() { try {} catch (error) { toast.add({ title: t(dynamic), color: 'error' }); } }`,
89
- `export function useSave() { try {} catch (error) { toast.add({ title: t('save.failed'), color: color() }); } }`,
90
- `export function useSave() { try {} catch (error) { toast.add({ title: t('save.failed'), icon: icon(), color: 'error' }); } }`,
91
- `<script>try {} catch (error) { toast.add({ title: t('save.failed'), color: 'error' }); }</script>`,
92
- ]) {
93
- const file = source.startsWith("<script") ? "manual.vue" : "manual.ts";
94
- const result = migrateApiErrorSource(file, source);
95
- assert.equal(result.output, source);
96
- assert.deepEqual(Object.keys(result.manual[0]), [
97
- "file",
98
- "line",
99
- "reason",
100
- "expression",
101
- ]);
102
- assert.equal(result.manual[0].reason, "manual-error-presentation");
103
- }
104
47
  const linkRoot = mkdtempSync(join(tmpdir(), "dms-api-error-cli-"));
105
48
  const appRoot = mkdtempSync(
106
49
  fileURLToPath(new URL("../.api-error-cli-", import.meta.url)),
@@ -120,15 +63,8 @@ try {
120
63
  { encoding: "utf8" },
121
64
  );
122
65
  assert.notEqual(checker.status, 0, "checker fails through a symlinked path");
123
- const codemod = spawnSync(
124
- process.execPath,
125
- [join(scripts, "codemods", "api-error-presenter.mjs"), join(appRoot, "app")],
126
- { encoding: "utf8", cwd: appRoot },
127
- );
128
- assert.equal(codemod.status, 0, codemod.stderr);
129
- assert.match(codemod.stdout, /"reason":"manual-error-presentation"/);
130
66
  } finally {
131
67
  rmSync(linkRoot, { recursive: true, force: true });
132
68
  rmSync(appRoot, { recursive: true, force: true });
133
69
  }
134
- console.log("PASS API error invariant and codemod fixtures");
70
+ console.log("PASS API error invariant fixtures");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/dms",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "description": "AntelopeJS DMS. Plug in your PostgreSQL, MongoDB or RethinkDB database and your coding agent builds a back office and analytics dashboard as typed TypeScript. Real-time updates on the data the DMS manages.",
5
5
  "keywords": [
6
6
  "antelopejs",
@@ -124,7 +124,6 @@
124
124
  },
125
125
  "./package.json": "./package.json",
126
126
  "./checks/api-error-presentation": "./frontend-vue/scripts/check-api-error-presentation.mjs",
127
- "./codemods/api-error-presenter": "./frontend-vue/scripts/codemods/api-error-presenter.mjs",
128
127
  "./interfaces/*": null
129
128
  },
130
129
  "publishConfig": {
@@ -1,196 +0,0 @@
1
- import { readFileSync, realpathSync, writeFileSync } from "node:fs";
2
- import { createRequire } from "node:module";
3
- import { resolve } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- import {
6
- apiErrorSources,
7
- apiErrorScripts,
8
- analyzeApiErrorScript,
9
- } from "../check-api-error-presentation.mjs";
10
-
11
- const require = createRequire(resolve(process.cwd(), "package.json"));
12
- const tools = {
13
- ts: require("typescript"),
14
- parse: require("vue/compiler-sfc").parse,
15
- };
16
- const { ts } = tools;
17
-
18
- function translatedKey(expression) {
19
- if (
20
- !expression ||
21
- !ts.isCallExpression(expression) ||
22
- !["t", "processI18n"].includes(expression.expression.getText())
23
- )
24
- return null;
25
- const key = expression.arguments[0];
26
- return expression.arguments.length === 1 && key && ts.isStringLiteral(key)
27
- ? key.text
28
- : null;
29
- }
30
-
31
- function literalKey(payload) {
32
- if (!payload || !ts.isObjectLiteralExpression(payload)) return null;
33
- const allowed = new Set(["title", "description", "color", "icon"]);
34
- if (
35
- payload.properties.some(
36
- (property) =>
37
- !ts.isPropertyAssignment(property) ||
38
- !allowed.has(property.name.getText()) ||
39
- (["title", "description"].includes(property.name.getText())
40
- ? translatedKey(property.initializer) === null
41
- : !(
42
- ts.isStringLiteral(property.initializer) ||
43
- (property.name.getText() === "color" &&
44
- /^Color\.(error|warning|info)$/.test(
45
- property.initializer.getText(),
46
- ))
47
- )),
48
- )
49
- )
50
- return null;
51
- const title =
52
- payload.properties.find(
53
- (property) => property.name.getText() === "description",
54
- )?.initializer ??
55
- payload.properties.find((property) => property.name.getText() === "title")
56
- ?.initializer;
57
- return translatedKey(title);
58
- }
59
-
60
- function composableScope(node) {
61
- let outer = null;
62
- for (let parent = node.parent; parent; parent = parent.parent) {
63
- if (
64
- (ts.isFunctionDeclaration(parent) ||
65
- ts.isFunctionExpression(parent) ||
66
- ts.isArrowFunction(parent)) &&
67
- ts.isBlock(parent.body)
68
- )
69
- outer = parent;
70
- }
71
- const name = outer?.name ?? outer?.parent?.name;
72
- return name && ts.isIdentifier(name) && /^use[A-Z]/.test(name.text)
73
- ? outer.body
74
- : null;
75
- }
76
-
77
- /** Rewrites supported setup/composable catches and returns unsupported cases. */
78
- export function migrateApiErrorSource(file, source) {
79
- const edits = [];
80
- const manual = [];
81
- for (const script of apiErrorScripts(file, source, tools)) {
82
- const calls = analyzeApiErrorScript(script.source, file, ts);
83
- let capture = false;
84
- for (const call of calls) {
85
- const fallbackKey = literalKey(call.payload);
86
- const standalone = ts.isExpressionStatement(call.node.parent);
87
- const safeCatch = call.failure && /^[A-Za-z_$][\w$]*$/.test(call.failure);
88
- const collision = /\b(?:presentError|useApiErrorPresenter)\b/.test(
89
- script.source,
90
- );
91
- const hasSetup = file.endsWith(".vue")
92
- ? script.isSetup
93
- : composableScope(call.node);
94
- if (
95
- fallbackKey &&
96
- standalone &&
97
- safeCatch &&
98
- hasSetup &&
99
- call.method === "add" &&
100
- !collision
101
- ) {
102
- edits.push({
103
- start: script.offset + call.node.getStart(),
104
- end: script.offset + call.node.end,
105
- text: `presentError(${call.failure}, { fallbackKey: ${JSON.stringify(fallbackKey)} })`,
106
- });
107
- capture = true;
108
- } else {
109
- manual.push({
110
- file,
111
- line: source
112
- .slice(0, script.offset + call.node.getStart())
113
- .split("\n").length,
114
- reason: "manual-error-presentation",
115
- expression: call.node.getText(call.tree),
116
- });
117
- }
118
- }
119
- if (capture) {
120
- const tree = ts.createSourceFile(
121
- file,
122
- script.source,
123
- ts.ScriptTarget.Latest,
124
- true,
125
- );
126
- const imports = tree.statements.filter(ts.isImportDeclaration);
127
- const offset = imports.at(-1)?.end ?? 0;
128
- const declaration = file.endsWith(".vue")
129
- ? "\nconst { presentError } = useApiErrorPresenter();\n"
130
- : `\nimport { useApiErrorPresenter } from "#dms-core/app/composables/useApiErrorPresenter";\n`;
131
- if (file.endsWith(".vue"))
132
- edits.push({
133
- start: script.offset + offset,
134
- end: script.offset + offset,
135
- text: declaration,
136
- });
137
- else {
138
- const scopes = new Set();
139
- for (const call of calls) {
140
- if (
141
- !edits.some(
142
- (edit) => edit.start === script.offset + call.node.getStart(),
143
- )
144
- )
145
- continue;
146
- const outer = composableScope(call.node);
147
- if (outer) scopes.add(outer.getStart() + 1);
148
- }
149
- for (const start of scopes)
150
- edits.push({
151
- start: script.offset + start,
152
- end: script.offset + start,
153
- text: "\nconst { presentError } = useApiErrorPresenter();\n",
154
- });
155
- edits.push({
156
- start: script.offset + offset,
157
- end: script.offset + offset,
158
- text: declaration,
159
- });
160
- }
161
- }
162
- }
163
- let output = source;
164
- for (const edit of edits.sort((a, b) => b.start - a.start))
165
- output = output.slice(0, edit.start) + edit.text + output.slice(edit.end);
166
- return { output, rewritten: output !== source, manual };
167
- }
168
-
169
- /** Dry-run by default; writes only source cases accepted by the bounded migration. */
170
- export function migrateApiErrorFile(file, write = false) {
171
- const result = migrateApiErrorSource(file, readFileSync(file, "utf8"));
172
- if (write && result.rewritten) writeFileSync(file, result.output);
173
- return result;
174
- }
175
-
176
- if (
177
- process.argv[1] &&
178
- realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))
179
- ) {
180
- const roots = process.argv
181
- .slice(2)
182
- .filter((argument) => argument !== "--write");
183
- if (!roots.length)
184
- throw new Error(
185
- "Pass app directories. Add --write to apply safe rewrites. Manual cases are JSONL on stdout.",
186
- );
187
- for (const file of roots.flatMap(apiErrorSources)) {
188
- const result = migrateApiErrorFile(file, process.argv.includes("--write"));
189
- for (const entry of result.manual)
190
- process.stdout.write(`${JSON.stringify(entry)}\n`);
191
- if (result.rewritten)
192
- process.stderr.write(
193
- `${process.argv.includes("--write") ? "rewrote" : "would rewrite"} ${file}\n`,
194
- );
195
- }
196
- }