picklist corrigido
This commit is contained in:
@@ -72,6 +72,12 @@ A cláusula é aplicada via `entityQuery` com filtro CZQL. Comandos de modifica
|
|||||||
|
|
||||||
Campos **nullable** permitem limpar o valor escolhendo "Limpar campo (null)" ou digitando `null`.
|
Campos **nullable** permitem limpar o valor escolhendo "Limpar campo (null)" ou digitando `null`.
|
||||||
|
|
||||||
|
## Campos PickList
|
||||||
|
|
||||||
|
Campos com `presentationType: PickList` (listas de opções customizadas ou do sistema) exibem uma **lista carregada da API** com nomes legíveis (ex: `Conteúdo`). O app envia automaticamente o ID correto (`/C_GenericTaskSetor/<guid>`), evitando erros ao digitar o nome manualmente.
|
||||||
|
|
||||||
|
Campos de **referência** (`ReferenceToObject`, ex: User, Project) continuam exigindo o ID manual ou JSON `{"id":"/Tipo/guid"}`.
|
||||||
|
|
||||||
## Variáveis de ambiente
|
## Variáveis de ambiente
|
||||||
|
|
||||||
| Variável | Descrição |
|
| Variável | Descrição |
|
||||||
@@ -102,7 +108,8 @@ Exemplo resumido:
|
|||||||
"typeName": "Task",
|
"typeName": "Task",
|
||||||
"filter": "Todos",
|
"filter": "Todos",
|
||||||
"fieldName": "C_MeuCampo",
|
"fieldName": "C_MeuCampo",
|
||||||
"value": true
|
"value": { "id": "/C_GenericTaskSetor/abc123" },
|
||||||
|
"valueLabel": "Conteúdo (/C_GenericTaskSetor/abc123)"
|
||||||
},
|
},
|
||||||
"summary": { "total": 10, "updated": 9, "errors": 1 },
|
"summary": { "total": 10, "updated": 9, "errors": 1 },
|
||||||
"results": [
|
"results": [
|
||||||
|
|||||||
+22
-7
@@ -16,7 +16,12 @@ import {
|
|||||||
createClarizenClient,
|
createClarizenClient,
|
||||||
} from "./clarizen/client.js";
|
} from "./clarizen/client.js";
|
||||||
import { formatFieldHint } from "./metadata/field-hints.js";
|
import { formatFieldHint } from "./metadata/field-hints.js";
|
||||||
|
import { isPickListField } from "./metadata/picklist.js";
|
||||||
import { getEntityMetadata, pickEntityType, pickField } from "./metadata/picker.js";
|
import { getEntityMetadata, pickEntityType, pickField } from "./metadata/picker.js";
|
||||||
|
import {
|
||||||
|
promptPickListValue,
|
||||||
|
type FieldValueSelection,
|
||||||
|
} from "./parse/prompt-picklist.js";
|
||||||
import {
|
import {
|
||||||
formatValueForDisplay,
|
formatValueForDisplay,
|
||||||
parseFieldValue,
|
parseFieldValue,
|
||||||
@@ -27,7 +32,7 @@ import { fetchAllEntities } from "./query/fetch-all.js";
|
|||||||
import { promptEntityFilter } from "./query/prompt-filter.js";
|
import { promptEntityFilter } from "./query/prompt-filter.js";
|
||||||
import { buildRunLog, writeRunLog } from "./log/run-log.js";
|
import { buildRunLog, writeRunLog } from "./log/run-log.js";
|
||||||
import { bulkUpdateField } from "./update/bulk-field.js";
|
import { bulkUpdateField } from "./update/bulk-field.js";
|
||||||
import type { FieldDescription, JsonValue } from "@arco/clarizen-lib";
|
import type { Clarizen, FieldDescription, JsonValue } from "@arco/clarizen-lib";
|
||||||
|
|
||||||
function exitCancel(): never {
|
function exitCancel(): never {
|
||||||
cancel("Operação cancelada.");
|
cancel("Operação cancelada.");
|
||||||
@@ -35,8 +40,13 @@ function exitCancel(): never {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function promptFieldValue(
|
async function promptFieldValue(
|
||||||
|
clarizen: Clarizen,
|
||||||
field: FieldDescription,
|
field: FieldDescription,
|
||||||
): Promise<JsonValue | symbol> {
|
): Promise<FieldValueSelection | symbol> {
|
||||||
|
if (isPickListField(field)) {
|
||||||
|
return promptPickListValue(clarizen, field);
|
||||||
|
}
|
||||||
|
|
||||||
console.log("\n" + formatFieldHint(field) + "\n");
|
console.log("\n" + formatFieldHint(field) + "\n");
|
||||||
|
|
||||||
if (field.nullable) {
|
if (field.nullable) {
|
||||||
@@ -48,7 +58,7 @@ async function promptFieldValue(
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
if (isCancel(action)) return action;
|
if (isCancel(action)) return action;
|
||||||
if (action === "clear") return null;
|
if (action === "clear") return { value: null, displayLabel: "(vazio / null)" };
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -64,7 +74,8 @@ async function promptFieldValue(
|
|||||||
if (isCancel(raw)) return raw;
|
if (isCancel(raw)) return raw;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return parseFieldValue(raw, field);
|
const value = parseFieldValue(raw, field);
|
||||||
|
return { value };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof ValueParseError) {
|
if (err instanceof ValueParseError) {
|
||||||
console.error(`\n Erro: ${err.message}\n`);
|
console.error(`\n Erro: ${err.message}\n`);
|
||||||
@@ -107,8 +118,11 @@ async function main(): Promise<void> {
|
|||||||
const { field } = fieldChoice;
|
const { field } = fieldChoice;
|
||||||
const fieldName = field.name!;
|
const fieldName = field.name!;
|
||||||
|
|
||||||
const parsedValue = await promptFieldValue(field);
|
const valueSelection = await promptFieldValue(clarizen, field);
|
||||||
if (isCancel(parsedValue)) exitCancel();
|
if (isCancel(valueSelection)) exitCancel();
|
||||||
|
|
||||||
|
const parsedValue = valueSelection.value;
|
||||||
|
const valueDisplayLabel = valueSelection.displayLabel;
|
||||||
|
|
||||||
const fetchSpinner = spinner();
|
const fetchSpinner = spinner();
|
||||||
fetchSpinner.start("Buscando objetos...");
|
fetchSpinner.start("Buscando objetos...");
|
||||||
@@ -137,7 +151,7 @@ Resumo da operação:
|
|||||||
Tipo: ${entityLabel}
|
Tipo: ${entityLabel}
|
||||||
Filtro: ${formatFilterSummary(entityFilter)}
|
Filtro: ${formatFilterSummary(entityFilter)}
|
||||||
Campo: ${field.label ?? fieldName} (${fieldName}) — ${field.type ?? "?"}
|
Campo: ${field.label ?? fieldName} (${fieldName}) — ${field.type ?? "?"}
|
||||||
Valor: ${formatValueForDisplay(parsedValue)}
|
Valor: ${formatValueForDisplay(parsedValue, valueDisplayLabel)}
|
||||||
Objetos: ${records.length.toLocaleString("pt-BR")} registro(s)
|
Objetos: ${records.length.toLocaleString("pt-BR")} registro(s)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -168,6 +182,7 @@ Resumo da operação:
|
|||||||
field,
|
field,
|
||||||
fieldName,
|
fieldName,
|
||||||
parsedValue,
|
parsedValue,
|
||||||
|
valueDisplayLabel,
|
||||||
records,
|
records,
|
||||||
result,
|
result,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export interface RunLog {
|
|||||||
fieldLabel?: string;
|
fieldLabel?: string;
|
||||||
fieldType?: string;
|
fieldType?: string;
|
||||||
value: JsonValue;
|
value: JsonValue;
|
||||||
|
valueLabel?: string;
|
||||||
};
|
};
|
||||||
summary: {
|
summary: {
|
||||||
total: number;
|
total: number;
|
||||||
@@ -43,6 +44,7 @@ export interface BuildRunLogParams {
|
|||||||
field: FieldDescription;
|
field: FieldDescription;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
parsedValue: JsonValue;
|
parsedValue: JsonValue;
|
||||||
|
valueDisplayLabel?: string;
|
||||||
records: EntityRecord[];
|
records: EntityRecord[];
|
||||||
result: BulkUpdateResult;
|
result: BulkUpdateResult;
|
||||||
}
|
}
|
||||||
@@ -60,6 +62,7 @@ export function buildRunLog(params: BuildRunLogParams): RunLog {
|
|||||||
field,
|
field,
|
||||||
fieldName,
|
fieldName,
|
||||||
parsedValue,
|
parsedValue,
|
||||||
|
valueDisplayLabel,
|
||||||
records,
|
records,
|
||||||
result,
|
result,
|
||||||
} = params;
|
} = params;
|
||||||
@@ -101,6 +104,7 @@ export function buildRunLog(params: BuildRunLogParams): RunLog {
|
|||||||
fieldLabel: field.label,
|
fieldLabel: field.label,
|
||||||
fieldType: field.type,
|
fieldType: field.type,
|
||||||
value: parsedValue,
|
value: parsedValue,
|
||||||
|
valueLabel: valueDisplayLabel,
|
||||||
},
|
},
|
||||||
summary: {
|
summary: {
|
||||||
total: records.length,
|
total: records.length,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { FieldDescription, FieldType } from "@arco/clarizen-lib";
|
import type { FieldDescription, FieldType } from "@arco/clarizen-lib";
|
||||||
|
import { isPickListField } from "./picklist.js";
|
||||||
|
|
||||||
const TYPE_EXAMPLES: Record<FieldType, string> = {
|
const TYPE_EXAMPLES: Record<FieldType, string> = {
|
||||||
Boolean: "true, false, sim, não",
|
Boolean: "true, false, sim, não",
|
||||||
@@ -20,11 +21,20 @@ export function getFieldTypeExample(type: FieldType | undefined): string {
|
|||||||
|
|
||||||
export function formatFieldHint(field: FieldDescription): string {
|
export function formatFieldHint(field: FieldDescription): string {
|
||||||
const type = field.type ?? "desconhecido";
|
const type = field.type ?? "desconhecido";
|
||||||
const example = getFieldTypeExample(field.type);
|
const lines: string[] = [];
|
||||||
const lines = [
|
|
||||||
`Tipo: ${type}`,
|
if (isPickListField(field)) {
|
||||||
`Exemplo: ${example}`,
|
lines.push("Tipo: PickList (lista de opções)");
|
||||||
];
|
lines.push("Escolha um valor na lista — não digite o nome exibido manualmente.");
|
||||||
|
const refType = (field as { referencedEntities?: string[] }).referencedEntities?.[0];
|
||||||
|
if (refType) {
|
||||||
|
lines.push(`Opções carregadas de: ${refType}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const example = getFieldTypeExample(field.type);
|
||||||
|
lines.push(`Tipo: ${type}`);
|
||||||
|
lines.push(`Exemplo: ${example}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (field.nullable) {
|
if (field.nullable) {
|
||||||
lines.push("Este campo aceita valor vazio (null) para limpar.");
|
lines.push("Este campo aceita valor vazio (null) para limpar.");
|
||||||
@@ -39,8 +49,9 @@ export function formatFieldHint(field: FieldDescription): string {
|
|||||||
export function formatFieldOptionLabel(field: FieldDescription): string {
|
export function formatFieldOptionLabel(field: FieldDescription): string {
|
||||||
const label = field.label?.trim() || field.name || "?";
|
const label = field.label?.trim() || field.name || "?";
|
||||||
const name = field.name ?? "?";
|
const name = field.name ?? "?";
|
||||||
const type = field.type ?? "?";
|
const presentation =
|
||||||
return `${label} (${name}) — ${type}`;
|
field.presentationType === "PickList" ? "PickList" : (field.type ?? "?");
|
||||||
|
return `${label} (${name}) — ${presentation}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isUpdatableField(field: FieldDescription): boolean {
|
export function isUpdatableField(field: FieldDescription): boolean {
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ async function pickFromList<T extends { value: string; label: string }>(
|
|||||||
return select({ message, options });
|
return select({ message, options });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pickFromFiltered<T extends { value: string; label: string }>(
|
export async function pickFromFiltered<T extends { value: string; label: string }>(
|
||||||
message: string,
|
message: string,
|
||||||
allOptions: T[],
|
allOptions: T[],
|
||||||
filterPrompt: string,
|
filterPrompt: string,
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import type { Clarizen, FieldDescription } from "@arco/clarizen-lib";
|
||||||
|
import { resolveDisplayField } from "../query/resolve-display-field.js";
|
||||||
|
|
||||||
|
const PAGE_SIZE = 500;
|
||||||
|
|
||||||
|
export interface FieldWithRefs extends FieldDescription {
|
||||||
|
referencedEntities?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PickListOption {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPickListField(field: FieldWithRefs): boolean {
|
||||||
|
return field.presentationType === "PickList";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPickListEntityType(field: FieldWithRefs): string | undefined {
|
||||||
|
return field.referencedEntities?.[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractLabel(
|
||||||
|
entity: Record<string, unknown>,
|
||||||
|
displayField: string,
|
||||||
|
): string | undefined {
|
||||||
|
const objectFields = entity.objectFields as Record<string, unknown> | undefined;
|
||||||
|
const value = entity[displayField] ?? objectFields?.[displayField];
|
||||||
|
if (value === null || value === undefined) return undefined;
|
||||||
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
if (typeof value === "object" && value !== null && "name" in value) {
|
||||||
|
const name = (value as { name?: unknown }).name;
|
||||||
|
if (name !== null && name !== undefined) return String(name);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function idSuffix(id: string): string {
|
||||||
|
return id.includes("/") ? id.split("/").pop()! : id;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchPickListOptions(
|
||||||
|
clarizen: Clarizen,
|
||||||
|
pickListTypeName: string,
|
||||||
|
onProgress?: (count: number) => void,
|
||||||
|
): Promise<PickListOption[]> {
|
||||||
|
const meta = await clarizen.metadata.describeMetadata({
|
||||||
|
typeNames: [pickListTypeName],
|
||||||
|
flags: ["fields"],
|
||||||
|
});
|
||||||
|
const entityMeta = meta.entityDescriptions?.find(
|
||||||
|
(e) => e.typeName === pickListTypeName,
|
||||||
|
);
|
||||||
|
if (!entityMeta) {
|
||||||
|
throw new Error(`Metadata não encontrada para picklist ${pickListTypeName}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayField = resolveDisplayField(entityMeta);
|
||||||
|
const options: PickListOption[] = [];
|
||||||
|
let from = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const result = await clarizen.data.entityQuery({
|
||||||
|
typeName: pickListTypeName,
|
||||||
|
fields: [displayField],
|
||||||
|
paging: { from, limit: PAGE_SIZE },
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const entity of result.entities) {
|
||||||
|
if (!entity.id) continue;
|
||||||
|
const id = String(entity.id);
|
||||||
|
const label =
|
||||||
|
extractLabel(entity as Record<string, unknown>, displayField) ??
|
||||||
|
idSuffix(id);
|
||||||
|
options.push({ id, label });
|
||||||
|
}
|
||||||
|
|
||||||
|
onProgress?.(options.length);
|
||||||
|
|
||||||
|
if (!result.paging?.hasMore) break;
|
||||||
|
from += PAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options.sort((a, b) => a.label.localeCompare(b.label, "pt-BR"));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatPickListOptionLabel(option: PickListOption): string {
|
||||||
|
return `${option.label} (${option.id})`;
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
import type { Clarizen, JsonValue } from "@arco/clarizen-lib";
|
||||||
|
import { isCancel, select, spinner, text } from "@clack/prompts";
|
||||||
|
import { formatFieldHint } from "../metadata/field-hints.js";
|
||||||
|
import {
|
||||||
|
fetchPickListOptions,
|
||||||
|
formatPickListOptionLabel,
|
||||||
|
getPickListEntityType,
|
||||||
|
type FieldWithRefs,
|
||||||
|
} from "../metadata/picklist.js";
|
||||||
|
import { pickFromFiltered } from "../metadata/picker.js";
|
||||||
|
import { parseFieldValue, ValueParseError } from "./value.js";
|
||||||
|
|
||||||
|
export interface FieldValueSelection {
|
||||||
|
value: JsonValue;
|
||||||
|
displayLabel?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pickPickListOption(
|
||||||
|
field: FieldWithRefs,
|
||||||
|
options: Array<{ value: string; label: string }>,
|
||||||
|
): Promise<string | symbol> {
|
||||||
|
const fieldLabel = field.label ?? field.name ?? "valor";
|
||||||
|
return pickFromFiltered(
|
||||||
|
`Escolha o valor para ${fieldLabel}`,
|
||||||
|
options,
|
||||||
|
"Como deseja buscar na lista?",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function promptPickListSelection(
|
||||||
|
clarizen: Clarizen,
|
||||||
|
field: FieldWithRefs,
|
||||||
|
pickListTypeName: string,
|
||||||
|
): Promise<FieldValueSelection | symbol> {
|
||||||
|
const loadSpinner = spinner();
|
||||||
|
loadSpinner.start(`Carregando opções de ${pickListTypeName}...`);
|
||||||
|
|
||||||
|
const pickListOptions = await fetchPickListOptions(
|
||||||
|
clarizen,
|
||||||
|
pickListTypeName,
|
||||||
|
(count) => {
|
||||||
|
loadSpinner.message(
|
||||||
|
`Carregando opções de ${pickListTypeName}... (${count})`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
loadSpinner.stop(`${pickListOptions.length} opção(ões) carregada(s).`);
|
||||||
|
|
||||||
|
if (pickListOptions.length === 0) {
|
||||||
|
console.log(
|
||||||
|
`\nNenhuma opção encontrada para ${pickListTypeName}. Informe o ID manualmente.\n`,
|
||||||
|
);
|
||||||
|
return promptManualEntityValue(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectOptions = pickListOptions.map((o) => ({
|
||||||
|
value: o.id,
|
||||||
|
label: formatPickListOptionLabel(o),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const selectedId = await pickPickListOption(field, selectOptions);
|
||||||
|
if (isCancel(selectedId)) return selectedId;
|
||||||
|
|
||||||
|
const match = pickListOptions.find((o) => o.id === selectedId);
|
||||||
|
return {
|
||||||
|
value: { id: selectedId },
|
||||||
|
displayLabel: match
|
||||||
|
? `${match.label} (${match.id})`
|
||||||
|
: String(selectedId),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function promptManualEntityValue(
|
||||||
|
field: FieldWithRefs,
|
||||||
|
): Promise<FieldValueSelection | symbol> {
|
||||||
|
while (true) {
|
||||||
|
const raw = await text({
|
||||||
|
message: "Informe o ID da entidade (ex: /Tipo/guid)",
|
||||||
|
validate: (value) => (!value?.trim() ? "Valor obrigatório." : undefined),
|
||||||
|
});
|
||||||
|
if (isCancel(raw)) return raw;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const value = parseFieldValue(raw, field);
|
||||||
|
return { value, displayLabel: formatEntityDisplayLabel(value) };
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ValueParseError) {
|
||||||
|
console.error(`\n Erro: ${err.message}\n`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatEntityDisplayLabel(value: JsonValue): string | undefined {
|
||||||
|
if (value && typeof value === "object" && "id" in value) {
|
||||||
|
const id = (value as { id?: unknown }).id;
|
||||||
|
if (typeof id === "string") return id;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function promptPickListValue(
|
||||||
|
clarizen: Clarizen,
|
||||||
|
field: FieldWithRefs,
|
||||||
|
): Promise<FieldValueSelection | symbol> {
|
||||||
|
console.log("\n" + formatFieldHint(field) + "\n");
|
||||||
|
|
||||||
|
if (field.nullable) {
|
||||||
|
const action = await select({
|
||||||
|
message: "O que deseja fazer com este campo?",
|
||||||
|
options: [
|
||||||
|
{ value: "set", label: "Definir um valor" },
|
||||||
|
{ value: "clear", label: "Limpar campo (null)" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (isCancel(action)) return action;
|
||||||
|
if (action === "clear") {
|
||||||
|
return { value: null, displayLabel: "(vazio / null)" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pickListTypeName = getPickListEntityType(field);
|
||||||
|
if (!pickListTypeName) {
|
||||||
|
console.log(
|
||||||
|
"\nPicklist sem tipo de referência na metadata. Informe o ID manualmente.\n",
|
||||||
|
);
|
||||||
|
return promptManualEntityValue(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
return promptPickListSelection(clarizen, field, pickListTypeName);
|
||||||
|
}
|
||||||
+9
-1
@@ -111,8 +111,16 @@ export function parseFieldValue(
|
|||||||
return parseByType(raw, field.type);
|
return parseByType(raw, field.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatValueForDisplay(value: JsonValue): string {
|
export function formatValueForDisplay(
|
||||||
|
value: JsonValue,
|
||||||
|
displayLabel?: string,
|
||||||
|
): string {
|
||||||
|
if (displayLabel) return displayLabel;
|
||||||
if (value === null) return "(vazio / null)";
|
if (value === null) return "(vazio / null)";
|
||||||
if (typeof value === "string") return `"${value}"`;
|
if (typeof value === "string") return `"${value}"`;
|
||||||
|
if (typeof value === "object" && value !== null && "id" in value) {
|
||||||
|
const id = (value as { id?: unknown }).id;
|
||||||
|
if (typeof id === "string") return id;
|
||||||
|
}
|
||||||
return JSON.stringify(value);
|
return JSON.stringify(value);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user