@objectstack/objectql 7.4.1 → 7.6.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.
package/dist/index.d.mts CHANGED
@@ -419,6 +419,7 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
419
419
  graphql?: string | undefined;
420
420
  packages?: string | undefined;
421
421
  workflow?: string | undefined;
422
+ approvals?: string | undefined;
422
423
  realtime?: string | undefined;
423
424
  notifications?: string | undefined;
424
425
  ai?: string | undefined;
@@ -549,14 +550,14 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
549
550
  provider: "api";
550
551
  read?: {
551
552
  url: string;
552
- method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
553
+ method: "POST" | "PATCH" | "PUT" | "DELETE" | "GET";
553
554
  headers?: Record<string, string> | undefined;
554
555
  params?: Record<string, unknown> | undefined;
555
556
  body?: unknown;
556
557
  } | undefined;
557
558
  write?: {
558
559
  url: string;
559
- method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
560
+ method: "POST" | "PATCH" | "PUT" | "DELETE" | "GET";
560
561
  headers?: Record<string, string> | undefined;
561
562
  params?: Record<string, unknown> | undefined;
562
563
  body?: unknown;
@@ -1849,7 +1850,7 @@ declare class ObjectQL implements IDataEngine {
1849
1850
  * **fail-closed** — the write throws rather than persist cleartext.
1850
1851
  *
1851
1852
  * Mirrors the Settings subsystem's ICryptoProvider wiring; the host (e.g.
1852
- * `serve`) injects `InMemoryCryptoProvider` in dev and a KMS/Vault-backed
1853
+ * `serve`) injects `LocalCryptoProvider` in dev and a KMS/Vault-backed
1853
1854
  * provider in production.
1854
1855
  */
1855
1856
  setCryptoProvider(provider: ICryptoProvider): void;
