@telorun/http-server 0.1.3 → 0.1.4

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,13 @@
1
1
  # @telorun/http-server
2
2
 
3
+ ## 0.1.4
4
+
5
+ ### Patch Changes
6
+
7
+ - Automated release.
8
+ - Updated dependencies
9
+ - @telorun/sdk@0.2.5
10
+
3
11
  ## 0.1.3
4
12
 
5
13
  ### Patch Changes
@@ -37,16 +37,6 @@ export class HttpServerApi {
37
37
  }
38
38
  async init() { }
39
39
  register(app, prefix = "") {
40
- // Register custom error handler for validation errors
41
- app.setErrorHandler((error, request, reply) => {
42
- const mappedError = convertFastifyValidationError(error);
43
- if (mappedError) {
44
- reply.code(400);
45
- return reply.send(mappedError);
46
- }
47
- // Let Fastify handle other errors normally
48
- throw error;
49
- });
50
40
  if (prefix) {
51
41
  app.register(async (scoped) => {
52
42
  this.registerRoutes(scoped);
@@ -83,15 +73,11 @@ export class HttpServerApi {
83
73
  schema.response = Object.keys(route.response.statuses).reduce((acc, status) => {
84
74
  const statusConfig = route.response.statuses[status];
85
75
  if (statusConfig.schema) {
86
- acc[status] = {};
87
- if (statusConfig.schema.query) {
88
- acc[status].querystring = statusConfig.schema.query;
89
- }
90
76
  if (statusConfig.schema.body) {
91
- acc[status].body = statusConfig.schema.body;
77
+ acc[status] = statusConfig.schema.body;
92
78
  }
93
- if (statusConfig.schema.headers) {
94
- acc[status].headers = statusConfig.schema.headers;
79
+ else {
80
+ acc[status] = {};
95
81
  }
96
82
  }
97
83
  return acc;
@@ -269,68 +255,6 @@ function normalizeHeaders(headers) {
269
255
  }
270
256
  return normalized;
271
257
  }
272
- /**
273
- * Converts Fastify validation errors to standardized Telo format
274
- * Returns null if the error is not a validation error
275
- */
276
- function convertFastifyValidationError(error) {
277
- // Check if this is a Fastify validation error
278
- if (!error || typeof error !== "object" || error.code !== "FST_ERR_VALIDATION") {
279
- return null;
280
- }
281
- const message = error.message || "";
282
- const details = [];
283
- // Parse Fastify validation error message to extract location and field
284
- // Format examples:
285
- // "querystring must have required property 'name'"
286
- // "body must be object"
287
- // "params.userId must be string"
288
- let location = "body"; // default
289
- let fieldPath = "";
290
- let validationMessage = "Validation failed";
291
- // Try to extract location from message
292
- if (message.includes("querystring")) {
293
- location = "query";
294
- }
295
- else if (message.includes("params")) {
296
- location = "params";
297
- }
298
- else if (message.includes("headers")) {
299
- location = "headers";
300
- }
301
- else if (message.includes("body")) {
302
- location = "body";
303
- }
304
- // Extract field name from "must have required property 'fieldName'" pattern
305
- const requiredMatch = message.match(/must have required property '([^']+)'/);
306
- if (requiredMatch) {
307
- fieldPath = requiredMatch[1];
308
- validationMessage = `is a required property`;
309
- }
310
- else {
311
- // Extract field from "fieldName must be" pattern
312
- const fieldMatch = message.match(/^(?:querystring|body|params|headers)\.?(\w+)\s/);
313
- if (fieldMatch) {
314
- fieldPath = fieldMatch[1];
315
- }
316
- validationMessage = message
317
- .replace(/^(?:querystring|body|params|headers)\.?\w*\s/, "")
318
- .replace(" must ", " ");
319
- }
320
- if (fieldPath || message) {
321
- details.push({
322
- location,
323
- path: fieldPath,
324
- message: validationMessage,
325
- });
326
- }
327
- return {
328
- error: "ValidationError",
329
- message: "Request validation failed",
330
- status: 400,
331
- details,
332
- };
333
- }
334
258
  /**
335
259
  * Legacy function - kept for compatibility but not used
336
260
  * Converts framework-specific validation errors to standardized Telo format
@@ -1,5 +1,6 @@
1
1
  import swagger from "@fastify/swagger";
2
2
  import apiReference from "@scalar/fastify-api-reference";
3
+ import addFormats from "ajv-formats";
3
4
  import Fastify from "fastify";
4
5
  class HttpServer {
5
6
  releaseHold = null;
@@ -18,13 +19,23 @@ class HttpServer {
18
19
  if (!this.port) {
19
20
  throw new Error("Http.Server port is required");
20
21
  }
21
- this.app = Fastify({ logger: true });
22
+ this.app = Fastify({ logger: true, ajv: { plugins: [addFormats.default] } });
22
23
  }
23
24
  async init() {
24
- this.setupPlugins();
25
+ await this.setupPlugins();
25
26
  this.setupRoutes();
26
27
  }
27
28
  async setupPlugins() {
29
+ // Register custom error handler for validation errors
30
+ this.app.setErrorHandler((error, request, reply) => {
31
+ const mappedError = convertFastifyValidationError(error);
32
+ if (mappedError) {
33
+ reply.code(400);
34
+ return reply.send(mappedError);
35
+ }
36
+ // Let Fastify handle other errors normally
37
+ throw error;
38
+ });
28
39
  if (this.resource.openapi) {
29
40
  const servers = [];
30
41
  // const routesByName = new Map<string, HttpRouteResource>();
@@ -56,7 +67,7 @@ class HttpServer {
56
67
  const type = mount.type || "";
57
68
  const { kind, name } = parseType(type);
58
69
  const prefix = mount.path || "";
59
- const api = this.ctx.getResourcesByName(kind, name);
70
+ const api = this.ctx.moduleContext.getInvokable(name);
60
71
  if (!api) {
61
72
  throw new Error(`Failed to mount Http.Api at "${prefix}": ${type} not found`);
62
73
  }
@@ -102,3 +113,65 @@ function parseType(type) {
102
113
  }
103
114
  return { kind: type.slice(0, separator), name: type.slice(separator + 1) };
104
115
  }
116
+ /**
117
+ * Converts Fastify validation errors to standardized Telo format
118
+ * Returns null if the error is not a validation error
119
+ */
120
+ function convertFastifyValidationError(error) {
121
+ // Check if this is a Fastify validation error
122
+ if (!error || typeof error !== "object" || error.code !== "FST_ERR_VALIDATION") {
123
+ return null;
124
+ }
125
+ const message = error.message || "";
126
+ const details = [];
127
+ // Parse Fastify validation error message to extract location and field
128
+ // Format examples:
129
+ // "querystring must have required property 'name'"
130
+ // "body must be object"
131
+ // "params.userId must be string"
132
+ let location = "body"; // default
133
+ let fieldPath = "";
134
+ let validationMessage = "Validation failed";
135
+ // Try to extract location from message
136
+ if (message.includes("querystring")) {
137
+ location = "query";
138
+ }
139
+ else if (message.includes("params")) {
140
+ location = "params";
141
+ }
142
+ else if (message.includes("headers")) {
143
+ location = "headers";
144
+ }
145
+ else if (message.includes("body")) {
146
+ location = "body";
147
+ }
148
+ // Extract field name from "must have required property 'fieldName'" pattern
149
+ const requiredMatch = message.match(/must have required property '([^']+)'/);
150
+ if (requiredMatch) {
151
+ fieldPath = requiredMatch[1];
152
+ validationMessage = `is a required property`;
153
+ }
154
+ else {
155
+ // Extract field from "fieldName must be" pattern
156
+ const fieldMatch = message.match(/^(?:querystring|body|params|headers)\.?(\w+)\s/);
157
+ if (fieldMatch) {
158
+ fieldPath = fieldMatch[1];
159
+ }
160
+ validationMessage = message
161
+ .replace(/^(?:querystring|body|params|headers)\.?\w*\s/, "")
162
+ .replace(" must ", " ");
163
+ }
164
+ if (fieldPath || message) {
165
+ details.push({
166
+ location,
167
+ path: fieldPath,
168
+ message: validationMessage,
169
+ });
170
+ }
171
+ return {
172
+ error: "ValidationError",
173
+ message: "Request validation failed",
174
+ status: 400,
175
+ details,
176
+ };
177
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/http-server",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -17,12 +17,13 @@
17
17
  }
18
18
  },
19
19
  "dependencies": {
20
+ "@fastify/swagger": "^9.6.1",
21
+ "@scalar/fastify-api-reference": "^1.44.6",
20
22
  "@sinclair/typebox": "^0.34.48",
21
23
  "ajv": "^8.17.1",
24
+ "ajv-formats": "^3.0.1",
22
25
  "fastify": "^5.7.2",
23
- "@fastify/swagger": "^9.6.1",
24
- "@scalar/fastify-api-reference": "^1.44.6",
25
- "@telorun/sdk": "0.2.4"
26
+ "@telorun/sdk": "0.2.5"
26
27
  },
27
28
  "devDependencies": {
28
29
  "@types/node": "^20.0.0",
@@ -52,17 +52,6 @@ export class HttpServerApi implements ResourceInstance {
52
52
  async init() {}
53
53
 
54
54
  register(app: FastifyInstance, prefix = "") {
55
- // Register custom error handler for validation errors
56
- app.setErrorHandler((error, request, reply) => {
57
- const mappedError = convertFastifyValidationError(error);
58
- if (mappedError) {
59
- reply.code(400);
60
- return reply.send(mappedError);
61
- }
62
- // Let Fastify handle other errors normally
63
- throw error;
64
- });
65
-
66
55
  if (prefix) {
67
56
  app.register(
68
57
  async (scoped) => {
@@ -107,15 +96,10 @@ export class HttpServerApi implements ResourceInstance {
107
96
  (acc, status) => {
108
97
  const statusConfig = route.response.statuses[status];
109
98
  if (statusConfig.schema) {
110
- acc[status] = {};
111
- if (statusConfig.schema.query) {
112
- acc[status].querystring = statusConfig.schema.query;
113
- }
114
99
  if (statusConfig.schema.body) {
115
- acc[status].body = statusConfig.schema.body;
116
- }
117
- if (statusConfig.schema.headers) {
118
- acc[status].headers = statusConfig.schema.headers;
100
+ acc[status] = statusConfig.schema.body;
101
+ } else {
102
+ acc[status] = {};
119
103
  }
120
104
  }
121
105
  return acc;
@@ -325,72 +309,6 @@ function normalizeHeaders(headers: Record<string, any>): Record<string, any> {
325
309
  return normalized;
326
310
  }
327
311
 
328
- /**
329
- * Converts Fastify validation errors to standardized Telo format
330
- * Returns null if the error is not a validation error
331
- */
332
- function convertFastifyValidationError(error: any): Record<string, any> | null {
333
- // Check if this is a Fastify validation error
334
- if (!error || typeof error !== "object" || error.code !== "FST_ERR_VALIDATION") {
335
- return null;
336
- }
337
-
338
- const message = error.message || "";
339
- const details = [];
340
-
341
- // Parse Fastify validation error message to extract location and field
342
- // Format examples:
343
- // "querystring must have required property 'name'"
344
- // "body must be object"
345
- // "params.userId must be string"
346
-
347
- let location = "body"; // default
348
- let fieldPath = "";
349
- let validationMessage = "Validation failed";
350
-
351
- // Try to extract location from message
352
- if (message.includes("querystring")) {
353
- location = "query";
354
- } else if (message.includes("params")) {
355
- location = "params";
356
- } else if (message.includes("headers")) {
357
- location = "headers";
358
- } else if (message.includes("body")) {
359
- location = "body";
360
- }
361
-
362
- // Extract field name from "must have required property 'fieldName'" pattern
363
- const requiredMatch = message.match(/must have required property '([^']+)'/);
364
- if (requiredMatch) {
365
- fieldPath = requiredMatch[1];
366
- validationMessage = `is a required property`;
367
- } else {
368
- // Extract field from "fieldName must be" pattern
369
- const fieldMatch = message.match(/^(?:querystring|body|params|headers)\.?(\w+)\s/);
370
- if (fieldMatch) {
371
- fieldPath = fieldMatch[1];
372
- }
373
- validationMessage = message
374
- .replace(/^(?:querystring|body|params|headers)\.?\w*\s/, "")
375
- .replace(" must ", " ");
376
- }
377
-
378
- if (fieldPath || message) {
379
- details.push({
380
- location,
381
- path: fieldPath,
382
- message: validationMessage,
383
- });
384
- }
385
-
386
- return {
387
- error: "ValidationError",
388
- message: "Request validation failed",
389
- status: 400,
390
- details,
391
- };
392
- }
393
-
394
312
  /**
395
313
  * Legacy function - kept for compatibility but not used
396
314
  * Converts framework-specific validation errors to standardized Telo format
@@ -1,6 +1,7 @@
1
1
  import swagger from "@fastify/swagger";
2
2
  import apiReference from "@scalar/fastify-api-reference";
3
3
  import type { ResourceContext, ResourceInstance, RuntimeResource } from "@telorun/sdk";
4
+ import addFormats from "ajv-formats";
4
5
  import Fastify, { FastifyInstance } from "fastify";
5
6
  import { HttpServerApi } from "./http-api-controller.js";
6
7
 
@@ -86,15 +87,25 @@ class HttpServer implements ResourceInstance {
86
87
  if (!this.port) {
87
88
  throw new Error("Http.Server port is required");
88
89
  }
89
- this.app = Fastify({ logger: true });
90
+ this.app = Fastify({ logger: true, ajv: { plugins: [addFormats.default as any] } });
90
91
  }
91
92
 
92
93
  async init() {
93
- this.setupPlugins();
94
+ await this.setupPlugins();
94
95
  this.setupRoutes();
95
96
  }
96
97
 
97
98
  private async setupPlugins() {
99
+ // Register custom error handler for validation errors
100
+ this.app.setErrorHandler((error, request, reply) => {
101
+ const mappedError = convertFastifyValidationError(error);
102
+ if (mappedError) {
103
+ reply.code(400);
104
+ return reply.send(mappedError);
105
+ }
106
+ // Let Fastify handle other errors normally
107
+ throw error;
108
+ });
98
109
  if (this.resource.openapi) {
99
110
  const servers = [];
100
111
  // const routesByName = new Map<string, HttpRouteResource>();
@@ -128,7 +139,7 @@ class HttpServer implements ResourceInstance {
128
139
  const { kind, name } = parseType(type);
129
140
  const prefix = mount.path || "";
130
141
 
131
- const api: HttpServerApi = this.ctx.getResourcesByName(kind, name) as any;
142
+ const api = this.ctx.moduleContext.getInvokable(name) as unknown as HttpServerApi;
132
143
 
133
144
  if (!api) {
134
145
  throw new Error(`Failed to mount Http.Api at "${prefix}": ${type} not found`);
@@ -181,3 +192,69 @@ function parseType(type: string): { kind: string; name: string } {
181
192
  }
182
193
  return { kind: type.slice(0, separator), name: type.slice(separator + 1) };
183
194
  }
195
+
196
+ /**
197
+ * Converts Fastify validation errors to standardized Telo format
198
+ * Returns null if the error is not a validation error
199
+ */
200
+ function convertFastifyValidationError(error: any): Record<string, any> | null {
201
+ // Check if this is a Fastify validation error
202
+ if (!error || typeof error !== "object" || error.code !== "FST_ERR_VALIDATION") {
203
+ return null;
204
+ }
205
+
206
+ const message = error.message || "";
207
+ const details = [];
208
+
209
+ // Parse Fastify validation error message to extract location and field
210
+ // Format examples:
211
+ // "querystring must have required property 'name'"
212
+ // "body must be object"
213
+ // "params.userId must be string"
214
+
215
+ let location = "body"; // default
216
+ let fieldPath = "";
217
+ let validationMessage = "Validation failed";
218
+
219
+ // Try to extract location from message
220
+ if (message.includes("querystring")) {
221
+ location = "query";
222
+ } else if (message.includes("params")) {
223
+ location = "params";
224
+ } else if (message.includes("headers")) {
225
+ location = "headers";
226
+ } else if (message.includes("body")) {
227
+ location = "body";
228
+ }
229
+
230
+ // Extract field name from "must have required property 'fieldName'" pattern
231
+ const requiredMatch = message.match(/must have required property '([^']+)'/);
232
+ if (requiredMatch) {
233
+ fieldPath = requiredMatch[1];
234
+ validationMessage = `is a required property`;
235
+ } else {
236
+ // Extract field from "fieldName must be" pattern
237
+ const fieldMatch = message.match(/^(?:querystring|body|params|headers)\.?(\w+)\s/);
238
+ if (fieldMatch) {
239
+ fieldPath = fieldMatch[1];
240
+ }
241
+ validationMessage = message
242
+ .replace(/^(?:querystring|body|params|headers)\.?\w*\s/, "")
243
+ .replace(" must ", " ");
244
+ }
245
+
246
+ if (fieldPath || message) {
247
+ details.push({
248
+ location,
249
+ path: fieldPath,
250
+ message: validationMessage,
251
+ });
252
+ }
253
+
254
+ return {
255
+ error: "ValidationError",
256
+ message: "Request validation failed",
257
+ status: 400,
258
+ details,
259
+ };
260
+ }
package/dist/openapi.js DELETED
@@ -1,152 +0,0 @@
1
- import swagger from '@fastify/swagger';
2
- import apiReference from '@scalar/fastify-api-reference';
3
- function getResourceConfig(resource) {
4
- return resource;
5
- }
6
- export function register(ctx) { }
7
- export function create(resource, ctx) {
8
- const openApiResource = resource;
9
- const config = getResourceConfig(openApiResource);
10
- const apiRefs = config.apis || [];
11
- if (apiRefs.length === 0) {
12
- throw new Error(`OpenApi.Spec "${resource.metadata.name}" is missing apis`);
13
- }
14
- const handler = (payload) => {
15
- const serverResource = payload?.resource;
16
- const app = payload?.app;
17
- if (!serverResource || !app) {
18
- throw new Error(`OpenApi.Spec handler missing Http.Server resource or Fastify app`);
19
- }
20
- const matchedApis = resolveApis(apiRefs, ctx);
21
- const mounts = getResourceConfig(serverResource).mounts || [];
22
- const servers = buildServers(serverResource, mounts, matchedApis);
23
- if (servers.length === 0) {
24
- return;
25
- }
26
- const paths = buildPaths(matchedApis, mounts);
27
- const info = config.info && typeof config.info === 'object'
28
- ? config.info
29
- : {
30
- title: resource.metadata.name,
31
- version: resource.version || '1.0.0',
32
- };
33
- const routePrefix = config.path || `/openapi/${resource.metadata.name}`;
34
- app.register(swagger, {
35
- openapi: {
36
- openapi: '3.0.0',
37
- info,
38
- servers,
39
- paths,
40
- },
41
- routePrefix,
42
- });
43
- app.register(apiReference, {
44
- routePrefix,
45
- });
46
- };
47
- const httpServers = ctx.getResources('Http.Server');
48
- return {
49
- init: async () => {
50
- // Register listeners after all resources are initialized
51
- for (const server of httpServers) {
52
- ctx.on('Http.Server.Ready', server.metadata.name, handler);
53
- }
54
- },
55
- teardown: () => {
56
- for (const server of httpServers) {
57
- ctx.offResourceEvent('Http.Server', server.metadata.name, 'Ready', handler);
58
- }
59
- },
60
- };
61
- }
62
- function resolveApis(apiRefs, ctx) {
63
- const apis = [];
64
- for (const ref of apiRefs) {
65
- const { kind, name } = parseRef(ref);
66
- if (!kind || !name) {
67
- throw new Error(`Reference not found: ${ref}`);
68
- }
69
- if (kind !== 'Http.Api' && kind !== 'Http.Route') {
70
- throw new Error(`Reference not supported: ${ref}`);
71
- }
72
- const resource = ctx.kernel.registry.get(kind)?.get(name);
73
- if (!resource) {
74
- throw new Error(`Reference not found: ${ref}`);
75
- }
76
- apis.push(resource);
77
- }
78
- return apis;
79
- }
80
- function buildServers(server, mounts, apis) {
81
- const config = getResourceConfig(server);
82
- const host = config.host || '0.0.0.0';
83
- const port = Number(config.port || 0);
84
- if (!port) {
85
- return [];
86
- }
87
- // Server URL should be the base URL without mount paths
88
- // Paths will include the mount prefix
89
- return [{ url: `http://${host}:${port}` }];
90
- }
91
- function buildPaths(apis, mounts) {
92
- const paths = {};
93
- const mountPrefixByType = new Map();
94
- for (const mount of mounts) {
95
- if (mount.type) {
96
- mountPrefixByType.set(mount.type, mount.path || '');
97
- }
98
- }
99
- for (const api of apis) {
100
- const prefix = mountPrefixByType.get(`${api.kind}.${api.metadata.name}`) || '';
101
- if (api.kind === 'Http.Route') {
102
- const config = getResourceConfig(api);
103
- const path = joinPath(prefix, api.metadata?.path || config.path || '');
104
- const method = (api.metadata?.method ||
105
- config.method ||
106
- 'GET').toLowerCase();
107
- if (!path) {
108
- continue;
109
- }
110
- if (!paths[path]) {
111
- paths[path] = {};
112
- }
113
- paths[path][method] = { responses: { '200': { description: 'OK' } } };
114
- continue;
115
- }
116
- const routes = getResourceConfig(api).routes || [];
117
- for (const route of routes) {
118
- if (typeof route === 'string') {
119
- continue;
120
- }
121
- const request = route.request || {};
122
- const path = joinPath(prefix, request.path || '');
123
- const method = (request.method || 'GET').toLowerCase();
124
- if (!path) {
125
- continue;
126
- }
127
- if (!paths[path]) {
128
- paths[path] = {};
129
- }
130
- paths[path][method] = { responses: { '200': { description: 'OK' } } };
131
- }
132
- }
133
- return paths;
134
- }
135
- function parseRef(ref) {
136
- const separator = ref.lastIndexOf('.');
137
- if (separator <= 0 || separator === ref.length - 1) {
138
- return { kind: '', name: '' };
139
- }
140
- return { kind: ref.slice(0, separator), name: ref.slice(separator + 1) };
141
- }
142
- function joinPath(prefix, path) {
143
- if (!prefix) {
144
- return path;
145
- }
146
- if (!path) {
147
- return prefix;
148
- }
149
- const trimmedPrefix = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
150
- const trimmedPath = path.startsWith('/') ? path : `/${path}`;
151
- return `${trimmedPrefix}${trimmedPath}`;
152
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,36 +0,0 @@
1
- // Test file to validate error conversion logic
2
- import { convertFastifyValidationError } from "./http-api-controller.js";
3
- function testValidationError() {
4
- // Test case 1: Querystring validation error
5
- const error1 = {
6
- code: "FST_ERR_VALIDATION",
7
- message: "querystring must have required property 'name'",
8
- statusCode: 400,
9
- error: "Bad Request",
10
- };
11
- const result1 = convertFastifyValidationError(error1);
12
- console.log("Test 1 - Querystring required field:", JSON.stringify(result1, null, 2));
13
- console.log("✓ Location:", result1?.details[0].location === "query" ? "PASS" : "FAIL");
14
- console.log("✓ Path:", result1?.details[0].path === "name" ? "PASS" : "FAIL");
15
- console.log("✓ Message:", result1?.details[0].message.includes("required") ? "PASS" : "FAIL");
16
- // Test case 2: Body validation error
17
- const error2 = {
18
- code: "FST_ERR_VALIDATION",
19
- message: "body must be object",
20
- statusCode: 400,
21
- };
22
- const result2 = convertFastifyValidationError(error2);
23
- console.log("\nTest 2 - Body type error:", JSON.stringify(result2, null, 2));
24
- console.log("✓ Location:", result2?.details[0].location === "body" ? "PASS" : "FAIL");
25
- // Test case 3: Non-validation error (should return null)
26
- const error3 = {
27
- code: "ERR_OTHER",
28
- message: "Some other error",
29
- };
30
- const result3 = convertFastifyValidationError(error3);
31
- console.log("\nTest 3 - Non-validation error:", result3 === null ? "PASS (null)" : "FAIL (not null)");
32
- }
33
- // Only run if this file is executed directly
34
- if (import.meta.url === `file://${process.argv[1]}`) {
35
- testValidationError();
36
- }