@easy-cms/core 0.47.4 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,534 @@
1
+ // src/errors.ts
2
+ function codeOfStatus(status) {
3
+ switch (status) {
4
+ case 400:
5
+ return "BAD_USER_INPUT";
6
+ case 401:
7
+ return "UNAUTHORIZED";
8
+ case 403:
9
+ return "FORBIDDEN";
10
+ case 404:
11
+ return "NOT_FOUND";
12
+ case 405:
13
+ return "METHOD_NOT_ALLOWED";
14
+ case 413:
15
+ return "PAYLOAD_TOO_LARGE";
16
+ case 415:
17
+ return "UNSUPPORTED_MEDIA_TYPE";
18
+ case 429:
19
+ return "TOO_MANY_REQUESTS";
20
+ default:
21
+ return status >= 400 && status < 500 ? "BAD_USER_INPUT" : "INTERNAL_SERVER_ERROR";
22
+ }
23
+ }
24
+ var EasyCMSError = class extends Error {
25
+ status;
26
+ code;
27
+ constructor(message, status, code = codeOfStatus(status)) {
28
+ super(message);
29
+ this.name = "EasyCMSError";
30
+ this.status = status;
31
+ this.code = code;
32
+ }
33
+ };
34
+ var ConfigError = class _ConfigError extends EasyCMSError {
35
+ issues;
36
+ constructor(issues) {
37
+ super(_ConfigError.format(issues), 500, "CONFIG_ERROR");
38
+ this.name = "ConfigError";
39
+ this.issues = issues;
40
+ }
41
+ static format(issues) {
42
+ const lines = issues.map((issue) => {
43
+ const hint = issue.hint ? `
44
+ \u2192 ${issue.hint}` : "";
45
+ return ` \u2022 ${issue.path}: ${issue.message}${hint}`;
46
+ });
47
+ const noun = issues.length === 1 ? "problem" : "problems";
48
+ return `Invalid Easy CMS config (${issues.length} ${noun}):
49
+ ${lines.join("\n")}`;
50
+ }
51
+ };
52
+ var ValidationError = class extends EasyCMSError {
53
+ errors;
54
+ constructor(collection, errors) {
55
+ const list = errors.map((e) => `${e.field}: ${e.message}`).join("; ");
56
+ super(`Invalid data for "${collection}": ${list}`, 400, "VALIDATION_ERROR");
57
+ this.name = "ValidationError";
58
+ this.errors = errors;
59
+ }
60
+ };
61
+ var NotFoundError = class extends EasyCMSError {
62
+ constructor(collection, id) {
63
+ super(`No document with id ${JSON.stringify(id)} in "${collection}"`, 404);
64
+ this.name = "NotFoundError";
65
+ }
66
+ };
67
+ var QueryError = class extends EasyCMSError {
68
+ constructor(message) {
69
+ super(message, 400);
70
+ this.name = "QueryError";
71
+ }
72
+ };
73
+ var SchemaError = class extends EasyCMSError {
74
+ constructor(message) {
75
+ super(message, 500);
76
+ this.name = "SchemaError";
77
+ }
78
+ };
79
+ var UnauthorizedError = class extends EasyCMSError {
80
+ constructor(message = "You must be logged in") {
81
+ super(message, 401);
82
+ this.name = "UnauthorizedError";
83
+ }
84
+ };
85
+ var ForbiddenError = class extends EasyCMSError {
86
+ constructor(message = "You are not allowed to do this") {
87
+ super(message, 403);
88
+ this.name = "ForbiddenError";
89
+ }
90
+ };
91
+ var TooManyRequestsError = class extends EasyCMSError {
92
+ /** When to try again, in seconds (the REST API sends it as `Retry-After`). */
93
+ retryAfter;
94
+ constructor(message, retryAfter) {
95
+ super(message, 429);
96
+ this.name = "TooManyRequestsError";
97
+ this.retryAfter = retryAfter;
98
+ }
99
+ };
100
+ var PayloadTooLargeError = class extends EasyCMSError {
101
+ constructor(message) {
102
+ super(message, 413);
103
+ this.name = "PayloadTooLargeError";
104
+ }
105
+ };
106
+
107
+ // src/media.ts
108
+ var EXTENSIONS = {
109
+ "image/png": "png",
110
+ "image/jpeg": "jpg",
111
+ "image/gif": "gif",
112
+ "image/webp": "webp",
113
+ "image/avif": "avif",
114
+ "image/svg+xml": "svg",
115
+ "application/pdf": "pdf",
116
+ "video/mp4": "mp4",
117
+ "video/webm": "webm",
118
+ "video/quicktime": "mov",
119
+ "audio/mpeg": "mp3",
120
+ "audio/wav": "wav",
121
+ "audio/ogg": "ogg",
122
+ "audio/mp4": "m4a",
123
+ "text/plain": "txt",
124
+ "text/csv": "csv",
125
+ "application/zip": "zip",
126
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
127
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",
128
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx",
129
+ "application/vnd.oasis.opendocument.text": "odt",
130
+ "application/vnd.oasis.opendocument.spreadsheet": "ods",
131
+ "application/vnd.oasis.opendocument.presentation": "odp"
132
+ };
133
+ var OFFICE = [
134
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
135
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
136
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation"
137
+ ];
138
+ var MIME_GROUPS = {
139
+ office: OFFICE,
140
+ documents: [
141
+ "application/pdf",
142
+ ...OFFICE,
143
+ "application/vnd.oasis.opendocument.text",
144
+ "application/vnd.oasis.opendocument.spreadsheet",
145
+ "application/vnd.oasis.opendocument.presentation",
146
+ "text/csv",
147
+ "text/plain"
148
+ ],
149
+ archives: ["application/zip"]
150
+ };
151
+ function expandMimeTypes(patterns) {
152
+ return [...new Set(patterns.flatMap((p) => MIME_GROUPS[p.toLowerCase()] ?? [p]))];
153
+ }
154
+ var startsWith = (data, bytes, offset = 0) => bytes.every((b, i) => data[offset + i] === b);
155
+ var ascii = (data, start, end) => String.fromCharCode(...data.subarray(start, end));
156
+ var contains = (data, text) => Buffer.from(data.buffer, data.byteOffset, data.byteLength).includes(text, 0, "latin1");
157
+ function zipType(data) {
158
+ if (ascii(data, 30, 38) === "mimetype") {
159
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
160
+ const start = 30 + view.getUint16(26, true) + view.getUint16(28, true);
161
+ const type = ascii(data, start, start + Math.min(view.getUint32(18, true), 80));
162
+ if (EXTENSIONS[type]?.startsWith("od")) return type;
163
+ }
164
+ if (contains(data, "word/document.xml")) return OFFICE[0];
165
+ if (contains(data, "xl/workbook.xml")) return OFFICE[1];
166
+ if (contains(data, "ppt/presentation.xml")) return OFFICE[2];
167
+ return "application/zip";
168
+ }
169
+ function sniffMimeType(data, name = "") {
170
+ if (startsWith(data, [137, 80, 78, 71, 13, 10, 26, 10])) return "image/png";
171
+ if (startsWith(data, [255, 216, 255])) return "image/jpeg";
172
+ if (ascii(data, 0, 6) === "GIF87a" || ascii(data, 0, 6) === "GIF89a") return "image/gif";
173
+ if (ascii(data, 0, 4) === "RIFF" && ascii(data, 8, 12) === "WEBP") return "image/webp";
174
+ if (ascii(data, 0, 4) === "RIFF" && ascii(data, 8, 12) === "WAVE") return "audio/wav";
175
+ if (ascii(data, 4, 8) === "ftyp") {
176
+ const brand = ascii(data, 8, 12);
177
+ if (brand === "avif" || brand === "avis") return "image/avif";
178
+ if (["isom", "iso2", "mp41", "mp42", "avc1", "M4V "].includes(brand)) return "video/mp4";
179
+ if (brand === "M4A ") return "audio/mp4";
180
+ if (brand === "qt ") return "video/quicktime";
181
+ }
182
+ if (ascii(data, 0, 5) === "%PDF-") return "application/pdf";
183
+ if (startsWith(data, [80, 75, 3, 4])) return zipType(data);
184
+ if (ascii(data, 0, 4) === "OggS") return "audio/ogg";
185
+ if (startsWith(data, [26, 69, 223, 163]) && ascii(data, 0, 64).includes("webm"))
186
+ return "video/webm";
187
+ if (ascii(data, 0, 3) === "ID3" || data[0] === 255 && ((data[1] ?? 0) & 230) === 226)
188
+ return "audio/mpeg";
189
+ const head = data.subarray(0, 4096);
190
+ if (head.includes(0)) return void 0;
191
+ let text;
192
+ try {
193
+ text = new TextDecoder("utf-8", { fatal: true }).decode(head);
194
+ } catch {
195
+ return void 0;
196
+ }
197
+ const trimmed = text.replace(/^/, "").trimStart();
198
+ if (/^(<\?xml[^>]*>\s*)?(<!--[\s\S]*?-->\s*)*(<!DOCTYPE svg[^>]*>\s*)?<svg[\s>]/i.test(trimmed))
199
+ return "image/svg+xml";
200
+ return /\.csv$/i.test(name) ? "text/csv" : "text/plain";
201
+ }
202
+ function mimeAllowed(type, allowed) {
203
+ return expandMimeTypes(allowed).some(
204
+ (pattern) => pattern.endsWith("/*") ? type.startsWith(pattern.slice(0, -1)) && type !== "image/svg+xml" : pattern === type
205
+ );
206
+ }
207
+ function imageDimensions(data, type) {
208
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
209
+ try {
210
+ switch (type) {
211
+ case "image/png":
212
+ return { width: view.getUint32(16), height: view.getUint32(20) };
213
+ case "image/gif":
214
+ return { width: view.getUint16(6, true), height: view.getUint16(8, true) };
215
+ case "image/webp": {
216
+ const chunk = ascii(data, 12, 16);
217
+ if (chunk === "VP8 ")
218
+ return {
219
+ width: view.getUint16(26, true) & 16383,
220
+ height: view.getUint16(28, true) & 16383
221
+ };
222
+ if (chunk === "VP8L") {
223
+ const bits = view.getUint32(21, true);
224
+ return { width: (bits & 16383) + 1, height: (bits >> 14 & 16383) + 1 };
225
+ }
226
+ if (chunk === "VP8X") {
227
+ const w = view.getUint16(24, true) | view.getUint8(26) << 16;
228
+ const h = view.getUint16(27, true) | view.getUint8(29) << 16;
229
+ return { width: w + 1, height: h + 1 };
230
+ }
231
+ return void 0;
232
+ }
233
+ case "image/jpeg": {
234
+ let offset = 2;
235
+ while (offset + 9 < data.length) {
236
+ if (data[offset] !== 255) return void 0;
237
+ const marker = data[offset + 1];
238
+ const length = view.getUint16(offset + 2);
239
+ if (marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker)) {
240
+ return { width: view.getUint16(offset + 7), height: view.getUint16(offset + 5) };
241
+ }
242
+ offset += 2 + length;
243
+ }
244
+ return void 0;
245
+ }
246
+ }
247
+ } catch {
248
+ return void 0;
249
+ }
250
+ return void 0;
251
+ }
252
+ function typeFromName(name) {
253
+ const extension = /\.([a-z0-9]+)$/i.exec(name)?.[1]?.toLowerCase();
254
+ if (!extension) return void 0;
255
+ if (extension === "jpeg") return "image/jpeg";
256
+ return Object.entries(EXTENSIONS).find(([, ext]) => ext === extension)?.[0];
257
+ }
258
+ function storageKey(originalName, type, random) {
259
+ const name = originalName.split(/[\\/]/).pop() ?? "";
260
+ const base = name.replace(/(?<=.)\.[^.]*$/, "").normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{M}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "file";
261
+ return `${base}-${random}.${EXTENSIONS[type] ?? "bin"}`;
262
+ }
263
+ function isPrivateKey(key) {
264
+ return /\.private[.-]/.test(key);
265
+ }
266
+ function withPrivacy(key, isPrivate) {
267
+ const plain = key.replace(/\.private(?=\.[^.]+$)/, "");
268
+ return isPrivate ? plain.replace(/\.([^.]+)$/, ".private.$1") : plain;
269
+ }
270
+ function sizeKey(filename, size) {
271
+ return filename.replace(/\.([^.]+)$/, `-${size}.$1`);
272
+ }
273
+ var PRIVATE_KEY = /^[\p{L}\p{M}\p{N}-]+\.private(-[\p{L}\p{N}_-]+)?\.[a-z0-9]+$/u;
274
+
275
+ // src/fields.ts
276
+ var uniqueWithinOf = (field) => field.uniqueWithin === void 0 ? [] : [field.uniqueWithin].flat();
277
+ var isHasMany = (field) => (field.type === "select" || field.type === "relationship" || field.type === "upload") && field.hasMany === true;
278
+ function mimeAllowedBy(mimeType, patterns) {
279
+ const type = mimeType.toLowerCase();
280
+ return expandMimeTypes(patterns).some((pattern) => {
281
+ const p = pattern.toLowerCase();
282
+ return p.endsWith("/*") ? type.startsWith(p.slice(0, -1)) && type !== SVG : type === p;
283
+ });
284
+ }
285
+ var SVG = "image/svg+xml";
286
+ var hasRows = (field) => field.type === "array" || field.type === "blocks";
287
+ function rowFields(field, row) {
288
+ if (field.type === "array") return field.fields;
289
+ const type = row && typeof row === "object" ? row.blockType : void 0;
290
+ return field.blocks.find((block) => block.slug === type)?.fields;
291
+ }
292
+ var FIELD_TYPES = [
293
+ "text",
294
+ "textarea",
295
+ "number",
296
+ "boolean",
297
+ "date",
298
+ "email",
299
+ "json",
300
+ "select",
301
+ "slug",
302
+ "richText",
303
+ "upload",
304
+ "relationship",
305
+ "array",
306
+ "group",
307
+ "blocks"
308
+ ];
309
+
310
+ // src/config.ts
311
+ var ADMIN_ICONS = [
312
+ "file-text",
313
+ "newspaper",
314
+ "book-open",
315
+ "notebook",
316
+ "folder",
317
+ "tag",
318
+ "tags",
319
+ "image",
320
+ "images",
321
+ "video",
322
+ "music",
323
+ "file",
324
+ "users",
325
+ "user",
326
+ "building",
327
+ "store",
328
+ "shopping-bag",
329
+ "shopping-cart",
330
+ "package",
331
+ "box",
332
+ "calendar",
333
+ "calendar-days",
334
+ "map-pin",
335
+ "globe",
336
+ "house",
337
+ "layout-grid",
338
+ "layers",
339
+ "star",
340
+ "heart",
341
+ "message-square",
342
+ "mail",
343
+ "phone",
344
+ "briefcase",
345
+ "graduation-cap",
346
+ "utensils",
347
+ "car",
348
+ "settings",
349
+ "sliders-horizontal",
350
+ "palette",
351
+ "megaphone",
352
+ "bell",
353
+ "link",
354
+ "quote",
355
+ "circle-help",
356
+ "award",
357
+ "ticket",
358
+ "camera",
359
+ "key",
360
+ "chart-column",
361
+ "chart-line"
362
+ ];
363
+ var PLUGIN_API_VERSION = 1;
364
+ function definePlugin(plugin, info) {
365
+ if (info) Object.defineProperty(plugin, "info", { value: info, configurable: true });
366
+ return plugin;
367
+ }
368
+ function defineConfig(config) {
369
+ return config;
370
+ }
371
+
372
+ // src/field-types.ts
373
+ var FIELD_TYPE_BASES = [
374
+ "text",
375
+ "textarea",
376
+ "email",
377
+ "number",
378
+ "boolean",
379
+ "date",
380
+ "json"
381
+ ];
382
+ function defineFieldType(definition) {
383
+ return definition;
384
+ }
385
+ var NAME = /^[a-z][a-zA-Z0-9]*$/;
386
+ function fieldTypeModules(config) {
387
+ const list = config.fieldTypes;
388
+ if (!Array.isArray(list)) return [];
389
+ return list.map((t) => t?.admin?.module).filter((m) => typeof m === "string");
390
+ }
391
+ function applyFieldTypes(config) {
392
+ const issues = [];
393
+ const list = config.fieldTypes;
394
+ if (list === void 0) return { config, issues };
395
+ if (!Array.isArray(list)) {
396
+ issues.push({ path: "fieldTypes", message: "must be an array of field types" });
397
+ return { config, issues };
398
+ }
399
+ const types = /* @__PURE__ */ new Map();
400
+ for (const [i, type] of list.entries()) {
401
+ const path = `fieldTypes[${i}]`;
402
+ if (!type || typeof type.name !== "string" || !NAME.test(type.name)) {
403
+ issues.push({ path: `${path}.name`, message: 'must be a lowercase name like "color"' });
404
+ } else if (FIELD_TYPES.includes(type.name)) {
405
+ issues.push({ path: `${path}.name`, message: `"${type.name}" is a built-in field type` });
406
+ } else if (types.has(type.name)) {
407
+ issues.push({ path: `${path}.name`, message: `"${type.name}" is listed twice` });
408
+ } else if (!FIELD_TYPE_BASES.includes(type.base)) {
409
+ issues.push({
410
+ path: `${path}.base`,
411
+ message: `must be one of ${FIELD_TYPE_BASES.join(", ")}`
412
+ });
413
+ } else {
414
+ types.set(type.name, type);
415
+ }
416
+ }
417
+ if (types.size === 0) return { config, issues };
418
+ const component = (base, field, props) => {
419
+ if (!base) return void 0;
420
+ const tag = typeof base === "string" ? base : base.tag;
421
+ const options = {
422
+ ...typeof base === "string" ? {} : base.props,
423
+ ...Object.fromEntries(
424
+ (props ?? []).filter((key) => field[key] !== void 0).map((key) => [key, field[key]])
425
+ )
426
+ };
427
+ return Object.keys(options).length ? { tag, props: options } : tag;
428
+ };
429
+ const convert = (fields, path) => fields.map((field, i) => {
430
+ const here = `${path}[${i}]`;
431
+ const own = field;
432
+ const type = types.get(own.type);
433
+ if (!type) {
434
+ if ((field.type === "group" || field.type === "array") && Array.isArray(field.fields))
435
+ return { ...field, fields: convert(field.fields, `${here}.fields`) };
436
+ if (field.type === "blocks" && Array.isArray(field.blocks))
437
+ return {
438
+ ...field,
439
+ blocks: field.blocks.map((block, b) => ({
440
+ ...block,
441
+ fields: convert(block.fields, `${here}.blocks[${b}].fields`)
442
+ }))
443
+ };
444
+ return field;
445
+ }
446
+ const problem = type.checkOptions?.(own);
447
+ if (problem) issues.push({ path: here, message: problem });
448
+ const ownValidate = own.validate;
449
+ const check = type.validate;
450
+ const admin = own.admin ?? {};
451
+ const input = admin.component ?? component(type.admin?.component, own, type.admin?.props);
452
+ const cell = admin.cell ?? component(type.admin?.cell, own, type.admin?.props);
453
+ return {
454
+ ...own,
455
+ type: type.base,
456
+ customType: type.name,
457
+ ...check || ownValidate ? {
458
+ validate: async (value, ctx) => {
459
+ const empty = value === null || value === void 0 || value === "";
460
+ if (check && !empty) {
461
+ const result = await check(value, { ...ctx, field: own });
462
+ if (result !== true) return result;
463
+ }
464
+ return ownValidate ? ownValidate(value, ctx) : true;
465
+ }
466
+ } : {},
467
+ ...input || cell ? {
468
+ admin: {
469
+ ...admin,
470
+ ...input ? { component: input } : {},
471
+ ...cell ? { cell } : {}
472
+ }
473
+ } : {}
474
+ };
475
+ });
476
+ return {
477
+ config: {
478
+ ...config,
479
+ ...config.collections ? {
480
+ collections: config.collections.map((c, i) => ({
481
+ ...c,
482
+ fields: convert(c.fields, `collections[${i}].fields`)
483
+ }))
484
+ } : {},
485
+ ...config.globals ? {
486
+ globals: config.globals.map((g, i) => ({
487
+ ...g,
488
+ fields: convert(g.fields, `globals[${i}].fields`)
489
+ }))
490
+ } : {}
491
+ },
492
+ issues
493
+ };
494
+ }
495
+
496
+ export {
497
+ codeOfStatus,
498
+ EasyCMSError,
499
+ ConfigError,
500
+ ValidationError,
501
+ NotFoundError,
502
+ QueryError,
503
+ SchemaError,
504
+ UnauthorizedError,
505
+ ForbiddenError,
506
+ TooManyRequestsError,
507
+ PayloadTooLargeError,
508
+ EXTENSIONS,
509
+ MIME_GROUPS,
510
+ expandMimeTypes,
511
+ sniffMimeType,
512
+ mimeAllowed,
513
+ imageDimensions,
514
+ typeFromName,
515
+ storageKey,
516
+ isPrivateKey,
517
+ withPrivacy,
518
+ sizeKey,
519
+ PRIVATE_KEY,
520
+ uniqueWithinOf,
521
+ isHasMany,
522
+ mimeAllowedBy,
523
+ hasRows,
524
+ rowFields,
525
+ FIELD_TYPES,
526
+ ADMIN_ICONS,
527
+ PLUGIN_API_VERSION,
528
+ definePlugin,
529
+ defineConfig,
530
+ FIELD_TYPE_BASES,
531
+ defineFieldType,
532
+ fieldTypeModules,
533
+ applyFieldTypes
534
+ };
@@ -0,0 +1,6 @@
1
+ // src/admin-elements.ts
2
+ var ADMIN_API_VERSION = 1;
3
+
4
+ export {
5
+ ADMIN_API_VERSION
6
+ };
@@ -22,8 +22,9 @@ function matchesCondition(condition, data) {
22
22
  const value = read(data, condition.field);
23
23
  if (condition.exists !== void 0 && empty(value) === condition.exists) return false;
24
24
  if ("equals" in condition && !same(value, condition.equals)) return false;
25
- if ("notEquals" in condition && same(value, condition.notEquals)) return false;
25
+ if ("not_equals" in condition && same(value, condition.not_equals)) return false;
26
26
  if (condition.in && !condition.in.some((v) => same(value, v))) return false;
27
+ if (condition.not_in?.some((v) => same(value, v))) return false;
27
28
  return true;
28
29
  }
