@tenonhq/dovetail-servicenow 0.0.17 → 0.0.19

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 CHANGED
@@ -221,6 +221,31 @@ live). The
221
221
  POST silently no-ops (stays draft). Sequence reverse-engineered from Workflow
222
222
  Studio HARs and validated live (2026-06-02, tenonworkstudio). Exit `0` published
223
223
  or dry-run; `2` created-but-not-published.
224
+ ### Create a table (with columns)
225
+
226
+ ```bash
227
+ npx dove-sn create-table \
228
+ --name x_cadso_core_error --label Error --scope x_cadso_core \
229
+ --columns "Key:string:255, Severity:choice:50, First Seen On:datetime, Occurence Count:integer:5" \
230
+ --number-prefix ERR --user-role x_cadso_core.user --update-set <sys_id> --dry-run --json
231
+ npx dove-sn create-table --from-json ./table.json # full spec from a file
232
+ ```
233
+
234
+ `create-table` mints a **brand-new** table (`sys_db_object`) **with its columns**.
235
+ A table create is a privileged platform op — a REST / Dovetail `createRecord`
236
+ insert into `sys_db_object` **orphans** the table (a metadata row with no physical
237
+ table, no ACLs, no scope wiring). So this replays the Studio form save: a single
238
+ `POST /sys_db_object.do` (form-login session → `g_ck`) whose body embeds every
239
+ column as a `sys_dictionary` list-edit XML blob (one `<record operation="add">`
240
+ per column) plus the Application-Navigator module — yielding the real 36-record
241
+ graph + the physical table + seeded ACLs. Friendly column types are mapped to
242
+ ServiceNow internal types (`string` → `string_full_utf8`, Studio's real String
243
+ type). `--dry-run` returns the plan + the column XML + the projected graph with no
244
+ session and no writes; the pure transforms are unit-tested. **The live write path
245
+ is pending a validated-live spike** — prefer `--dry-run`, and always verify the
246
+ `sys_update_xml` rows landed in the intended update set. Ground truth (the HAR
247
+ dissection) lives in the CTO repo's create-table docs.
248
+
224
249
  `test-flow` defaults to **validate** — a safe pre-flight (published? inputs match
225
250
  declared variables?) that never runs the flow; `--execute --confirm` runs it via
226
251
  the server-side FlowAPI runner (deploy `resources/runFlow.md` first).
package/dist/cli.js CHANGED
@@ -57,6 +57,7 @@ var __importStar = (this && this.__importStar) || (function () {
57
57
  })();
58
58
  Object.defineProperty(exports, "__esModule", { value: true });
59
59
  const fs = __importStar(require("fs"));
60
+ const path = __importStar(require("path"));
60
61
  const loadEnv_1 = require("./loadEnv");
61
62
  const client_1 = require("./client");
62
63
  const choices_1 = require("./choices");
@@ -77,6 +78,7 @@ const createFlow_1 = require("./flowDesigner/createFlow");
77
78
  const editFlow_1 = require("./flowDesigner/editFlow");
78
79
  const editActionType_1 = require("./flowDesigner/editActionType");
79
80
  const testFlow_1 = require("./flowDesigner/testFlow");
81
+ const table_1 = require("./table");
80
82
  const flowDesigner_formatter_2 = require("./flowDesigner-formatter");
