@jacebenson/jsn 4.3.13-dev.82.1 → 4.3.13-dev.84.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/commands/_generic.js +22 -20
- package/src/commands/_simple.js +30 -33
- package/src/commands/_ticket.js +21 -20
- package/src/commands/changes.js +2 -4
- package/src/commands/incidents.js +2 -4
- package/src/commands/records.js +43 -32
- package/src/commands/requests.js +7 -15
- package/src/commands/tasks.js +2 -4
- package/src/commands/tickets.js +18 -19
- package/src/commands/uipolicies.js +2 -27
- package/src/errors.js +7 -0
- package/src/essential-records.js +40 -0
- package/src/output.js +15 -0
- package/src/record-command-profiles.js +70 -0
- package/src/record-profiles.js +88 -0
- package/src/records-engine.js +57 -0
- package/src/related-lists.js +147 -0
- package/src/resolve-record.js +50 -28
package/src/resolve-record.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// resolve → mutate → report.
|
|
10
10
|
|
|
11
11
|
import { assertSafeExactMatch, isHexString } from './helpers.js';
|
|
12
|
-
import { errNotFound } from './errors.js';
|
|
12
|
+
import { errNotFound, errUsage, AppError, CodeAmbiguous } from './errors.js';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* True when the identifier looks like a ServiceNow sys_id (32 hex chars).
|
|
@@ -25,40 +25,60 @@ export function isSysId(identifier) {
|
|
|
25
25
|
*/
|
|
26
26
|
export function unwrapSysId(record) {
|
|
27
27
|
const sysId = record?.sys_id;
|
|
28
|
-
if (sysId && typeof sysId === 'object') return sysId.value ||
|
|
28
|
+
if (sysId && typeof sysId === 'object') return sysId.value || '';
|
|
29
29
|
return sysId || '';
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* @param {object} opts
|
|
37
|
-
* @param {string} opts.table — ServiceNow table
|
|
38
|
-
* @param {string} opts.identifier — sys_id (32 hex) or value of matchField
|
|
39
|
-
* @param {string} opts.matchField — field to match non-sys_id identifiers on
|
|
40
|
-
* (e.g. 'number', 'name', 'user_name')
|
|
41
|
-
* @param {string} [opts.resource] — label for the not-found error
|
|
42
|
-
* (e.g. 'User' → "User not found: <id>"); defaults to matchField
|
|
43
|
-
* @param {string[]} [opts.fields] — restrict sysparm_fields when set
|
|
44
|
-
* @returns {Promise<object>} the record (display values requested)
|
|
45
|
-
* @throws {AppError} code not_found when no record matches
|
|
33
|
+
* Exact lookups precede partial sys_id lookup. Configured human identifiers keep
|
|
34
|
+
* their legacy precedence; identifierField explicitly disambiguates collisions.
|
|
35
|
+
* Each query requests at most two rows so no operation silently picks a match.
|
|
46
36
|
*/
|
|
47
|
-
export async function resolveRecord(sdk, { table, identifier, matchField,
|
|
37
|
+
export async function resolveRecord(sdk, { table, identifier, matchField = 'sys_id', identifierField,
|
|
38
|
+
resource, fields, displayValue = 'all', partial = true }) {
|
|
48
39
|
assertSafeExactMatch(identifier);
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
params.set('sysparm_limit', '1');
|
|
53
|
-
params.set('sysparm_display_value', 'all');
|
|
54
|
-
if (fields && fields.length > 0) {
|
|
55
|
-
params.set('sysparm_fields', fields.join(','));
|
|
40
|
+
if (typeof identifier !== 'string' || !identifier.trim()) throw errUsage('An identifier is required.');
|
|
41
|
+
for (const value of [table, identifierField || matchField]) {
|
|
42
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value || '')) throw errUsage('Invalid table or identifier field name.');
|
|
56
43
|
}
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
44
|
+
const explicit = identifierField !== undefined;
|
|
45
|
+
const firstField = identifierField || (isSysId(identifier) ? 'sys_id' : matchField);
|
|
46
|
+
const attempts = [];
|
|
47
|
+
async function lookup(field, operator, mode) {
|
|
48
|
+
const params = new URLSearchParams({
|
|
49
|
+
sysparm_query: `${field}${operator}${identifier}`,
|
|
50
|
+
sysparm_limit: '2', sysparm_display_value: displayValue,
|
|
51
|
+
});
|
|
52
|
+
if (fields?.length) params.set('sysparm_fields', [...new Set(['sys_id', ...fields])].join(','));
|
|
53
|
+
const records = await sdk.list(table, params);
|
|
54
|
+
const resolution = { table, field, mode, matches: Math.min(records.length, 2),
|
|
55
|
+
...(records.length >= 2 ? { at_least: true } : {}) };
|
|
56
|
+
attempts.push(resolution);
|
|
57
|
+
if (records.length > 1) {
|
|
58
|
+
const error = new AppError(CodeAmbiguous, `Multiple ${resource || 'records'} found matching ${mode} ${field}: ${identifier}`,
|
|
59
|
+
'Use a longer/full sys_id or select the exact human field with --identifier-field <field>.');
|
|
60
|
+
error.resolution = { ...resolution, attempts };
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
return records[0];
|
|
60
64
|
}
|
|
61
|
-
|
|
65
|
+
let record = await lookup(firstField, '=', 'exact');
|
|
66
|
+
if (record) return record;
|
|
67
|
+
if (!explicit && firstField !== 'sys_id') {
|
|
68
|
+
record = await lookup('sys_id', '=', 'exact');
|
|
69
|
+
if (record) return record;
|
|
70
|
+
}
|
|
71
|
+
// An explicitly selected human field must never fall back to another field.
|
|
72
|
+
if (partial && (!explicit || firstField === 'sys_id')) {
|
|
73
|
+
record = await lookup('sys_id', 'STARTSWITH', 'prefix');
|
|
74
|
+
if (record) return record;
|
|
75
|
+
record = await lookup('sys_id', 'ENDSWITH', 'suffix');
|
|
76
|
+
if (record) return record;
|
|
77
|
+
}
|
|
78
|
+
const error = errNotFound(resource || matchField, identifier);
|
|
79
|
+
error.hint = 'No accessible record matched. Check the table/ACLs, supply a full or longer sys_id, or use --identifier-field <field> for an exact human identifier.';
|
|
80
|
+
error.resolution = { ...attempts.at(-1), attempts };
|
|
81
|
+
throw error;
|
|
62
82
|
}
|
|
63
83
|
|
|
64
84
|
/**
|
|
@@ -67,5 +87,7 @@ export async function resolveRecord(sdk, { table, identifier, matchField, resour
|
|
|
67
87
|
*/
|
|
68
88
|
export async function resolveSysId(sdk, opts) {
|
|
69
89
|
const record = await resolveRecord(sdk, { fields: ['sys_id'], ...opts });
|
|
70
|
-
|
|
90
|
+
const id = unwrapSysId(record);
|
|
91
|
+
if (!id) throw errUsage('The matched record has no readable sys_id; check field ACLs before retrying.');
|
|
92
|
+
return id;
|
|
71
93
|
}
|