29
30
  function conditionIssues(condition, siblings) {
@@ -36,7 +37,9 @@ function conditionIssues(condition, siblings) {
36
37
  if (typeof c.field !== "string" || c.field === "") return ["needs `field`, a sibling field"];
37
38
  const first = c.field.split(".")[0];
38
39
  if (!siblings.includes(first)) return [`"${c.field}" is not a field beside it`];
40
+ if ("notEquals" in c) return ["`notEquals` is now `not_equals`, as in `where`"];
39
41
  if (c.in !== void 0 && !Array.isArray(c.in)) return ["`in` must be a list"];
42
+ if (c.not_in !== void 0 && !Array.isArray(c.not_in)) return ["`not_in` must be a list"];
40
43
  if (c.exists !== void 0 && typeof c.exists !== "boolean")
41
44
  return ["`exists` must be true or false"];
42
45
  return [];
@@ -2,7 +2,8 @@
2
2
  * When a field is shown, from its sibling fields' values (the fields beside it, in its group,
3
3
  * array row or block):
4
4
  *
5
- * - `{ field: 'linkType', equals: 'external' }`, also `notEquals`, `in: [...]`, `exists: true`
5
+ * - `{ field: 'linkType', equals: 'external' }`, also `not_equals`, `in: [...]`, `not_in: [...]`,
6
+ * `exists: true` (named as in `where`)
6
7
  * - `{ and: [...] }`, `{ or: [...] }`, `{ not: … }`
7
8
  *
8
9
  * A hidden field isn't required, and keeps its value.
@@ -11,8 +12,9 @@ type FieldCondition = {
11
12
  /** A sibling field's name; a path with `.` reads inside a group, e.g. `link.type`. */
12
13
  readonly field: string;
13
14
  readonly equals?: unknown;
14
- readonly notEquals?: unknown;
15
+ readonly not_equals?: unknown;
15
16
  readonly in?: readonly unknown[];
17
+ readonly not_in?: readonly unknown[];
16
18
  /** `true`: has a value (not empty); `false`: empty. */
17
19
  readonly exists?: boolean;
18
20
  } | {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  conditionIssues,
3
3
  matchesCondition
4
- } from "./chunk-C4KJ5TFK.js";
4
+ } from "./chunk-U266LV4M.js";
5
5
  export {
6
6
  conditionIssues,
7
7
  matchesCondition