81
83
  function parseArgs(argv) {
82
84
  var command = argv[0] || "";
@@ -699,6 +701,11 @@ function printHelp() {
699
701
  " (--name <n> --template <sys_id> --scope <sys_id>\n" +
700
702
  " [--trigger-table <t>] [--trigger-condition <q>] [--log-message <m>]\n" +
701
703
  " [--internal-name <n>] [--description <d>] [--dry-run] [--json])\n" +
704
+ " create-table Create a NEW table (sys_db_object) WITH columns, via the Studio form save\n" +
705
+ " (--name <x_scope_t> --label <l> --scope <s>\n" +
706
+ " --columns \"Label:type:max, ...\" OR --from-json <spec.json>\n" +
707
+ " [--extends <t>] [--number-prefix <p>] [--user-role <r>]\n" +
708
+ " [--no-acls] [--no-menu] [--update-set <sys_id>] [--dry-run] [--json])\n" +
702
709
  " test-flow Validate (default) or run a flow/subflow\n" +
703
710
  " (--sys-id <sys_id> [--execute --confirm] [--inputs <json>] [--json])\n" +
704
711
  " edit-flow Patch a flow/subflow (rename, description, step inputs)\n" +
@@ -708,6 +715,92 @@ function printHelp() {
708
715
  " --env <path> Load credentials from a specific .env file (also --env-file,\n" +
709
716
  " or the DOVETAIL_ENV_FILE env var). Default: .env in the cwd.\n");
710
717
  }
718
+ /** Parse inline `--columns "Label:type:max, Other:choice, ..."` into ColumnSpec[]. */
719
+ function parseColumnsInline(input) {
720
+ var out = [];
721
+ if (!input)
722
+ return out;
723
+ var parts = input.split(",");
724
+ for (var i = 0; i < parts.length; i += 1) {
725
+ var piece = parts[i].trim();
726
+ if (!piece)
727
+ continue;
728
+ var seg = piece.split(":");
729
+ var label = (seg[0] || "").trim();
730
+ var type = (seg[1] || "string").trim();
731
+ var max = (seg[2] || "").trim();
732
+ if (!label)
733
+ continue;
734
+ var col = { label: label, type: type };
735
+ if (max)
736
+ col.max_length = max;
737
+ out.push(col);
738
+ }
739
+ return out;
740
+ }
741
+ /**
742
+ * dove-sn create-table:
743
+ * --name x_cadso_core_error --label Error --scope x_cadso_core
744
+ * --columns "Key:string:255, Severity:choice:50, Occurence Count:integer:5"
745
+ * [--extends sys_metadata] [--number-prefix ERR] [--user-role x_cadso_core.user]
746
+ * [--no-acls] [--no-menu] [--update-set <sys_id>] [--save-action <sys_id>]
747
+ * [--from-json <spec.json>] [--dry-run] [--json]
748
+ */
749
+ async function runCreateTable(flags) {
750
+ var spec = {};
751
+ if (flags["from-json"]) {
752
+ spec = JSON.parse(fs.readFileSync(path.resolve(flags["from-json"]), "utf8"));
753
+ }
754
+ var name = flags.name || spec.name;
755
+ var label = flags.label || spec.label;
756
+ var scope = flags.scope || spec.scope;
757
+ var columns = flags.columns ? parseColumnsInline(flags.columns) : (spec.columns || []);
758
+ if (!name || !label || !scope || columns.length === 0) {
759
+ process.stderr.write("create-table: --name, --label, --scope and --columns (or --from-json) are required\n");
760
+ return 1;
761
+ }
762
+ var params = {
763
+ client: (0, client_1.createClient)({}),
764
+ name: name,
765
+ label: label,
766
+ scope: scope,
767
+ columns: columns
768
+ };
769
+ var ext = flags.extends || spec.extendsTable;
770
+ if (ext)
771
+ params.extendsTable = ext;
772
+ var prefix = flags["number-prefix"] || spec.numberPrefix;
773
+ if (prefix)
774
+ params.numberPrefix = prefix;
775
+ var role = flags["user-role"] || spec.userRole;
776
+ if (role)
777
+ params.userRole = role;
778
+ if (flags["no-acls"] === "true" || spec.createAccessControls === false)
779
+ params.createAccessControls = false;
780
+ if (flags["no-menu"] === "true" || spec.showInMenu === false)
781
+ params.showInMenu = false;
782
+ var us = flags["update-set"] || spec.updateSetSysId;
783
+ if (us)
784
+ params.updateSetSysId = us;
785
+ var sa = flags["save-action"] || spec.saveActionSysId;
786
+ if (sa)
787
+ params.saveActionSysId = sa;
788
+ if (flags["dry-run"] === "true" || spec.dryRun === true)
789
+ params.dryRun = true;
790
+ var result = await (0, table_1.createTable)(params);
791
+ if (flags.json === "true") {
792
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
793
+ }
794
+ else {
795
+ process.stdout.write("[" + result.status + "] " + result.name + " (" + result.label + ") scope=" + result.scopeSysId
796
+ + " — " + result.columns + " columns, projected graph " + result.graph.total + " records"
797
+ + (result.tableSysId ? " — sys_id " + result.tableSysId : "")
798
+ + "\n" + result.note + "\n");
799
+ }
800
+ if (result.status === "failed")
801
+ return 2;
802
+ return 0;
803
+ }
711
804
  async function main() {
712
805
  var parsed = parseArgs(process.argv.slice(2));
713
806
  // Load credentials before any command runs. `--env`/`--env-file` (or the
@@ -736,6 +829,9 @@ async function main() {
736
829
  if (parsed.command === "create-flow") {
737
830
  return await runCreateFlow(parsed.flags);
738
831
  }
832
+ if (parsed.command === "create-table") {
833
+ return await runCreateTable(parsed.flags);
834
+ }
739
835
  if (parsed.command === "test-flow") {
740
836
  return await runTestFlow(parsed.flags);
741
837
  }
package/dist/client.js CHANGED
@@ -278,6 +278,17 @@ function createClient(config = {}) {
278
278
  },
279
279
  claude: {
280
280
  createRecord: async function (params) {
281
+ // Guard: a bare record insert into sys_db_object creates only an
282
+ // orphaned metadata row — no physical table and no ACLs. Table creation
283
+ // is a privileged platform operation that must run through the proper
284
+ // lifecycle, never a generic createRecord call.
285
+ if (params && params.table === "sys_db_object") {
286
+ throw new Error("Refusing to insert sys_db_object via createRecord: a bare insert " +
287
+ "orphans the table (no physical table or ACLs are created). " +
288
+ "Create tables via the dove-sn create-table capability (in build), " +
289
+ "or in Studio then run `dove refresh -s <scope>` to materialise " +
290
+ "the new records locally.");
291
+ }
281
292
  var data = await dovetailRequest("POST", "createRecord", params, null, "claude.createRecord(" + params.table + ")");
282
293
  return data.result || data;
283
294
  },
package/dist/index.d.ts CHANGED
@@ -17,3 +17,5 @@ export { sincPlugin } from "./plugin";
17
17
  export { listTemplates, verifyArtifact, cloneSubflow, cloneActionType, triggerPublication, publishActionType, editActionType, readFlow, readActionType, publishFlow, copyFlow, createFlow, buildPublishModel, editFlow, testFlow, DEFAULT_RUN_FLOW_PATH, generateSysId, topoSort, executeWritePlan, WriteOrderError } from "./flowDesigner";
18
18
  export type { TemplateRef, ListTemplatesParams, FlowKind, VerifyExpect, VerifyFound, VerifyFailure, VerifyReport, VerifyArtifactParams, CloneSubflowParams, CloneSubflowResult, CloneActionTypeParams, CloneActionTypeResult, TriggerPublicationParams, TriggerPublicationResult, PublishActionTypeParams, PublishActionTypeResult, EditActionTypeParams, EditActionTypeResult, EditActionTypeOps, ReadFlowParams, ReadFlowResult, FlowStep, FlowVariable, ReadActionTypeParams, ReadActionTypeResult, ActionIo, PublishFlowParams, PublishFlowResult, CopyFlowParams, CopyFlowResult, CreateFlowParams, CreateFlowResult, EditFlowParams, EditFlowResult, EditFlowOps, StepInputPatch, TestFlowParams, TestFlowResult, WriteOp, WriteOpResult } from "./flowDesigner";
19
19
  export type { ServiceNowClientConfig, ChoiceValue, ChoiceType, AddChoicesParams, AddChoicesResult, ChoiceActionResult, DictionaryRecord, UpdateSetRecord, LayoutAction, LayoutRecordResult, LayoutResult, CreateViewParams, CreateViewResult, FormSectionSpec, SetFormLayoutParams, SetListLayoutParams, SetRelatedListsParams } from "./types";
20
+ export { createTable, projectTableGraph, buildColumnXml, normalizeColumns, resolveType, applyTableSaveOverlay, defaultAccessFlags, TYPE_MAP, DEFAULT_SUPER_CLASS, DEFAULT_SAVE_ACTION } from "./table";
21
+ export type { CreateTableParams, CreateTableResult, TableGraph, NormalizedColumn, ColumnSpec, AccessFlags, OverlaySpec } from "./table";
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * REST API so every change lands in the target update set and scope.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.WriteOrderError = exports.executeWritePlan = exports.topoSort = exports.generateSysId = exports.DEFAULT_RUN_FLOW_PATH = exports.testFlow = exports.editFlow = exports.buildPublishModel = exports.createFlow = exports.copyFlow = exports.publishFlow = exports.readActionType = exports.readFlow = exports.editActionType = exports.publishActionType = exports.triggerPublication = exports.cloneActionType = exports.cloneSubflow = exports.verifyArtifact = exports.listTemplates = exports.sincPlugin = exports.formatCreateViewResult = exports.formatLayoutResult = exports.setRelatedLists = exports.setFormLayout = exports.setListLayout = exports.createView = exports.formatAddChoicesResult = exports.addChoicesToField = exports.createClient = void 0;
9
+ 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 = exports.editFlow = exports.buildPublishModel = exports.createFlow = exports.copyFlow = exports.publishFlow = exports.readActionType = exports.readFlow = exports.editActionType = exports.publishActionType = exports.triggerPublication = exports.cloneActionType = exports.cloneSubflow = exports.verifyArtifact = exports.listTemplates = exports.sincPlugin = exports.formatCreateViewResult = exports.formatLayoutResult = exports.setRelatedLists = exports.setFormLayout = exports.setListLayout = exports.createView = exports.formatAddChoicesResult = exports.addChoicesToField = exports.createClient = void 0;
10
10
  var client_1 = require("./client");
11
11
  Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return client_1.createClient; } });
12
12
  var choices_1 = require("./choices");
@@ -47,3 +47,14 @@ Object.defineProperty(exports, "generateSysId", { enumerable: true, get: functio
47
47
  Object.defineProperty(exports, "topoSort", { enumerable: true, get: function () { return flowDesigner_1.topoSort; } });
48
48
  Object.defineProperty(exports, "executeWritePlan", { enumerable: true, get: function () { return flowDesigner_1.executeWritePlan; } });
49
49
  Object.defineProperty(exports, "WriteOrderError", { enumerable: true, get: function () { return flowDesigner_1.WriteOrderError; } });
50
+ var table_1 = require("./table");
51
+ Object.defineProperty(exports, "createTable", { enumerable: true, get: function () { return table_1.createTable; } });
52
+ Object.defineProperty(exports, "projectTableGraph", { enumerable: true, get: function () { return table_1.projectTableGraph; } });
53
+ Object.defineProperty(exports, "buildColumnXml", { enumerable: true, get: function () { return table_1.buildColumnXml; } });
54
+ Object.defineProperty(exports, "normalizeColumns", { enumerable: true, get: function () { return table_1.normalizeColumns; } });
55
+ Object.defineProperty(exports, "resolveType", { enumerable: true, get: function () { return table_1.resolveType; } });
56
+ Object.defineProperty(exports, "applyTableSaveOverlay", { enumerable: true, get: function () { return table_1.applyTableSaveOverlay; } });
57
+ Object.defineProperty(exports, "defaultAccessFlags", { enumerable: true, get: function () { return table_1.defaultAccessFlags; } });
58
+ Object.defineProperty(exports, "TYPE_MAP", { enumerable: true, get: function () { return table_1.TYPE_MAP; } });
59
+ Object.defineProperty(exports, "DEFAULT_SUPER_CLASS", { enumerable: true, get: function () { return table_1.DEFAULT_SUPER_CLASS; } });
60
+ Object.defineProperty(exports, "DEFAULT_SAVE_ACTION", { enumerable: true, get: function () { return table_1.DEFAULT_SAVE_ACTION; } });
@@ -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", "flow_view", "action_view", "flow_publish", "flow_copy", "flow_create", "flow_test", "flow_edit"];
13
+ export declare var TOOL_NAMES: readonly ["create_view", "set_list_layout", "set_form_layout", "set_related_lists", "add_choices_to_field", "flow_view", "action_view", "flow_publish", "flow_copy", "flow_create", "flow_test", "flow_edit", "create_table"];
14
14
  export type ToolName = typeof TOOL_NAMES[number];
15
15
  export interface RegistryDeps {
16
16
  /** Optional client injection for tests; defaults to createClient({}). */
@@ -25,6 +25,7 @@ const copyFlow_1 = require("../flowDesigner/copyFlow");
25
25
  const createFlow_1 = require("../flowDesigner/createFlow");
26
26
  const editFlow_1 = require("../flowDesigner/editFlow");
27
27
  const testFlow_1 = require("../flowDesigner/testFlow");
28
+ const table_1 = require("../table");
28
29
  const schemas_1 = require("./schemas");
29
30
  exports.TOOL_NAMES = [
30
31
  "create_view",
@@ -38,7 +39,8 @@ exports.TOOL_NAMES = [
38
39
  "flow_copy",
39
40
  "flow_create",
40
41
  "flow_test",
41
- "flow_edit"
42
+ "flow_edit",
43
+ "create_table"
42
44
  ];
43
45
  // Annotation presets (READ_ONLY / WRITE_ADDITIVE_IDEMPOTENT / WRITE_CREATE /
44
46
  // WRITE_OVERWRITE / WRITE_EXECUTE) come from @tenonhq/dovetail-mcp-kit.
@@ -227,6 +229,40 @@ function buildDescriptors(deps = {}) {
227
229
  updateSetSysId: p.updateSetSysId
228
230
  });
229
231
  }
232
+ },
233
+ {
234
+ name: "create_table",
235
+ annotations: dovetail_mcp_kit_1.WRITE_CREATE,
236
+ description: "Create a NEW ServiceNow table (sys_db_object) WITH its columns, headless and faithfully. "
237
+ + "A table create is a privileged platform op — a REST/createRecord insert ORPHANS the table "
238
+ + "(metadata row, no physical table, no ACLs). This replays the Studio form save "
239
+ + "(POST /sys_db_object.do) so the real 36-record graph + the physical table + seeded ACLs "
240
+ + "are created. name (x_scope_*), label, scope, and columns[] are required; extendsTable "
241
+ + "defaults to sys_metadata; friendly column types are mapped to internal types "
242
+ + "(string -> string_full_utf8). dryRun:true returns the plan + the column XML + the projected "
243
+ + "graph with no session and no writes. NOTE: the live write path is pending a validated-live "
244
+ + "spike — prefer dryRun until confirmed, and always verify the sys_update_xml landed in the "
245
+ + "intended update set.",
246
+ shape: schemas_1.createTableSchema.shape,
247
+ handler: async function (args) {
248
+ var p = schemas_1.createTableSchema.parse(args);
249
+ return (0, table_1.createTable)({
250
+ client: client(),
251
+ name: p.name,
252
+ label: p.label,
253
+ scope: p.scope,
254
+ columns: p.columns,
255
+ extendsTable: p.extendsTable,
256
+ numberPrefix: p.numberPrefix,
257
+ userRole: p.userRole,
258
+ createAccessControls: p.createAccessControls,
259
+ access: p.access,
260
+ showInMenu: p.showInMenu,
261
+ updateSetSysId: p.updateSetSysId,
262
+ saveActionSysId: p.saveActionSysId,
263
+ dryRun: p.dryRun
264
+ });
265
+ }
230
266
  }
231
267
  ];
232
268
  }
