@lowdefy/errors 0.0.0-experimental-20260202125814
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/LICENSE +201 -0
- package/dist/ConfigError.js +92 -0
- package/dist/ConfigWarning.js +37 -0
- package/dist/LowdefyError.js +65 -0
- package/dist/PluginError.js +109 -0
- package/dist/ServiceError.js +157 -0
- package/dist/build/ConfigError.js +120 -0
- package/dist/build/ConfigMessage.js +122 -0
- package/dist/build/ConfigWarning.js +104 -0
- package/dist/build/index.js +37 -0
- package/dist/build/resolveConfigLocation.js +63 -0
- package/dist/build/resolveErrorConfigLocation.js +45 -0
- package/dist/client/ConfigError.js +79 -0
- package/dist/client/index.js +30 -0
- package/dist/deserializeError.js +36 -0
- package/dist/index.js +48 -0
- package/dist/server/index.js +30 -0
- package/package.json +54 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2024 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import BaseConfigError from '../ConfigError.js';
|
|
16
|
+
import ConfigMessage from './ConfigMessage.js';
|
|
17
|
+
/**
|
|
18
|
+
* Build-time configuration error class.
|
|
19
|
+
* Extends base ConfigError with synchronous location resolution using keyMap/refMap.
|
|
20
|
+
* All formatting happens in the constructor - properties are ready for the logger.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // Standard usage with message and context
|
|
24
|
+
* throw new ConfigError({
|
|
25
|
+
* message: 'Connection id missing.',
|
|
26
|
+
* configKey: block['~k'],
|
|
27
|
+
* context,
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // YAML parse error - pass error object instead of message
|
|
32
|
+
* throw new ConfigError({
|
|
33
|
+
* error: yamlParseError,
|
|
34
|
+
* filePath: 'lowdefy.yaml',
|
|
35
|
+
* configDirectory,
|
|
36
|
+
* });
|
|
37
|
+
*/ let ConfigError = class ConfigError extends BaseConfigError {
|
|
38
|
+
/**
|
|
39
|
+
* Creates a ConfigError instance with build-time formatting.
|
|
40
|
+
* All location resolution happens here - no format() method needed.
|
|
41
|
+
*
|
|
42
|
+
* @param {Object} params
|
|
43
|
+
* @param {string} [params.message] - The error message (or use params.error for YAML errors)
|
|
44
|
+
* @param {Error} [params.error] - Original error (for YAML parse errors - extracts line number)
|
|
45
|
+
* @param {string} [params.configKey] - Config key (~k) for keyMap lookup
|
|
46
|
+
* @param {Object} [params.operatorLocation] - { ref, line } for direct refMap lookup
|
|
47
|
+
* @param {string} [params.filePath] - Direct file path (for raw mode)
|
|
48
|
+
* @param {number|string} [params.lineNumber] - Direct line number (for raw mode)
|
|
49
|
+
* @param {Object} [params.context] - Build context with keyMap, refMap, directories
|
|
50
|
+
* @param {string} [params.configDirectory] - Config directory (for raw mode without context)
|
|
51
|
+
* @param {string} [params.checkSlug] - The specific check being performed
|
|
52
|
+
* @param {*} [params.received] - The value that caused the error (for logger to format)
|
|
53
|
+
*/ constructor({ message, error, configKey, operatorLocation, filePath, lineNumber, context, configDirectory, checkSlug, received }){
|
|
54
|
+
// Handle YAML parse errors - extract line number from error message
|
|
55
|
+
let finalMessage = message;
|
|
56
|
+
let finalLineNumber = lineNumber;
|
|
57
|
+
if (error instanceof Error) {
|
|
58
|
+
finalMessage = `Could not parse YAML. ${error.message}`;
|
|
59
|
+
const lineMatch = error.message.match(/at line (\d+)/);
|
|
60
|
+
finalLineNumber = lineMatch ? lineMatch[1] : null;
|
|
61
|
+
}
|
|
62
|
+
// Call base constructor with minimal info first
|
|
63
|
+
super({
|
|
64
|
+
message: finalMessage,
|
|
65
|
+
configKey,
|
|
66
|
+
checkSlug
|
|
67
|
+
});
|
|
68
|
+
// Store all properties for the logger
|
|
69
|
+
this.configKey = configKey ?? null;
|
|
70
|
+
this.checkSlug = checkSlug;
|
|
71
|
+
this.operatorLocation = operatorLocation;
|
|
72
|
+
this.received = received;
|
|
73
|
+
// Check for ~ignoreBuildChecks suppression
|
|
74
|
+
this.suppressed = ConfigMessage.shouldSuppress({
|
|
75
|
+
configKey,
|
|
76
|
+
keyMap: context?.keyMap,
|
|
77
|
+
checkSlug
|
|
78
|
+
});
|
|
79
|
+
if (this.suppressed) {
|
|
80
|
+
this.message = '';
|
|
81
|
+
this.source = null;
|
|
82
|
+
this.config = null;
|
|
83
|
+
this.link = null;
|
|
84
|
+
this.resolved = true;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Resolve location based on available info
|
|
88
|
+
let location = null;
|
|
89
|
+
const configDir = configDirectory ?? context?.directories?.config;
|
|
90
|
+
if (configKey && context?.keyMap) {
|
|
91
|
+
// Mode 1: Use configKey -> keyMap -> refMap path (standard case after addKeys)
|
|
92
|
+
location = ConfigMessage.resolveLocation({
|
|
93
|
+
configKey,
|
|
94
|
+
context
|
|
95
|
+
});
|
|
96
|
+
} else if (operatorLocation && context?.refMap) {
|
|
97
|
+
// Mode 2: Use operatorLocation directly with refMap (early build stages)
|
|
98
|
+
location = ConfigMessage.resolveOperatorLocation({
|
|
99
|
+
operatorLocation,
|
|
100
|
+
context
|
|
101
|
+
});
|
|
102
|
+
} else if (filePath) {
|
|
103
|
+
// Mode 3: Use raw filePath/lineNumber (YAML parse errors, etc.)
|
|
104
|
+
location = ConfigMessage.resolveRawLocation({
|
|
105
|
+
filePath,
|
|
106
|
+
lineNumber: finalLineNumber,
|
|
107
|
+
configDirectory: configDir
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
// Set location properties
|
|
111
|
+
this.source = location?.source ?? null;
|
|
112
|
+
this.config = location?.config ?? null;
|
|
113
|
+
this.link = location?.link ?? null;
|
|
114
|
+
// Set message (no prefix - logger uses error.name for display)
|
|
115
|
+
this.message = finalMessage;
|
|
116
|
+
// Mark as resolved
|
|
117
|
+
this.resolved = true;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
export default ConfigError;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2024 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import path from 'path';
|
|
16
|
+
import resolveConfigLocation from './resolveConfigLocation.js';
|
|
17
|
+
/**
|
|
18
|
+
* Valid check slugs for ~ignoreBuildChecks.
|
|
19
|
+
* Keys are the slug names, values are descriptions for error messages.
|
|
20
|
+
* These suppress BUILD-TIME validation only - runtime errors still occur.
|
|
21
|
+
*/ export const VALID_CHECK_SLUGS = {
|
|
22
|
+
'state-refs': 'Undefined _state reference warnings',
|
|
23
|
+
'payload-refs': 'Undefined _payload reference warnings',
|
|
24
|
+
'step-refs': 'Undefined _step reference warnings',
|
|
25
|
+
'link-refs': 'Invalid Link action page reference warnings',
|
|
26
|
+
'request-refs': 'Invalid Request action reference warnings',
|
|
27
|
+
'connection-refs': 'Nonexistent connection ID references',
|
|
28
|
+
types: 'All type validation (blocks, operators, actions, requests, connections)',
|
|
29
|
+
schema: 'JSON schema validation errors'
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Base class for config message utilities.
|
|
33
|
+
* Provides shared utilities for ConfigError and ConfigWarning.
|
|
34
|
+
*/ let ConfigMessage = class ConfigMessage {
|
|
35
|
+
/**
|
|
36
|
+
* Checks if a message should be suppressed based on ~ignoreBuildChecks.
|
|
37
|
+
* Walks up the parent chain looking for suppressions that cover this check.
|
|
38
|
+
* This walk happens ONLY when an error/warning is about to be logged.
|
|
39
|
+
*
|
|
40
|
+
* @param {Object} params
|
|
41
|
+
* @param {string} params.configKey - Config key (~k) of the error location
|
|
42
|
+
* @param {Object} params.keyMap - The keyMap from build context
|
|
43
|
+
* @param {string} [params.checkSlug] - The specific check being performed (e.g., 'state-refs')
|
|
44
|
+
* @returns {boolean} True if message should be suppressed
|
|
45
|
+
*/ static shouldSuppress({ configKey, keyMap, checkSlug }) {
|
|
46
|
+
if (!configKey || !keyMap) return false;
|
|
47
|
+
let currentKey = configKey;
|
|
48
|
+
let depth = 0;
|
|
49
|
+
const MAX_DEPTH = 100; // Guard against circular parents
|
|
50
|
+
while(currentKey && depth < MAX_DEPTH){
|
|
51
|
+
const entry = keyMap[currentKey];
|
|
52
|
+
if (!entry) break;
|
|
53
|
+
const ignoredChecks = entry['~ignoreBuildChecks'];
|
|
54
|
+
if (ignoredChecks === true) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
if (Array.isArray(ignoredChecks) && checkSlug && ignoredChecks.includes(checkSlug)) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
currentKey = entry['~k_parent'];
|
|
61
|
+
depth++;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolves location from configKey using keyMap and refMap.
|
|
67
|
+
* @param {Object} params
|
|
68
|
+
* @param {string} params.configKey - Config key (~k) for keyMap lookup
|
|
69
|
+
* @param {Object} params.context - Build context with keyMap, refMap, directories
|
|
70
|
+
* @returns {Object|null} Location object { source, config, link } or null
|
|
71
|
+
*/ static resolveLocation({ configKey, context }) {
|
|
72
|
+
if (!configKey || !context?.keyMap) return null;
|
|
73
|
+
return resolveConfigLocation({
|
|
74
|
+
configKey,
|
|
75
|
+
keyMap: context.keyMap,
|
|
76
|
+
refMap: context.refMap,
|
|
77
|
+
configDirectory: context.directories?.config
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Resolves location directly from operatorLocation (ref + line) without keyMap.
|
|
82
|
+
* Used during early build stages (buildRefs) when keyMap doesn't exist yet.
|
|
83
|
+
* @param {Object} params
|
|
84
|
+
* @param {Object} params.operatorLocation - { ref, line }
|
|
85
|
+
* @param {Object} params.context - Build context with refMap, directories
|
|
86
|
+
* @returns {Object|null} Location object { source, link } or null
|
|
87
|
+
*/ static resolveOperatorLocation({ operatorLocation, context }) {
|
|
88
|
+
if (!operatorLocation) return null;
|
|
89
|
+
const refEntry = context?.refMap?.[operatorLocation.ref];
|
|
90
|
+
const filePath = refEntry?.path ?? 'lowdefy.yaml';
|
|
91
|
+
const lineNumber = operatorLocation.line;
|
|
92
|
+
const source = lineNumber ? `${filePath}:${lineNumber}` : filePath;
|
|
93
|
+
let link = null;
|
|
94
|
+
if (context?.directories?.config) {
|
|
95
|
+
link = path.join(context.directories.config, filePath) + (lineNumber ? `:${lineNumber}` : '');
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
source,
|
|
99
|
+
link
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Resolves location from raw filePath and lineNumber.
|
|
104
|
+
* Used for YAML parse errors and other cases without config context.
|
|
105
|
+
* @param {Object} params
|
|
106
|
+
* @param {string} params.filePath - Direct file path
|
|
107
|
+
* @param {number|string} [params.lineNumber] - Direct line number
|
|
108
|
+
* @param {string} [params.configDirectory] - Config directory for link
|
|
109
|
+
* @returns {Object} Location object { source, link }
|
|
110
|
+
*/ static resolveRawLocation({ filePath, lineNumber, configDirectory }) {
|
|
111
|
+
const source = lineNumber ? `${filePath}:${lineNumber}` : filePath;
|
|
112
|
+
let link = null;
|
|
113
|
+
if (configDirectory) {
|
|
114
|
+
link = path.join(configDirectory, filePath) + (lineNumber ? `:${lineNumber}` : '');
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
source,
|
|
118
|
+
link
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
export default ConfigMessage;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2024 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import ConfigError from './ConfigError.js';
|
|
16
|
+
import ConfigMessage from './ConfigMessage.js';
|
|
17
|
+
/**
|
|
18
|
+
* Build-time configuration warning class.
|
|
19
|
+
* All formatting happens in the constructor - properties are ready for the logger.
|
|
20
|
+
*
|
|
21
|
+
* In development: Creates a warning with source and message properties
|
|
22
|
+
* In production with prodError: Throws ConfigError instead
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* const warning = new ConfigWarning({ message, configKey, context });
|
|
26
|
+
* if (!warning.suppressed) {
|
|
27
|
+
* logger.warn({ source: warning.source }, warning.message);
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // Throws ConfigError in prod mode when prodError is true
|
|
32
|
+
* new ConfigWarning({ message, configKey, context, prodError: true });
|
|
33
|
+
*/ let ConfigWarning = class ConfigWarning {
|
|
34
|
+
/**
|
|
35
|
+
* @param {Object} params
|
|
36
|
+
* @param {string} params.message - The warning message
|
|
37
|
+
* @param {string} [params.configKey] - Config key (~k) for location resolution
|
|
38
|
+
* @param {Object} [params.operatorLocation] - { ref, line } for direct refMap lookup
|
|
39
|
+
* @param {string} [params.filePath] - Direct file path (for raw mode)
|
|
40
|
+
* @param {number|string} [params.lineNumber] - Direct line number (for raw mode)
|
|
41
|
+
* @param {Object} [params.context] - Build context with keyMap, refMap, directories, stage
|
|
42
|
+
* @param {string} [params.configDirectory] - Config directory (for raw mode without context)
|
|
43
|
+
* @param {string} [params.checkSlug] - The specific check being performed (e.g., 'state-refs')
|
|
44
|
+
* @param {boolean} [params.prodError] - If true, throw ConfigError in prod mode
|
|
45
|
+
* @param {*} [params.received] - The value that caused the warning (for logger to format)
|
|
46
|
+
* @throws {ConfigError} When prodError is true and context.stage is 'prod'
|
|
47
|
+
*/ constructor({ message, configKey, operatorLocation, filePath, lineNumber, context, configDirectory, checkSlug, prodError, received }){
|
|
48
|
+
// In prod mode with prodError flag, throw ConfigError instead
|
|
49
|
+
if (prodError && context?.stage === 'prod') {
|
|
50
|
+
throw new ConfigError({
|
|
51
|
+
message,
|
|
52
|
+
configKey,
|
|
53
|
+
operatorLocation,
|
|
54
|
+
context,
|
|
55
|
+
checkSlug,
|
|
56
|
+
received
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Store all properties for the logger
|
|
60
|
+
this.configKey = configKey ?? null;
|
|
61
|
+
this.checkSlug = checkSlug;
|
|
62
|
+
this.received = received;
|
|
63
|
+
// Check for ~ignoreBuildChecks suppression
|
|
64
|
+
this.suppressed = ConfigMessage.shouldSuppress({
|
|
65
|
+
configKey,
|
|
66
|
+
keyMap: context?.keyMap,
|
|
67
|
+
checkSlug
|
|
68
|
+
});
|
|
69
|
+
if (this.suppressed) {
|
|
70
|
+
this.message = '';
|
|
71
|
+
this.source = null;
|
|
72
|
+
this.config = null;
|
|
73
|
+
this.link = null;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// Resolve location based on available info
|
|
77
|
+
let location = null;
|
|
78
|
+
const configDir = configDirectory ?? context?.directories?.config;
|
|
79
|
+
if (configKey && context?.keyMap) {
|
|
80
|
+
location = ConfigMessage.resolveLocation({
|
|
81
|
+
configKey,
|
|
82
|
+
context
|
|
83
|
+
});
|
|
84
|
+
} else if (operatorLocation && context?.refMap) {
|
|
85
|
+
location = ConfigMessage.resolveOperatorLocation({
|
|
86
|
+
operatorLocation,
|
|
87
|
+
context
|
|
88
|
+
});
|
|
89
|
+
} else if (filePath) {
|
|
90
|
+
location = ConfigMessage.resolveRawLocation({
|
|
91
|
+
filePath,
|
|
92
|
+
lineNumber,
|
|
93
|
+
configDirectory: configDir
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
// Set location properties
|
|
97
|
+
this.source = location?.source ?? null;
|
|
98
|
+
this.config = location?.config ?? null;
|
|
99
|
+
this.link = location?.link ?? null;
|
|
100
|
+
// Set message (no prefix - logger uses class name for display)
|
|
101
|
+
this.message = message;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
export default ConfigWarning;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2024 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ /**
|
|
16
|
+
* @lowdefy/errors/build - Build-time error classes.
|
|
17
|
+
*
|
|
18
|
+
* Use this entry point for build-time code that needs synchronous location resolution
|
|
19
|
+
* using keyMap and refMap from the build context.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* import { ConfigError, ConfigWarning, ConfigMessage } from '@lowdefy/errors/build';
|
|
23
|
+
*
|
|
24
|
+
* throw new ConfigError({
|
|
25
|
+
* message: 'Connection id missing.',
|
|
26
|
+
* configKey: block['~k'],
|
|
27
|
+
* context,
|
|
28
|
+
* });
|
|
29
|
+
*/ import ConfigError from './ConfigError.js';
|
|
30
|
+
import ConfigWarning from './ConfigWarning.js';
|
|
31
|
+
import ConfigMessage, { VALID_CHECK_SLUGS } from './ConfigMessage.js';
|
|
32
|
+
import resolveConfigLocation from './resolveConfigLocation.js';
|
|
33
|
+
import resolveErrorConfigLocation from './resolveErrorConfigLocation.js';
|
|
34
|
+
import LowdefyError from '../LowdefyError.js';
|
|
35
|
+
import PluginError from '../PluginError.js';
|
|
36
|
+
import ServiceError from '../ServiceError.js';
|
|
37
|
+
export { ConfigError, ConfigMessage, ConfigWarning, LowdefyError, PluginError, resolveConfigLocation, resolveErrorConfigLocation, ServiceError, VALID_CHECK_SLUGS };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2024 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import path from 'path';
|
|
16
|
+
/**
|
|
17
|
+
* Resolves a config key (~k) to source and config location.
|
|
18
|
+
*
|
|
19
|
+
* @param {Object} params
|
|
20
|
+
* @param {string} params.configKey - The ~k value from the config object
|
|
21
|
+
* @param {Object} params.keyMap - The keyMap from build output
|
|
22
|
+
* @param {Object} params.refMap - The refMap from build output
|
|
23
|
+
* @param {string} [params.configDirectory] - Absolute path to config directory for clickable links
|
|
24
|
+
* @returns {Object|null} Location object with source, config, and link, or null if not resolvable
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const location = resolveConfigLocation({
|
|
28
|
+
* configKey: 'abc123',
|
|
29
|
+
* keyMap: { 'abc123': { key: 'root.pages[0:home].blocks[0:header]', '~r': 'ref1', '~l': 5 } },
|
|
30
|
+
* refMap: { 'ref1': { path: 'pages/home.yaml' } },
|
|
31
|
+
* configDirectory: '/Users/dev/myapp'
|
|
32
|
+
* });
|
|
33
|
+
* // Returns: {
|
|
34
|
+
* // source: 'pages/home.yaml:5',
|
|
35
|
+
* // config: 'root.pages[0:home].blocks[0:header]',
|
|
36
|
+
* // link: '/Users/dev/myapp/pages/home.yaml:5'
|
|
37
|
+
* // }
|
|
38
|
+
*/ function resolveConfigLocation({ configKey, keyMap, refMap, configDirectory }) {
|
|
39
|
+
if (!configKey || !keyMap || !keyMap[configKey]) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const keyEntry = keyMap[configKey];
|
|
43
|
+
const refId = keyEntry['~r'];
|
|
44
|
+
const lineNumber = keyEntry['~l'];
|
|
45
|
+
const refEntry = refMap?.[refId];
|
|
46
|
+
const filePath = refEntry?.path || 'lowdefy.yaml';
|
|
47
|
+
// source: filepath:line (e.g., "lowdefy.yaml:16")
|
|
48
|
+
const source = lineNumber ? `${filePath}:${lineNumber}` : filePath;
|
|
49
|
+
// config: the config path (e.g., "root.pages[0:home].blocks[0:header]")
|
|
50
|
+
const config = keyEntry.key;
|
|
51
|
+
// Absolute path for clickable links in VSCode terminal
|
|
52
|
+
let link = null;
|
|
53
|
+
if (configDirectory) {
|
|
54
|
+
const absolutePath = path.resolve(configDirectory, filePath);
|
|
55
|
+
link = lineNumber ? `${absolutePath}:${lineNumber}` : absolutePath;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
source,
|
|
59
|
+
config,
|
|
60
|
+
link
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export default resolveConfigLocation;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2024 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import resolveConfigLocation from './resolveConfigLocation.js';
|
|
16
|
+
/**
|
|
17
|
+
* Resolves error config location at runtime by reading keyMap and refMap files.
|
|
18
|
+
* Used by server-side error logging to trace errors back to source files.
|
|
19
|
+
*
|
|
20
|
+
* @param {Object} params
|
|
21
|
+
* @param {Object} params.error - Error object with optional configKey property
|
|
22
|
+
* @param {Function} params.readConfigFile - Async function to read config files
|
|
23
|
+
* @param {string} params.configDirectory - Absolute path to config directory
|
|
24
|
+
* @returns {Promise<Object|null>} Location object with source, config, and link, or null
|
|
25
|
+
*/ async function resolveErrorConfigLocation({ error, readConfigFile, configDirectory }) {
|
|
26
|
+
if (!error?.configKey) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const [keyMap, refMap] = await Promise.all([
|
|
31
|
+
readConfigFile('keyMap.json'),
|
|
32
|
+
readConfigFile('refMap.json')
|
|
33
|
+
]);
|
|
34
|
+
const location = resolveConfigLocation({
|
|
35
|
+
configKey: error.configKey,
|
|
36
|
+
keyMap,
|
|
37
|
+
refMap,
|
|
38
|
+
configDirectory
|
|
39
|
+
});
|
|
40
|
+
return location || null;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export default resolveErrorConfigLocation;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2024 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import BaseConfigError from '../ConfigError.js';
|
|
16
|
+
/**
|
|
17
|
+
* Client-side ConfigError with async location resolution via API.
|
|
18
|
+
*
|
|
19
|
+
* Extends the base ConfigError to add async resolution that calls
|
|
20
|
+
* the /api/client-error endpoint to resolve configKey to file:line.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const error = new ConfigError({ message: 'Invalid operator', configKey });
|
|
24
|
+
* await error.resolve(lowdefy);
|
|
25
|
+
* console.error(error.source); // "pages/home.yaml:42"
|
|
26
|
+
* console.error(error.message); // "Invalid operator"
|
|
27
|
+
*/ let ConfigError = class ConfigError extends BaseConfigError {
|
|
28
|
+
/**
|
|
29
|
+
* Resolves location from server (async).
|
|
30
|
+
* Updates this.message with the resolved location.
|
|
31
|
+
* @param {Object} lowdefy - Lowdefy context with basePath and pageId
|
|
32
|
+
* @param {Object} [options]
|
|
33
|
+
* @param {number} [options.timeout=1000] - Fetch timeout in ms
|
|
34
|
+
* @returns {Promise<ConfigError>} Returns this for chaining
|
|
35
|
+
*/ async resolve(lowdefy, { timeout = 1000 } = {}) {
|
|
36
|
+
// basePath can be empty string "" which is valid (no custom base path)
|
|
37
|
+
if (this.resolved || lowdefy?.basePath === undefined) {
|
|
38
|
+
this.resolved = true;
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
const controller = new AbortController();
|
|
42
|
+
const timeoutId = setTimeout(()=>controller.abort(), timeout);
|
|
43
|
+
try {
|
|
44
|
+
const response = await fetch(`${lowdefy.basePath}/api/client-error`, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: {
|
|
47
|
+
'Content-Type': 'application/json'
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify(this.serialize()),
|
|
50
|
+
signal: controller.signal,
|
|
51
|
+
credentials: 'same-origin'
|
|
52
|
+
});
|
|
53
|
+
clearTimeout(timeoutId);
|
|
54
|
+
if (response.ok) {
|
|
55
|
+
const result = await response.json();
|
|
56
|
+
this.source = result.source;
|
|
57
|
+
this.config = result.config;
|
|
58
|
+
this.link = result.link;
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
clearTimeout(timeoutId);
|
|
62
|
+
// Resolution failed (timeout or network error) - continue without location
|
|
63
|
+
}
|
|
64
|
+
this.resolved = true;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resolves location (if not already resolved) and logs to console.
|
|
69
|
+
* @param {Object} [lowdefy] - Lowdefy context for resolution
|
|
70
|
+
* @param {Object} [options] - Options for resolution
|
|
71
|
+
* @returns {Promise<void>}
|
|
72
|
+
*/ async log(lowdefy, options) {
|
|
73
|
+
if (!this.resolved && lowdefy) {
|
|
74
|
+
await this.resolve(lowdefy, options);
|
|
75
|
+
}
|
|
76
|
+
console.error(this.message);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
export default ConfigError;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2024 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ /**
|
|
16
|
+
* @lowdefy/errors/client - Client-side error classes.
|
|
17
|
+
*
|
|
18
|
+
* Use this entry point for client-side (browser) code that needs async location resolution.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* import { ConfigError, PluginError } from '@lowdefy/errors/client';
|
|
22
|
+
*
|
|
23
|
+
* const error = new ConfigError({ message: 'Invalid operator', configKey });
|
|
24
|
+
* await error.resolve(lowdefy);
|
|
25
|
+
*/ import ConfigError from './ConfigError.js';
|
|
26
|
+
import ConfigWarning from '../ConfigWarning.js';
|
|
27
|
+
import LowdefyError from '../LowdefyError.js';
|
|
28
|
+
import PluginError from '../PluginError.js';
|
|
29
|
+
import ServiceError from '../ServiceError.js';
|
|
30
|
+
export { ConfigError, ConfigWarning, LowdefyError, PluginError, ServiceError };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2024 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import ConfigError from './ConfigError.js';
|
|
16
|
+
import LowdefyError from './LowdefyError.js';
|
|
17
|
+
import PluginError from './PluginError.js';
|
|
18
|
+
import ServiceError from './ServiceError.js';
|
|
19
|
+
const errorTypes = {
|
|
20
|
+
ConfigError,
|
|
21
|
+
LowdefyError,
|
|
22
|
+
PluginError,
|
|
23
|
+
ServiceError
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Deserializes error data back into the appropriate error class.
|
|
27
|
+
* @param {Object} data - Serialized error data with ~err type marker
|
|
28
|
+
* @returns {Error} The deserialized error instance
|
|
29
|
+
*/ function deserializeError(data) {
|
|
30
|
+
const ErrorClass = errorTypes[data['~err']];
|
|
31
|
+
if (!ErrorClass) {
|
|
32
|
+
throw new Error(`Unknown error type: ${data['~err']}`);
|
|
33
|
+
}
|
|
34
|
+
return ErrorClass.deserialize(data);
|
|
35
|
+
}
|
|
36
|
+
export default deserializeError;
|