@botlr/cli 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +54 -11
- package/dist/cli.js +240 -21
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +109 -8
- package/dist/index.js +155 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SectionSourceScope, SectionManifest, SectionSourceFileInput, SectionSourceScopeSummary, SectionSourceSummary, SectionFieldSchema, SectionSchemaField, SectionFormBuilderNode, BotlrClient } from '@botlr/sdk';
|
|
1
|
+
import { SectionSourceScope, SectionManifest, SectionSourceFileInput, SectionSourceScopeSummary, SectionSourceSummary, SectionFieldSchema, SectionSchemaField, SectionFormBuilderNode, SectionSchema, SectionSchemaFormat, BotlrClient } from '@botlr/sdk';
|
|
2
2
|
|
|
3
3
|
declare const CONFIG_FILE = "botlr.config.json";
|
|
4
4
|
declare const DEFAULT_FRAMEWORK = "react-next";
|
|
@@ -308,17 +308,59 @@ interface SchemaDrift {
|
|
|
308
308
|
/** Props the dashboard defines that the code does not list. */
|
|
309
309
|
missingInCode: string[];
|
|
310
310
|
}
|
|
311
|
-
/** The manifest's `schema` when it is a
|
|
312
|
-
declare function manifestSchema(manifest: SectionManifest):
|
|
311
|
+
/** The manifest's `schema` when it is a schema object in either format, else null. */
|
|
312
|
+
declare function manifestSchema(manifest: SectionManifest): SectionSchema | null;
|
|
313
313
|
/**
|
|
314
|
-
* Key difference between the code's schema
|
|
315
|
-
*
|
|
316
|
-
* the keys match.
|
|
314
|
+
* Key difference between the code's schema and the definition's schema
|
|
315
|
+
* (either format on either side; FormBuilder schemas contribute their node
|
|
316
|
+
* names). Null when the manifest has no schema object or the keys match.
|
|
317
317
|
*/
|
|
318
318
|
declare function compareSchemas(manifest: SectionManifest, definitionSchema: unknown): SchemaDrift | null;
|
|
319
|
-
/** The warning text for a drift, one line per direction,
|
|
319
|
+
/** The warning text for a drift, one line per direction, naming the two commands that resolve it. */
|
|
320
320
|
declare function formatDriftWarning(name: string, drift: SchemaDrift): string;
|
|
321
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Field-key diff between two props schemas, for `botlr schema push` to show
|
|
324
|
+
* before it replaces the dashboard's schema with the code's. Field DSL keys
|
|
325
|
+
* are the object's keys; FormBuilder schemas contribute their node names
|
|
326
|
+
* (groups flattened), so the two formats can be compared with each other.
|
|
327
|
+
*/
|
|
328
|
+
|
|
329
|
+
interface SchemaKeyDiff {
|
|
330
|
+
/** Keys the new schema has that the current one lacks. */
|
|
331
|
+
added: string[];
|
|
332
|
+
/** Keys the current schema has that the new one lacks. */
|
|
333
|
+
removed: string[];
|
|
334
|
+
/** Keys both have. */
|
|
335
|
+
kept: string[];
|
|
336
|
+
}
|
|
337
|
+
/** The field keys of a schema in either format, in schema order and without duplicates. */
|
|
338
|
+
declare function schemaKeys(schema: unknown): string[];
|
|
339
|
+
/** Keys added, removed and kept going from `current` to `next`; each list sorted. */
|
|
340
|
+
declare function diffSchemaKeys(current: unknown, next: unknown): SchemaKeyDiff;
|
|
341
|
+
interface SchemaPushPreview {
|
|
342
|
+
name: string;
|
|
343
|
+
/** The dashboard's current schema. */
|
|
344
|
+
current: unknown;
|
|
345
|
+
/** Its format as the server reports it, else as detected. */
|
|
346
|
+
currentFormat: SectionSchemaFormat | null;
|
|
347
|
+
/** The manifest's schema, about to replace it. */
|
|
348
|
+
next: unknown;
|
|
349
|
+
nextFormat: SectionSchemaFormat | null;
|
|
350
|
+
diff: SchemaKeyDiff;
|
|
351
|
+
}
|
|
352
|
+
/** The preview for a push, with formats detected from the schemas unless given. */
|
|
353
|
+
declare function previewSchemaPush(name: string, current: unknown, next: unknown, formats?: {
|
|
354
|
+
currentFormat?: SectionSchemaFormat | null;
|
|
355
|
+
nextFormat?: SectionSchemaFormat | null;
|
|
356
|
+
}): SchemaPushPreview;
|
|
357
|
+
/**
|
|
358
|
+
* The diff as the CLI prints it: one header line with both formats and
|
|
359
|
+
* field counts, then one line per non-empty direction. The `kept` line is
|
|
360
|
+
* always printed, with "none" when nothing survives.
|
|
361
|
+
*/
|
|
362
|
+
declare function formatSchemaDiff(preview: SchemaPushPreview): string;
|
|
363
|
+
|
|
322
364
|
/** A client from the resolved config. `needPersonalToken` makes a missing `BOTLR_PAT` an error up front. */
|
|
323
365
|
declare function clientFor(config: ResolvedConfig, options?: {
|
|
324
366
|
needPersonalToken?: boolean;
|
|
@@ -411,4 +453,63 @@ interface InitResult {
|
|
|
411
453
|
*/
|
|
412
454
|
declare function initSections(config: ResolvedConfig, options?: InitOptions): Promise<InitResult[]>;
|
|
413
455
|
|
|
414
|
-
|
|
456
|
+
interface SchemaPullOptions {
|
|
457
|
+
/** Section names; empty with `all: true` means every manifest in the sections directory. */
|
|
458
|
+
names: string[];
|
|
459
|
+
all?: boolean;
|
|
460
|
+
/** Do not write `<name>.props.ts`; also off when the config says `types: false`. */
|
|
461
|
+
noTypes?: boolean;
|
|
462
|
+
}
|
|
463
|
+
interface SchemaPullResult {
|
|
464
|
+
name: string;
|
|
465
|
+
/** Whether `<name>.botlr.json` changed; false when its `schema` already equalled the dashboard's. */
|
|
466
|
+
changed: boolean;
|
|
467
|
+
/** The manifest file. */
|
|
468
|
+
file: string;
|
|
469
|
+
format?: string;
|
|
470
|
+
/** What happened to `<name>.props.ts`; absent when types are off or the pull failed. */
|
|
471
|
+
types?: PropsTypesUpdate;
|
|
472
|
+
/** Why nothing was written: no manifest, or no section type of that name in the app. */
|
|
473
|
+
error?: string;
|
|
474
|
+
}
|
|
475
|
+
/** Copy the dashboard's schema into each manifest's `schema`, then regenerate the prop types. */
|
|
476
|
+
declare function pullSchemas(config: ResolvedConfig, options: SchemaPullOptions): Promise<SchemaPullResult[]>;
|
|
477
|
+
interface SchemaPushOptions {
|
|
478
|
+
name: string;
|
|
479
|
+
/** Replace the schema even when the dashboard's is in the other format. */
|
|
480
|
+
force?: boolean;
|
|
481
|
+
/**
|
|
482
|
+
* Asked once with the preview before anything is sent; return false to
|
|
483
|
+
* stop. Omit to push without asking (`--yes`).
|
|
484
|
+
*/
|
|
485
|
+
confirm?: (preview: SchemaPushPreview) => Promise<boolean> | boolean;
|
|
486
|
+
/** Do not write `<name>.props.ts` afterwards; also off when the config says `types: false`. */
|
|
487
|
+
noTypes?: boolean;
|
|
488
|
+
}
|
|
489
|
+
interface SchemaPushResult {
|
|
490
|
+
name: string;
|
|
491
|
+
preview: SchemaPushPreview;
|
|
492
|
+
/** False when `confirm` said no; nothing was sent. */
|
|
493
|
+
pushed: boolean;
|
|
494
|
+
/** From the server: false when the stored schema already equalled the manifest's. */
|
|
495
|
+
changed?: boolean;
|
|
496
|
+
previousFormat?: string;
|
|
497
|
+
format?: string;
|
|
498
|
+
types?: PropsTypesUpdate;
|
|
499
|
+
}
|
|
500
|
+
/** 409: the dashboard's schema is in the other format and `--force` was not given. */
|
|
501
|
+
declare class SchemaFormatConflict extends Error {
|
|
502
|
+
readonly sectionName: string;
|
|
503
|
+
readonly currentFormat: string;
|
|
504
|
+
readonly newFormat: string;
|
|
505
|
+
constructor(sectionName: string, currentFormat: string, newFormat: string, message?: string);
|
|
506
|
+
}
|
|
507
|
+
/** 422: the server refused the schema (not an object of fields, or the definition is not project-scoped). */
|
|
508
|
+
declare class SchemaRejected extends Error {
|
|
509
|
+
readonly sectionName: string;
|
|
510
|
+
constructor(sectionName: string, message: string);
|
|
511
|
+
}
|
|
512
|
+
/** Send the manifest's `schema` to the dashboard, after showing the diff and asking. */
|
|
513
|
+
declare function pushSchema(config: ResolvedConfig, options: SchemaPushOptions): Promise<SchemaPushResult>;
|
|
514
|
+
|
|
515
|
+
export { type BotlrCliConfigFile, CONFIG_FILE, ConfigError, type ConfigOverrides, DEFAULT_FRAMEWORK, DEFAULT_REGISTRY_FILE, DEFAULT_SECTIONS_DIR, type DefinitionMeta, type DeriveOptions, type DerivedManifest, type GenerateOptions, type InitOptions, type InitResult, LOCK_DIR, LOCK_FILE, type LockEntry, MANIFEST_KEY_ORDER, MANIFEST_SUFFIX, ManifestError, PROPS_SUFFIX, type PropField, type PropsTypesUpdate, type PullOptions, type PullResult, type PushOptions, type PushResult, REGISTRY_FILE, type RegistryAddition, type RegistryEditResult, type RegistryEntry, type RegistryUpdate, type ResolvedConfig, type SchemaDrift, SchemaFormatConflict, type SchemaKeyDiff, type SchemaPullOptions, type SchemaPullResult, type SchemaPushOptions, type SchemaPushPreview, type SchemaPushResult, SchemaRejected, type SectionStatus, type SectionStatusRow, type SectionsLock, type StatusInput, addRegistryEntry, clientFor, collectDslFields, collectFields, collectFormBuilderFields, compareSchemas, computeStatus, deriveManifests, diffSchemaKeys, findConfigFile, formatDriftWarning, formatManifest, formatSchemaDiff, generatePropsSource, hasRegistry, importPathFor, initSections, listManifests, listSections, loadConfig, lockPath, manifestPath, manifestSchema, manifestsEqual, nextLockEntry, normalizeManifest, optionUnion, parseDefinition, parseImports, parseRegistry, parseZodSchema, pascalCase, previewSchemaPush, propsInterfaceName, propsPath, pullSchemas, pullSections, pushSchema, pushSection, readConfigFile, readLock, readManifest, readNpmVersions, readSectionFiles, registerSection, remoteForLock, safeJoin, schemaFieldNames, schemaKeys, sectionStatuses, tsTypeForField, updateLock, validateManifest, writeLock, writeManifest, writeManifestIfChanged, writePropsTypes, writeSectionFiles, zodFieldToSchemaField };
|
package/dist/index.js
CHANGED
|
@@ -805,15 +805,15 @@ async function writePropsTypes(sectionsDir, manifest, schema) {
|
|
|
805
805
|
}
|
|
806
806
|
|
|
807
807
|
// src/drift.ts
|
|
808
|
-
import {
|
|
808
|
+
import { schemaFormat } from "@botlr/sdk";
|
|
809
809
|
function manifestSchema(manifest) {
|
|
810
810
|
const schema = manifest.schema;
|
|
811
|
-
return
|
|
811
|
+
return schemaFormat(schema) ? schema : null;
|
|
812
812
|
}
|
|
813
813
|
function compareSchemas(manifest, definitionSchema) {
|
|
814
814
|
const code = manifestSchema(manifest);
|
|
815
815
|
if (!code) return null;
|
|
816
|
-
const codeKeys = new Set(
|
|
816
|
+
const codeKeys = new Set(schemaFieldNames(code));
|
|
817
817
|
const dashboardKeys = new Set(schemaFieldNames(definitionSchema));
|
|
818
818
|
const missingInDashboard = [...codeKeys].filter((k) => !dashboardKeys.has(k)).sort();
|
|
819
819
|
const missingInCode = [...dashboardKeys].filter((k) => !codeKeys.has(k)).sort();
|
|
@@ -828,7 +828,53 @@ function formatDriftWarning(name, drift) {
|
|
|
828
828
|
if (drift.missingInCode.length) {
|
|
829
829
|
lines.push(` the dashboard defines props the code does not list: ${drift.missingInCode.join(", ")}`);
|
|
830
830
|
}
|
|
831
|
-
lines.push(` the dashboard owns the schema
|
|
831
|
+
lines.push(` the dashboard owns the schema; a code push never changes it. To resolve:`);
|
|
832
|
+
lines.push(` botlr schema pull ${name} takes the dashboard's schema into ${name}.botlr.json`);
|
|
833
|
+
lines.push(` botlr schema push ${name} sends the code's schema to the dashboard`);
|
|
834
|
+
return lines.join("\n");
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// src/schema-diff.ts
|
|
838
|
+
import { schemaFormat as schemaFormat2 } from "@botlr/sdk";
|
|
839
|
+
function schemaKeys(schema) {
|
|
840
|
+
return [...new Set(schemaFieldNames(schema))];
|
|
841
|
+
}
|
|
842
|
+
function diffSchemaKeys(current, next) {
|
|
843
|
+
const currentKeys = new Set(schemaKeys(current));
|
|
844
|
+
const nextKeys = new Set(schemaKeys(next));
|
|
845
|
+
return {
|
|
846
|
+
added: [...nextKeys].filter((k) => !currentKeys.has(k)).sort(),
|
|
847
|
+
removed: [...currentKeys].filter((k) => !nextKeys.has(k)).sort(),
|
|
848
|
+
kept: [...nextKeys].filter((k) => currentKeys.has(k)).sort()
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
function previewSchemaPush(name, current, next, formats = {}) {
|
|
852
|
+
return {
|
|
853
|
+
name,
|
|
854
|
+
current,
|
|
855
|
+
currentFormat: formats.currentFormat ?? schemaFormat2(current),
|
|
856
|
+
next,
|
|
857
|
+
nextFormat: formats.nextFormat ?? schemaFormat2(next),
|
|
858
|
+
diff: diffSchemaKeys(current, next)
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
function plural(n, word) {
|
|
862
|
+
return `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
863
|
+
}
|
|
864
|
+
function formatName(format) {
|
|
865
|
+
return format ?? "unknown";
|
|
866
|
+
}
|
|
867
|
+
function formatSchemaDiff(preview) {
|
|
868
|
+
const { diff } = preview;
|
|
869
|
+
const header = `schema of '${preview.name}': dashboard has ${formatName(preview.currentFormat)} (${plural(schemaKeys(preview.current).length, "field")}), code has ${formatName(preview.nextFormat)} (${plural(schemaKeys(preview.next).length, "field")})`;
|
|
870
|
+
const lines = [header];
|
|
871
|
+
if (diff.added.length) lines.push(` + added ${diff.added.join(", ")}`);
|
|
872
|
+
if (diff.removed.length) lines.push(` - removed ${diff.removed.join(", ")}`);
|
|
873
|
+
lines.push(` = kept ${diff.kept.length ? diff.kept.join(", ") : "none"}`);
|
|
874
|
+
if (preview.currentFormat && preview.nextFormat && preview.currentFormat !== preview.nextFormat) {
|
|
875
|
+
lines.push(` the formats differ: the push needs --force to replace ${preview.currentFormat} with ${preview.nextFormat}`);
|
|
876
|
+
}
|
|
877
|
+
if (!diff.added.length && !diff.removed.length) lines.push(" the field keys match; only field settings may change");
|
|
832
878
|
return lines.join("\n");
|
|
833
879
|
}
|
|
834
880
|
|
|
@@ -1078,6 +1124,103 @@ async function initSections(config, options = {}) {
|
|
|
1078
1124
|
}
|
|
1079
1125
|
return results;
|
|
1080
1126
|
}
|
|
1127
|
+
|
|
1128
|
+
// src/commands/schema.ts
|
|
1129
|
+
import { BotlrError, BotlrSchemaFormatConflictError, botlrErrorMessage } from "@botlr/sdk";
|
|
1130
|
+
function isNotFound(error) {
|
|
1131
|
+
return error instanceof BotlrError && error.status === 404;
|
|
1132
|
+
}
|
|
1133
|
+
async function pullSchemas(config, options) {
|
|
1134
|
+
const client = clientFor(config);
|
|
1135
|
+
const names = options.all ? await listManifests(config.sectionsDir) : options.names;
|
|
1136
|
+
const results = [];
|
|
1137
|
+
for (const name of names) {
|
|
1138
|
+
let manifest;
|
|
1139
|
+
try {
|
|
1140
|
+
manifest = await readManifest(config.sectionsDir, name);
|
|
1141
|
+
} catch (error) {
|
|
1142
|
+
results.push({ name, changed: false, file: "", error: error.message });
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
let definition;
|
|
1146
|
+
try {
|
|
1147
|
+
definition = (await client.sections.getSchema(name)).definition;
|
|
1148
|
+
} catch (error) {
|
|
1149
|
+
if (!isNotFound(error)) throw error;
|
|
1150
|
+
results.push({ name, changed: false, file: "", error: `the app has no section type '${name}' (botlr push ${name} --create adds one)` });
|
|
1151
|
+
continue;
|
|
1152
|
+
}
|
|
1153
|
+
const updated = { ...manifest, schema: definition.schema };
|
|
1154
|
+
const written = await writeManifestIfChanged(config.sectionsDir, name, updated);
|
|
1155
|
+
const types = config.types && !options.noTypes ? await writePropsTypes(config.sectionsDir, manifest, definition.schema) : void 0;
|
|
1156
|
+
results.push({ name, changed: written.changed, file: written.file, format: definition.format, types });
|
|
1157
|
+
}
|
|
1158
|
+
return results;
|
|
1159
|
+
}
|
|
1160
|
+
var SchemaFormatConflict = class extends Error {
|
|
1161
|
+
constructor(sectionName, currentFormat, newFormat, message) {
|
|
1162
|
+
super(message ?? `the schema of '${sectionName}' is stored as ${currentFormat}, the manifest's is ${newFormat}`);
|
|
1163
|
+
this.sectionName = sectionName;
|
|
1164
|
+
this.currentFormat = currentFormat;
|
|
1165
|
+
this.newFormat = newFormat;
|
|
1166
|
+
this.name = "SchemaFormatConflict";
|
|
1167
|
+
}
|
|
1168
|
+
sectionName;
|
|
1169
|
+
currentFormat;
|
|
1170
|
+
newFormat;
|
|
1171
|
+
};
|
|
1172
|
+
var SchemaRejected = class extends Error {
|
|
1173
|
+
constructor(sectionName, message) {
|
|
1174
|
+
super(message);
|
|
1175
|
+
this.sectionName = sectionName;
|
|
1176
|
+
this.name = "SchemaRejected";
|
|
1177
|
+
}
|
|
1178
|
+
sectionName;
|
|
1179
|
+
};
|
|
1180
|
+
async function pushSchema(config, options) {
|
|
1181
|
+
const client = clientFor(config, { needPersonalToken: true });
|
|
1182
|
+
const { name } = options;
|
|
1183
|
+
const manifest = await readManifest(config.sectionsDir, name);
|
|
1184
|
+
const next = manifest.schema;
|
|
1185
|
+
if (!next || typeof next !== "object" || Array.isArray(next)) {
|
|
1186
|
+
throw new ManifestError(
|
|
1187
|
+
`${name}.botlr.json has no 'schema' object to push; add one, or run 'botlr schema pull ${name}' to take the dashboard's`
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
let current;
|
|
1191
|
+
try {
|
|
1192
|
+
current = (await client.sections.getSchema(name)).definition;
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
if (!isNotFound(error)) throw error;
|
|
1195
|
+
throw new ManifestError(`the app has no section type '${name}'; push the code first with 'botlr push ${name} --create'`);
|
|
1196
|
+
}
|
|
1197
|
+
const preview = previewSchemaPush(name, current.schema, next, { currentFormat: current.format });
|
|
1198
|
+
if (options.confirm && !await options.confirm(preview)) {
|
|
1199
|
+
return { name, preview, pushed: false };
|
|
1200
|
+
}
|
|
1201
|
+
let response;
|
|
1202
|
+
try {
|
|
1203
|
+
response = await client.sections.putSchema(name, { schema: next, force: options.force ?? false });
|
|
1204
|
+
} catch (error) {
|
|
1205
|
+
if (error instanceof BotlrSchemaFormatConflictError) {
|
|
1206
|
+
throw new SchemaFormatConflict(name, error.currentFormat, error.newFormat, botlrErrorMessage(error));
|
|
1207
|
+
}
|
|
1208
|
+
if (error instanceof BotlrError && error.status === 422) {
|
|
1209
|
+
throw new SchemaRejected(name, botlrErrorMessage(error) ?? "the server rejected the schema (422)");
|
|
1210
|
+
}
|
|
1211
|
+
throw error;
|
|
1212
|
+
}
|
|
1213
|
+
const types = config.types && !options.noTypes ? await writePropsTypes(config.sectionsDir, manifest, response.definition.schema) : void 0;
|
|
1214
|
+
return {
|
|
1215
|
+
name,
|
|
1216
|
+
preview,
|
|
1217
|
+
pushed: true,
|
|
1218
|
+
changed: response.changed,
|
|
1219
|
+
previousFormat: response.previousFormat,
|
|
1220
|
+
format: response.definition.format,
|
|
1221
|
+
types
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1081
1224
|
export {
|
|
1082
1225
|
CONFIG_FILE,
|
|
1083
1226
|
ConfigError,
|
|
@@ -1091,6 +1234,8 @@ export {
|
|
|
1091
1234
|
ManifestError,
|
|
1092
1235
|
PROPS_SUFFIX,
|
|
1093
1236
|
REGISTRY_FILE,
|
|
1237
|
+
SchemaFormatConflict,
|
|
1238
|
+
SchemaRejected,
|
|
1094
1239
|
addRegistryEntry,
|
|
1095
1240
|
clientFor,
|
|
1096
1241
|
collectDslFields,
|
|
@@ -1099,9 +1244,11 @@ export {
|
|
|
1099
1244
|
compareSchemas,
|
|
1100
1245
|
computeStatus,
|
|
1101
1246
|
deriveManifests,
|
|
1247
|
+
diffSchemaKeys,
|
|
1102
1248
|
findConfigFile,
|
|
1103
1249
|
formatDriftWarning,
|
|
1104
1250
|
formatManifest,
|
|
1251
|
+
formatSchemaDiff,
|
|
1105
1252
|
generatePropsSource,
|
|
1106
1253
|
hasRegistry,
|
|
1107
1254
|
importPathFor,
|
|
@@ -1121,9 +1268,12 @@ export {
|
|
|
1121
1268
|
parseRegistry,
|
|
1122
1269
|
parseZodSchema,
|
|
1123
1270
|
pascalCase,
|
|
1271
|
+
previewSchemaPush,
|
|
1124
1272
|
propsInterfaceName,
|
|
1125
1273
|
propsPath,
|
|
1274
|
+
pullSchemas,
|
|
1126
1275
|
pullSections,
|
|
1276
|
+
pushSchema,
|
|
1127
1277
|
pushSection,
|
|
1128
1278
|
readConfigFile,
|
|
1129
1279
|
readLock,
|
|
@@ -1134,6 +1284,7 @@ export {
|
|
|
1134
1284
|
remoteForLock,
|
|
1135
1285
|
safeJoin,
|
|
1136
1286
|
schemaFieldNames,
|
|
1287
|
+
schemaKeys,
|
|
1137
1288
|
sectionStatuses,
|
|
1138
1289
|
tsTypeForField,
|
|
1139
1290
|
updateLock,
|