@@ -382,3 +382,95 @@ export declare var editFlowSchema: z.ZodObject<{
382
382
  updateSetSysId?: string | undefined;
383
383
  apply?: boolean | undefined;
384
384
  }>;
385
+ export declare var columnSpecSchema: z.ZodObject<{
386
+ label: z.ZodString;
387
+ type: z.ZodString;
388
+ name: z.ZodOptional<z.ZodString>;
389
+ max_length: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
390
+ reference: z.ZodOptional<z.ZodString>;
391
+ }, "strip", z.ZodTypeAny, {
392
+ type: string;
393
+ label: string;
394
+ name?: string | undefined;
395
+ max_length?: string | number | undefined;
396
+ reference?: string | undefined;
397
+ }, {
398
+ type: string;
399
+ label: string;
400
+ name?: string | undefined;
401
+ max_length?: string | number | undefined;
402
+ reference?: string | undefined;
403
+ }>;
404
+ export declare var createTableSchema: z.ZodObject<{
405
+ name: z.ZodString;
406
+ label: z.ZodString;
407
+ scope: z.ZodString;
408
+ columns: z.ZodArray<z.ZodObject<{
409
+ label: z.ZodString;
410
+ type: z.ZodString;
411
+ name: z.ZodOptional<z.ZodString>;
412
+ max_length: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
413
+ reference: z.ZodOptional<z.ZodString>;
414
+ }, "strip", z.ZodTypeAny, {
415
+ type: string;
416
+ label: string;
417
+ name?: string | undefined;
418
+ max_length?: string | number | undefined;
419
+ reference?: string | undefined;
420
+ }, {
421
+ type: string;
422
+ label: string;
423
+ name?: string | undefined;
424
+ max_length?: string | number | undefined;
425
+ reference?: string | undefined;
426
+ }>, "many">;
427
+ extendsTable: z.ZodOptional<z.ZodString>;
428
+ numberPrefix: z.ZodOptional<z.ZodString>;
429
+ userRole: z.ZodOptional<z.ZodString>;
430
+ createAccessControls: z.ZodOptional<z.ZodBoolean>;
431
+ access: z.ZodOptional<z.ZodString>;
432
+ showInMenu: z.ZodOptional<z.ZodBoolean>;
433
+ updateSetSysId: z.ZodOptional<z.ZodString>;
434
+ saveActionSysId: z.ZodOptional<z.ZodString>;
435
+ dryRun: z.ZodOptional<z.ZodBoolean>;
436
+ }, "strip", z.ZodTypeAny, {
437
+ name: string;
438
+ columns: {
439
+ type: string;
440
+ label: string;
441
+ name?: string | undefined;
442
+ max_length?: string | number | undefined;
443
+ reference?: string | undefined;
444
+ }[];
445
+ label: string;
446
+ scope: string;
447
+ dryRun?: boolean | undefined;
448
+ updateSetSysId?: string | undefined;
449
+ extendsTable?: string | undefined;
450
+ numberPrefix?: string | undefined;
451
+ userRole?: string | undefined;
452
+ createAccessControls?: boolean | undefined;
453
+ access?: string | undefined;
454
+ showInMenu?: boolean | undefined;
455
+ saveActionSysId?: string | undefined;
456
+ }, {
457
+ name: string;
458
+ columns: {
459
+ type: string;
460
+ label: string;
461
+ name?: string | undefined;
462
+ max_length?: string | number | undefined;
463
+ reference?: string | undefined;
464
+ }[];
465
+ label: string;
466
+ scope: string;
467
+ dryRun?: boolean | undefined;
468
+ updateSetSysId?: string | undefined;
469
+ extendsTable?: string | undefined;
470
+ numberPrefix?: string | undefined;
471
+ userRole?: string | undefined;
472
+ createAccessControls?: boolean | undefined;
473
+ access?: string | undefined;
474
+ showInMenu?: boolean | undefined;
475
+ saveActionSysId?: string | undefined;
476
+ }>;
@@ -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.editFlowSchema = exports.stepInputPatchSchema = exports.testFlowSchema = exports.createFlowSchema = exports.copyFlowSchema = exports.publishFlowSchema = exports.viewActionSchema = exports.viewFlowSchema = exports.addChoicesToFieldSchema = exports.choiceValueSchema = exports.setRelatedListsSchema = exports.setFormLayoutSchema = exports.formSectionSchema = exports.setListLayoutSchema = exports.createViewSchema = void 0;
7
+ exports.createTableSchema = exports.columnSpecSchema = exports.editFlowSchema = exports.stepInputPatchSchema = exports.testFlowSchema = exports.createFlowSchema = exports.copyFlowSchema = exports.publishFlowSchema = exports.viewActionSchema = exports.viewFlowSchema = 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),
@@ -113,3 +113,25 @@ exports.editFlowSchema = zod_1.z.object({
113
113
  scopeSysId: zod_1.z.string().optional(),
114
114
  updateSetSysId: zod_1.z.string().optional()
115
115
  });
