@dyrected/core 2.9.0 → 2.10.1
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/dist/{app-config-gEQJFvnN.d.cts → app-config-BLC7WoyL.d.cts} +439 -44
- package/dist/{app-config-gEQJFvnN.d.ts → app-config-BLC7WoyL.d.ts} +439 -44
- package/dist/{chunk-XRIONGRY.js → chunk-H7DVVW2P.js} +115 -30
- package/dist/index.cjs +526 -67
- package/dist/index.d.cts +401 -86
- package/dist/index.d.ts +401 -86
- package/dist/index.js +408 -38
- package/dist/server.cjs +160 -39
- package/dist/server.d.cts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +45 -10
- package/dist/{where-sanitizer-3MNFMYA2.js → where-sanitizer-47QPJQ6O.js} +3 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2300,8 +2300,10 @@ var index_exports = {};
|
|
|
2300
2300
|
__export(index_exports, {
|
|
2301
2301
|
BUILTIN_JEXL_HELPERS: () => BUILTIN_JEXL_HELPERS,
|
|
2302
2302
|
ConfigValidationError: () => ConfigValidationError,
|
|
2303
|
+
FieldConditionBuilder: () => FieldConditionBuilder,
|
|
2303
2304
|
LIFECYCLE_EVENTS_COLLECTION: () => LIFECYCLE_EVENTS_COLLECTION,
|
|
2304
2305
|
LIFECYCLE_EVENT_NAMES: () => LIFECYCLE_EVENT_NAMES,
|
|
2306
|
+
MatchBuilder: () => MatchBuilder,
|
|
2305
2307
|
WORKFLOW_HISTORY_COLLECTION: () => WORKFLOW_HISTORY_COLLECTION,
|
|
2306
2308
|
abs: () => abs,
|
|
2307
2309
|
addDays: () => addDays,
|
|
@@ -2383,6 +2385,7 @@ __export(index_exports, {
|
|
|
2383
2385
|
floor: () => floor,
|
|
2384
2386
|
formatConfigDiagnostics: () => formatConfigDiagnostics,
|
|
2385
2387
|
formatDate: () => formatDate,
|
|
2388
|
+
formatJexlValue: () => formatJexlValue,
|
|
2386
2389
|
generateDefaultDetailSchema: () => generateDefaultDetailSchema,
|
|
2387
2390
|
generateOpenApi: () => generateOpenApi,
|
|
2388
2391
|
get: () => get,
|
|
@@ -2434,6 +2437,7 @@ __export(index_exports, {
|
|
|
2434
2437
|
validateDeclarativeAccessExpression: () => validateDeclarativeAccessExpression,
|
|
2435
2438
|
validateDeclarativeHookExpression: () => validateDeclarativeHookExpression,
|
|
2436
2439
|
validatePreviewUrlExpression: () => validatePreviewUrlExpression,
|
|
2440
|
+
when: () => when,
|
|
2437
2441
|
wordCount: () => wordCount,
|
|
2438
2442
|
workflowCapabilities: () => workflowCapabilities
|
|
2439
2443
|
});
|
|
@@ -2449,37 +2453,10 @@ var LIFECYCLE_EVENT_NAMES = [
|
|
|
2449
2453
|
|
|
2450
2454
|
// src/types/views.ts
|
|
2451
2455
|
function defineView(config) {
|
|
2452
|
-
return
|
|
2453
|
-
slug: config.slug,
|
|
2454
|
-
label: config.label,
|
|
2455
|
-
icon: config.icon,
|
|
2456
|
-
layout: config.layout ?? "table",
|
|
2457
|
-
filter: config.filter,
|
|
2458
|
-
groupBy: config.groupBy,
|
|
2459
|
-
dateField: config.dateField,
|
|
2460
|
-
startDateField: config.startDateField,
|
|
2461
|
-
endDateField: config.endDateField,
|
|
2462
|
-
columns: config.columns,
|
|
2463
|
-
sort: config.sort,
|
|
2464
|
-
actions: config.actions,
|
|
2465
|
-
features: config.features,
|
|
2466
|
-
actionOrder: config.actionOrder,
|
|
2467
|
-
metrics: config.metrics,
|
|
2468
|
-
access: config.access
|
|
2469
|
-
};
|
|
2456
|
+
return config;
|
|
2470
2457
|
}
|
|
2471
2458
|
function defineAction(config) {
|
|
2472
|
-
return
|
|
2473
|
-
name: config.name,
|
|
2474
|
-
label: config.label,
|
|
2475
|
-
icon: config.icon,
|
|
2476
|
-
type: config.type ?? "row",
|
|
2477
|
-
confirm: config.confirm,
|
|
2478
|
-
fields: config.fields,
|
|
2479
|
-
mutation: config.mutation,
|
|
2480
|
-
handler: config.handler,
|
|
2481
|
-
access: config.access
|
|
2482
|
-
};
|
|
2459
|
+
return config;
|
|
2483
2460
|
}
|
|
2484
2461
|
|
|
2485
2462
|
// src/auth/token.ts
|
|
@@ -3303,6 +3280,11 @@ function registerJexlHelpers(jexlInstance = import_jexl.default) {
|
|
|
3303
3280
|
try {
|
|
3304
3281
|
jexlInstance.addBinaryOp("and", 20, (left, right) => Boolean(left && right));
|
|
3305
3282
|
jexlInstance.addBinaryOp("or", 10, (left, right) => Boolean(left || right));
|
|
3283
|
+
jexlInstance.addBinaryOp("in", 30, (left, right) => {
|
|
3284
|
+
if (Array.isArray(right)) return right.includes(left);
|
|
3285
|
+
if (typeof right === "string") return right.includes(String(left ?? ""));
|
|
3286
|
+
return false;
|
|
3287
|
+
});
|
|
3306
3288
|
} catch {
|
|
3307
3289
|
}
|
|
3308
3290
|
}
|
|
@@ -4298,7 +4280,7 @@ function normalizeConfig(config) {
|
|
|
4298
4280
|
function assertNever(op, context) {
|
|
4299
4281
|
throw new Error(`[dyrected/core] Unhandled where operator "${op}" in ${context}`);
|
|
4300
4282
|
}
|
|
4301
|
-
function parseSqlWhere(where, getJsonField, placeholder = "?", likeOperator = "LIKE") {
|
|
4283
|
+
function parseSqlWhere(where, getJsonField, placeholder = "?", likeOperator = placeholder === "pg" ? "ILIKE" : "LIKE") {
|
|
4302
4284
|
const params = [];
|
|
4303
4285
|
let pgIndex = 1;
|
|
4304
4286
|
function next() {
|
|
@@ -4309,8 +4291,16 @@ function parseSqlWhere(where, getJsonField, placeholder = "?", likeOperator = "L
|
|
|
4309
4291
|
}
|
|
4310
4292
|
function buildOperator(field, value) {
|
|
4311
4293
|
const c = col(field);
|
|
4294
|
+
const isJsonExtract = c.startsWith("data->>") || c.startsWith("json_extract(") || c.startsWith("JSON_UNQUOTE(");
|
|
4312
4295
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
4313
|
-
|
|
4296
|
+
if (value === null) {
|
|
4297
|
+
return `${c} IS NULL`;
|
|
4298
|
+
}
|
|
4299
|
+
if (value === "" && placeholder === "pg") {
|
|
4300
|
+
params.push("");
|
|
4301
|
+
return `(${c})::text = ${next()}`;
|
|
4302
|
+
}
|
|
4303
|
+
params.push(typeof value === "boolean" && isJsonExtract ? String(value) : value);
|
|
4314
4304
|
return `${c} = ${next()}`;
|
|
4315
4305
|
}
|
|
4316
4306
|
const entries = Object.entries(value);
|
|
@@ -4321,18 +4311,63 @@ function parseSqlWhere(where, getJsonField, placeholder = "?", likeOperator = "L
|
|
|
4321
4311
|
return buildSingleOp(c, op, operand);
|
|
4322
4312
|
}
|
|
4323
4313
|
function buildSingleOp(c, op, operand) {
|
|
4314
|
+
const isJsonExtract = c.startsWith("data->>") || c.startsWith("json_extract(") || c.startsWith("JSON_UNQUOTE(");
|
|
4324
4315
|
switch (op) {
|
|
4325
4316
|
case "equals":
|
|
4326
|
-
|
|
4317
|
+
if (operand === null) {
|
|
4318
|
+
return `${c} IS NULL`;
|
|
4319
|
+
}
|
|
4320
|
+
if (operand === "" && placeholder === "pg") {
|
|
4321
|
+
params.push("");
|
|
4322
|
+
return `(${c})::text = ${next()}`;
|
|
4323
|
+
}
|
|
4324
|
+
if (typeof operand === "boolean") {
|
|
4325
|
+
if (placeholder === "?") {
|
|
4326
|
+
params.push(operand ? 1 : 0);
|
|
4327
|
+
} else if (isJsonExtract) {
|
|
4328
|
+
params.push(String(operand));
|
|
4329
|
+
} else {
|
|
4330
|
+
params.push(operand);
|
|
4331
|
+
}
|
|
4332
|
+
} else {
|
|
4333
|
+
params.push(operand);
|
|
4334
|
+
}
|
|
4327
4335
|
return `${c} = ${next()}`;
|
|
4328
4336
|
case "not_equals":
|
|
4329
|
-
|
|
4337
|
+
if (operand === null) {
|
|
4338
|
+
return `${c} IS NOT NULL`;
|
|
4339
|
+
}
|
|
4340
|
+
if (operand === "" && placeholder === "pg") {
|
|
4341
|
+
params.push("");
|
|
4342
|
+
return `(${c})::text != ${next()}`;
|
|
4343
|
+
}
|
|
4344
|
+
if (typeof operand === "boolean") {
|
|
4345
|
+
if (placeholder === "?") {
|
|
4346
|
+
params.push(operand ? 1 : 0);
|
|
4347
|
+
} else if (isJsonExtract) {
|
|
4348
|
+
params.push(String(operand));
|
|
4349
|
+
} else {
|
|
4350
|
+
params.push(operand);
|
|
4351
|
+
}
|
|
4352
|
+
} else {
|
|
4353
|
+
params.push(operand);
|
|
4354
|
+
}
|
|
4330
4355
|
return `${c} != ${next()}`;
|
|
4331
4356
|
case "in": {
|
|
4332
4357
|
const vals = Array.isArray(operand) ? operand : [operand];
|
|
4333
4358
|
if (vals.length === 0) return "1=0";
|
|
4334
4359
|
const placeholders = vals.map((v) => {
|
|
4335
|
-
|
|
4360
|
+
if (typeof v === "boolean") {
|
|
4361
|
+
if (placeholder === "?") {
|
|
4362
|
+
params.push(v ? 1 : 0);
|
|
4363
|
+
} else if (isJsonExtract) {
|
|
4364
|
+
params.push(String(v));
|
|
4365
|
+
} else {
|
|
4366
|
+
params.push(v);
|
|
4367
|
+
}
|
|
4368
|
+
} else {
|
|
4369
|
+
params.push(v);
|
|
4370
|
+
}
|
|
4336
4371
|
return next();
|
|
4337
4372
|
});
|
|
4338
4373
|
return `${c} IN (${placeholders.join(", ")})`;
|
|
@@ -4341,7 +4376,7 @@ function parseSqlWhere(where, getJsonField, placeholder = "?", likeOperator = "L
|
|
|
4341
4376
|
const vals = Array.isArray(operand) ? operand : [operand];
|
|
4342
4377
|
if (vals.length === 0) return "1=1";
|
|
4343
4378
|
const placeholders = vals.map((v) => {
|
|
4344
|
-
params.push(typeof v === "boolean" ? String(v) : v);
|
|
4379
|
+
params.push(typeof v === "boolean" && isJsonExtract ? String(v) : v);
|
|
4345
4380
|
return next();
|
|
4346
4381
|
});
|
|
4347
4382
|
return `${c} NOT IN (${placeholders.join(", ")})`;
|
|
@@ -4367,10 +4402,10 @@ function parseSqlWhere(where, getJsonField, placeholder = "?", likeOperator = "L
|
|
|
4367
4402
|
case "contains":
|
|
4368
4403
|
case "like":
|
|
4369
4404
|
params.push(`%${operand}%`);
|
|
4370
|
-
return `${c} ${likeOperator} ${next()}`;
|
|
4405
|
+
return placeholder === "pg" ? `(${c})::text ${likeOperator} ${next()}` : `${c} ${likeOperator} ${next()}`;
|
|
4371
4406
|
case "starts_with":
|
|
4372
4407
|
params.push(`${operand}%`);
|
|
4373
|
-
return `${c} ${likeOperator} ${next()}`;
|
|
4408
|
+
return placeholder === "pg" ? `(${c})::text ${likeOperator} ${next()}` : `${c} ${likeOperator} ${next()}`;
|
|
4374
4409
|
case "exists":
|
|
4375
4410
|
return operand ? `${c} IS NOT NULL` : `${c} IS NULL`;
|
|
4376
4411
|
default:
|
|
@@ -5151,39 +5186,94 @@ function generateOpenApi(config) {
|
|
|
5151
5186
|
post: {
|
|
5152
5187
|
tags: [collectionTag],
|
|
5153
5188
|
summary: `Aggregate ${labels.plural}`,
|
|
5154
|
-
description: `Compute statistical aggregations (count, sum, avg, min, max) across ${labels.plural}.`,
|
|
5189
|
+
description: `Compute statistical aggregations (count, countDistinct, distinct, sum, avg, min, max, groupBy) across ${labels.plural}.`,
|
|
5190
|
+
parameters: [
|
|
5191
|
+
{
|
|
5192
|
+
name: "groupBy",
|
|
5193
|
+
in: "query",
|
|
5194
|
+
description: "Optional field name to compute aggregates grouped by distinct values",
|
|
5195
|
+
required: false,
|
|
5196
|
+
schema: { type: "string" }
|
|
5197
|
+
}
|
|
5198
|
+
],
|
|
5155
5199
|
requestBody: {
|
|
5156
5200
|
required: true,
|
|
5157
5201
|
content: {
|
|
5158
5202
|
"application/json": {
|
|
5159
5203
|
schema: {
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
sum: { type: "string", description: "Field to sum" },
|
|
5166
|
-
avg: { type: "string", description: "Field to average" },
|
|
5167
|
-
min: { type: "string", description: "Field for minimum" },
|
|
5168
|
-
max: { type: "string", description: "Field for maximum" },
|
|
5169
|
-
cast: {
|
|
5170
|
-
type: "string",
|
|
5171
|
-
enum: [
|
|
5172
|
-
"number",
|
|
5173
|
-
"integer",
|
|
5174
|
-
"float",
|
|
5175
|
-
"boolean",
|
|
5176
|
-
"date",
|
|
5177
|
-
"string"
|
|
5178
|
-
]
|
|
5179
|
-
},
|
|
5180
|
-
where: {
|
|
5204
|
+
oneOf: [
|
|
5205
|
+
{
|
|
5206
|
+
type: "object",
|
|
5207
|
+
description: "Flat aggregate operations dictionary",
|
|
5208
|
+
additionalProperties: {
|
|
5181
5209
|
type: "object",
|
|
5182
|
-
|
|
5183
|
-
|
|
5210
|
+
properties: {
|
|
5211
|
+
count: { type: "string", example: "*" },
|
|
5212
|
+
countDistinct: { type: "string", description: "Field to count unique non-null values" },
|
|
5213
|
+
distinct: { type: "string", description: "Field to extract unique non-null values" },
|
|
5214
|
+
sum: { type: "string", description: "Field to sum" },
|
|
5215
|
+
avg: { type: "string", description: "Field to average" },
|
|
5216
|
+
min: { type: "string", description: "Field for minimum" },
|
|
5217
|
+
max: { type: "string", description: "Field for maximum" },
|
|
5218
|
+
cast: {
|
|
5219
|
+
type: "string",
|
|
5220
|
+
enum: [
|
|
5221
|
+
"number",
|
|
5222
|
+
"integer",
|
|
5223
|
+
"float",
|
|
5224
|
+
"boolean",
|
|
5225
|
+
"date",
|
|
5226
|
+
"string"
|
|
5227
|
+
]
|
|
5228
|
+
},
|
|
5229
|
+
where: {
|
|
5230
|
+
type: "object",
|
|
5231
|
+
description: "Per-aggregate filter condition",
|
|
5232
|
+
additionalProperties: true
|
|
5233
|
+
}
|
|
5234
|
+
}
|
|
5184
5235
|
}
|
|
5236
|
+
},
|
|
5237
|
+
{
|
|
5238
|
+
type: "object",
|
|
5239
|
+
description: "Grouped aggregate payload with explicit groupBy property",
|
|
5240
|
+
properties: {
|
|
5241
|
+
groupBy: { type: "string", description: "Field name to group aggregates by" },
|
|
5242
|
+
aggregates: {
|
|
5243
|
+
type: "object",
|
|
5244
|
+
additionalProperties: {
|
|
5245
|
+
type: "object",
|
|
5246
|
+
properties: {
|
|
5247
|
+
count: { type: "string", example: "*" },
|
|
5248
|
+
countDistinct: { type: "string", description: "Field to count unique non-null values" },
|
|
5249
|
+
distinct: { type: "string", description: "Field to extract unique non-null values" },
|
|
5250
|
+
sum: { type: "string", description: "Field to sum" },
|
|
5251
|
+
avg: { type: "string", description: "Field to average" },
|
|
5252
|
+
min: { type: "string", description: "Field for minimum" },
|
|
5253
|
+
max: { type: "string", description: "Field for maximum" },
|
|
5254
|
+
cast: {
|
|
5255
|
+
type: "string",
|
|
5256
|
+
enum: [
|
|
5257
|
+
"number",
|
|
5258
|
+
"integer",
|
|
5259
|
+
"float",
|
|
5260
|
+
"boolean",
|
|
5261
|
+
"date",
|
|
5262
|
+
"string"
|
|
5263
|
+
]
|
|
5264
|
+
},
|
|
5265
|
+
where: {
|
|
5266
|
+
type: "object",
|
|
5267
|
+
description: "Per-aggregate filter condition",
|
|
5268
|
+
additionalProperties: true
|
|
5269
|
+
}
|
|
5270
|
+
}
|
|
5271
|
+
}
|
|
5272
|
+
}
|
|
5273
|
+
},
|
|
5274
|
+
required: ["aggregates"]
|
|
5185
5275
|
}
|
|
5186
|
-
|
|
5276
|
+
]
|
|
5187
5277
|
}
|
|
5188
5278
|
}
|
|
5189
5279
|
}
|
|
@@ -5194,11 +5284,36 @@ function generateOpenApi(config) {
|
|
|
5194
5284
|
content: {
|
|
5195
5285
|
"application/json": {
|
|
5196
5286
|
schema: {
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5287
|
+
oneOf: [
|
|
5288
|
+
{
|
|
5289
|
+
type: "object",
|
|
5290
|
+
description: "Flat aggregate results map",
|
|
5291
|
+
additionalProperties: {
|
|
5292
|
+
oneOf: [
|
|
5293
|
+
{ type: "number", nullable: true },
|
|
5294
|
+
{ type: "array", items: { type: "string" } }
|
|
5295
|
+
]
|
|
5296
|
+
}
|
|
5297
|
+
},
|
|
5298
|
+
{
|
|
5299
|
+
type: "object",
|
|
5300
|
+
description: "Grouped aggregate results breakdown",
|
|
5301
|
+
properties: {
|
|
5302
|
+
groups: {
|
|
5303
|
+
type: "object",
|
|
5304
|
+
additionalProperties: {
|
|
5305
|
+
type: "object",
|
|
5306
|
+
additionalProperties: {
|
|
5307
|
+
oneOf: [
|
|
5308
|
+
{ type: "number", nullable: true },
|
|
5309
|
+
{ type: "array", items: { type: "string" } }
|
|
5310
|
+
]
|
|
5311
|
+
}
|
|
5312
|
+
}
|
|
5313
|
+
}
|
|
5314
|
+
}
|
|
5315
|
+
}
|
|
5316
|
+
]
|
|
5202
5317
|
}
|
|
5203
5318
|
}
|
|
5204
5319
|
}
|
|
@@ -5972,6 +6087,346 @@ function isDetailItemVisible(item, doc, user) {
|
|
|
5972
6087
|
return true;
|
|
5973
6088
|
}
|
|
5974
6089
|
|
|
6090
|
+
// src/utils/conditions.ts
|
|
6091
|
+
function formatJexlValue(v) {
|
|
6092
|
+
if (v === null || v === void 0) return "null";
|
|
6093
|
+
if (typeof v === "boolean" || typeof v === "number") return String(v);
|
|
6094
|
+
if (typeof v === "string") {
|
|
6095
|
+
if (v.startsWith("'") && v.endsWith("'") || v.startsWith('"') && v.endsWith('"') || v.includes("+") || v.includes("?") || v.includes("(") || v.includes(" ")) {
|
|
6096
|
+
return v;
|
|
6097
|
+
}
|
|
6098
|
+
if (v.startsWith("/")) {
|
|
6099
|
+
return `'${v}'`;
|
|
6100
|
+
}
|
|
6101
|
+
if (v === "null" || v === "true" || v === "false") {
|
|
6102
|
+
return v;
|
|
6103
|
+
}
|
|
6104
|
+
if (/^[a-zA-Z_$][a-zA-Z0-9_$.]*$/.test(v)) {
|
|
6105
|
+
return v;
|
|
6106
|
+
}
|
|
6107
|
+
return `'${v}'`;
|
|
6108
|
+
}
|
|
6109
|
+
return String(v);
|
|
6110
|
+
}
|
|
6111
|
+
var MatchBuilder = class {
|
|
6112
|
+
branches = [];
|
|
6113
|
+
/**
|
|
6114
|
+
* Adds a conditional branch (`condition ? value : ...`).
|
|
6115
|
+
*/
|
|
6116
|
+
case(condition, value) {
|
|
6117
|
+
this.branches.push({ condition, value });
|
|
6118
|
+
return this;
|
|
6119
|
+
}
|
|
6120
|
+
/**
|
|
6121
|
+
* Final fallback value when no cases match.
|
|
6122
|
+
*/
|
|
6123
|
+
otherwise(fallback = "null") {
|
|
6124
|
+
if (this.branches.length === 0) return formatJexlValue(fallback);
|
|
6125
|
+
let expr = formatJexlValue(fallback);
|
|
6126
|
+
for (let i = this.branches.length - 1; i >= 0; i--) {
|
|
6127
|
+
const b = this.branches[i];
|
|
6128
|
+
expr = `${b.condition} ? ${formatJexlValue(b.value)} : ${expr}`;
|
|
6129
|
+
}
|
|
6130
|
+
return expr;
|
|
6131
|
+
}
|
|
6132
|
+
};
|
|
6133
|
+
var FieldConditionBuilder = class {
|
|
6134
|
+
constructor(field) {
|
|
6135
|
+
this.field = field;
|
|
6136
|
+
}
|
|
6137
|
+
field;
|
|
6138
|
+
/**
|
|
6139
|
+
* Generates equality condition (`field == value`).
|
|
6140
|
+
*/
|
|
6141
|
+
equals(value) {
|
|
6142
|
+
return `${this.field} == ${JSON.stringify(value)}`;
|
|
6143
|
+
}
|
|
6144
|
+
/**
|
|
6145
|
+
* Generates inequality condition (`field != value`).
|
|
6146
|
+
*/
|
|
6147
|
+
notEquals(value) {
|
|
6148
|
+
return `${this.field} != ${JSON.stringify(value)}`;
|
|
6149
|
+
}
|
|
6150
|
+
/**
|
|
6151
|
+
* Generates set membership condition (`field in [val1, val2]`).
|
|
6152
|
+
*/
|
|
6153
|
+
in(...values) {
|
|
6154
|
+
const flat = values.flat();
|
|
6155
|
+
return `${this.field} in [${flat.map((v) => JSON.stringify(v)).join(", ")}]`;
|
|
6156
|
+
}
|
|
6157
|
+
/**
|
|
6158
|
+
* Generates inverted set membership condition (`!(field in [val1, val2])`).
|
|
6159
|
+
*/
|
|
6160
|
+
notIn(...values) {
|
|
6161
|
+
const flat = values.flat();
|
|
6162
|
+
return `!(${this.field} in [${flat.map((v) => JSON.stringify(v)).join(", ")}])`;
|
|
6163
|
+
}
|
|
6164
|
+
/**
|
|
6165
|
+
* Generates boolean true check (`field == true`).
|
|
6166
|
+
*/
|
|
6167
|
+
isTrue() {
|
|
6168
|
+
return `${this.field} == true`;
|
|
6169
|
+
}
|
|
6170
|
+
/**
|
|
6171
|
+
* Generates boolean false check (`field != true`).
|
|
6172
|
+
*/
|
|
6173
|
+
isFalse() {
|
|
6174
|
+
return `${this.field} != true`;
|
|
6175
|
+
}
|
|
6176
|
+
/**
|
|
6177
|
+
* Checks that the field is neither null/undefined nor an empty string.
|
|
6178
|
+
*/
|
|
6179
|
+
notEmpty() {
|
|
6180
|
+
return `${this.field} != null && ${this.field} != ''`;
|
|
6181
|
+
}
|
|
6182
|
+
/**
|
|
6183
|
+
* Checks that the field is null, undefined, or an empty string.
|
|
6184
|
+
*/
|
|
6185
|
+
isEmpty() {
|
|
6186
|
+
return `${this.field} == null || ${this.field} == ''`;
|
|
6187
|
+
}
|
|
6188
|
+
/**
|
|
6189
|
+
* Numerical greater than (`field > num`).
|
|
6190
|
+
*/
|
|
6191
|
+
greaterThan(num) {
|
|
6192
|
+
return `${this.field} > ${typeof num === "number" ? num : num}`;
|
|
6193
|
+
}
|
|
6194
|
+
/**
|
|
6195
|
+
* Numerical greater than or equal (`field >= num`).
|
|
6196
|
+
*/
|
|
6197
|
+
greaterThanOrEqual(num) {
|
|
6198
|
+
return `${this.field} >= ${typeof num === "number" ? num : num}`;
|
|
6199
|
+
}
|
|
6200
|
+
/**
|
|
6201
|
+
* Numerical less than (`field < num`).
|
|
6202
|
+
*/
|
|
6203
|
+
lessThan(num) {
|
|
6204
|
+
return `${this.field} < ${typeof num === "number" ? num : num}`;
|
|
6205
|
+
}
|
|
6206
|
+
/**
|
|
6207
|
+
* Numerical less than or equal (`field <= num`).
|
|
6208
|
+
*/
|
|
6209
|
+
lessThanOrEqual(num) {
|
|
6210
|
+
return `${this.field} <= ${typeof num === "number" ? num : num}`;
|
|
6211
|
+
}
|
|
6212
|
+
/**
|
|
6213
|
+
* Numerical range check (`field >= min && field <= max`).
|
|
6214
|
+
*/
|
|
6215
|
+
between(min2, max2) {
|
|
6216
|
+
return `${this.field} >= ${min2} && ${this.field} <= ${max2}`;
|
|
6217
|
+
}
|
|
6218
|
+
/**
|
|
6219
|
+
* String prefix check using JEXL `startsWith(field, prefix)`.
|
|
6220
|
+
*/
|
|
6221
|
+
startsWith(prefix) {
|
|
6222
|
+
return `startsWith(${this.field}, ${JSON.stringify(prefix)})`;
|
|
6223
|
+
}
|
|
6224
|
+
/**
|
|
6225
|
+
* String suffix check using JEXL `endsWith(field, suffix)`.
|
|
6226
|
+
*/
|
|
6227
|
+
endsWith(suffix) {
|
|
6228
|
+
return `endsWith(${this.field}, ${JSON.stringify(suffix)})`;
|
|
6229
|
+
}
|
|
6230
|
+
/**
|
|
6231
|
+
* Substring or array item check using JEXL `includes(field, item)`.
|
|
6232
|
+
*/
|
|
6233
|
+
contains(val) {
|
|
6234
|
+
return `includes(${this.field}, ${JSON.stringify(val)})`;
|
|
6235
|
+
}
|
|
6236
|
+
/**
|
|
6237
|
+
* Checks if array/string length exceeds a threshold.
|
|
6238
|
+
*/
|
|
6239
|
+
hasLengthGreaterThan(length2) {
|
|
6240
|
+
return `length(${this.field}) > ${length2}`;
|
|
6241
|
+
}
|
|
6242
|
+
/**
|
|
6243
|
+
* Checks if array/string length is at least a threshold.
|
|
6244
|
+
*/
|
|
6245
|
+
hasLengthAtLeast(length2) {
|
|
6246
|
+
return `length(${this.field}) >= ${length2}`;
|
|
6247
|
+
}
|
|
6248
|
+
};
|
|
6249
|
+
function createWhen() {
|
|
6250
|
+
const whenFn = ((fieldName) => new FieldConditionBuilder(fieldName));
|
|
6251
|
+
whenFn.user = (userProp) => new FieldConditionBuilder(`user.${userProp}`);
|
|
6252
|
+
whenFn.sibling = (siblingProp) => new FieldConditionBuilder(`siblingData.${siblingProp}`);
|
|
6253
|
+
whenFn.concat = (...parts) => {
|
|
6254
|
+
if (parts.length === 0) return "''";
|
|
6255
|
+
return parts.map((p) => formatJexlValue(p)).join(" + ");
|
|
6256
|
+
};
|
|
6257
|
+
whenFn.slugify = (field) => `slugify(${field})`;
|
|
6258
|
+
whenFn.lower = (field) => `lower(${field})`;
|
|
6259
|
+
whenFn.upper = (field) => `upper(${field})`;
|
|
6260
|
+
whenFn.trim = (field) => `trim(${field})`;
|
|
6261
|
+
whenFn.access = {
|
|
6262
|
+
isOwner: (ownerField = "author") => {
|
|
6263
|
+
return `${ownerField} == user.id || ${ownerField}.id == user.id`;
|
|
6264
|
+
},
|
|
6265
|
+
isAdmin: () => {
|
|
6266
|
+
return "user.role == 'admin' || (user.roles != null && 'admin' in user.roles)";
|
|
6267
|
+
},
|
|
6268
|
+
hasRole: (...roles) => {
|
|
6269
|
+
return whenFn.userRole(...roles);
|
|
6270
|
+
},
|
|
6271
|
+
isPublishedOrAdmin: (statusField = "status") => {
|
|
6272
|
+
return `${statusField} == 'published' || user.role == 'admin' || (user.roles != null && 'admin' in user.roles)`;
|
|
6273
|
+
}
|
|
6274
|
+
};
|
|
6275
|
+
whenFn.then = (condition, ifTrue, ifFalse = "null") => {
|
|
6276
|
+
return `${condition} ? ${formatJexlValue(ifTrue)} : ${formatJexlValue(ifFalse)}`;
|
|
6277
|
+
};
|
|
6278
|
+
whenFn.match = () => new MatchBuilder();
|
|
6279
|
+
whenFn.cases = (...branches) => {
|
|
6280
|
+
const builder = new MatchBuilder();
|
|
6281
|
+
let fallback = "null";
|
|
6282
|
+
for (const b of branches) {
|
|
6283
|
+
if (Array.isArray(b)) {
|
|
6284
|
+
builder.case(b[0], b[1]);
|
|
6285
|
+
} else {
|
|
6286
|
+
fallback = b;
|
|
6287
|
+
}
|
|
6288
|
+
}
|
|
6289
|
+
return builder.otherwise(fallback);
|
|
6290
|
+
};
|
|
6291
|
+
whenFn.all = (...conditions) => {
|
|
6292
|
+
const valid = conditions.filter(Boolean);
|
|
6293
|
+
if (valid.length === 0) return "true";
|
|
6294
|
+
if (valid.length === 1) return valid[0];
|
|
6295
|
+
return valid.map((c) => c.includes(" ") ? `(${c})` : c).join(" && ");
|
|
6296
|
+
};
|
|
6297
|
+
whenFn.and = whenFn.all;
|
|
6298
|
+
whenFn.any = (...conditions) => {
|
|
6299
|
+
const valid = conditions.filter(Boolean);
|
|
6300
|
+
if (valid.length === 0) return "true";
|
|
6301
|
+
if (valid.length === 1) return valid[0];
|
|
6302
|
+
return valid.map((c) => c.includes(" ") ? `(${c})` : c).join(" || ");
|
|
6303
|
+
};
|
|
6304
|
+
whenFn.or = whenFn.any;
|
|
6305
|
+
whenFn.not = (condition) => {
|
|
6306
|
+
if (!condition) return "false";
|
|
6307
|
+
return `!(${condition})`;
|
|
6308
|
+
};
|
|
6309
|
+
whenFn.variant = (...variants) => {
|
|
6310
|
+
const flat = variants.flat();
|
|
6311
|
+
if (flat.length === 0) return "true";
|
|
6312
|
+
if (flat.length === 1) return `variant == '${flat[0]}'`;
|
|
6313
|
+
return `variant in [${flat.map((v) => `'${v}'`).join(", ")}]`;
|
|
6314
|
+
};
|
|
6315
|
+
whenFn.notVariant = (...variants) => {
|
|
6316
|
+
const flat = variants.flat();
|
|
6317
|
+
if (flat.length === 0) return "false";
|
|
6318
|
+
if (flat.length === 1) return `variant != '${flat[0]}'`;
|
|
6319
|
+
return `!(variant in [${flat.map((v) => `'${v}'`).join(", ")}])`;
|
|
6320
|
+
};
|
|
6321
|
+
whenFn.block = (...blocks) => {
|
|
6322
|
+
const flat = blocks.flat();
|
|
6323
|
+
if (flat.length === 0) return "true";
|
|
6324
|
+
if (flat.length === 1) return `block == '${flat[0]}'`;
|
|
6325
|
+
return `block in [${flat.map((b) => `'${b}'`).join(", ")}]`;
|
|
6326
|
+
};
|
|
6327
|
+
whenFn.notBlock = (...blocks) => {
|
|
6328
|
+
const flat = blocks.flat();
|
|
6329
|
+
if (flat.length === 0) return "false";
|
|
6330
|
+
if (flat.length === 1) return `block != '${flat[0]}'`;
|
|
6331
|
+
return `!(block in [${flat.map((b) => `'${b}'`).join(", ")}])`;
|
|
6332
|
+
};
|
|
6333
|
+
whenFn.fieldEquals = (field, value) => {
|
|
6334
|
+
return `${field} == ${JSON.stringify(value)}`;
|
|
6335
|
+
};
|
|
6336
|
+
whenFn.fieldNotEquals = (field, value) => {
|
|
6337
|
+
return `${field} != ${JSON.stringify(value)}`;
|
|
6338
|
+
};
|
|
6339
|
+
whenFn.fieldIn = (field, ...values) => {
|
|
6340
|
+
const flat = values.flat();
|
|
6341
|
+
return `${field} in [${flat.map((v) => JSON.stringify(v)).join(", ")}]`;
|
|
6342
|
+
};
|
|
6343
|
+
whenFn.fieldNotIn = (field, ...values) => {
|
|
6344
|
+
const flat = values.flat();
|
|
6345
|
+
return `!(${field} in [${flat.map((v) => JSON.stringify(v)).join(", ")}])`;
|
|
6346
|
+
};
|
|
6347
|
+
whenFn.fieldNotEmpty = (field) => {
|
|
6348
|
+
return `${field} != null && ${field} != ''`;
|
|
6349
|
+
};
|
|
6350
|
+
whenFn.fieldEmpty = (field) => {
|
|
6351
|
+
return `${field} == null || ${field} == ''`;
|
|
6352
|
+
};
|
|
6353
|
+
whenFn.fieldIsTrue = (field) => {
|
|
6354
|
+
return `${field} == true`;
|
|
6355
|
+
};
|
|
6356
|
+
whenFn.fieldIsFalse = (field) => {
|
|
6357
|
+
return `${field} != true`;
|
|
6358
|
+
};
|
|
6359
|
+
whenFn.fieldGreaterThan = (field, value) => {
|
|
6360
|
+
return `${field} > ${value}`;
|
|
6361
|
+
};
|
|
6362
|
+
whenFn.fieldGreaterThanOrEqual = (field, value) => {
|
|
6363
|
+
return `${field} >= ${value}`;
|
|
6364
|
+
};
|
|
6365
|
+
whenFn.fieldLessThan = (field, value) => {
|
|
6366
|
+
return `${field} < ${value}`;
|
|
6367
|
+
};
|
|
6368
|
+
whenFn.fieldLessThanOrEqual = (field, value) => {
|
|
6369
|
+
return `${field} <= ${value}`;
|
|
6370
|
+
};
|
|
6371
|
+
whenFn.fieldBetween = (field, min2, max2) => {
|
|
6372
|
+
return `${field} >= ${min2} && ${field} <= ${max2}`;
|
|
6373
|
+
};
|
|
6374
|
+
whenFn.fieldStartsWith = (field, prefix) => {
|
|
6375
|
+
return `startsWith(${field}, ${JSON.stringify(prefix)})`;
|
|
6376
|
+
};
|
|
6377
|
+
whenFn.fieldEndsWith = (field, suffix) => {
|
|
6378
|
+
return `endsWith(${field}, ${JSON.stringify(suffix)})`;
|
|
6379
|
+
};
|
|
6380
|
+
whenFn.fieldContains = (field, item) => {
|
|
6381
|
+
return `includes(${field}, ${JSON.stringify(item)})`;
|
|
6382
|
+
};
|
|
6383
|
+
whenFn.arrayNotEmpty = (field) => {
|
|
6384
|
+
return `length(${field}) > 0`;
|
|
6385
|
+
};
|
|
6386
|
+
whenFn.arrayEmpty = (field) => {
|
|
6387
|
+
return `length(${field}) == 0`;
|
|
6388
|
+
};
|
|
6389
|
+
whenFn.arrayCountGreaterThan = (field, count2) => {
|
|
6390
|
+
return `length(${field}) > ${count2}`;
|
|
6391
|
+
};
|
|
6392
|
+
whenFn.arrayCountAtLeast = (field, count2) => {
|
|
6393
|
+
return `length(${field}) >= ${count2}`;
|
|
6394
|
+
};
|
|
6395
|
+
whenFn.arrayCountLessThan = (field, count2) => {
|
|
6396
|
+
return `length(${field}) < ${count2}`;
|
|
6397
|
+
};
|
|
6398
|
+
whenFn.userRole = (...roles) => {
|
|
6399
|
+
const flat = roles.flat();
|
|
6400
|
+
if (flat.length === 1) {
|
|
6401
|
+
return `user.role == '${flat[0]}' || (user.roles != null && '${flat[0]}' in user.roles)`;
|
|
6402
|
+
}
|
|
6403
|
+
const roleArray = `[${flat.map((r) => `'${r}'`).join(", ")}]`;
|
|
6404
|
+
return `user.role in ${roleArray} || (user.roles != null && includes(${roleArray}, user.role))`;
|
|
6405
|
+
};
|
|
6406
|
+
whenFn.userEmailDomain = (domain) => {
|
|
6407
|
+
const clean = domain.startsWith("@") ? domain : `@${domain}`;
|
|
6408
|
+
return `endsWith(user.email, '${clean}')`;
|
|
6409
|
+
};
|
|
6410
|
+
whenFn.userAttributeEquals = (attr, value) => {
|
|
6411
|
+
return `user.${attr} == ${JSON.stringify(value)}`;
|
|
6412
|
+
};
|
|
6413
|
+
whenFn.isNewDocument = () => {
|
|
6414
|
+
return "id == null";
|
|
6415
|
+
};
|
|
6416
|
+
whenFn.isExistingDocument = () => {
|
|
6417
|
+
return "id != null";
|
|
6418
|
+
};
|
|
6419
|
+
whenFn.statusEquals = (status) => {
|
|
6420
|
+
return `status == '${status}'`;
|
|
6421
|
+
};
|
|
6422
|
+
whenFn.statusIn = (...statuses) => {
|
|
6423
|
+
const flat = statuses.flat();
|
|
6424
|
+
return `status in [${flat.map((s) => `'${s}'`).join(", ")}]`;
|
|
6425
|
+
};
|
|
6426
|
+
return whenFn;
|
|
6427
|
+
}
|
|
6428
|
+
var when = createWhen();
|
|
6429
|
+
|
|
5975
6430
|
// src/index.ts
|
|
5976
6431
|
function defineCollection(config) {
|
|
5977
6432
|
return config;
|
|
@@ -6023,8 +6478,10 @@ function defineTab(args) {
|
|
|
6023
6478
|
0 && (module.exports = {
|
|
6024
6479
|
BUILTIN_JEXL_HELPERS,
|
|
6025
6480
|
ConfigValidationError,
|
|
6481
|
+
FieldConditionBuilder,
|
|
6026
6482
|
LIFECYCLE_EVENTS_COLLECTION,
|
|
6027
6483
|
LIFECYCLE_EVENT_NAMES,
|
|
6484
|
+
MatchBuilder,
|
|
6028
6485
|
WORKFLOW_HISTORY_COLLECTION,
|
|
6029
6486
|
abs,
|
|
6030
6487
|
addDays,
|
|
@@ -6106,6 +6563,7 @@ function defineTab(args) {
|
|
|
6106
6563
|
floor,
|
|
6107
6564
|
formatConfigDiagnostics,
|
|
6108
6565
|
formatDate,
|
|
6566
|
+
formatJexlValue,
|
|
6109
6567
|
generateDefaultDetailSchema,
|
|
6110
6568
|
generateOpenApi,
|
|
6111
6569
|
get,
|
|
@@ -6157,6 +6615,7 @@ function defineTab(args) {
|
|
|
6157
6615
|
validateDeclarativeAccessExpression,
|
|
6158
6616
|
validateDeclarativeHookExpression,
|
|
6159
6617
|
validatePreviewUrlExpression,
|
|
6618
|
+
when,
|
|
6160
6619
|
wordCount,
|
|
6161
6620
|
workflowCapabilities
|
|
6162
6621
|
});
|