@objectstack/objectql 7.5.0 → 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 +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +140 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +130 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -6
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: "
|
|
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: "
|
|
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 `
|
|
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: "
|
|
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: "
|
|
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 `
|
|
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
|
-
|
|
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
|
-
|
|
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 `
|
|
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.
|
|
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);
|