@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/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
- params.push(typeof value === "boolean" ? String(value) : value);
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
- params.push(typeof operand === "boolean" ? String(operand) : operand);
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
- params.push(typeof operand === "boolean" ? String(operand) : operand);
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
- params.push(typeof v === "boolean" ? String(v) : v);
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
- type: "object",
5161
- additionalProperties: {
5162
- type: "object",
5163
- properties: {
5164
- count: { type: "string", example: "*" },
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
- description: "Per-aggregate filter condition",
5183
- additionalProperties: true
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
- type: "object",
5198
- additionalProperties: {
5199
- type: "number",
5200
- nullable: true
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
  });