116
+ exports.columnSpecSchema = zod_1.z.object({
117
+ label: zod_1.z.string().min(1),
118
+ type: zod_1.z.string().min(1),
119
+ name: zod_1.z.string().optional(),
120
+ max_length: zod_1.z.union([zod_1.z.string(), zod_1.z.number()]).optional(),
121
+ reference: zod_1.z.string().optional()
122
+ });
123
+ exports.createTableSchema = zod_1.z.object({
124
+ name: zod_1.z.string().min(1),
125
+ label: zod_1.z.string().min(1),
126
+ scope: zod_1.z.string().min(1),
127
+ columns: zod_1.z.array(exports.columnSpecSchema).min(1),
128
+ extendsTable: zod_1.z.string().optional(),
129
+ numberPrefix: zod_1.z.string().optional(),
130
+ userRole: zod_1.z.string().optional(),
131
+ createAccessControls: zod_1.z.boolean().optional(),
132
+ access: zod_1.z.string().optional(),
133
+ showInMenu: zod_1.z.boolean().optional(),
134
+ updateSetSysId: zod_1.z.string().optional(),
135
+ saveActionSysId: zod_1.z.string().optional(),
136
+ dryRun: zod_1.z.boolean().optional()
137
+ });
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Build the embedded list-edit column XML that rides inside the sys_db_object.do
3
+ * table-save POST. This is the ONE place ServiceNow's Studio form encodes new
4
+ * columns: a `<record_update table="sys_dictionary">` envelope with one
5
+ * `<record operation="add">` per column. Ground truth: a real Studio table-create
6
+ * HAR (`x_cadso_core_error`, tenonworkstudio, 2026-06-13) — see
7
+ * CTO docs/servicenow-create-table-har-analysis.md.
8
+ *
9
+ * Pure + deterministic (callers pass the per-column sys_ids) so it is unit-tested
10
+ * without a network. ES6 only, no optional chaining.
11
+ */
12
+ /** A normalized column ready to render. `type` is the ServiceNow internal_type. */
13
+ export interface NormalizedColumn {
14
+ /** Display label (Studio derives `element` from this server-side). */
15
+ label: string;
16
+ /** sys_dictionary.internal_type, e.g. "string_full_utf8", "choice", "integer". */
17
+ type: string;
18
+ /** max_length as a string; "" when the type carries none (e.g. glide_date_time). */
19
+ maxLength: string;
20
+ /** Reference target table for type "reference"; "" otherwise (rendered as NULL). */
21
+ reference: string;
22
+ }
23
+ /** Minimal XML-attr/text escaping — the values cross an XML boundary. */
24
+ export declare function xmlEscape(s: string): string;
25
+ /**
26
+ * Build the full `<record_update>` column blob. `sysIds[i]` is the fresh
27
+ * client-generated sys_id for column i (length must match `columns`).
28
+ */
29
+ export declare function buildColumnXml(columns: Array<NormalizedColumn>, sysIds: Array<string>): string;
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ /**
3
+ * Build the embedded list-edit column XML that rides inside the sys_db_object.do
4
+ * table-save POST. This is the ONE place ServiceNow's Studio form encodes new
5
+ * columns: a `<record_update table="sys_dictionary">` envelope with one
6
+ * `<record operation="add">` per column. Ground truth: a real Studio table-create
7
+ * HAR (`x_cadso_core_error`, tenonworkstudio, 2026-06-13) — see
8
+ * CTO docs/servicenow-create-table-har-analysis.md.
9
+ *
10
+ * Pure + deterministic (callers pass the per-column sys_ids) so it is unit-tested
11
+ * without a network. ES6 only, no optional chaining.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.xmlEscape = xmlEscape;
15
+ exports.buildColumnXml = buildColumnXml;
16
+ /**
17
+ * The 14 per-record fields Studio submits, in the HAR's exact order. Most are
18
+ * static (server fills them); only column_label, internal_type, reference and
19
+ * max_length carry capability data.
20
+ */
21
+ function renderRecord(col, sysId) {
22
+ var hasMax = col.maxLength !== "";
23
+ var ref = col.reference ? col.reference : "NULL";
24
+ var parts = [];
25
+ parts.push('<record sys_id="' + sysId + '" operation="add">');
26
+ // column_label — the only field the user really types; element is derived from it.
27
+ parts.push(field("column_label", { modified: true, dsp_set: true, value: "", display: col.label }));
28
+ parts.push(field("element", { value: "" }));
29
+ parts.push(field("internal_type", { modified: true, value_set: true, value: col.type }));
30
+ parts.push(field("reference", { value: ref }));
31
+ if (hasMax) {
32
+ parts.push(field("max_length", { modified: true, dsp_set: true, value: "", display: col.maxLength }));
33
+ }
34
+ else {
35
+ parts.push(field("max_length", { value: "" }));
36
+ }
37
+ parts.push(field("default_value", { value: "" }));
38
+ parts.push(field("display", { value: "false" }));
39
+ parts.push(field("sys_updated_on", { value: "" }));
40
+ parts.push(field("sys_updated_by", { value: "" }));
41
+ parts.push(field("attributes", { value: "" }));
42
+ parts.push(field("read_only", { value: "false" }));
43
+ parts.push(field("sys_created_on", { value: "" }));
44
+ parts.push(field("sys_created_by", { value: "" }));
45
+ parts.push(field("name", { value: "" }));
46
+ parts.push("</record>");
47
+ return parts.join("");
48
+ }
49
+ function field(name, opts) {
50
+ var modified = opts.modified === true ? "true" : "false";
51
+ var valueSet = opts.value_set === true ? "true" : "false";
52
+ var dspSet = opts.dsp_set === true ? "true" : "false";
53
+ var inner = "<value>" + xmlEscape(opts.value) + "</value>";
54
+ if (opts.dsp_set === true) {
55
+ inner += "<display_value>" + xmlEscape(opts.display === undefined ? "" : opts.display) + "</display_value>";
56
+ }
57
+ return '<field name="' + name + '" modified="' + modified
58
+ + '" value_set="' + valueSet + '" dsp_set="' + dspSet + '">' + inner + "</field>";
59
+ }
60
+ /** Minimal XML-attr/text escaping — the values cross an XML boundary. */
61
+ function xmlEscape(s) {
62
+ return String(s)
63
+ .replace(/&/g, "&amp;")
64
+ .replace(/</g, "&lt;")
65
+ .replace(/>/g, "&gt;")
66
+ .replace(/"/g, "&quot;");
67
+ }
68
+ /**
69
+ * Build the full `<record_update>` column blob. `sysIds[i]` is the fresh
70
+ * client-generated sys_id for column i (length must match `columns`).
71
+ */
72
+ function buildColumnXml(columns, sysIds) {
73
+ if (!Array.isArray(columns)) {
74
+ throw new Error("buildColumnXml: columns must be an array.");
75
+ }
76
+ if (!Array.isArray(sysIds) || sysIds.length !== columns.length) {
77
+ throw new Error("buildColumnXml: need exactly one sys_id per column (got "
78
+ + (Array.isArray(sysIds) ? sysIds.length : "none") + " for " + columns.length + " columns).");
79
+ }
80
+ var open = '<record_update table="sys_dictionary" field="null" query="nameONE IN^element!=NULL^ORDERBYelement">';
81
+ var body = "";
82
+ for (var i = 0; i < columns.length; i += 1) {
83
+ body += renderRecord(columns[i], sysIds[i]);
84
+ }
85
+ return open + body + "</record_update>";
86
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Pure transforms for the table-save form POST: normalize a friendly column spec
3
+ * into ServiceNow internal types, and overlay the capability's values onto the
4
+ * fields harvested from the live new-record form.
5
+ *
6
+ * The transport (GET the new-record form, harvest fields, POST) lives in
7
+ * createTable.ts; everything here is deterministic and unit-tested. ES6 only.
8
+ */
9
+ import { NormalizedColumn } from "./buildColumnXml";
10
+ /** Friendly input column (what a caller / MCP tool passes). */
11
+ export interface ColumnSpec {
12
+ name?: string;
13
+ label: string;
14
+ type: string;
15
+ max_length?: string | number;
16
+ reference?: string;
17
+ }
18
+ /**
19
+ * Friendly column type -> ServiceNow internal_type. Studio's default for a
20
+ * "String" is `string_full_utf8`, NOT plain `string` (confirmed in the HAR), so
21
+ * a caller saying "string" gets the type the platform actually writes.
22
+ */
23
+ export declare var TYPE_MAP: Record<string, string>;
24
+ /** Resolve a friendly-or-internal type string to the internal_type to write. */
25
+ export declare function resolveType(type: string): string;
26
+ /** Validate + normalize the friendly column list. Throws on the broken cases. */
27
+ export declare function normalizeColumns(cols: Array<ColumnSpec>): Array<NormalizedColumn>;
28
+ /** Per-operation access flags; all default true (matches Studio's create defaults). */
29
+ export interface AccessFlags {
30
+ read: boolean;
31
+ create: boolean;
32
+ update: boolean;
33
+ delete: boolean;
34
+ ws: boolean;
35
+ configuration: boolean;
36
+ alter: boolean;
37
+ actions: boolean;
38
+ client_scripts: boolean;
39
+ }
40
+ export declare function defaultAccessFlags(): AccessFlags;
41
+ /** Everything the overlay needs that the new-record form can't supply. */
42
+ export interface OverlaySpec {
43
+ name: string;
44
+ label: string;
45
+ tableSysId: string;
46
+ saveActionSysId: string;
47
+ superClassSysId: string;
48
+ superClassLabel: string;
49
+ scopeSysId: string;
50
+ scopeLabel: string;
51
+ numberPrefix: string;
52
+ userRoleSysId: string;
53
+ userRoleLabel: string;
54
+ createAccessControls: boolean;
55
+ access: string;
56
+ accessFlags: AccessFlags;
57
+ /** Application sys_id for the nav module; "" disables Show-in-menu. */
58
+ selectedApplicationSysId: string;
59
+ menuName: string;
60
+ /** The discovered `ni.java...ListEditFormatterAction[sys_db_object.REL:<relId>]` key. */
61
+ listEditKey: string;
62
+ columnXml: string;
63
+ }
64
+ /** Build the `...ShowInMenuFormatterAction[<part>]` form key. */
65
+ export declare function showInMenuKey(part: string): string;
66
+ /** Build the `...ListEditFormatterAction[sys_db_object.REL:<relId>]` form key. */
67
+ export declare function listEditKey(relId: string): string;
68
+ /**
69
+ * Overlay the capability's values onto the base field map harvested from the live
70
+ * new-record form. Returns a NEW object (base is not mutated). Sets exactly the
71
+ * keys Studio mutates for a table create; everything else (sysparm_ck,
72
+ * sysparm_encoded_record, the dynamic `<sysid>_text` field, all sys_original.*
73
+ * defaults) is preserved from the harvested form.
74
+ */
75
+ export declare function applyTableSaveOverlay(base: Record<string, string>, o: OverlaySpec): Record<string, string>;