@ferricstore/ferricstore 0.1.5 → 0.1.6
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 +20 -1
- package/dist/index.cjs +187 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -7
- package/dist/index.d.ts +39 -7
- package/dist/index.js +187 -11
- package/dist/index.js.map +1 -1
- package/docs/api/assets/hierarchy.js +1 -1
- package/docs/api/assets/navigation.js +1 -1
- package/docs/api/assets/search.js +1 -1
- package/docs/api/classes/FerricStoreClient.html +8 -2
- package/docs/api/classes/FerricStoreError.html +1 -1
- package/docs/api/classes/FlowAlreadyExistsError.html +1 -1
- package/docs/api/classes/FlowNotFoundError.html +1 -1
- package/docs/api/classes/FlowWrongStateError.html +1 -1
- package/docs/api/classes/InvalidCommandError.html +1 -1
- package/docs/api/classes/LockHeldError.html +1 -1
- package/docs/api/classes/LockNotOwnedError.html +1 -1
- package/docs/api/classes/OverloadedError.html +1 -1
- package/docs/api/classes/StaleLeaseError.html +1 -1
- package/docs/api/classes/Workflow.html +3 -2
- package/docs/api/classes/WorkflowClient.html +2 -2
- package/docs/api/classes/WorkflowContext.html +2 -2
- package/docs/api/classes/WorkflowFlowCommands.html +2 -2
- package/docs/api/classes/WorkflowWorker.html +2 -2
- package/docs/api/hierarchy.html +1 -1
- package/docs/api/index.html +6 -1
- package/docs/api/interfaces/CancelOptions.html +2 -2
- package/docs/api/interfaces/CompleteOptions.html +2 -2
- package/docs/api/interfaces/CompleteOutcome.html +1 -1
- package/docs/api/interfaces/CreateManyOptions.html +1 -1
- package/docs/api/interfaces/FailOptions.html +2 -2
- package/docs/api/interfaces/FailOutcome.html +1 -1
- package/docs/api/interfaces/FerricStoreClientFromUrlOptions.html +1 -1
- package/docs/api/interfaces/FerricStoreClientOptions.html +1 -1
- package/docs/api/interfaces/FlowPolicyOptions.html +7 -0
- package/docs/api/interfaces/FlowStatePolicy.html +3 -0
- package/docs/api/interfaces/InvocationCreateOptions.html +4 -0
- package/docs/api/interfaces/MutateOptions.html +2 -2
- package/docs/api/interfaces/NamedValueMutation.html +1 -1
- package/docs/api/interfaces/ReadOptions.html +2 -2
- package/docs/api/interfaces/ReclaimOptions.html +1 -1
- package/docs/api/interfaces/RequestContext.html +4 -0
- package/docs/api/interfaces/RequestContextOptions.html +2 -0
- package/docs/api/interfaces/RetryOptions.html +2 -2
- package/docs/api/interfaces/RetryOutcome.html +1 -1
- package/docs/api/interfaces/SearchOptions.html +2 -2
- package/docs/api/interfaces/StateOptions.html +3 -2
- package/docs/api/interfaces/StateRegistration.html +3 -2
- package/docs/api/interfaces/TDigestCreateOptions.html +1 -1
- package/docs/api/interfaces/TDigestMergeOptions.html +1 -1
- package/docs/api/interfaces/TransitionOptions.html +2 -2
- package/docs/api/interfaces/TransitionOutcome.html +1 -1
- package/docs/api/interfaces/WorkflowOptions.html +2 -2
- package/docs/api/interfaces/WorkflowWorkerResult.html +2 -2
- package/docs/api/modules.html +1 -1
- package/docs/api/types/FlowStateMode.html +1 -0
- package/docs/api/types/FlowStatePolicyLike.html +1 -0
- package/docs/api/types/ManagementPairs.html +1 -1
- package/docs/api/types/SearchStateMeta.html +1 -1
- package/docs/api/types/WorkflowHandler.html +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ const { FerricStoreClient, JsonCodec } = require("@ferricstore/ferricstore");
|
|
|
34
34
|
docker run -p 6388:6388 \
|
|
35
35
|
-e FERRICSTORE_PROTECTED_MODE=false \
|
|
36
36
|
-v ferricstore_data:/data \
|
|
37
|
-
ghcr.io/ferricstore/ferricstore:0.7.
|
|
37
|
+
ghcr.io/ferricstore/ferricstore:0.7.5
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
## Cluster-aware client
|
|
@@ -171,6 +171,25 @@ for (const job of jobs) {
|
|
|
171
171
|
}
|
|
172
172
|
```
|
|
173
173
|
|
|
174
|
+
FIFO Flow state policy is opt-in per state:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
await flow.installPolicy("email", {
|
|
178
|
+
states: {
|
|
179
|
+
queued: { mode: "fifo" }
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await flow.create("email-3", {
|
|
184
|
+
partitionKey: "tenant-a:email",
|
|
185
|
+
payload: Buffer.from("welcome"),
|
|
186
|
+
state: "queued",
|
|
187
|
+
type: "email"
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
FIFO states require a `partitionKey`; priority is for parallel states.
|
|
192
|
+
|
|
174
193
|
## FerricStore KV And Data Structures
|
|
175
194
|
|
|
176
195
|
The same client exposes typed helpers for FerricStore's Redis-compatible store commands:
|
package/dist/index.cjs
CHANGED
|
@@ -574,14 +574,64 @@ function commandExec(args) {
|
|
|
574
574
|
if (args.length === 0) {
|
|
575
575
|
throw new FerricStoreError("command requires at least one argument");
|
|
576
576
|
}
|
|
577
|
+
const commandArgs = args.slice(1);
|
|
578
|
+
const payload = {
|
|
579
|
+
args: commandArgs,
|
|
580
|
+
command: asText(args[0]).toUpperCase()
|
|
581
|
+
};
|
|
582
|
+
if (commandArgs.length >= 2 && commandTokenIs(commandArgs[commandArgs.length - 2], "REQUEST_CONTEXT")) {
|
|
583
|
+
const requestContext = normalizeRequestContext(commandArgs[commandArgs.length - 1]);
|
|
584
|
+
payload.args = commandArgs.slice(0, -2);
|
|
585
|
+
if (requestContext != null) {
|
|
586
|
+
payload.request_context = requestContext;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
577
589
|
return {
|
|
578
590
|
opcode: OPCODES.commandExec,
|
|
579
|
-
payload
|
|
580
|
-
args: args.slice(1),
|
|
581
|
-
command: asText(args[0]).toUpperCase()
|
|
582
|
-
}
|
|
591
|
+
payload
|
|
583
592
|
};
|
|
584
593
|
}
|
|
594
|
+
function normalizeRequestContext(context) {
|
|
595
|
+
if (!isPlainObject(context)) {
|
|
596
|
+
return void 0;
|
|
597
|
+
}
|
|
598
|
+
const out = {};
|
|
599
|
+
const subject = optionalText(context.subject);
|
|
600
|
+
const tenant = optionalText(context.tenant);
|
|
601
|
+
const scopes = normalizeRequestContextScopes(context.scopes);
|
|
602
|
+
if (subject != null) {
|
|
603
|
+
out.subject = subject;
|
|
604
|
+
}
|
|
605
|
+
if (tenant != null) {
|
|
606
|
+
out.tenant = tenant;
|
|
607
|
+
}
|
|
608
|
+
if (scopes.length > 0) {
|
|
609
|
+
out.scopes = scopes;
|
|
610
|
+
}
|
|
611
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
612
|
+
}
|
|
613
|
+
function normalizeRequestContextScopes(scopes) {
|
|
614
|
+
const values = typeof scopes === "string" || import_node_buffer.Buffer.isBuffer(scopes) || scopes instanceof Uint8Array ? asText(scopes).split(/\s+/u) : Array.isArray(scopes) ? scopes.map((scope) => optionalText(scope)).filter((scope) => scope != null) : [];
|
|
615
|
+
return [...new Set(values.filter((scope) => scope.length > 0))];
|
|
616
|
+
}
|
|
617
|
+
function optionalText(value) {
|
|
618
|
+
if (value == null || value === "") {
|
|
619
|
+
return void 0;
|
|
620
|
+
}
|
|
621
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean" || import_node_buffer.Buffer.isBuffer(value) || value instanceof Uint8Array) {
|
|
622
|
+
return asText(value);
|
|
623
|
+
}
|
|
624
|
+
return void 0;
|
|
625
|
+
}
|
|
626
|
+
function commandTokenIs(value, expected) {
|
|
627
|
+
if (typeof value === "string") {
|
|
628
|
+
return value.toUpperCase() === expected;
|
|
629
|
+
}
|
|
630
|
+
if (import_node_buffer.Buffer.isBuffer(value) || value instanceof Uint8Array) {
|
|
631
|
+
return asText(value).toUpperCase() === expected;
|
|
632
|
+
}
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
585
635
|
function pipelineCommand(commands) {
|
|
586
636
|
const compact = compactPipelinePayload(commands);
|
|
587
637
|
if (compact != null) {
|
|
@@ -4442,6 +4492,59 @@ var FerricStoreClient = class _FerricStoreClient {
|
|
|
4442
4492
|
);
|
|
4443
4493
|
return Array.isArray(response) ? [...response] : [];
|
|
4444
4494
|
}
|
|
4495
|
+
async invocationDefinitionPut(definition, options = {}) {
|
|
4496
|
+
return normalizeAdminResponse(
|
|
4497
|
+
await this.command(
|
|
4498
|
+
...commandWithRequestContext(
|
|
4499
|
+
"INVOCATION.DEFINITION.PUT",
|
|
4500
|
+
[jsonArg(definition)],
|
|
4501
|
+
options.requestContext
|
|
4502
|
+
)
|
|
4503
|
+
)
|
|
4504
|
+
);
|
|
4505
|
+
}
|
|
4506
|
+
async invocationDefinitionGet(name, options = {}) {
|
|
4507
|
+
return normalizeAdminResponse(
|
|
4508
|
+
await this.command(
|
|
4509
|
+
...commandWithRequestContext("INVOCATION.DEFINITION.GET", [name], options.requestContext)
|
|
4510
|
+
)
|
|
4511
|
+
);
|
|
4512
|
+
}
|
|
4513
|
+
async invocationDefinitionList(options = {}) {
|
|
4514
|
+
const response = normalizeAdminResponse(
|
|
4515
|
+
await this.command(
|
|
4516
|
+
...commandWithRequestContext("INVOCATION.DEFINITION.LIST", [], options.requestContext)
|
|
4517
|
+
)
|
|
4518
|
+
);
|
|
4519
|
+
return Array.isArray(response) ? [...response] : [];
|
|
4520
|
+
}
|
|
4521
|
+
async invocationCreate(name, attrs, options = {}) {
|
|
4522
|
+
const envelope = { attrs };
|
|
4523
|
+
if (options.context != null) {
|
|
4524
|
+
envelope.context = options.context;
|
|
4525
|
+
}
|
|
4526
|
+
if (options.idempotencyKey != null) {
|
|
4527
|
+
envelope.idempotency_key = options.idempotencyKey;
|
|
4528
|
+
}
|
|
4529
|
+
return normalizeAdminResponse(
|
|
4530
|
+
await this.command(
|
|
4531
|
+
...commandWithRequestContext("INVOCATION.CREATE", [name, jsonArg(envelope)], options.requestContext)
|
|
4532
|
+
)
|
|
4533
|
+
);
|
|
4534
|
+
}
|
|
4535
|
+
async invocationGet(id, options = {}) {
|
|
4536
|
+
return normalizeAdminResponse(
|
|
4537
|
+
await this.command(...commandWithRequestContext("INVOCATION.GET", [id], options.requestContext))
|
|
4538
|
+
);
|
|
4539
|
+
}
|
|
4540
|
+
async invocationPartitionList(name, options = {}) {
|
|
4541
|
+
const args = [name];
|
|
4542
|
+
append(args, "SCOPE", options.scope);
|
|
4543
|
+
const response = normalizeAdminResponse(
|
|
4544
|
+
await this.command(...commandWithRequestContext("INVOCATION.PARTITION.LIST", args, options.requestContext))
|
|
4545
|
+
);
|
|
4546
|
+
return Array.isArray(response) ? [...response] : [];
|
|
4547
|
+
}
|
|
4445
4548
|
async create(id, options) {
|
|
4446
4549
|
const currentNowMs = options.nowMs ?? nowMs();
|
|
4447
4550
|
const args = [
|
|
@@ -4474,7 +4577,6 @@ var FerricStoreClient = class _FerricStoreClient {
|
|
|
4474
4577
|
async enqueue(id, options) {
|
|
4475
4578
|
return await this.create(id, {
|
|
4476
4579
|
...options,
|
|
4477
|
-
priority: options.priority ?? 0,
|
|
4478
4580
|
state: options.state ?? "queued"
|
|
4479
4581
|
});
|
|
4480
4582
|
}
|
|
@@ -4486,7 +4588,6 @@ var FerricStoreClient = class _FerricStoreClient {
|
|
|
4486
4588
|
return await this.createMany(options.partitionKey, items, {
|
|
4487
4589
|
...options,
|
|
4488
4590
|
independent: options.independent ?? true,
|
|
4489
|
-
priority: options.priority ?? 0,
|
|
4490
4591
|
state: options.state ?? "queued"
|
|
4491
4592
|
});
|
|
4492
4593
|
}
|
|
@@ -4501,7 +4602,6 @@ var FerricStoreClient = class _FerricStoreClient {
|
|
|
4501
4602
|
const response = await this.createMany(bucket, groupItems, {
|
|
4502
4603
|
...options,
|
|
4503
4604
|
independent: options.independent ?? true,
|
|
4504
|
-
priority: options.priority ?? 0,
|
|
4505
4605
|
state: options.state ?? "queued"
|
|
4506
4606
|
});
|
|
4507
4607
|
const expanded = expandManyResponse(response, indexedItems.length);
|
|
@@ -4654,8 +4754,7 @@ var FerricStoreClient = class _FerricStoreClient {
|
|
|
4654
4754
|
...options,
|
|
4655
4755
|
includeState: options.includeState ?? false,
|
|
4656
4756
|
jobOnly: true,
|
|
4657
|
-
limit: options.limit ?? 100
|
|
4658
|
-
priority: options.priority ?? 0
|
|
4757
|
+
limit: options.limit ?? 100
|
|
4659
4758
|
});
|
|
4660
4759
|
}
|
|
4661
4760
|
async reclaim(type, options) {
|
|
@@ -5006,9 +5105,14 @@ var FerricStoreClient = class _FerricStoreClient {
|
|
|
5006
5105
|
const args = ["FLOW.POLICY.SET", type];
|
|
5007
5106
|
append(args, "INDEXED_STATE_META", options.indexedStateMeta);
|
|
5008
5107
|
append(args, "STATE", options.state);
|
|
5108
|
+
appendPolicyMode(args, options.state, options.mode);
|
|
5009
5109
|
if (options.retry != null) {
|
|
5010
5110
|
appendRetryPolicy(args, options.retry);
|
|
5011
5111
|
}
|
|
5112
|
+
for (const [state, policy] of Object.entries(options.states ?? {})) {
|
|
5113
|
+
args.push("STATE", state);
|
|
5114
|
+
appendFlowStatePolicy(args, policy);
|
|
5115
|
+
}
|
|
5012
5116
|
append(args, "RETENTION_TTL_MS", options.retentionTtlMs);
|
|
5013
5117
|
return await this.command(...args);
|
|
5014
5118
|
}
|
|
@@ -5388,6 +5492,16 @@ function appendSearchStateMeta(args, state, stateMeta) {
|
|
|
5388
5492
|
function normalizeAdminResponse(value) {
|
|
5389
5493
|
return normalizeRefMeta(value);
|
|
5390
5494
|
}
|
|
5495
|
+
function jsonArg(value) {
|
|
5496
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
5497
|
+
}
|
|
5498
|
+
function commandWithRequestContext(command, args, requestContext) {
|
|
5499
|
+
const commandArgs = [command, ...args];
|
|
5500
|
+
if (requestContext != null) {
|
|
5501
|
+
commandArgs.push("REQUEST_CONTEXT", requestContext);
|
|
5502
|
+
}
|
|
5503
|
+
return commandArgs;
|
|
5504
|
+
}
|
|
5391
5505
|
function infoText(value) {
|
|
5392
5506
|
if (typeof value === "string" || Buffer.isBuffer(value)) {
|
|
5393
5507
|
return text(value);
|
|
@@ -5419,6 +5533,19 @@ function managementPairArgs(pairs2) {
|
|
|
5419
5533
|
}
|
|
5420
5534
|
return args;
|
|
5421
5535
|
}
|
|
5536
|
+
function appendFlowStatePolicy(args, policy) {
|
|
5537
|
+
if (isFlowStatePolicy(policy)) {
|
|
5538
|
+
appendPolicyMode(args, "state", policy.mode);
|
|
5539
|
+
if (policy.retry != null) {
|
|
5540
|
+
appendRetryPolicy(args, policy.retry);
|
|
5541
|
+
}
|
|
5542
|
+
return;
|
|
5543
|
+
}
|
|
5544
|
+
appendRetryPolicy(args, policy);
|
|
5545
|
+
}
|
|
5546
|
+
function isFlowStatePolicy(policy) {
|
|
5547
|
+
return typeof policy === "object" && policy != null && ("mode" in policy || "retry" in policy);
|
|
5548
|
+
}
|
|
5422
5549
|
function ruleArgs(rules) {
|
|
5423
5550
|
return typeof rules === "string" ? [rules] : [...rules];
|
|
5424
5551
|
}
|
|
@@ -5511,6 +5638,24 @@ function appendRetryPolicy(args, policy) {
|
|
|
5511
5638
|
append(args, "JITTER_PCT", policy.jitterPct);
|
|
5512
5639
|
append(args, "EXHAUSTED_TO", policy.exhaustedTo);
|
|
5513
5640
|
}
|
|
5641
|
+
function appendPolicyMode(args, state, mode) {
|
|
5642
|
+
if (mode == null) {
|
|
5643
|
+
return;
|
|
5644
|
+
}
|
|
5645
|
+
if (state == null) {
|
|
5646
|
+
throw new Error("policy mode requires state");
|
|
5647
|
+
}
|
|
5648
|
+
switch (mode) {
|
|
5649
|
+
case "fifo":
|
|
5650
|
+
args.push("MODE", "FIFO");
|
|
5651
|
+
return;
|
|
5652
|
+
case "parallel":
|
|
5653
|
+
args.push("MODE", "PARALLEL");
|
|
5654
|
+
return;
|
|
5655
|
+
default:
|
|
5656
|
+
throw new Error("policy mode must be 'fifo' or 'parallel'");
|
|
5657
|
+
}
|
|
5658
|
+
}
|
|
5514
5659
|
function appendPayloadRead(args, payload, maxBytes) {
|
|
5515
5660
|
if (payload === false) {
|
|
5516
5661
|
args.push("NOPAYLOAD");
|
|
@@ -5975,6 +6120,7 @@ var Workflow = class {
|
|
|
5975
6120
|
exceptionPolicy: normalizeExceptionPolicy(options.exceptionPolicy),
|
|
5976
6121
|
handler,
|
|
5977
6122
|
leaseMs: options.leaseMs ?? 3e4,
|
|
6123
|
+
...options.mode == null ? {} : { mode: normalizeStateMode(options.mode) },
|
|
5978
6124
|
name,
|
|
5979
6125
|
returnRecord: options.returnRecord ?? false,
|
|
5980
6126
|
valueMaxBytes: options.valueMaxBytes ?? this.valueConfig.valueMaxBytes,
|
|
@@ -6009,6 +6155,21 @@ var Workflow = class {
|
|
|
6009
6155
|
worker(options = {}) {
|
|
6010
6156
|
return new WorkflowWorker(this, options);
|
|
6011
6157
|
}
|
|
6158
|
+
async installPolicy(options = {}) {
|
|
6159
|
+
const states = {};
|
|
6160
|
+
for (const registration of this.states.values()) {
|
|
6161
|
+
if (registration.mode != null || registration.retryPolicy != null) {
|
|
6162
|
+
states[registration.name] = {
|
|
6163
|
+
...registration.mode == null ? {} : { mode: registration.mode },
|
|
6164
|
+
...registration.retryPolicy == null ? {} : { retry: registration.retryPolicy }
|
|
6165
|
+
};
|
|
6166
|
+
}
|
|
6167
|
+
}
|
|
6168
|
+
return await this.client.installPolicy(this.type, {
|
|
6169
|
+
...options,
|
|
6170
|
+
states
|
|
6171
|
+
});
|
|
6172
|
+
}
|
|
6012
6173
|
stateNames() {
|
|
6013
6174
|
return [...this.states.keys()];
|
|
6014
6175
|
}
|
|
@@ -6269,13 +6430,15 @@ var WorkflowWorker = class {
|
|
|
6269
6430
|
}
|
|
6270
6431
|
async applyJob(job, registration) {
|
|
6271
6432
|
const ctx = new WorkflowContext(this.workflow, job, registration.name);
|
|
6433
|
+
let outcome;
|
|
6272
6434
|
try {
|
|
6273
6435
|
const value = await registration.handler(ctx);
|
|
6274
|
-
|
|
6275
|
-
await applyOutcome(this.workflow.client, ctx, outcome, registration.returnRecord);
|
|
6436
|
+
outcome = isOutcome(value) ? value : complete({ result: value });
|
|
6276
6437
|
} catch (error) {
|
|
6277
6438
|
await this.applyHandlerError(ctx, registration, error);
|
|
6439
|
+
return;
|
|
6278
6440
|
}
|
|
6441
|
+
await applyOutcome(this.workflow.client, ctx, outcome, registration.returnRecord);
|
|
6279
6442
|
}
|
|
6280
6443
|
async applyHandlerError(ctx, registration, error) {
|
|
6281
6444
|
const policy = this.options.exceptionPolicy ?? registration.exceptionPolicy;
|
|
@@ -6306,6 +6469,7 @@ async function applyOutcome(client, ctx, outcome, returnRecord) {
|
|
|
6306
6469
|
}
|
|
6307
6470
|
}
|
|
6308
6471
|
async function applyTransition(client, ctx, outcome, returnRecord) {
|
|
6472
|
+
validateTransitionPolicy(ctx, outcome);
|
|
6309
6473
|
await client.transition(ctx.id, {
|
|
6310
6474
|
dropValues: outcome.dropValues,
|
|
6311
6475
|
fencingToken: ctx.fencingToken,
|
|
@@ -6323,6 +6487,12 @@ async function applyTransition(client, ctx, outcome, returnRecord) {
|
|
|
6323
6487
|
values: outcome.values
|
|
6324
6488
|
});
|
|
6325
6489
|
}
|
|
6490
|
+
function validateTransitionPolicy(ctx, outcome) {
|
|
6491
|
+
const target = ctx.workflow.stateRegistration(outcome.toState);
|
|
6492
|
+
if (target?.mode === "fifo" && outcome.priority != null) {
|
|
6493
|
+
throw new Error("priority is not supported for fifo state");
|
|
6494
|
+
}
|
|
6495
|
+
}
|
|
6326
6496
|
async function applyComplete(client, ctx, outcome, returnRecord) {
|
|
6327
6497
|
await client.complete(ctx.id, {
|
|
6328
6498
|
dropValues: outcome.dropValues,
|
|
@@ -6381,6 +6551,12 @@ function errorToPayload(error) {
|
|
|
6381
6551
|
}
|
|
6382
6552
|
return error;
|
|
6383
6553
|
}
|
|
6554
|
+
function normalizeStateMode(mode) {
|
|
6555
|
+
if (mode === "fifo" || mode === "parallel") {
|
|
6556
|
+
return mode;
|
|
6557
|
+
}
|
|
6558
|
+
throw new Error("state mode must be 'fifo' or 'parallel'");
|
|
6559
|
+
}
|
|
6384
6560
|
function valueRefToString(value) {
|
|
6385
6561
|
if (typeof value === "string") {
|
|
6386
6562
|
return value;
|