@objectstack/objectql 9.8.0 → 9.9.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 +21 -47
- package/dist/index.d.ts +21 -47
- package/dist/index.js +54 -163
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +50 -159
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
package/dist/index.d.mts
CHANGED
|
@@ -636,6 +636,8 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
636
636
|
variant?: "link" | "primary" | "secondary" | "danger" | "ghost" | undefined;
|
|
637
637
|
confirmText?: string | undefined;
|
|
638
638
|
successMessage?: string | undefined;
|
|
639
|
+
errorMessage?: string | undefined;
|
|
640
|
+
undoable?: boolean | undefined;
|
|
639
641
|
resultDialog?: {
|
|
640
642
|
title?: string | undefined;
|
|
641
643
|
description?: string | undefined;
|
|
@@ -1161,49 +1163,6 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
1161
1163
|
totalHits: number;
|
|
1162
1164
|
truncated: boolean;
|
|
1163
1165
|
}>;
|
|
1164
|
-
/**
|
|
1165
|
-
* Convert a qualified Lead into an Account + Contact (+ optional
|
|
1166
|
-
* Opportunity) and mark the Lead as converted. Mirrors the Salesforce
|
|
1167
|
-
* lead-conversion model:
|
|
1168
|
-
*
|
|
1169
|
-
* - If `accountId` is provided, the lead's company info is NOT used
|
|
1170
|
-
* to create a new account; the new contact and opportunity link to
|
|
1171
|
-
* the existing account instead.
|
|
1172
|
-
* - If `contactId` is provided, no new contact is created either —
|
|
1173
|
-
* useful when the lead is a new contact at an existing account.
|
|
1174
|
-
* - `createOpportunity` defaults to true; pass `false` to convert
|
|
1175
|
-
* without producing an opportunity (some teams convert "logos
|
|
1176
|
-
* only" first).
|
|
1177
|
-
* - Lead is updated atomically: `is_converted=true`,
|
|
1178
|
-
* `converted_account`/`converted_contact`/`converted_opportunity`
|
|
1179
|
-
* pointers, `converted_date`, and `status='converted'`.
|
|
1180
|
-
*
|
|
1181
|
-
* Atomicity is enforced via the default driver's transaction support
|
|
1182
|
-
* when available; otherwise a best-effort compensation (delete
|
|
1183
|
-
* already-created child records on failure) is attempted. Permission
|
|
1184
|
-
* checks on each child object are inherited from the caller's
|
|
1185
|
-
* execution context so SecurityPlugin still gates account/contact/
|
|
1186
|
-
* opportunity creates.
|
|
1187
|
-
*/
|
|
1188
|
-
convertLead(request: {
|
|
1189
|
-
leadId: string;
|
|
1190
|
-
accountId?: string;
|
|
1191
|
-
contactId?: string;
|
|
1192
|
-
createOpportunity?: boolean;
|
|
1193
|
-
opportunity?: {
|
|
1194
|
-
name?: string;
|
|
1195
|
-
amount?: number;
|
|
1196
|
-
close_date?: string;
|
|
1197
|
-
stage?: string;
|
|
1198
|
-
};
|
|
1199
|
-
convertedStatus?: string;
|
|
1200
|
-
context?: any;
|
|
1201
|
-
}): Promise<{
|
|
1202
|
-
lead: any;
|
|
1203
|
-
account: any;
|
|
1204
|
-
contact: any;
|
|
1205
|
-
opportunity: any | null;
|
|
1206
|
-
}>;
|
|
1207
1166
|
getMetaItemCached(request: {
|
|
1208
1167
|
type: string;
|
|
1209
1168
|
name: string;
|
|
@@ -2472,7 +2431,10 @@ declare class ObjectQL implements IDataEngine {
|
|
|
2472
2431
|
* - `set_null` → clear the foreign key,
|
|
2473
2432
|
* - `restrict` → refuse the delete when dependents exist.
|
|
2474
2433
|
* `master_detail` defaults to `cascade` (the parent owns the child
|
|
2475
|
-
* lifecycle); `lookup` defaults to `set_null
|
|
2434
|
+
* lifecycle); `lookup` defaults to `set_null` — except a `set_null` default
|
|
2435
|
+
* on a REQUIRED lookup escalates to `restrict` (you can't null a NOT NULL
|
|
2436
|
+
* FK; restricting with a clear dependent-count message beats a misleading
|
|
2437
|
+
* "<field> is required" 400 from the child). Only runs for single-id
|
|
2476
2438
|
* deletes — multi/predicate deletes skip cascade (logged).
|
|
2477
2439
|
*/
|
|
2478
2440
|
private cascadeDeleteRelations;
|
|
@@ -2715,13 +2677,25 @@ declare class ScopedContext {
|
|
|
2715
2677
|
/**
|
|
2716
2678
|
* Group + aggregate raw rows according to the AST's `groupBy` /
|
|
2717
2679
|
* `aggregations`. When neither is present, returns the rows unchanged.
|
|
2680
|
+
*
|
|
2681
|
+
* `timezone` (ADR-0053 Phase 2) shifts date bucketing to a reference timezone
|
|
2682
|
+
* so a row near a tz day-boundary lands in the right day/week/month/quarter.
|
|
2683
|
+
* It is only consulted by `groupBy` items carrying a `dateGranularity`; an
|
|
2684
|
+
* unset or `'UTC'` value keeps the historical UTC bucketing.
|
|
2718
2685
|
*/
|
|
2719
|
-
declare function applyInMemoryAggregation(rows: any[], ast: Pick<QueryAST, 'groupBy' | 'aggregations'
|
|
2686
|
+
declare function applyInMemoryAggregation(rows: any[], ast: Pick<QueryAST, 'groupBy' | 'aggregations'>, timezone?: string): any[];
|
|
2720
2687
|
/**
|
|
2721
2688
|
* Bucket a date-like value into an ISO-formatted period label. Weeks start
|
|
2722
2689
|
* Monday and use ISO week numbering.
|
|
2690
|
+
*
|
|
2691
|
+
* `timezone` (ADR-0053 Phase 2) resolves the calendar day in a reference zone
|
|
2692
|
+
* so an instant near a tz day-boundary buckets where a user in that zone would
|
|
2693
|
+
* expect. An unset / `'UTC'` / invalid zone keeps the historical UTC bucketing.
|
|
2694
|
+
* The y/m/d are taken in the reference zone and the ISO-week math then runs on
|
|
2695
|
+
* a UTC date built from those parts — the parts already carry the zone shift,
|
|
2696
|
+
* so the week boundary lands correctly without re-applying any offset.
|
|
2723
2697
|
*/
|
|
2724
|
-
declare function bucketDateValue(value: unknown, granularity: DateGranularityValue): string;
|
|
2698
|
+
declare function bucketDateValue(value: unknown, granularity: DateGranularityValue, timezone?: string): string;
|
|
2725
2699
|
|
|
2726
2700
|
/**
|
|
2727
2701
|
* Hook Execution Metrics
|
|
@@ -2917,7 +2891,7 @@ declare function wrapDeclarativeHook(meta: Hook, handler: HookHandler, opts?: Wr
|
|
|
2917
2891
|
|
|
2918
2892
|
interface FieldValidationError {
|
|
2919
2893
|
field: string;
|
|
2920
|
-
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';
|
|
2894
|
+
code: 'required' | 'min_length' | 'max_length' | 'min_value' | 'max_value' | 'invalid_email' | 'invalid_url' | 'invalid_phone' | 'invalid_number' | 'invalid_boolean' | 'invalid_date' | 'invalid_time' | 'invalid_option' | 'invalid_transition' | 'rule_violation' | 'invalid_format' | 'invalid_json' | 'json_schema_violation';
|
|
2921
2895
|
message: string;
|
|
2922
2896
|
/** Allowed values for select/multiselect, when applicable. */
|
|
2923
2897
|
options?: string[];
|
package/dist/index.d.ts
CHANGED
|
@@ -636,6 +636,8 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
636
636
|
variant?: "link" | "primary" | "secondary" | "danger" | "ghost" | undefined;
|
|
637
637
|
confirmText?: string | undefined;
|
|
638
638
|
successMessage?: string | undefined;
|
|
639
|
+
errorMessage?: string | undefined;
|
|
640
|
+
undoable?: boolean | undefined;
|
|
639
641
|
resultDialog?: {
|
|
640
642
|
title?: string | undefined;
|
|
641
643
|
description?: string | undefined;
|
|
@@ -1161,49 +1163,6 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
1161
1163
|
totalHits: number;
|
|
1162
1164
|
truncated: boolean;
|
|
1163
1165
|
}>;
|
|
1164
|
-
/**
|
|
1165
|
-
* Convert a qualified Lead into an Account + Contact (+ optional
|
|
1166
|
-
* Opportunity) and mark the Lead as converted. Mirrors the Salesforce
|
|
1167
|
-
* lead-conversion model:
|
|
1168
|
-
*
|
|
1169
|
-
* - If `accountId` is provided, the lead's company info is NOT used
|
|
1170
|
-
* to create a new account; the new contact and opportunity link to
|
|
1171
|
-
* the existing account instead.
|
|
1172
|
-
* - If `contactId` is provided, no new contact is created either —
|
|
1173
|
-
* useful when the lead is a new contact at an existing account.
|
|
1174
|
-
* - `createOpportunity` defaults to true; pass `false` to convert
|
|
1175
|
-
* without producing an opportunity (some teams convert "logos
|
|
1176
|
-
* only" first).
|
|
1177
|
-
* - Lead is updated atomically: `is_converted=true`,
|
|
1178
|
-
* `converted_account`/`converted_contact`/`converted_opportunity`
|
|
1179
|
-
* pointers, `converted_date`, and `status='converted'`.
|
|
1180
|
-
*
|
|
1181
|
-
* Atomicity is enforced via the default driver's transaction support
|
|
1182
|
-
* when available; otherwise a best-effort compensation (delete
|
|
1183
|
-
* already-created child records on failure) is attempted. Permission
|
|
1184
|
-
* checks on each child object are inherited from the caller's
|
|
1185
|
-
* execution context so SecurityPlugin still gates account/contact/
|
|
1186
|
-
* opportunity creates.
|
|
1187
|
-
*/
|
|
1188
|
-
convertLead(request: {
|
|
1189
|
-
leadId: string;
|
|
1190
|
-
accountId?: string;
|
|
1191
|
-
contactId?: string;
|
|
1192
|
-
createOpportunity?: boolean;
|
|
1193
|
-
opportunity?: {
|
|
1194
|
-
name?: string;
|
|
1195
|
-
amount?: number;
|
|
1196
|
-
close_date?: string;
|
|
1197
|
-
stage?: string;
|
|
1198
|
-
};
|
|
1199
|
-
convertedStatus?: string;
|
|
1200
|
-
context?: any;
|
|
1201
|
-
}): Promise<{
|
|
1202
|
-
lead: any;
|
|
1203
|
-
account: any;
|
|
1204
|
-
contact: any;
|
|
1205
|
-
opportunity: any | null;
|
|
1206
|
-
}>;
|
|
1207
1166
|
getMetaItemCached(request: {
|
|
1208
1167
|
type: string;
|
|
1209
1168
|
name: string;
|
|
@@ -2472,7 +2431,10 @@ declare class ObjectQL implements IDataEngine {
|
|
|
2472
2431
|
* - `set_null` → clear the foreign key,
|
|
2473
2432
|
* - `restrict` → refuse the delete when dependents exist.
|
|
2474
2433
|
* `master_detail` defaults to `cascade` (the parent owns the child
|
|
2475
|
-
* lifecycle); `lookup` defaults to `set_null
|
|
2434
|
+
* lifecycle); `lookup` defaults to `set_null` — except a `set_null` default
|
|
2435
|
+
* on a REQUIRED lookup escalates to `restrict` (you can't null a NOT NULL
|
|
2436
|
+
* FK; restricting with a clear dependent-count message beats a misleading
|
|
2437
|
+
* "<field> is required" 400 from the child). Only runs for single-id
|
|
2476
2438
|
* deletes — multi/predicate deletes skip cascade (logged).
|
|
2477
2439
|
*/
|
|
2478
2440
|
private cascadeDeleteRelations;
|
|
@@ -2715,13 +2677,25 @@ declare class ScopedContext {
|
|
|
2715
2677
|
/**
|
|
2716
2678
|
* Group + aggregate raw rows according to the AST's `groupBy` /
|
|
2717
2679
|
* `aggregations`. When neither is present, returns the rows unchanged.
|
|
2680
|
+
*
|
|
2681
|
+
* `timezone` (ADR-0053 Phase 2) shifts date bucketing to a reference timezone
|
|
2682
|
+
* so a row near a tz day-boundary lands in the right day/week/month/quarter.
|
|
2683
|
+
* It is only consulted by `groupBy` items carrying a `dateGranularity`; an
|
|
2684
|
+
* unset or `'UTC'` value keeps the historical UTC bucketing.
|
|
2718
2685
|
*/
|
|
2719
|
-
declare function applyInMemoryAggregation(rows: any[], ast: Pick<QueryAST, 'groupBy' | 'aggregations'
|
|
2686
|
+
declare function applyInMemoryAggregation(rows: any[], ast: Pick<QueryAST, 'groupBy' | 'aggregations'>, timezone?: string): any[];
|
|
2720
2687
|
/**
|
|
2721
2688
|
* Bucket a date-like value into an ISO-formatted period label. Weeks start
|
|
2722
2689
|
* Monday and use ISO week numbering.
|
|
2690
|
+
*
|
|
2691
|
+
* `timezone` (ADR-0053 Phase 2) resolves the calendar day in a reference zone
|
|
2692
|
+
* so an instant near a tz day-boundary buckets where a user in that zone would
|
|
2693
|
+
* expect. An unset / `'UTC'` / invalid zone keeps the historical UTC bucketing.
|
|
2694
|
+
* The y/m/d are taken in the reference zone and the ISO-week math then runs on
|
|
2695
|
+
* a UTC date built from those parts — the parts already carry the zone shift,
|
|
2696
|
+
* so the week boundary lands correctly without re-applying any offset.
|
|
2723
2697
|
*/
|
|
2724
|
-
declare function bucketDateValue(value: unknown, granularity: DateGranularityValue): string;
|
|
2698
|
+
declare function bucketDateValue(value: unknown, granularity: DateGranularityValue, timezone?: string): string;
|
|
2725
2699
|
|
|
2726
2700
|
/**
|
|
2727
2701
|
* Hook Execution Metrics
|
|
@@ -2917,7 +2891,7 @@ declare function wrapDeclarativeHook(meta: Hook, handler: HookHandler, opts?: Wr
|
|
|
2917
2891
|
|
|
2918
2892
|
interface FieldValidationError {
|
|
2919
2893
|
field: string;
|
|
2920
|
-
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';
|
|
2894
|
+
code: 'required' | 'min_length' | 'max_length' | 'min_value' | 'max_value' | 'invalid_email' | 'invalid_url' | 'invalid_phone' | 'invalid_number' | 'invalid_boolean' | 'invalid_date' | 'invalid_time' | 'invalid_option' | 'invalid_transition' | 'rule_violation' | 'invalid_format' | 'invalid_json' | 'json_schema_violation';
|
|
2921
2895
|
message: string;
|
|
2922
2896
|
/** Allowed values for select/multiselect, when applicable. */
|
|
2923
2897
|
options?: string[];
|
package/dist/index.js
CHANGED
|
@@ -4345,145 +4345,6 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
4345
4345
|
};
|
|
4346
4346
|
}
|
|
4347
4347
|
// ==========================================
|
|
4348
|
-
// Lead Convert (M10.6)
|
|
4349
|
-
// ==========================================
|
|
4350
|
-
/**
|
|
4351
|
-
* Convert a qualified Lead into an Account + Contact (+ optional
|
|
4352
|
-
* Opportunity) and mark the Lead as converted. Mirrors the Salesforce
|
|
4353
|
-
* lead-conversion model:
|
|
4354
|
-
*
|
|
4355
|
-
* - If `accountId` is provided, the lead's company info is NOT used
|
|
4356
|
-
* to create a new account; the new contact and opportunity link to
|
|
4357
|
-
* the existing account instead.
|
|
4358
|
-
* - If `contactId` is provided, no new contact is created either —
|
|
4359
|
-
* useful when the lead is a new contact at an existing account.
|
|
4360
|
-
* - `createOpportunity` defaults to true; pass `false` to convert
|
|
4361
|
-
* without producing an opportunity (some teams convert "logos
|
|
4362
|
-
* only" first).
|
|
4363
|
-
* - Lead is updated atomically: `is_converted=true`,
|
|
4364
|
-
* `converted_account`/`converted_contact`/`converted_opportunity`
|
|
4365
|
-
* pointers, `converted_date`, and `status='converted'`.
|
|
4366
|
-
*
|
|
4367
|
-
* Atomicity is enforced via the default driver's transaction support
|
|
4368
|
-
* when available; otherwise a best-effort compensation (delete
|
|
4369
|
-
* already-created child records on failure) is attempted. Permission
|
|
4370
|
-
* checks on each child object are inherited from the caller's
|
|
4371
|
-
* execution context so SecurityPlugin still gates account/contact/
|
|
4372
|
-
* opportunity creates.
|
|
4373
|
-
*/
|
|
4374
|
-
async convertLead(request) {
|
|
4375
|
-
const leadId = String(request.leadId || "").trim();
|
|
4376
|
-
if (!leadId) {
|
|
4377
|
-
const err = new Error("leadId is required");
|
|
4378
|
-
err.status = 400;
|
|
4379
|
-
err.code = "INVALID_REQUEST";
|
|
4380
|
-
throw err;
|
|
4381
|
-
}
|
|
4382
|
-
const ctx = request.context;
|
|
4383
|
-
const ctxOpt = ctx !== void 0 ? { context: ctx } : void 0;
|
|
4384
|
-
const lead = await this.engine.findOne("lead", { where: { id: leadId }, ...ctxOpt });
|
|
4385
|
-
if (!lead) {
|
|
4386
|
-
const err = new Error(`Lead '${leadId}' not found`);
|
|
4387
|
-
err.status = 404;
|
|
4388
|
-
err.code = "LEAD_NOT_FOUND";
|
|
4389
|
-
throw err;
|
|
4390
|
-
}
|
|
4391
|
-
if (lead.is_converted) {
|
|
4392
|
-
const err = new Error(`Lead '${leadId}' is already converted`);
|
|
4393
|
-
err.status = 409;
|
|
4394
|
-
err.code = "LEAD_ALREADY_CONVERTED";
|
|
4395
|
-
throw err;
|
|
4396
|
-
}
|
|
4397
|
-
const runConversion = async (trxCtx) => {
|
|
4398
|
-
const opCtx = trxCtx ?? ctx;
|
|
4399
|
-
const trxCtxOpt = opCtx !== void 0 ? { context: opCtx } : void 0;
|
|
4400
|
-
let account;
|
|
4401
|
-
if (request.accountId) {
|
|
4402
|
-
account = await this.engine.findOne("account", { where: { id: request.accountId }, ...trxCtxOpt });
|
|
4403
|
-
if (!account) {
|
|
4404
|
-
const err = new Error(`Account '${request.accountId}' not found`);
|
|
4405
|
-
err.status = 404;
|
|
4406
|
-
err.code = "ACCOUNT_NOT_FOUND";
|
|
4407
|
-
throw err;
|
|
4408
|
-
}
|
|
4409
|
-
} else {
|
|
4410
|
-
const accountPayload = {
|
|
4411
|
-
name: lead.company || `${lead.first_name ?? ""} ${lead.last_name ?? ""}`.trim() || "Untitled Account"
|
|
4412
|
-
};
|
|
4413
|
-
if (lead.industry) accountPayload.industry = lead.industry;
|
|
4414
|
-
if (lead.annual_revenue) accountPayload.annual_revenue = lead.annual_revenue;
|
|
4415
|
-
if (lead.number_of_employees) accountPayload.employees = lead.number_of_employees;
|
|
4416
|
-
if (lead.website) accountPayload.website = lead.website;
|
|
4417
|
-
if (lead.phone) accountPayload.phone = lead.phone;
|
|
4418
|
-
if (lead.address) accountPayload.billing_address = lead.address;
|
|
4419
|
-
if (lead.owner) accountPayload.owner = lead.owner;
|
|
4420
|
-
account = await this.engine.insert("account", accountPayload, trxCtxOpt);
|
|
4421
|
-
}
|
|
4422
|
-
let contact;
|
|
4423
|
-
if (request.contactId) {
|
|
4424
|
-
contact = await this.engine.findOne("contact", { where: { id: request.contactId }, ...trxCtxOpt });
|
|
4425
|
-
if (!contact) {
|
|
4426
|
-
const err = new Error(`Contact '${request.contactId}' not found`);
|
|
4427
|
-
err.status = 404;
|
|
4428
|
-
err.code = "CONTACT_NOT_FOUND";
|
|
4429
|
-
throw err;
|
|
4430
|
-
}
|
|
4431
|
-
} else {
|
|
4432
|
-
const contactPayload = {
|
|
4433
|
-
first_name: lead.first_name ?? "",
|
|
4434
|
-
last_name: lead.last_name ?? lead.company ?? "Unknown"
|
|
4435
|
-
};
|
|
4436
|
-
if (lead.salutation) contactPayload.salutation = lead.salutation;
|
|
4437
|
-
if (lead.email) contactPayload.email = lead.email;
|
|
4438
|
-
if (lead.phone) contactPayload.phone = lead.phone;
|
|
4439
|
-
if (lead.mobile) contactPayload.mobile = lead.mobile;
|
|
4440
|
-
if (lead.title) contactPayload.title = lead.title;
|
|
4441
|
-
if (lead.address) contactPayload.mailing_address = lead.address;
|
|
4442
|
-
if (lead.owner) contactPayload.owner = lead.owner;
|
|
4443
|
-
if (account?.id) contactPayload.account = account.id;
|
|
4444
|
-
contact = await this.engine.insert("contact", contactPayload, trxCtxOpt);
|
|
4445
|
-
}
|
|
4446
|
-
let opportunity = null;
|
|
4447
|
-
const shouldCreateOpp = request.createOpportunity !== false;
|
|
4448
|
-
if (shouldCreateOpp) {
|
|
4449
|
-
const oppOverrides = request.opportunity ?? {};
|
|
4450
|
-
const defaultName = oppOverrides.name || `${account?.name ?? lead.company ?? "Lead"} - New Opportunity`;
|
|
4451
|
-
const defaultClose = oppOverrides.close_date || new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3).toISOString().slice(0, 10);
|
|
4452
|
-
const oppPayload = {
|
|
4453
|
-
name: defaultName,
|
|
4454
|
-
stage: oppOverrides.stage ?? "qualification",
|
|
4455
|
-
close_date: defaultClose
|
|
4456
|
-
};
|
|
4457
|
-
if (oppOverrides.amount !== void 0) oppPayload.amount = oppOverrides.amount;
|
|
4458
|
-
else if (lead.annual_revenue) oppPayload.amount = lead.annual_revenue;
|
|
4459
|
-
if (account?.id) oppPayload.account = account.id;
|
|
4460
|
-
if (contact?.id) oppPayload.primary_contact = contact.id;
|
|
4461
|
-
if (lead.owner) oppPayload.owner = lead.owner;
|
|
4462
|
-
if (lead.lead_source) oppPayload.lead_source = lead.lead_source;
|
|
4463
|
-
opportunity = await this.engine.insert("opportunity", oppPayload, trxCtxOpt);
|
|
4464
|
-
}
|
|
4465
|
-
const leadUpdate = {
|
|
4466
|
-
is_converted: true,
|
|
4467
|
-
status: request.convertedStatus ?? "converted",
|
|
4468
|
-
converted_account: account?.id ?? null,
|
|
4469
|
-
converted_contact: contact?.id ?? null,
|
|
4470
|
-
converted_opportunity: opportunity?.id ?? null,
|
|
4471
|
-
converted_date: (/* @__PURE__ */ new Date()).toISOString()
|
|
4472
|
-
};
|
|
4473
|
-
const updatedLead = await this.engine.update("lead", leadUpdate, {
|
|
4474
|
-
where: { id: leadId },
|
|
4475
|
-
...trxCtxOpt
|
|
4476
|
-
});
|
|
4477
|
-
return {
|
|
4478
|
-
lead: updatedLead ?? { ...lead, ...leadUpdate },
|
|
4479
|
-
account,
|
|
4480
|
-
contact,
|
|
4481
|
-
opportunity
|
|
4482
|
-
};
|
|
4483
|
-
};
|
|
4484
|
-
return this.engine.transaction(runConversion, ctx);
|
|
4485
|
-
}
|
|
4486
|
-
// ==========================================
|
|
4487
4348
|
// Metadata Caching
|
|
4488
4349
|
// ==========================================
|
|
4489
4350
|
async getMetaItemCached(request) {
|
|
@@ -6316,7 +6177,7 @@ var ObjectStackProtocolImplementation = _ObjectStackProtocolImplementation;
|
|
|
6316
6177
|
// src/engine.ts
|
|
6317
6178
|
var import_node_async_hooks = require("async_hooks");
|
|
6318
6179
|
var import_kernel6 = require("@objectstack/spec/kernel");
|
|
6319
|
-
var
|
|
6180
|
+
var import_core2 = require("@objectstack/core");
|
|
6320
6181
|
var import_system2 = require("@objectstack/spec/system");
|
|
6321
6182
|
|
|
6322
6183
|
// src/secret-fields.ts
|
|
@@ -6847,11 +6708,20 @@ function validateOne(name, def, value) {
|
|
|
6847
6708
|
if (value === 0 || value === 1 || value === "0" || value === "1" || value === "true" || value === "false") return null;
|
|
6848
6709
|
return { field: name, code: "invalid_boolean", message: `${name} must be true or false` };
|
|
6849
6710
|
}
|
|
6850
|
-
if (t === "date" || t === "datetime"
|
|
6711
|
+
if (t === "date" || t === "datetime") {
|
|
6851
6712
|
if (value instanceof Date) return null;
|
|
6852
6713
|
if (typeof value === "string" && !Number.isNaN(Date.parse(value))) return null;
|
|
6853
6714
|
return { field: name, code: "invalid_date", message: `${name} must be a valid ${t} (ISO-8601)` };
|
|
6854
6715
|
}
|
|
6716
|
+
if (t === "time") {
|
|
6717
|
+
if (value instanceof Date) return null;
|
|
6718
|
+
if (typeof value === "string") {
|
|
6719
|
+
const timeOfDay = /^([01]\d|2[0-3]):[0-5]\d(:[0-5]\d(\.\d+)?)?(Z|[+-]([01]\d|2[0-3]):?[0-5]\d)?$/;
|
|
6720
|
+
const hasDate = /\d{4}-\d{2}-\d{2}/.test(value);
|
|
6721
|
+
if (timeOfDay.test(value.trim()) || hasDate && !Number.isNaN(Date.parse(value))) return null;
|
|
6722
|
+
}
|
|
6723
|
+
return { field: name, code: "invalid_time", message: `${name} must be a valid time (HH:MM or HH:MM:SS)` };
|
|
6724
|
+
}
|
|
6855
6725
|
if (t === "select" || t === "radio") {
|
|
6856
6726
|
const allowed = optionValues(def.options);
|
|
6857
6727
|
if (allowed.length > 0 && !allowed.includes(String(value))) {
|
|
@@ -7167,7 +7037,8 @@ function legalNextStates(objectSchema, field, currentState) {
|
|
|
7167
7037
|
}
|
|
7168
7038
|
|
|
7169
7039
|
// src/in-memory-aggregation.ts
|
|
7170
|
-
|
|
7040
|
+
var import_core = require("@objectstack/core");
|
|
7041
|
+
function applyInMemoryAggregation(rows, ast, timezone) {
|
|
7171
7042
|
const groupBy = ast.groupBy ?? [];
|
|
7172
7043
|
const aggregations = ast.aggregations ?? [];
|
|
7173
7044
|
if (groupBy.length === 0 && aggregations.length === 0) return rows;
|
|
@@ -7180,7 +7051,7 @@ function applyInMemoryAggregation(rows, ast) {
|
|
|
7180
7051
|
const parts = [];
|
|
7181
7052
|
for (const g of groupBy) {
|
|
7182
7053
|
const fieldName = typeof g === "string" ? g : g.alias ?? g.field;
|
|
7183
|
-
const value = projectGroupValue(row, g);
|
|
7054
|
+
const value = projectGroupValue(row, g, timezone);
|
|
7184
7055
|
key[fieldName] = value;
|
|
7185
7056
|
parts.push(`${fieldName}=${value}`);
|
|
7186
7057
|
}
|
|
@@ -7199,11 +7070,11 @@ function applyInMemoryAggregation(rows, ast) {
|
|
|
7199
7070
|
}
|
|
7200
7071
|
return out;
|
|
7201
7072
|
}
|
|
7202
|
-
function projectGroupValue(row, g) {
|
|
7073
|
+
function projectGroupValue(row, g, timezone) {
|
|
7203
7074
|
const field = typeof g === "string" ? g : g.field;
|
|
7204
7075
|
const v = row?.[field];
|
|
7205
7076
|
if (typeof g !== "string" && g.dateGranularity) {
|
|
7206
|
-
return bucketDateValue(v, g.dateGranularity);
|
|
7077
|
+
return bucketDateValue(v, g.dateGranularity, timezone);
|
|
7207
7078
|
}
|
|
7208
7079
|
return v == null ? "(null)" : String(v);
|
|
7209
7080
|
}
|
|
@@ -7281,12 +7152,11 @@ function toNumber(v) {
|
|
|
7281
7152
|
const n = Number(v);
|
|
7282
7153
|
return Number.isFinite(n) ? n : 0;
|
|
7283
7154
|
}
|
|
7284
|
-
function bucketDateValue(value, granularity) {
|
|
7155
|
+
function bucketDateValue(value, granularity, timezone) {
|
|
7285
7156
|
if (value == null) return "(null)";
|
|
7286
7157
|
const d = value instanceof Date ? value : new Date(String(value));
|
|
7287
7158
|
if (Number.isNaN(d.getTime())) return "(null)";
|
|
7288
|
-
const y =
|
|
7289
|
-
const m = d.getUTCMonth() + 1;
|
|
7159
|
+
const { year: y, month: m, day } = (0, import_core.calendarPartsInTzOrUtc)(d, timezone);
|
|
7290
7160
|
switch (granularity) {
|
|
7291
7161
|
case "year":
|
|
7292
7162
|
return String(y);
|
|
@@ -7295,9 +7165,9 @@ function bucketDateValue(value, granularity) {
|
|
|
7295
7165
|
case "month":
|
|
7296
7166
|
return `${y}-${String(m).padStart(2, "0")}`;
|
|
7297
7167
|
case "day":
|
|
7298
|
-
return `${y}-${String(m).padStart(2, "0")}-${String(
|
|
7168
|
+
return `${y}-${String(m).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
7299
7169
|
case "week": {
|
|
7300
|
-
const target = new Date(Date.UTC(y,
|
|
7170
|
+
const target = new Date(Date.UTC(y, m - 1, day));
|
|
7301
7171
|
const dayNum = (target.getUTCDay() + 6) % 7;
|
|
7302
7172
|
target.setUTCDate(target.getUTCDate() - dayNum + 3);
|
|
7303
7173
|
const firstThursday = new Date(Date.UTC(target.getUTCFullYear(), 0, 4));
|
|
@@ -7340,12 +7210,16 @@ function planFormulaProjection(schema, requestedFields) {
|
|
|
7340
7210
|
}
|
|
7341
7211
|
return { plan };
|
|
7342
7212
|
}
|
|
7343
|
-
function applyFormulaPlan(plan, records) {
|
|
7213
|
+
function applyFormulaPlan(plan, records, execCtx, nowSnapshot) {
|
|
7344
7214
|
if (!plan.length) return;
|
|
7215
|
+
const now = nowSnapshot ?? /* @__PURE__ */ new Date();
|
|
7216
|
+
const timezone = execCtx?.timezone;
|
|
7217
|
+
const user = execCtx?.userId ? { id: String(execCtx.userId), role: execCtx?.roles?.[0] } : void 0;
|
|
7218
|
+
const org = execCtx?.tenantId ? { id: String(execCtx.tenantId) } : void 0;
|
|
7345
7219
|
for (const rec of records) {
|
|
7346
7220
|
if (rec == null) continue;
|
|
7347
7221
|
for (const fp of plan) {
|
|
7348
|
-
const r = import_formula4.ExpressionEngine.evaluate(fp.expression, { record: rec });
|
|
7222
|
+
const r = import_formula4.ExpressionEngine.evaluate(fp.expression, { now, timezone, user, org, record: rec });
|
|
7349
7223
|
rec[fp.name] = r.ok ? r.value : null;
|
|
7350
7224
|
}
|
|
7351
7225
|
}
|
|
@@ -7423,7 +7297,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
7423
7297
|
* parent objects that aggregate it. Invalidated when packages register. */
|
|
7424
7298
|
this.summaryIndex = null;
|
|
7425
7299
|
this.hostContext = hostContext;
|
|
7426
|
-
this.logger = hostContext.logger || (0,
|
|
7300
|
+
this.logger = hostContext.logger || (0, import_core2.createLogger)({ level: "info", format: "pretty" });
|
|
7427
7301
|
if (process?.env?.OBJECTQL_STRICT_HOOKS === "1") {
|
|
7428
7302
|
this._strictHookBinding = true;
|
|
7429
7303
|
}
|
|
@@ -7777,6 +7651,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
7777
7651
|
if (typeof dv === "object" && dv !== null && dv.dialect && typeof dv.source === "string") {
|
|
7778
7652
|
const result = import_formula4.ExpressionEngine.evaluate(dv, {
|
|
7779
7653
|
now,
|
|
7654
|
+
timezone: execCtx?.timezone,
|
|
7780
7655
|
user: execCtx?.userId ? { id: String(execCtx.userId), role: execCtx?.roles?.[0] } : void 0,
|
|
7781
7656
|
org: execCtx?.tenantId ? { id: String(execCtx.tenantId) } : void 0,
|
|
7782
7657
|
record: out,
|
|
@@ -8713,7 +8588,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
8713
8588
|
hookContext.input.options = this.buildDriverOptions(opCtx.context, hookContext.input.options);
|
|
8714
8589
|
try {
|
|
8715
8590
|
let result = await driver.find(object, hookContext.input.ast, hookContext.input.options);
|
|
8716
|
-
if (Array.isArray(result)) applyFormulaPlan(_findFormula.plan, result);
|
|
8591
|
+
if (Array.isArray(result)) applyFormulaPlan(_findFormula.plan, result, opCtx.context);
|
|
8717
8592
|
if (ast.expand && Object.keys(ast.expand).length > 0 && Array.isArray(result)) {
|
|
8718
8593
|
result = await this.expandRelatedRecords(object, result, ast.expand, 0, opCtx.context);
|
|
8719
8594
|
}
|
|
@@ -8757,7 +8632,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
8757
8632
|
await this.executeWithMiddleware(opCtx, async () => {
|
|
8758
8633
|
const findOneOpts = this.buildDriverOptions(opCtx.context);
|
|
8759
8634
|
let result = await driver.findOne(objectName, opCtx.ast, findOneOpts);
|
|
8760
|
-
if (result != null) applyFormulaPlan(_findOneFormula.plan, [result]);
|
|
8635
|
+
if (result != null) applyFormulaPlan(_findOneFormula.plan, [result], opCtx.context);
|
|
8761
8636
|
if (ast.expand && Object.keys(ast.expand).length > 0 && result != null) {
|
|
8762
8637
|
const expanded = await this.expandRelatedRecords(objectName, [result], ast.expand, 0, opCtx.context);
|
|
8763
8638
|
result = expanded[0];
|
|
@@ -8972,7 +8847,10 @@ var _ObjectQL = class _ObjectQL {
|
|
|
8972
8847
|
* - `set_null` → clear the foreign key,
|
|
8973
8848
|
* - `restrict` → refuse the delete when dependents exist.
|
|
8974
8849
|
* `master_detail` defaults to `cascade` (the parent owns the child
|
|
8975
|
-
* lifecycle); `lookup` defaults to `set_null
|
|
8850
|
+
* lifecycle); `lookup` defaults to `set_null` — except a `set_null` default
|
|
8851
|
+
* on a REQUIRED lookup escalates to `restrict` (you can't null a NOT NULL
|
|
8852
|
+
* FK; restricting with a clear dependent-count message beats a misleading
|
|
8853
|
+
* "<field> is required" 400 from the child). Only runs for single-id
|
|
8976
8854
|
* deletes — multi/predicate deletes skip cascade (logged).
|
|
8977
8855
|
*/
|
|
8978
8856
|
async cascadeDeleteRelations(object, id, context, depth = 0) {
|
|
@@ -8998,7 +8876,10 @@ var _ObjectQL = class _ObjectQL {
|
|
|
8998
8876
|
resolvedRef = void 0;
|
|
8999
8877
|
}
|
|
9000
8878
|
if (ref !== object && resolvedRef !== object) continue;
|
|
9001
|
-
|
|
8879
|
+
let behavior = fdef.type === "master_detail" ? fdef.deleteBehavior === "restrict" ? "restrict" : "cascade" : fdef.deleteBehavior || "set_null";
|
|
8880
|
+
if (behavior === "set_null" && fdef.required === true) {
|
|
8881
|
+
behavior = "restrict";
|
|
8882
|
+
}
|
|
9002
8883
|
let dependents;
|
|
9003
8884
|
try {
|
|
9004
8885
|
dependents = await this.find(childName, { where: { [fieldName]: id }, context });
|
|
@@ -9007,9 +8888,16 @@ var _ObjectQL = class _ObjectQL {
|
|
|
9007
8888
|
}
|
|
9008
8889
|
if (!dependents || dependents.length === 0) continue;
|
|
9009
8890
|
if (behavior === "restrict") {
|
|
9010
|
-
|
|
9011
|
-
|
|
8891
|
+
const reason = fdef.deleteBehavior !== "restrict" && fdef.required === true ? ` (${fieldName} is required, so it cannot be cleared)` : "";
|
|
8892
|
+
const err = new Error(
|
|
8893
|
+
`Cannot delete ${object} (${id}): ${dependents.length} dependent ${childName} record(s) reference it via ${fieldName}${reason}. Delete or reassign them first, or set deleteBehavior:'cascade' on ${childName}.${fieldName}.`
|
|
9012
8894
|
);
|
|
8895
|
+
err.code = "DELETE_RESTRICTED";
|
|
8896
|
+
err.status = 409;
|
|
8897
|
+
err.object = object;
|
|
8898
|
+
err.dependentObject = childName;
|
|
8899
|
+
err.dependentCount = dependents.length;
|
|
8900
|
+
throw err;
|
|
9013
8901
|
}
|
|
9014
8902
|
for (const dep of dependents) {
|
|
9015
8903
|
const depId = dep?.id;
|
|
@@ -9143,11 +9031,14 @@ var _ObjectQL = class _ObjectQL {
|
|
|
9143
9031
|
if (!g?.dateGranularity) return true;
|
|
9144
9032
|
return granularityCaps?.[g.dateGranularity] === true;
|
|
9145
9033
|
});
|
|
9146
|
-
|
|
9034
|
+
const tz = query.timezone;
|
|
9035
|
+
const hasDateBucket = structuredItems.some((g) => !!g?.dateGranularity);
|
|
9036
|
+
const tzRequiresInMemory = !!tz && tz !== "UTC" && hasDateBucket;
|
|
9037
|
+
if (typeof drv.aggregate === "function" && allStructuredSupported && !tzRequiresInMemory) {
|
|
9147
9038
|
return drv.aggregate(object, ast, this.buildDriverOptions(opCtx.context));
|
|
9148
9039
|
}
|
|
9149
9040
|
const raw = await driver.find(object, ast, this.buildDriverOptions(opCtx.context));
|
|
9150
|
-
return applyInMemoryAggregation(raw, ast);
|
|
9041
|
+
return applyInMemoryAggregation(raw, ast, tz);
|
|
9151
9042
|
});
|
|
9152
9043
|
return opCtx.result;
|
|
9153
9044
|
}
|
|
@@ -10354,9 +10245,9 @@ var ObjectQLPlugin = class {
|
|
|
10354
10245
|
};
|
|
10355
10246
|
|
|
10356
10247
|
// src/kernel-factory.ts
|
|
10357
|
-
var
|
|
10248
|
+
var import_core3 = require("@objectstack/core");
|
|
10358
10249
|
async function createObjectQLKernel(options = {}) {
|
|
10359
|
-
const kernel = new
|
|
10250
|
+
const kernel = new import_core3.ObjectKernel();
|
|
10360
10251
|
await kernel.use(new ObjectQLPlugin());
|
|
10361
10252
|
if (options.plugins) {
|
|
10362
10253
|
for (const plugin of options.plugins) {
|