@tenonhq/dovetail-servicenow 0.0.45 → 0.0.47
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/README.md +73 -1
- package/dist/cli.js +149 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -2
- package/dist/mcp/registry.d.ts +1 -1
- package/dist/mcp/registry.js +68 -0
- package/dist/mcp/schemas.d.ts +41 -0
- package/dist/mcp/schemas.js +26 -1
- package/dist/table/createIndex.d.ts +118 -0
- package/dist/table/createIndex.js +460 -0
- package/dist/table/formSession.d.ts +24 -0
- package/dist/table/formSession.js +61 -0
- package/dist/table/index.d.ts +6 -2
- package/dist/table/index.js +11 -1
- package/dist/table/listIndexes.d.ts +68 -0
- package/dist/table/listIndexes.js +110 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -652,6 +652,73 @@ Three consequences, each reported rather than hidden:
|
|
|
652
652
|
single request goes out. Exit codes: `0` created / skipped / dry-run, `1` bad args, `2`
|
|
653
653
|
failed (the lying-row case included).
|
|
654
654
|
|
|
655
|
+
### List a table's indexes
|
|
656
|
+
|
|
657
|
+
```bash
|
|
658
|
+
npx dove-sn index-list --table x_cadso_automate_message_batch_recipient --json
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
Read-only: no form session, no writes. **`v_db_index` is the index read surface** - and
|
|
662
|
+
the only one. `sys_index` fails an API-LEVEL ACL (HTTP 403) for every identity including
|
|
663
|
+
admin, and `sys_index_column` does not exist at all (HTTP 400 `Invalid table`), so there
|
|
664
|
+
is no two-table index model to join and nothing to cross-check against.
|
|
665
|
+
|
|
666
|
+
Each row comes back as `{ name, columns, type, rawColumns }`. `columns` is the view's
|
|
667
|
+
bracketed `column_names` cell (`"[phone]"`, `"[a,b]"`) **parsed** into a list - never
|
|
668
|
+
substring-matched, because `"[owner_id]"` contains `"owner"`. `type` is `access_method`.
|
|
669
|
+
|
|
670
|
+
**Uniqueness is not readable.** `v_db_index` has no uniqueness field, so a unique index
|
|
671
|
+
and an ordinary one are indistinguishable in it: `unique` is left **absent** rather than
|
|
672
|
+
guessed, and `uniqueness-enforced` is reported in `unverified` on every result. Only a
|
|
673
|
+
duplicate-insert test proves enforcement. An empty result more likely means the table name
|
|
674
|
+
is wrong than that the table is unindexed - every physical table has a `PRIMARY`.
|
|
675
|
+
|
|
676
|
+
### Create an index (composite and non-unique included)
|
|
677
|
+
|
|
678
|
+
> **A DATABASE INDEX IS A PHYSICAL, PER-INSTANCE CHANGE. IT IS NOT CAPTURED IN AN UPDATE
|
|
679
|
+
> SET AND DOES NOT TRAVEL WITH A PROMOTION.** Re-run `index-create` against every
|
|
680
|
+
> environment that needs the index (dev, test, uat, staging, prod). There is deliberately
|
|
681
|
+
> no `--update-set` - passing one is an error, not a silent no-op.
|
|
682
|
+
|
|
683
|
+
```bash
|
|
684
|
+
# Dry-run (the DEFAULT) - sends nothing and reads nothing
|
|
685
|
+
npx dove-sn index-create --table x_cadso_journey_instance --columns state,created_on
|
|
686
|
+
|
|
687
|
+
# Send it
|
|
688
|
+
npx dove-sn index-create \
|
|
689
|
+
--table x_cadso_journey_instance --columns state,created_on --confirm --json
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
This is what `add-index` cannot do. `sys_dictionary.unique` - the only record-shaped lever
|
|
693
|
+
- is **per-column and unique-only**, so composite and plain indexes have no record path at
|
|
694
|
+
all. `index-create` instead replays the platform's own index-creator form
|
|
695
|
+
(`sys_action=create_index`, `sysparm_index_table`, `sysparm_fields`,
|
|
696
|
+
`sysparm_unique_index_SKIP`) over a form-login session. That contract is lifted from the
|
|
697
|
+
instance's shipped `index_creator_information` UI macro, not from a guess, and the POST
|
|
698
|
+
target is taken from the rendered page's own `<form action>`.
|
|
699
|
+
|
|
700
|
+
- **Dry-run by default.** Without `--confirm` nothing is sent *and nothing is read*;
|
|
701
|
+
`--dry-run` forces a plan even with `--confirm`.
|
|
702
|
+
- **Idempotent.** On the live path `v_db_index` is read first, and an index over *exactly*
|
|
703
|
+
these columns short-circuits to `already-exists` with no form session and no write.
|
|
704
|
+
Column **order** is part of an index's identity - `[a,b]` is not `[b,a]`.
|
|
705
|
+
- **`--name` is refused.** The platform's form has no name input; ServiceNow names the
|
|
706
|
+
index itself. Reporting a name the instance does not carry would be a lie, so the
|
|
707
|
+
created index's *real* name is returned in `name` instead.
|
|
708
|
+
- **The read-back is the proof.** After the POST the index is polled for in `v_db_index`
|
|
709
|
+
(default 10 checks, 3 s apart - a build on a populated table is asynchronous). If it
|
|
710
|
+
never appears the status is `failed`: a form processor returning a page is not evidence
|
|
711
|
+
an ALTER ran, and a unique index cannot build over duplicate values (EMPTY counts).
|
|
712
|
+
- **Uniqueness is still never claimed.** `uniqueness-enforced` stays in `unverified` on
|
|
713
|
+
every status.
|
|
714
|
+
|
|
715
|
+
**Requires a username+password identity that can form-log-in.** An instance on
|
|
716
|
+
API-key-only auth, SSO or MFA rejects the form login however valid the API key is; the
|
|
717
|
+
verb fails at the session with that diagnosis rather than a mystery 302, and no `.do`
|
|
718
|
+
replay (including `create-table`'s) can work in that state.
|
|
719
|
+
|
|
720
|
+
Exit codes: `0` created / already-exists / dry-run, `1` bad args, `2` failed.
|
|
721
|
+
|
|
655
722
|
### Set a field on a record
|
|
656
723
|
|
|
657
724
|
Set scalar field value(s) on an **existing** data record, capture the change into
|
|
@@ -954,7 +1021,12 @@ Claude Code and agents: `create_view`, `set_list_layout`, `set_form_layout`,
|
|
|
954
1021
|
`set_related_lists`, `add_choices_to_field`, the schema verbs `create_table` /
|
|
955
1022
|
`add_column` / `add_index` (a single-column unique index via `sys_dictionary.unique`,
|
|
956
1023
|
read back from the `v_db_index` view - uniqueness enforcement is always reported
|
|
957
|
-
unverified)
|
|
1024
|
+
unverified) / `index_list` (read-only: a table's database indexes from `v_db_index`,
|
|
1025
|
+
the only index read surface - `sys_index` is API-level-ACL 403 and `sys_index_column`
|
|
1026
|
+
does not exist) / `index_create` (create an index, composite and non-unique included, by
|
|
1027
|
+
replaying the platform index-creator form; dry-run by default, idempotent, read back from
|
|
1028
|
+
`v_db_index` - and **not** captured in an update set, because a database index is a
|
|
1029
|
+
physical per-instance change), the record-write verbs `set_field` (update scalar fields on an
|
|
958
1030
|
existing record) and `create_record` (insert one record) — both update-set-captured
|
|
959
1031
|
and read-back-verified — `host_assets` (deploy a built dist/), plus the Flow Designer
|
|
960
1032
|
tools `flow_view` (read a flow/subflow's step graph), `action_view` (read an action
|
package/dist/cli.js
CHANGED
|
@@ -1300,6 +1300,25 @@ function printHelp() {
|
|
|
1300
1300
|
" is treated exactly like a proven collision. Success is\n" +
|
|
1301
1301
|
" read back from v_db_index; that view has no uniqueness field, so\n" +
|
|
1302
1302
|
" ENFORCEMENT is always reported unverified.\n" +
|
|
1303
|
+
" index-list List a table's DATABASE INDEXES from the v_db_index view (read-only)\n" +
|
|
1304
|
+
" (--table <name> [--json])\n" +
|
|
1305
|
+
" sys_index is API-level-ACL 403 and sys_index_column does not\n" +
|
|
1306
|
+
" exist, so v_db_index is the only index read surface. It has no\n" +
|
|
1307
|
+
" uniqueness field, so WHICH indexes are unique is always\n" +
|
|
1308
|
+
" reported unverified.\n" +
|
|
1309
|
+
" index-create Create a DATABASE INDEX (composite and non-unique included) by\n" +
|
|
1310
|
+
" replaying the platform index-creator form, then read it back\n" +
|
|
1311
|
+
" DRY-RUN BY DEFAULT — nothing is sent or read without --confirm\n" +
|
|
1312
|
+
" (--table <name> --columns <a[,b,...]> [--unique]\n" +
|
|
1313
|
+
" [--access-method <m>] [--confirm] [--dry-run]\n" +
|
|
1314
|
+
" [--poll-attempts <n>] [--poll-interval-ms <n>] [--debug] [--json])\n" +
|
|
1315
|
+
" A DATABASE INDEX IS PHYSICAL AND PER-INSTANCE: it is NOT captured\n" +
|
|
1316
|
+
" in an update set and does NOT travel with a promotion — re-run it\n" +
|
|
1317
|
+
" against every environment. There is no --update-set for that\n" +
|
|
1318
|
+
" reason. Idempotent: an index over exactly those columns already\n" +
|
|
1319
|
+
" present returns already-exists with no write. --name is REFUSED —\n" +
|
|
1320
|
+
" the platform's form has no name input; the real name is returned.\n" +
|
|
1321
|
+
" Needs a username+password identity that can form-log-in.\n" +
|
|
1303
1322
|
" set-column Update an EXISTING column's SCHEMA (label/mandatory/default/read-only/max-length),\n" +
|
|
1304
1323
|
" into an update set, then verify against the instance\n" +
|
|
1305
1324
|
" (--table <t> --column <c> --update-set <sys_id>\n" +
|
|
@@ -1662,6 +1681,130 @@ async function runAddIndex(flags) {
|
|
|
1662
1681
|
return 2;
|
|
1663
1682
|
return 0;
|
|
1664
1683
|
}
|
|
1684
|
+
/**
|
|
1685
|
+
* dove-sn index-list:
|
|
1686
|
+
* --table x_cadso_automate_message_batch_recipient [--json]
|
|
1687
|
+
*
|
|
1688
|
+
* Read-only. Lists the table's database indexes from the v_db_index view — the only
|
|
1689
|
+
* index read surface an instance exposes (sys_index is API-level-ACL 403,
|
|
1690
|
+
* sys_index_column does not exist).
|
|
1691
|
+
*
|
|
1692
|
+
* Exit codes: 0 read, 1 bad args.
|
|
1693
|
+
*/
|
|
1694
|
+
async function runIndexList(flags) {
|
|
1695
|
+
if (!flags.table) {
|
|
1696
|
+
process.stderr.write("index-list: --table <name> is required\n");
|
|
1697
|
+
return 1;
|
|
1698
|
+
}
|
|
1699
|
+
var result = await (0, table_1.listIndexes)({
|
|
1700
|
+
client: (0, client_1.createClient)({}),
|
|
1701
|
+
table: flags.table,
|
|
1702
|
+
});
|
|
1703
|
+
if (flags.json === "true") {
|
|
1704
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
1705
|
+
return 0;
|
|
1706
|
+
}
|
|
1707
|
+
process.stdout.write(result.table + " — " + result.indexes.length + " index(es)\n");
|
|
1708
|
+
for (var i = 0; i < result.indexes.length; i += 1) {
|
|
1709
|
+
var idx = result.indexes[i];
|
|
1710
|
+
process.stdout.write(" " +
|
|
1711
|
+
(idx.name || "(unnamed)") +
|
|
1712
|
+
" [" +
|
|
1713
|
+
idx.columns.join(", ") +
|
|
1714
|
+
"] " +
|
|
1715
|
+
(idx.type || "(no access_method)") +
|
|
1716
|
+
"\n");
|
|
1717
|
+
}
|
|
1718
|
+
process.stdout.write(result.note + "\nUNVERIFIED: " + result.unverified.join(", ") + "\n");
|
|
1719
|
+
return 0;
|
|
1720
|
+
}
|
|
1721
|
+
/**
|
|
1722
|
+
* dove-sn index-create:
|
|
1723
|
+
* --table x_cadso_core_u_smoke --columns a,b [--unique] [--access-method <m>]
|
|
1724
|
+
* [--confirm] [--dry-run] [--poll-attempts <n>] [--poll-interval-ms <n>]
|
|
1725
|
+
* [--debug] [--json]
|
|
1726
|
+
*
|
|
1727
|
+
* DRY-RUN BY DEFAULT — nothing is sent (and nothing is even READ) without --confirm;
|
|
1728
|
+
* --dry-run forces a plan even with it. There is deliberately NO --update-set: a
|
|
1729
|
+
* database index is physical and is not captured in one.
|
|
1730
|
+
*
|
|
1731
|
+
* Exit codes: 0 created / already-exists / dry-run, 1 bad args, 2 failed (which
|
|
1732
|
+
* includes "the form was posted but no index was read back").
|
|
1733
|
+
*/
|
|
1734
|
+
async function runIndexCreate(flags) {
|
|
1735
|
+
var columns = splitList(flags.columns || "");
|
|
1736
|
+
if (!flags.table || columns.length === 0) {
|
|
1737
|
+
process.stderr.write("index-create: --table <name> and --columns <a[,b,...]> are required\n");
|
|
1738
|
+
return 1;
|
|
1739
|
+
}
|
|
1740
|
+
if (flags["update-set"]) {
|
|
1741
|
+
process.stderr.write("index-create: --update-set is not accepted. A database index is a PHYSICAL, " +
|
|
1742
|
+
"PER-INSTANCE change — it is NOT captured in an update set and does not " +
|
|
1743
|
+
"travel with a promotion. Run index-create against each environment.\n");
|
|
1744
|
+
return 1;
|
|
1745
|
+
}
|
|
1746
|
+
var params = {
|
|
1747
|
+
client: (0, client_1.createClient)({}),
|
|
1748
|
+
table: flags.table,
|
|
1749
|
+
columns: columns,
|
|
1750
|
+
unique: flags.unique === "true",
|
|
1751
|
+
confirm: flags.confirm === "true",
|
|
1752
|
+
dryRun: flags["dry-run"] === "true",
|
|
1753
|
+
};
|
|
1754
|
+
if (flags.name !== undefined)
|
|
1755
|
+
params.name = flags.name;
|
|
1756
|
+
if (flags["access-method"])
|
|
1757
|
+
params.accessMethod = flags["access-method"];
|
|
1758
|
+
if (flags["form-path"])
|
|
1759
|
+
params.formPath = flags["form-path"];
|
|
1760
|
+
if (flags.debug === "true")
|
|
1761
|
+
params.debug = true;
|
|
1762
|
+
// A non-integer poll setting would make the bounded wait unbounded (or zero).
|
|
1763
|
+
// Reject it here with a named message instead of letting NaN reach the loop.
|
|
1764
|
+
var numeric = [
|
|
1765
|
+
["poll-attempts", "pollAttempts"],
|
|
1766
|
+
["poll-interval-ms", "pollIntervalMs"],
|
|
1767
|
+
];
|
|
1768
|
+
for (var n = 0; n < numeric.length; n += 1) {
|
|
1769
|
+
var raw = flags[numeric[n][0]];
|
|
1770
|
+
if (raw === undefined)
|
|
1771
|
+
continue;
|
|
1772
|
+
var value = Number(raw);
|
|
1773
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
1774
|
+
process.stderr.write("index-create: --" +
|
|
1775
|
+
numeric[n][0] +
|
|
1776
|
+
" must be a positive integer (got '" +
|
|
1777
|
+
raw +
|
|
1778
|
+
"')\n");
|
|
1779
|
+
return 1;
|
|
1780
|
+
}
|
|
1781
|
+
params[numeric[n][1]] = value;
|
|
1782
|
+
}
|
|
1783
|
+
var result = await (0, table_1.createIndex)(params);
|
|
1784
|
+
if (flags.json === "true") {
|
|
1785
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
1786
|
+
}
|
|
1787
|
+
else {
|
|
1788
|
+
process.stdout.write("[" +
|
|
1789
|
+
result.status +
|
|
1790
|
+
"] " +
|
|
1791
|
+
result.table +
|
|
1792
|
+
" [" +
|
|
1793
|
+
result.columns.join(", ") +
|
|
1794
|
+
"]" +
|
|
1795
|
+
(result.name ? " -> " + result.name : "") +
|
|
1796
|
+
(result.instance ? " on " + result.instance : "") +
|
|
1797
|
+
(result.verified ? " — verified" : "") +
|
|
1798
|
+
"\n" +
|
|
1799
|
+
result.note +
|
|
1800
|
+
"\nUNVERIFIED: " +
|
|
1801
|
+
result.unverified.join(", ") +
|
|
1802
|
+
"\n");
|
|
1803
|
+
}
|
|
1804
|
+
if (result.status === "failed")
|
|
1805
|
+
return 2;
|
|
1806
|
+
return 0;
|
|
1807
|
+
}
|
|
1665
1808
|
/** Parse a CLI boolean flag. Bare `--mandatory` means true; `--mandatory false` means
|
|
1666
1809
|
* false. Anything else is rejected rather than quietly coerced to `true`. */
|
|
1667
1810
|
function parseBoolFlag(name, raw, verb = "set-column") {
|
|
@@ -2544,6 +2687,12 @@ async function main() {
|
|
|
2544
2687
|
if (parsed.command === "add-index") {
|
|
2545
2688
|
return await runAddIndex(parsed.flags);
|
|
2546
2689
|
}
|
|
2690
|
+
if (parsed.command === "index-list") {
|
|
2691
|
+
return await runIndexList(parsed.flags);
|
|
2692
|
+
}
|
|
2693
|
+
if (parsed.command === "index-create") {
|
|
2694
|
+
return await runIndexCreate(parsed.flags);
|
|
2695
|
+
}
|
|
2547
2696
|
if (parsed.command === "set-column") {
|
|
2548
2697
|
return await runSetColumn(parsed.flags, parsed.bare);
|
|
2549
2698
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -21,8 +21,8 @@ export { sincPlugin } from "./plugin";
|
|
|
21
21
|
export { listTemplates, verifyArtifact, cloneSubflow, cloneActionType, resolveScope, slugInternalName, fetchActionSteps, findStepInput, triggerPublication, publishActionType, editActionType, defineActionType, planActionDefinition, validateDefineSpec, applyStepOps, verifySteps, summarizeSteps, formatStepPill, readFlow, readActionType, publishFlow, copyFlow, createFlow, buildPublishModel, editFlow, testFlow, DEFAULT_RUN_FLOW_PATH, generateSysId, topoSort, executeWritePlan, WriteOrderError, } from "./flowDesigner";
|
|
22
22
|
export type { TemplateRef, ListTemplatesParams, FlowKind, VerifyExpect, VerifyFound, VerifyFailure, VerifyReport, VerifyArtifactParams, CloneSubflowParams, CloneSubflowResult, CloneActionTypeParams, CloneActionTypeResult, CloneActionTypePlan, CloneActionTypeStepReport, TriggerPublicationParams, TriggerPublicationResult, PublishActionTypeParams, PublishActionTypeResult, EditActionTypeParams, EditActionTypeResult, EditActionTypeOps, DefineActionSpec, DefineActionInputSpec, DefineActionOutputSpec, DefineStepSpec, DefineStepVarSpec, DefineStepOutputSpec, DefineStepValue, DefineActionTypeParams, DefineActionTypeResult, DefineActionDiff, DefineActionPlan, PlanActionDefinitionParams, ActionView, StepView, StepOps, StepRecord, StepSummary, StepIoSummary, PatchStepScriptOp, SetStepInputOp, AddStepOutputOp, AddStepInputOp, ApplyStepOpsResult, VerifyStepsResult, ReadFlowParams, ReadFlowResult, FlowStep, FlowVariable, ReadActionTypeParams, ReadActionTypeResult, ActionIo, PublishFlowParams, PublishFlowResult, CopyFlowParams, CopyFlowResult, CreateFlowParams, CreateFlowResult, EditFlowParams, EditFlowResult, EditFlowOps, StepInputPatch, TestFlowParams, TestFlowResult, WriteOp, WriteOpResult, } from "./flowDesigner";
|
|
23
23
|
export type { ServiceNowClientConfig, ChoiceValue, ChoiceType, AddChoicesParams, AddChoicesResult, ChoiceActionResult, RemoveChoicesParams, RemoveChoicesResult, ChoiceRemovalResult, DictionaryRecord, UpdateSetRecord, LayoutAction, LayoutRecordResult, LayoutResult, CreateViewParams, CreateViewResult, FormSectionSpec, SetFormLayoutParams, SetListLayoutParams, SetRelatedListsParams, ChunkRole, ChunkInfo, ChunkResult, PrunedResult, HostAssetsParams, HostAssetsResult, } from "./types";
|
|
24
|
-
export { createTable, projectTableGraph, buildColumnXml, normalizeColumns, resolveType, applyTableSaveOverlay, defaultAccessFlags, TYPE_MAP, DEFAULT_SUPER_CLASS, DEFAULT_SAVE_ACTION, addColumn, deriveElement, addIndex, parseIndexColumns, indexMatchesColumns, setColumn, resolveAttributes, toStoredValue, setTable, resolveTableAttributes, } from "./table";
|
|
25
|
-
export type { CreateTableParams, CreateTableResult, AddColumnParams, AddColumnResult, AddIndexParams, AddIndexResult, AddIndexVerification, SetColumnParams, SetColumnResult, ColumnAttributes, AttributeChange, SetTableParams, SetTableResult, TableAttributes, TableGraph, NormalizedColumn, ColumnSpec, AccessFlags, OverlaySpec, } from "./table";
|
|
24
|
+
export { createTable, projectTableGraph, buildColumnXml, normalizeColumns, resolveType, applyTableSaveOverlay, defaultAccessFlags, TYPE_MAP, DEFAULT_SUPER_CLASS, DEFAULT_SAVE_ACTION, addColumn, deriveElement, addIndex, parseIndexColumns, indexMatchesColumns, listIndexes, createIndex, validateCreateIndex, DEFAULT_INDEX_FORM_PATH, NOT_IN_UPDATE_SET, setColumn, resolveAttributes, toStoredValue, setTable, resolveTableAttributes, } from "./table";
|
|
25
|
+
export type { CreateTableParams, CreateTableResult, AddColumnParams, AddColumnResult, AddIndexParams, AddIndexResult, AddIndexVerification, ListIndexesParams, ListIndexesResult, TableIndex, CreateIndexParams, CreateIndexResult, SetColumnParams, SetColumnResult, ColumnAttributes, AttributeChange, SetTableParams, SetTableResult, TableAttributes, TableGraph, NormalizedColumn, ColumnSpec, AccessFlags, OverlaySpec, } from "./table";
|
|
26
26
|
export { setField } from "./setField";
|
|
27
27
|
export { createRecord } from "./createRecord";
|
|
28
28
|
export type { RecordWriteResult, SetFieldParams, SetFieldResult, } from "./setField";
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.editFlow = exports.buildPublishModel = exports.createFlow = exports.copyFlow = exports.publishFlow = exports.readActionType = exports.readFlow = exports.formatStepPill = exports.summarizeSteps = exports.verifySteps = exports.applyStepOps = exports.validateDefineSpec = exports.planActionDefinition = exports.defineActionType = exports.editActionType = exports.publishActionType = exports.triggerPublication = exports.findStepInput = exports.fetchActionSteps = exports.slugInternalName = exports.resolveScope = exports.cloneActionType = exports.cloneSubflow = exports.verifyArtifact = exports.listTemplates = exports.sincPlugin = exports.formatCreateViewResult = exports.formatLayoutResult = exports.setRelatedLists = exports.setFormLayout = exports.setListLayout = exports.createView = exports.formatRemoveChoicesResult = exports.formatAddChoicesResult = exports.formatHostAssetsResult = exports.classifyChunks = exports.hostAssets = exports.ChoiceWriteError = exports.removeChoicesFromField = exports.addChoicesToField = exports.resolveConfigFromEnvFile = exports.createClientFromEnvFile = exports.assertWriteAllowed = exports.evaluateWriteGate = exports.isCiEnvironment = exports.resolveExecutionContext = exports.PROCESSFLOW_PATH_PREFIX = exports.isProcessflowPath = exports.resolveFlowAuth = exports.createClient = void 0;
|
|
10
|
-
exports.
|
|
11
|
-
exports.SECRET_INTERNAL_TYPES = exports.SENTINEL = exports.isCapturable = exports.secretFieldsFromDictionary = exports.mergeSecretRules = exports.loadSecretRules = exports.defaultSecretRules = exports.escapeRegExp = exports.encodeXmlText = exports.encodeXmlEntities = exports.stripJsonValue = exports.stripField = exports.plannedStrips = exports.recordFieldNames = exports.readRecordTable = exports.readField = exports.verifyStripped = exports.stripSecrets = exports.DEFAULT_EXPORT_APP_TIMEOUT_MS = exports.buildPublishFields = exports.buildCreateSetFields = exports.exportApp = exports.DEFAULT_MAX_ROWS = exports.MAX_PAGE_SIZE = exports.DEFAULT_PAGE_SIZE = exports.UPDATE_XML_FIELDS = exports.xmlEscape = exports.formatUnloadDate = exports.parseStatsCount = exports.refreshTypeFields = exports.fetchUpdateXmlRows = exports.countUpdateXml = exports.countUnloadRecords = exports.renderUnload = void 0;
|
|
10
|
+
exports.parseCicdProgress = exports.parseCicdPublishResponse = exports.harvestProgressResults = exports.flattenSteps = exports.classifyProgress = exports.parseProgressTree = exports.parseXmlAnswer = exports.PUBLISH_TARGETS = exports.parsePublishTargets = exports.describeTarget = exports.resolveUpdateSetNaming = exports.buildPublishToUpdateSetFields = exports.buildCreateUpdateSetFields = exports.buildStartFields = exports.publishApp = exports.INVOKE_REST_METHODS = exports.invokeRest = exports.createRecord = exports.setField = exports.resolveTableAttributes = exports.setTable = exports.toStoredValue = exports.resolveAttributes = exports.setColumn = exports.NOT_IN_UPDATE_SET = exports.DEFAULT_INDEX_FORM_PATH = exports.validateCreateIndex = exports.createIndex = exports.listIndexes = exports.indexMatchesColumns = exports.parseIndexColumns = exports.addIndex = exports.deriveElement = exports.addColumn = exports.DEFAULT_SAVE_ACTION = exports.DEFAULT_SUPER_CLASS = exports.TYPE_MAP = exports.defaultAccessFlags = exports.applyTableSaveOverlay = exports.resolveType = exports.normalizeColumns = exports.buildColumnXml = exports.projectTableGraph = exports.createTable = exports.WriteOrderError = exports.executeWritePlan = exports.topoSort = exports.generateSysId = exports.DEFAULT_RUN_FLOW_PATH = exports.testFlow = void 0;
|
|
11
|
+
exports.SECRET_INTERNAL_TYPES = exports.SENTINEL = exports.isCapturable = exports.secretFieldsFromDictionary = exports.mergeSecretRules = exports.loadSecretRules = exports.defaultSecretRules = exports.escapeRegExp = exports.encodeXmlText = exports.encodeXmlEntities = exports.stripJsonValue = exports.stripField = exports.plannedStrips = exports.recordFieldNames = exports.readRecordTable = exports.readField = exports.verifyStripped = exports.stripSecrets = exports.DEFAULT_EXPORT_APP_TIMEOUT_MS = exports.buildPublishFields = exports.buildCreateSetFields = exports.exportApp = exports.DEFAULT_MAX_ROWS = exports.MAX_PAGE_SIZE = exports.DEFAULT_PAGE_SIZE = exports.UPDATE_XML_FIELDS = exports.xmlEscape = exports.formatUnloadDate = exports.parseStatsCount = exports.refreshTypeFields = exports.fetchUpdateXmlRows = exports.countUpdateXml = exports.countUnloadRecords = exports.renderUnload = exports.renderRemoteUpdateSet = exports.renderUpdateXmlRow = exports.exportUpdateSet = exports.PUBLISH_POLL_DELAYS_MS = exports.DEFAULT_PUBLISH_TIMEOUT_MS = void 0;
|
|
12
12
|
var client_1 = require("./client");
|
|
13
13
|
Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return client_1.createClient; } });
|
|
14
14
|
Object.defineProperty(exports, "resolveFlowAuth", { enumerable: true, get: function () { return client_1.resolveFlowAuth; } });
|
|
@@ -94,6 +94,11 @@ Object.defineProperty(exports, "deriveElement", { enumerable: true, get: functio
|
|
|
94
94
|
Object.defineProperty(exports, "addIndex", { enumerable: true, get: function () { return table_1.addIndex; } });
|
|
95
95
|
Object.defineProperty(exports, "parseIndexColumns", { enumerable: true, get: function () { return table_1.parseIndexColumns; } });
|
|
96
96
|
Object.defineProperty(exports, "indexMatchesColumns", { enumerable: true, get: function () { return table_1.indexMatchesColumns; } });
|
|
97
|
+
Object.defineProperty(exports, "listIndexes", { enumerable: true, get: function () { return table_1.listIndexes; } });
|
|
98
|
+
Object.defineProperty(exports, "createIndex", { enumerable: true, get: function () { return table_1.createIndex; } });
|
|
99
|
+
Object.defineProperty(exports, "validateCreateIndex", { enumerable: true, get: function () { return table_1.validateCreateIndex; } });
|
|
100
|
+
Object.defineProperty(exports, "DEFAULT_INDEX_FORM_PATH", { enumerable: true, get: function () { return table_1.DEFAULT_INDEX_FORM_PATH; } });
|
|
101
|
+
Object.defineProperty(exports, "NOT_IN_UPDATE_SET", { enumerable: true, get: function () { return table_1.NOT_IN_UPDATE_SET; } });
|
|
97
102
|
Object.defineProperty(exports, "setColumn", { enumerable: true, get: function () { return table_1.setColumn; } });
|
|
98
103
|
Object.defineProperty(exports, "resolveAttributes", { enumerable: true, get: function () { return table_1.resolveAttributes; } });
|
|
99
104
|
Object.defineProperty(exports, "toStoredValue", { enumerable: true, get: function () { return table_1.toStoredValue; } });
|
package/dist/mcp/registry.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import type { ToolAnnotations } from "@tenonhq/dovetail-mcp-kit";
|
|
12
12
|
import type { ServiceNowClient } from "../client";
|
|
13
|
-
export declare var TOOL_NAMES: readonly ["create_view", "set_list_layout", "set_form_layout", "set_related_lists", "add_choices_to_field", "remove_choices_from_field", "flow_view", "action_view", "action_edit", "action_clone", "action_define", "flow_publish", "flow_copy", "flow_create", "flow_test", "flow_edit", "create_table", "add_column", "add_index", "set_column", "set_table", "set_field", "create_record", "host_assets", "invoke_rest", "app_publish", "update_set_export", "app_export"];
|
|
13
|
+
export declare var TOOL_NAMES: readonly ["create_view", "set_list_layout", "set_form_layout", "set_related_lists", "add_choices_to_field", "remove_choices_from_field", "flow_view", "action_view", "action_edit", "action_clone", "action_define", "flow_publish", "flow_copy", "flow_create", "flow_test", "flow_edit", "create_table", "add_column", "add_index", "index_list", "index_create", "set_column", "set_table", "set_field", "create_record", "host_assets", "invoke_rest", "app_publish", "update_set_export", "app_export"];
|
|
14
14
|
export type ToolName = (typeof TOOL_NAMES)[number];
|
|
15
15
|
export interface RegistryDeps {
|
|
16
16
|
/** Optional client injection for tests; defaults to createClient({}). */
|
package/dist/mcp/registry.js
CHANGED
|
@@ -57,6 +57,8 @@ exports.TOOL_NAMES = [
|
|
|
57
57
|
"create_table",
|
|
58
58
|
"add_column",
|
|
59
59
|
"add_index",
|
|
60
|
+
"index_list",
|
|
61
|
+
"index_create",
|
|
60
62
|
"set_column",
|
|
61
63
|
"set_table",
|
|
62
64
|
"set_field",
|
|
@@ -509,6 +511,72 @@ function buildDescriptors(deps = {}) {
|
|
|
509
511
|
});
|
|
510
512
|
},
|
|
511
513
|
},
|
|
514
|
+
{
|
|
515
|
+
name: "index_list",
|
|
516
|
+
annotations: dovetail_mcp_kit_1.READ_ONLY,
|
|
517
|
+
description: "List the DATABASE INDEXES on a ServiceNow table. Read-only — no form session, no " +
|
|
518
|
+
"writes. Reads the v_db_index VIEW, which is the only index read surface an instance " +
|
|
519
|
+
"exposes: sys_index fails an API-LEVEL ACL (HTTP 403) for every identity including " +
|
|
520
|
+
"admin (an ACL that refuses GET refuses POST), and sys_index_column does not exist at " +
|
|
521
|
+
"all (HTTP 400 'Invalid table') — there is no two-table index model to join. Each row " +
|
|
522
|
+
"returns { name, columns, type, rawColumns }: `columns` is v_db_index's bracketed " +
|
|
523
|
+
"`column_names` cell ('[phone]', '[a,b]') PARSED into a list, never substring-matched, " +
|
|
524
|
+
"and `type` is access_method (btree for essentially everything). UNIQUENESS IS NOT " +
|
|
525
|
+
"READABLE: v_db_index carries no uniqueness field, so a unique index and an ordinary " +
|
|
526
|
+
"one are indistinguishable in it — `unique` is therefore left ABSENT rather than " +
|
|
527
|
+
"guessed, and 'uniqueness-enforced' is always reported in unverified. Only a " +
|
|
528
|
+
"duplicate-insert test proves enforcement. An empty result more likely means the table " +
|
|
529
|
+
"name is wrong than that the table is unindexed (every physical table has a PRIMARY).",
|
|
530
|
+
shape: schemas_1.listIndexesSchema.shape,
|
|
531
|
+
handler: async function (args) {
|
|
532
|
+
var p = schemas_1.listIndexesSchema.parse(args);
|
|
533
|
+
return (0, table_1.listIndexes)({ client: client(), table: p.table });
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
name: "index_create",
|
|
538
|
+
annotations: dovetail_mcp_kit_1.WRITE_ADDITIVE_IDEMPOTENT,
|
|
539
|
+
description: "Create a DATABASE INDEX on an EXISTING ServiceNow table — including the COMPOSITE and " +
|
|
540
|
+
"NON-UNIQUE indexes add_index cannot build. A DATABASE INDEX IS A PHYSICAL, PER-INSTANCE " +
|
|
541
|
+
"CHANGE: IT IS NOT CAPTURED IN AN UPDATE SET AND DOES NOT TRAVEL WITH A PROMOTION, so it " +
|
|
542
|
+
"must be re-run against every environment that needs it — which is why this tool takes " +
|
|
543
|
+
"no updateSetSysId. There is no record path to an index (sys_index is API-level-ACL 403, " +
|
|
544
|
+
"sys_index_column does not exist, and sys_dictionary.unique — the add_index lever — is " +
|
|
545
|
+
"per-column and unique-only), so this replays the platform's own index-creator form " +
|
|
546
|
+
"(sys_action=create_index) over a form-login session. DRY-RUN BY DEFAULT: without " +
|
|
547
|
+
"confirm:true NOTHING is sent and nothing is read; dryRun:true forces a dry-run even " +
|
|
548
|
+
"with confirm. IDEMPOTENT: on the live path v_db_index is read first, and an index over " +
|
|
549
|
+
"exactly these columns returns 'already-exists' with no write. `columns` is ORDERED — " +
|
|
550
|
+
"order is part of an index's identity and is preserved verbatim. `name` is REFUSED: the " +
|
|
551
|
+
"platform's form has no name input (ServiceNow names the index itself), so accepting one " +
|
|
552
|
+
"would mean reporting a name the instance does not carry — the created index's real name " +
|
|
553
|
+
"comes back in `name`. After the POST the index is polled for in v_db_index (default 10 " +
|
|
554
|
+
"checks, 3s apart, since a build on a populated table is asynchronous); if it never " +
|
|
555
|
+
"appears the result is 'failed', because a form processor returning a page is not " +
|
|
556
|
+
"evidence an ALTER ran — and a unique index cannot build over duplicate values, EMPTY " +
|
|
557
|
+
"included. verified:true means a matching row was READ BACK. 'uniqueness-enforced' is " +
|
|
558
|
+
"ALWAYS in unverified: v_db_index has no uniqueness field, so enforcement is provable " +
|
|
559
|
+
"only by a duplicate-insert test. Requires a username+password identity that can form-" +
|
|
560
|
+
"log-in; an instance on API-key-only auth or SSO/MFA will fail at the session with a " +
|
|
561
|
+
"diagnosis, because no .do replay can work there.",
|
|
562
|
+
shape: schemas_1.createIndexSchema.shape,
|
|
563
|
+
handler: async function (args) {
|
|
564
|
+
var p = schemas_1.createIndexSchema.parse(args);
|
|
565
|
+
return (0, table_1.createIndex)({
|
|
566
|
+
client: client(),
|
|
567
|
+
table: p.table,
|
|
568
|
+
columns: p.columns,
|
|
569
|
+
unique: p.unique,
|
|
570
|
+
name: p.name,
|
|
571
|
+
accessMethod: p.accessMethod,
|
|
572
|
+
confirm: p.confirm,
|
|
573
|
+
dryRun: p.dryRun,
|
|
574
|
+
pollAttempts: p.pollAttempts,
|
|
575
|
+
pollIntervalMs: p.pollIntervalMs,
|
|
576
|
+
debug: p.debug,
|
|
577
|
+
});
|
|
578
|
+
},
|
|
579
|
+
},
|
|
512
580
|
{
|
|
513
581
|
name: "set_column",
|
|
514
582
|
annotations: dovetail_mcp_kit_1.WRITE_OVERWRITE,
|
package/dist/mcp/schemas.d.ts
CHANGED
|
@@ -1930,6 +1930,47 @@ export declare var addIndexSchema: z.ZodObject<{
|
|
|
1930
1930
|
scope?: string | undefined;
|
|
1931
1931
|
updateSetSysId?: string | undefined;
|
|
1932
1932
|
}>;
|
|
1933
|
+
export declare var listIndexesSchema: z.ZodObject<{
|
|
1934
|
+
table: z.ZodString;
|
|
1935
|
+
}, "strip", z.ZodTypeAny, {
|
|
1936
|
+
table: string;
|
|
1937
|
+
}, {
|
|
1938
|
+
table: string;
|
|
1939
|
+
}>;
|
|
1940
|
+
export declare var createIndexSchema: z.ZodObject<{
|
|
1941
|
+
table: z.ZodString;
|
|
1942
|
+
columns: z.ZodArray<z.ZodString, "many">;
|
|
1943
|
+
unique: z.ZodOptional<z.ZodBoolean>;
|
|
1944
|
+
name: z.ZodOptional<z.ZodString>;
|
|
1945
|
+
accessMethod: z.ZodOptional<z.ZodString>;
|
|
1946
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
1947
|
+
dryRun: z.ZodOptional<z.ZodBoolean>;
|
|
1948
|
+
pollAttempts: z.ZodOptional<z.ZodNumber>;
|
|
1949
|
+
pollIntervalMs: z.ZodOptional<z.ZodNumber>;
|
|
1950
|
+
debug: z.ZodOptional<z.ZodBoolean>;
|
|
1951
|
+
}, "strip", z.ZodTypeAny, {
|
|
1952
|
+
columns: string[];
|
|
1953
|
+
table: string;
|
|
1954
|
+
name?: string | undefined;
|
|
1955
|
+
confirm?: boolean | undefined;
|
|
1956
|
+
dryRun?: boolean | undefined;
|
|
1957
|
+
debug?: boolean | undefined;
|
|
1958
|
+
unique?: boolean | undefined;
|
|
1959
|
+
accessMethod?: string | undefined;
|
|
1960
|
+
pollAttempts?: number | undefined;
|
|
1961
|
+
pollIntervalMs?: number | undefined;
|
|
1962
|
+
}, {
|
|
1963
|
+
columns: string[];
|
|
1964
|
+
table: string;
|
|
1965
|
+
name?: string | undefined;
|
|
1966
|
+
confirm?: boolean | undefined;
|
|
1967
|
+
dryRun?: boolean | undefined;
|
|
1968
|
+
debug?: boolean | undefined;
|
|
1969
|
+
unique?: boolean | undefined;
|
|
1970
|
+
accessMethod?: string | undefined;
|
|
1971
|
+
pollAttempts?: number | undefined;
|
|
1972
|
+
pollIntervalMs?: number | undefined;
|
|
1973
|
+
}>;
|
|
1933
1974
|
export declare var columnAttributesSchema: z.ZodObject<{
|
|
1934
1975
|
label: z.ZodOptional<z.ZodString>;
|
|
1935
1976
|
mandatory: z.ZodOptional<z.ZodBoolean>;
|
package/dist/mcp/schemas.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* own file so registry.ts stays focused on wiring.
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.invokeRestSchema = exports.publishAppSchema = exports.exportAppSchema = exports.exportUpdateSetSchema = exports.createRecordSchema = exports.setFieldSchema = exports.setTableSchema = exports.tableAttributesSchema = exports.setColumnSchema = exports.columnAttributesSchema = exports.addIndexSchema = exports.addColumnSchema = exports.createTableSchema = exports.columnSpecSchema = exports.hostAssetsSchema = exports.editFlowSchema = exports.defineActionSchema = exports.defineActionSpecSchema = exports.cloneActionSchema = exports.editActionSchema = exports.stepInputPatchSchema = exports.testFlowSchema = exports.createFlowSchema = exports.copyFlowSchema = exports.publishFlowSchema = exports.viewActionSchema = exports.viewFlowSchema = exports.removeChoicesFromFieldSchema = exports.addChoicesToFieldSchema = exports.choiceValueSchema = exports.setRelatedListsSchema = exports.setFormLayoutSchema = exports.formSectionSchema = exports.setListLayoutSchema = exports.createViewSchema = void 0;
|
|
7
|
+
exports.invokeRestSchema = exports.publishAppSchema = exports.exportAppSchema = exports.exportUpdateSetSchema = exports.createRecordSchema = exports.setFieldSchema = exports.setTableSchema = exports.tableAttributesSchema = exports.setColumnSchema = exports.columnAttributesSchema = exports.createIndexSchema = exports.listIndexesSchema = exports.addIndexSchema = exports.addColumnSchema = exports.createTableSchema = exports.columnSpecSchema = exports.hostAssetsSchema = exports.editFlowSchema = exports.defineActionSchema = exports.defineActionSpecSchema = exports.cloneActionSchema = exports.editActionSchema = exports.stepInputPatchSchema = exports.testFlowSchema = exports.createFlowSchema = exports.copyFlowSchema = exports.publishFlowSchema = exports.viewActionSchema = exports.viewFlowSchema = exports.removeChoicesFromFieldSchema = exports.addChoicesToFieldSchema = exports.choiceValueSchema = exports.setRelatedListsSchema = exports.setFormLayoutSchema = exports.formSectionSchema = exports.setListLayoutSchema = exports.createViewSchema = void 0;
|
|
8
8
|
const zod_1 = require("zod");
|
|
9
9
|
exports.createViewSchema = zod_1.z.object({
|
|
10
10
|
name: zod_1.z.string().min(1),
|
|
@@ -378,6 +378,31 @@ exports.addIndexSchema = zod_1.z.object({
|
|
|
378
378
|
dryRun: zod_1.z.boolean().optional(),
|
|
379
379
|
debug: zod_1.z.boolean().optional(),
|
|
380
380
|
});
|
|
381
|
+
// index_list is read-only: a table name and nothing else. `v_db_index` keys on the table
|
|
382
|
+
// NAME, not a sys_id, so no sys_id form is offered — accepting one would mean resolving it
|
|
383
|
+
// and then listing a table the caller never named.
|
|
384
|
+
exports.listIndexesSchema = zod_1.z.object({
|
|
385
|
+
table: zod_1.z.string().min(1),
|
|
386
|
+
});
|
|
387
|
+
// index_create takes an ordered column LIST — order is part of an index's identity, so it
|
|
388
|
+
// is preserved verbatim and never sorted. `name` is accepted ONLY so that asking for one
|
|
389
|
+
// earns the explanation that the platform's index-creator form has no name input (the
|
|
390
|
+
// same contract set-column uses for internalType/element); createIndex refuses it.
|
|
391
|
+
// confirm is the write gate: without confirm:true the tool is a pure dry-run that sends
|
|
392
|
+
// nothing and reads nothing. There is deliberately NO updateSetSysId — a database index is
|
|
393
|
+
// physical and is not captured in an update set.
|
|
394
|
+
exports.createIndexSchema = zod_1.z.object({
|
|
395
|
+
table: zod_1.z.string().min(1),
|
|
396
|
+
columns: zod_1.z.array(zod_1.z.string().min(1)).min(1),
|
|
397
|
+
unique: zod_1.z.boolean().optional(),
|
|
398
|
+
name: zod_1.z.string().optional(),
|
|
399
|
+
accessMethod: zod_1.z.string().min(1).optional(),
|
|
400
|
+
confirm: zod_1.z.boolean().optional(),
|
|
401
|
+
dryRun: zod_1.z.boolean().optional(),
|
|
402
|
+
pollAttempts: zod_1.z.number().int().positive().optional(),
|
|
403
|
+
pollIntervalMs: zod_1.z.number().int().positive().optional(),
|
|
404
|
+
debug: zod_1.z.boolean().optional(),
|
|
405
|
+
});
|
|
381
406
|
// set-column takes a CLOSED attribute set, not an open field map: an unbounded write to
|
|
382
407
|
// sys_dictionary lets a caller silently corrupt the schema. internalType and element are
|
|
383
408
|
// listed but are NOT settable — ServiceNow honours neither on an existing column, and
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createIndex — create a database index on an EXISTING ServiceNow table, including
|
|
3
|
+
* the COMPOSITE and NON-UNIQUE indexes `add-index` cannot build, by replaying the
|
|
4
|
+
* platform's own index-creator form.
|
|
5
|
+
*
|
|
6
|
+
* ──────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
* A DATABASE INDEX IS A PHYSICAL, PER-INSTANCE CHANGE. IT IS **NOT** CAPTURED IN AN
|
|
8
|
+
* UPDATE SET, AND IT DOES **NOT** TRAVEL WITH A PROMOTION. Every environment that
|
|
9
|
+
* needs the index must have this run against it. That is why this verb takes no
|
|
10
|
+
* `updateSetSysId` — there is nothing for one to capture.
|
|
11
|
+
* ──────────────────────────────────────────────────────────────────────────────
|
|
12
|
+
*
|
|
13
|
+
* WHY A FORM REPLAY. There is no record path to an index:
|
|
14
|
+
* - `sys_index` is API-level-ACL 403 for every identity (an ACL that refuses GET
|
|
15
|
+
* refuses POST), and `sys_index_column` does not exist (HTTP 400 "Invalid
|
|
16
|
+
* table"). So the "insert two records" design is not merely blocked, it is
|
|
17
|
+
* not a real model of the platform.
|
|
18
|
+
* - `sys_dictionary.unique` is the one record-shaped lever, and it is PER-COLUMN
|
|
19
|
+
* and UNIQUE-ONLY. That lever is already shipped as `add-index`; this verb
|
|
20
|
+
* exists for everything that lever cannot express.
|
|
21
|
+
*
|
|
22
|
+
* THE FORM CONTRACT IS THE INSTANCE'S OWN. It was not guessed and it was not read
|
|
23
|
+
* off a stale HAR: it is lifted from the shipped `index_creator_information`
|
|
24
|
+
* sys_ui_macro and the `v_index_creator` "New" UI action, both read live from a
|
|
25
|
+
* Tenon instance 2026-09-30. The macro's own inputs are:
|
|
26
|
+
*
|
|
27
|
+
* sys_action = "create_index" (hidden)
|
|
28
|
+
* sysparm_index_table = <table>
|
|
29
|
+
* sysparm_fields = <comma-separated column list>
|
|
30
|
+
* sysparm_unique_index_SKIP = "on" (the Unique Index checkbox)
|
|
31
|
+
* sysparm_access_method = <method> (only when
|
|
32
|
+
* glide.ui.index_method.enabled is true)
|
|
33
|
+
*
|
|
34
|
+
* and the dialog is the `index_creator_dialog` UI page. The POST target is taken
|
|
35
|
+
* from that page's own `<form action>` when it renders one, falling back to the
|
|
36
|
+
* page itself — harvest-then-use, the same discipline createTable applies to the
|
|
37
|
+
* list-edit key rather than hard-coding a guess.
|
|
38
|
+
*
|
|
39
|
+
* THERE IS NO INDEX-NAME INPUT. The form has none; ServiceNow names the index
|
|
40
|
+
* itself (in practice after the leading column). A `name` is therefore REFUSED
|
|
41
|
+
* rather than silently dropped — a caller who asked for `idx_phone_created` and got
|
|
42
|
+
* `phone` would have no way to know, and the whole point of this module is that the
|
|
43
|
+
* instance is read back rather than believed.
|
|
44
|
+
*
|
|
45
|
+
* THE WRITE IS NEVER THE PROOF. After the POST the index is looked for in
|
|
46
|
+
* `v_db_index` on a bounded poll (index builds are asynchronous on a populated
|
|
47
|
+
* table). If it never appears, this FAILS loudly — a 200 from a form processor is
|
|
48
|
+
* not evidence an ALTER ran. `verified:true` means a matching row was READ BACK.
|
|
49
|
+
*
|
|
50
|
+
* WHAT IS STILL NOT PROVABLE: that a unique index actually REJECTS duplicates.
|
|
51
|
+
* `v_db_index` has no uniqueness field, so "uniqueness-enforced" is reported in
|
|
52
|
+
* `unverified` on every status, success included.
|
|
53
|
+
*
|
|
54
|
+
* ES6 only, no optional chaining, no `any`.
|
|
55
|
+
*/
|
|
56
|
+
import type { ServiceNowClient } from "../client";
|
|
57
|
+
/** The UI page that renders the index creator. Overridable per instance. */
|
|
58
|
+
export declare var DEFAULT_INDEX_FORM_PATH: string;
|
|
59
|
+
/** The standing caveat every caller must carry forward. */
|
|
60
|
+
export declare var NOT_IN_UPDATE_SET: string;
|
|
61
|
+
export interface CreateIndexParams {
|
|
62
|
+
/** REST client — used for the idempotency read and the read-back poll. */
|
|
63
|
+
client: ServiceNowClient;
|
|
64
|
+
/** The table to index, by NAME. */
|
|
65
|
+
table: string;
|
|
66
|
+
/** Ordered column list. Order matters to an index; it is preserved verbatim. */
|
|
67
|
+
columns: Array<string>;
|
|
68
|
+
/**
|
|
69
|
+
* REFUSED. The platform's index-creator form has no name input — ServiceNow names
|
|
70
|
+
* the index itself. Declared only so a caller can express it and be told why,
|
|
71
|
+
* rather than have it silently ignored.
|
|
72
|
+
*/
|
|
73
|
+
name?: string;
|
|
74
|
+
/** Tick the form's "Unique Index" checkbox. Default false (a plain index). */
|
|
75
|
+
unique?: boolean;
|
|
76
|
+
/** `sysparm_access_method`, when the instance exposes the selector. */
|
|
77
|
+
accessMethod?: string;
|
|
78
|
+
/** REQUIRED to write. Without it this is a dry-run, whatever else is passed. */
|
|
79
|
+
confirm?: boolean;
|
|
80
|
+
/** Force a dry-run even with confirm:true. */
|
|
81
|
+
dryRun?: boolean;
|
|
82
|
+
/** Override the index-creator page path (default DEFAULT_INDEX_FORM_PATH). */
|
|
83
|
+
formPath?: string;
|
|
84
|
+
/** Read-back poll shape. Defaults: 10 attempts, 3000 ms apart. */
|
|
85
|
+
pollAttempts?: number;
|
|
86
|
+
pollIntervalMs?: number;
|
|
87
|
+
/** Instance/creds for the form session (default: env, same precedence as the client). */
|
|
88
|
+
instance?: string;
|
|
89
|
+
user?: string;
|
|
90
|
+
password?: string;
|
|
91
|
+
/** Add diagnostic detail to the note. */
|
|
92
|
+
debug?: boolean;
|
|
93
|
+
}
|
|
94
|
+
export interface CreateIndexResult {
|
|
95
|
+
status: "created" | "already-exists" | "dry-run" | "failed";
|
|
96
|
+
/** True ONLY on a fresh, read-back-verified create. */
|
|
97
|
+
created: boolean;
|
|
98
|
+
table: string;
|
|
99
|
+
/** The index name as `v_db_index` reports it — never a name we asked for. */
|
|
100
|
+
name: string;
|
|
101
|
+
/** The requested column list, normalized. */
|
|
102
|
+
columns: Array<string>;
|
|
103
|
+
/** True ONLY when a matching `v_db_index` row was READ BACK. */
|
|
104
|
+
verified: boolean;
|
|
105
|
+
/** The host the write was aimed at — an index is per-instance, so this matters. */
|
|
106
|
+
instance: string;
|
|
107
|
+
/** Always includes "uniqueness-enforced". Never empty. */
|
|
108
|
+
unverified: Array<string>;
|
|
109
|
+
/** HTTP status of the form POST; 0 when none was sent. */
|
|
110
|
+
httpStatus: number;
|
|
111
|
+
note: string;
|
|
112
|
+
}
|
|
113
|
+
/** Validate every input BEFORE any network call. Returns the normalized columns. */
|
|
114
|
+
export declare function validateCreateIndex(params: CreateIndexParams): {
|
|
115
|
+
table: string;
|
|
116
|
+
columns: Array<string>;
|
|
117
|
+
};
|
|
118
|
+
export declare function createIndex(params: CreateIndexParams): Promise<CreateIndexResult>;
|