@@ -2379,7 +2380,7 @@ declare function wrapDeclarativeHook(meta: Hook, handler: HookHandler, opts?: Wr
2379
2380
 
2380
2381
  interface FieldValidationError {
2381
2382
  field: string;
2382
- code: 'required' | 'min_length' | 'max_length' | 'min_value' | 'max_value' | 'invalid_email' | 'invalid_url' | 'invalid_phone' | 'invalid_number' | 'invalid_boolean' | 'invalid_date' | 'invalid_option' | 'invalid_transition' | 'rule_violation';
2383
+ code: 'required' | 'min_length' | 'max_length' | 'min_value' | 'max_value' | 'invalid_email' | 'invalid_url' | 'invalid_phone' | 'invalid_number' | 'invalid_boolean' | 'invalid_date' | 'invalid_option' | 'invalid_transition' | 'rule_violation' | 'invalid_format' | 'invalid_json' | 'json_schema_violation';
2383
2384
  message: string;
2384
2385
  /** Allowed values for select/multiselect, when applicable. */
2385
2386
  options?: string[];
package/dist/index.d.ts CHANGED
@@ -419,6 +419,7 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
419
419
  graphql?: string | undefined;
420
420
  packages?: string | undefined;
421
421
  workflow?: string | undefined;
422
+ approvals?: string | undefined;
422
423
  realtime?: string | undefined;
423
424
  notifications?: string | undefined;
424
425
  ai?: string | undefined;
@@ -549,14 +550,14 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
549
550
  provider: "api";
550
551
  read?: {
551
552
  url: string;
552
- method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
553
+ method: "POST" | "PATCH" | "PUT" | "DELETE" | "GET";
553
554
  headers?: Record<string, string> | undefined;
554
555
  params?: Record<string, unknown> | undefined;
555
556
  body?: unknown;
556
557
  } | undefined;
557
558
  write?: {
558
559
  url: string;
559
- method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
560
+ method: "POST" | "PATCH" | "PUT" | "DELETE" | "GET";
560
561
  headers?: Record<string, string> | undefined;
561
562
  params?: Record<string, unknown> | undefined;
562
563
  body?: unknown;
@@ -1849,7 +1850,7 @@ declare class ObjectQL implements IDataEngine {
1849
1850
  * **fail-closed** — the write throws rather than persist cleartext.
1850
1851
  *
1851
1852
  * Mirrors the Settings subsystem's ICryptoProvider wiring; the host (e.g.
1852
- * `serve`) injects `InMemoryCryptoProvider` in dev and a KMS/Vault-backed
1853
+ * `serve`) injects `LocalCryptoProvider` in dev and a KMS/Vault-backed
1853
1854
  * provider in production.
1854
1855
  */
1855
1856
  setCryptoProvider(provider: ICryptoProvider): void;
@@ -2379,7 +2380,7 @@ declare function wrapDeclarativeHook(meta: Hook, handler: HookHandler, opts?: Wr
2379
2380
 
2380
2381
  interface FieldValidationError {
2381
2382
  field: string;
2382
- code: 'required' | 'min_length' | 'max_length' | 'min_value' | 'max_value' | 'invalid_email' | 'invalid_url' | 'invalid_phone' | 'invalid_number' | 'invalid_boolean' | 'invalid_date' | 'invalid_option' | 'invalid_transition' | 'rule_violation';
2383
+ code: 'required' | 'min_length' | 'max_length' | 'min_value' | 'max_value' | 'invalid_email' | 'invalid_url' | 'invalid_phone' | 'invalid_number' | 'invalid_boolean' | 'invalid_date' | 'invalid_option' | 'invalid_transition' | 'rule_violation' | 'invalid_format' | 'invalid_json' | 'json_schema_violation';
2383
2384
  message: string;
2384
2385
  /** Allowed values for select/multiselect, when applicable. */
2385
2386
  options?: string[];
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -5333,12 +5343,25 @@ function validateRecord(objectSchema, data, mode) {
5333
5343
 
5334
5344
  // src/validation/rule-validator.ts
5335
5345
  var import_formula2 = require("@objectstack/formula");
5346
+ var import_ajv = __toESM(require("ajv"));
5347
+ var ajv = new import_ajv.default({ allErrors: true, strict: false });
5348
+ var jsonSchemaCache = /* @__PURE__ */ new WeakMap();
5336
5349
  function needsPriorRecord(objectSchema) {
5337
5350
  const rules = objectSchema?.validations;
5338
5351
  if (!Array.isArray(rules)) return false;
5339
- return rules.some(
5340
- (r) => r != null && typeof r === "object" && (r.type === "state_machine" || r.type === "cross_field" || r.type === "script")
5341
- );
5352
+ return rules.some((r) => ruleNeedsPrior(r));
5353
+ }
5354
+ function ruleNeedsPrior(r) {
5355
+ if (r == null || typeof r !== "object") return false;
5356
+ const type = r.type;
5357
+ if (type === "state_machine" || type === "cross_field" || type === "script") {
5358
+ return true;
5359
+ }
5360
+ if (type === "conditional") {
5361
+ const c = r;
5362
+ return ruleNeedsPrior(c.then) || ruleNeedsPrior(c.otherwise);
5363
+ }
5364
+ return false;
5342
5365
  }
5343
5366
  function toExpression(cond) {
5344
5367
  return typeof cond === "string" ? { dialect: "cel", source: cond } : cond;
@@ -5348,6 +5371,7 @@ function evaluateValidationRules(objectSchema, data, mode, opts = {}) {
5348
5371
  if (!Array.isArray(rules) || rules.length === 0 || !data) return;
5349
5372
  const previous = opts.previous ?? void 0;
5350
5373
  const merged = { ...previous ?? {}, ...data };
5374
+ const ctx = { data, merged, previous, mode, logger: opts.logger };
5351
5375
  const errors = [];
5352
5376
  const ordered = rules.filter((r) => r != null && typeof r === "object").filter((r) => r.active !== false).filter((r) => {
5353
5377
  const events = r.events ?? ["insert", "update"];
@@ -5356,11 +5380,7 @@ function evaluateValidationRules(objectSchema, data, mode, opts = {}) {
5356
5380
  for (const rule of ordered) {
5357
5381
  let violation = null;
5358
5382
  try {
5359
- if (rule.type === "state_machine") {
5360
- violation = checkStateMachine(rule, mode, data, previous);
5361
- } else if (rule.type === "script" || rule.type === "cross_field") {
5362
- violation = checkPredicate(rule, merged, previous, opts.logger);
5363
- }
5383
+ violation = evaluateRule(rule, ctx);
5364
5384
  } catch (err) {
5365
5385
  opts.logger?.warn?.(`Validation rule '${rule.name}' threw \u2014 skipped`, err);
5366
5386
  continue;
@@ -5377,6 +5397,23 @@ function evaluateValidationRules(objectSchema, data, mode, opts = {}) {
5377
5397
  }
5378
5398
  if (errors.length > 0) throw new ValidationError(errors);
5379
5399
  }
5400
+ function evaluateRule(rule, ctx) {
5401
+ switch (rule.type) {
5402
+ case "state_machine":
5403
+ return checkStateMachine(rule, ctx.mode, ctx.data, ctx.previous);
5404
+ case "script":
5405
+ case "cross_field":
5406
+ return checkPredicate(rule, ctx.merged, ctx.previous, ctx.logger);
5407
+ case "format":
5408
+ return checkFormat(rule, ctx.data, ctx.logger);
5409
+ case "json_schema":
5410
+ return checkJsonSchema(rule, ctx.data, ctx.logger);
5411
+ case "conditional":
5412
+ return checkConditional(rule, ctx);
5413
+ default:
5414
+ return null;
5415
+ }
5416
+ }
5380
5417
  function checkStateMachine(rule, mode, data, previous) {
5381
5418
  if (mode === "insert" || !previous) return null;
5382
5419
  if (!(rule.field in data)) return null;
@@ -5416,6 +5453,99 @@ function checkPredicate(rule, record, previous, logger) {
5416
5453
  }
5417
5454
  return null;
5418
5455
  }
5456
+ var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
5457
+ var PHONE_RE2 = /^\+?[\d\s().-]{7,20}$/;
5458
+ function checkFormat(rule, data, logger) {
5459
+ if (!(rule.field in data)) return null;
5460
+ const value = data[rule.field];
5461
+ if (value === null || value === void 0 || value === "") return null;
5462
+ const str = String(value);
5463
+ if (rule.regex) {
5464
+ let re;
5465
+ try {
5466
+ re = new RegExp(rule.regex);
5467
+ } catch {
5468
+ logger?.warn?.(`Validation rule '${rule.name}' has an invalid regex \u2014 skipped`);
5469
+ return null;
5470
+ }
5471
+ if (!re.test(str)) return formatViolation(rule);
5472
+ }
5473
+ if (rule.format && !matchesNamedFormat(rule.format, str)) {
5474
+ return formatViolation(rule);
5475
+ }
5476
+ return null;
5477
+ }
5478
+ function matchesNamedFormat(format, str) {
5479
+ switch (format) {
5480
+ case "email":
5481
+ return EMAIL_RE2.test(str);
5482
+ case "phone":
5483
+ return PHONE_RE2.test(str);
5484
+ case "url":
5485
+ try {
5486
+ new URL(str);
5487
+ return true;
5488
+ } catch {
5489
+ return false;
5490
+ }
5491
+ case "json":
5492
+ try {
5493
+ JSON.parse(str);
5494
+ return true;
5495
+ } catch {
5496
+ return false;
5497
+ }
5498
+ default:
5499
+ return true;
5500
+ }
5501
+ }
5502
+ function formatViolation(rule) {
5503
+ return { field: rule.field, code: "invalid_format", message: rule.message };
5504
+ }
5505
+ function checkJsonSchema(rule, data, logger) {
5506
+ if (!(rule.field in data)) return null;
5507
+ let value = data[rule.field];
5508
+ if (value === null || value === void 0) return null;
5509
+ if (typeof value === "string") {
5510
+ try {
5511
+ value = JSON.parse(value);
5512
+ } catch {
5513
+ return { field: rule.field, code: "invalid_json", message: rule.message };
5514
+ }
5515
+ }
5516
+ let validate = jsonSchemaCache.get(rule.schema);
5517
+ if (!validate) {
5518
+ try {
5519
+ validate = ajv.compile(rule.schema);
5520
+ } catch (err) {
5521
+ logger?.warn?.(
5522
+ `Validation rule '${rule.name}' has an uncompilable JSON Schema \u2014 skipped`,
5523
+ err
5524
+ );
5525
+ return null;
5526
+ }
5527
+ jsonSchemaCache.set(rule.schema, validate);
5528
+ }
5529
+ if (!validate(value)) {
5530
+ return { field: rule.field, code: "json_schema_violation", message: rule.message };
5531
+ }
5532
+ return null;
5533
+ }
5534
+ function checkConditional(rule, ctx) {
5535
+ const result = import_formula2.ExpressionEngine.evaluate(toExpression(rule.when), {
5536
+ record: ctx.merged,
5537
+ previous: ctx.previous ?? void 0
5538
+ });
5539
+ if (!result.ok) {
5540
+ ctx.logger?.warn?.(
5541
+ `Validation rule '${rule.name}' when-predicate failed to evaluate (${result.error.kind}: ${result.error.message}) \u2014 skipped`
5542
+ );
5543
+ return null;
5544
+ }
5545
+ const branch = result.value === true ? rule.then : rule.otherwise;
5546
+ if (!branch || branch.active === false) return null;
5547
+ return evaluateRule(branch, ctx);
5548
+ }
5419
5549
  function legalNextStates(objectSchema, field, currentState) {
5420
5550
  const rules = objectSchema?.validations;
5421
5551
  if (!Array.isArray(rules)) return null;
@@ -6376,7 +6506,7 @@ var _ObjectQL = class _ObjectQL {
6376
6506
  * **fail-closed** — the write throws rather than persist cleartext.
6377
6507
  *
6378
6508
  * Mirrors the Settings subsystem's ICryptoProvider wiring; the host (e.g.
6379
- * `serve`) injects `InMemoryCryptoProvider` in dev and a KMS/Vault-backed
6509
+ * `serve`) injects `LocalCryptoProvider` in dev and a KMS/Vault-backed
6380
6510
  * provider in production.
6381
6511
  */
6382
6512
  setCryptoProvider(provider) {
@@ -6414,7 +6544,7 @@ var _ObjectQL = class _ObjectQL {
6414
6544
  }
6415
6545
  if (!this.cryptoProvider) {
6416
6546
  throw new Error(
6417
- `Cannot persist secret field "${object}.${field}": no CryptoProvider is registered. Wire one via engine.setCryptoProvider(...) (e.g. InMemoryCryptoProvider in dev, a KMS/Vault provider in production). Refusing to store cleartext (fail-closed).`
6547
+ `Cannot persist secret field "${object}.${field}": no CryptoProvider is registered. Wire one via engine.setCryptoProvider(...) (e.g. LocalCryptoProvider in dev, a KMS/Vault provider in production). Refusing to store cleartext (fail-closed).`
6418
6548
  );
6419
6549
  }
6420
6550
  const plain = typeof value === "string" ? value : JSON.stringify(value);