@objectstack/objectql 9.6.0 → 9.8.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 +45 -4
- package/dist/index.d.ts +45 -4
- package/dist/index.js +82 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +82 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
package/dist/index.d.mts
CHANGED
|
@@ -1065,6 +1065,30 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
1065
1065
|
id: any;
|
|
1066
1066
|
record: any;
|
|
1067
1067
|
}>;
|
|
1068
|
+
/**
|
|
1069
|
+
* Clone a record — read the source, drop engine-owned columns, and
|
|
1070
|
+
* insert a fresh copy. Gated by the object's `enable.clone` capability
|
|
1071
|
+
* (default `true`; only an explicit `enable.clone === false` disables it).
|
|
1072
|
+
*
|
|
1073
|
+
* Shallow by design: it duplicates the record's own scalar/business field
|
|
1074
|
+
* values, not its related child records. The insert path re-stamps audit
|
|
1075
|
+
* columns, regenerates `autonumber` fields, and recomputes derived
|
|
1076
|
+
* (`formula`/`summary`) fields, so the copy is a valid new row rather than
|
|
1077
|
+
* a byte-identical twin. Caller-supplied `overrides` are applied last and
|
|
1078
|
+
* win over the copied values — the natural place to set a new `name`,
|
|
1079
|
+
* clear a unique field, or reset status before insert.
|
|
1080
|
+
*/
|
|
1081
|
+
cloneData(request: {
|
|
1082
|
+
object: string;
|
|
1083
|
+
id: string;
|
|
1084
|
+
overrides?: Record<string, any>;
|
|
1085
|
+
context?: any;
|
|
1086
|
+
}): Promise<{
|
|
1087
|
+
object: string;
|
|
1088
|
+
id: any;
|
|
1089
|
+
sourceId: string;
|
|
1090
|
+
record: any;
|
|
1091
|
+
}>;
|
|
1068
1092
|
updateData(request: {
|
|
1069
1093
|
object: string;
|
|
1070
1094
|
id: string;
|
|
@@ -1999,6 +2023,23 @@ interface OperationContext {
|
|
|
1999
2023
|
context?: ExecutionContext;
|
|
2000
2024
|
result?: any;
|
|
2001
2025
|
}
|
|
2026
|
+
/**
|
|
2027
|
+
* Trailing options for the READ methods (find / findOne / count / aggregate).
|
|
2028
|
+
*
|
|
2029
|
+
* Historically the read methods took their execution context INSIDE the query
|
|
2030
|
+
* (`query.context`), while the WRITE methods (insert / update) took it in a
|
|
2031
|
+
* trailing `options.context`. That split was a footgun: the same `{ context }`
|
|
2032
|
+
* object is correct as the 3rd arg to `insert` but was SILENTLY DROPPED as the
|
|
2033
|
+
* 3rd arg to `find` — a class of bugs where an intended `isSystem` bypass just
|
|
2034
|
+
* vanished (e.g. control-plane reads coming back empty once org-scoping hooks
|
|
2035
|
+
* were added). We now ALSO accept `context` via this trailing options arg on the
|
|
2036
|
+
* read methods, so "execution context goes in the trailing options argument" is
|
|
2037
|
+
* one rule across reads and writes. `query.context` remains supported; when both
|
|
2038
|
+
* are given, `options.context` wins (it is the explicit channel).
|
|
2039
|
+
*/
|
|
2040
|
+
interface EngineReadOptions {
|
|
2041
|
+
context?: ExecutionContext;
|
|
2042
|
+
}
|
|
2002
2043
|
/**
|
|
2003
2044
|
* Engine Middleware (Onion model)
|
|
2004
2045
|
*/
|
|
@@ -2418,8 +2459,8 @@ declare class ObjectQL implements IDataEngine {
|
|
|
2418
2459
|
* @returns Records with expanded lookup fields (IDs replaced by full objects)
|
|
2419
2460
|
*/
|
|
2420
2461
|
private expandRelatedRecords;
|
|
2421
|
-
find(object: string, query?: EngineQueryOptions): Promise<any[]>;
|
|
2422
|
-
findOne(objectName: string, query?: EngineQueryOptions): Promise<any>;
|
|
2462
|
+
find(object: string, query?: EngineQueryOptions, options?: EngineReadOptions): Promise<any[]>;
|
|
2463
|
+
findOne(objectName: string, query?: EngineQueryOptions, options?: EngineReadOptions): Promise<any>;
|
|
2423
2464
|
insert(object: string, data: any | any[], options?: DataEngineInsertOptions): Promise<any>;
|
|
2424
2465
|
update(object: string, data: any, options?: EngineUpdateOptions): Promise<any>;
|
|
2425
2466
|
/**
|
|
@@ -2436,8 +2477,8 @@ declare class ObjectQL implements IDataEngine {
|
|
|
2436
2477
|
*/
|
|
2437
2478
|
private cascadeDeleteRelations;
|
|
2438
2479
|
delete(object: string, options?: EngineDeleteOptions): Promise<any>;
|
|
2439
|
-
count(object: string, query?: EngineCountOptions): Promise<number>;
|
|
2440
|
-
aggregate(object: string, query: EngineAggregateOptions): Promise<any[]>;
|
|
2480
|
+
count(object: string, query?: EngineCountOptions, options?: EngineReadOptions): Promise<number>;
|
|
2481
|
+
aggregate(object: string, query: EngineAggregateOptions, options?: EngineReadOptions): Promise<any[]>;
|
|
2441
2482
|
/**
|
|
2442
2483
|
* Run raw driver-specific commands (SQL for SqlDriver, REST for RestDriver, …).
|
|
2443
2484
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -1065,6 +1065,30 @@ declare class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
1065
1065
|
id: any;
|
|
1066
1066
|
record: any;
|
|
1067
1067
|
}>;
|
|
1068
|
+
/**
|
|
1069
|
+
* Clone a record — read the source, drop engine-owned columns, and
|
|
1070
|
+
* insert a fresh copy. Gated by the object's `enable.clone` capability
|
|
1071
|
+
* (default `true`; only an explicit `enable.clone === false` disables it).
|
|
1072
|
+
*
|
|
1073
|
+
* Shallow by design: it duplicates the record's own scalar/business field
|
|
1074
|
+
* values, not its related child records. The insert path re-stamps audit
|
|
1075
|
+
* columns, regenerates `autonumber` fields, and recomputes derived
|
|
1076
|
+
* (`formula`/`summary`) fields, so the copy is a valid new row rather than
|
|
1077
|
+
* a byte-identical twin. Caller-supplied `overrides` are applied last and
|
|
1078
|
+
* win over the copied values — the natural place to set a new `name`,
|
|
1079
|
+
* clear a unique field, or reset status before insert.
|
|
1080
|
+
*/
|
|
1081
|
+
cloneData(request: {
|
|
1082
|
+
object: string;
|
|
1083
|
+
id: string;
|
|
1084
|
+
overrides?: Record<string, any>;
|
|
1085
|
+
context?: any;
|
|
1086
|
+
}): Promise<{
|
|
1087
|
+
object: string;
|
|
1088
|
+
id: any;
|
|
1089
|
+
sourceId: string;
|
|
1090
|
+
record: any;
|
|
1091
|
+
}>;
|
|
1068
1092
|
updateData(request: {
|
|
1069
1093
|
object: string;
|
|
1070
1094
|
id: string;
|
|
@@ -1999,6 +2023,23 @@ interface OperationContext {
|
|
|
1999
2023
|
context?: ExecutionContext;
|
|
2000
2024
|
result?: any;
|
|
2001
2025
|
}
|
|
2026
|
+
/**
|
|
2027
|
+
* Trailing options for the READ methods (find / findOne / count / aggregate).
|
|
2028
|
+
*
|
|
2029
|
+
* Historically the read methods took their execution context INSIDE the query
|
|
2030
|
+
* (`query.context`), while the WRITE methods (insert / update) took it in a
|
|
2031
|
+
* trailing `options.context`. That split was a footgun: the same `{ context }`
|
|
2032
|
+
* object is correct as the 3rd arg to `insert` but was SILENTLY DROPPED as the
|
|
2033
|
+
* 3rd arg to `find` — a class of bugs where an intended `isSystem` bypass just
|
|
2034
|
+
* vanished (e.g. control-plane reads coming back empty once org-scoping hooks
|
|
2035
|
+
* were added). We now ALSO accept `context` via this trailing options arg on the
|
|
2036
|
+
* read methods, so "execution context goes in the trailing options argument" is
|
|
2037
|
+
* one rule across reads and writes. `query.context` remains supported; when both
|
|
2038
|
+
* are given, `options.context` wins (it is the explicit channel).
|
|
2039
|
+
*/
|
|
2040
|
+
interface EngineReadOptions {
|
|
2041
|
+
context?: ExecutionContext;
|
|
2042
|
+
}
|
|
2002
2043
|
/**
|
|
2003
2044
|
* Engine Middleware (Onion model)
|
|
2004
2045
|
*/
|
|
@@ -2418,8 +2459,8 @@ declare class ObjectQL implements IDataEngine {
|
|
|
2418
2459
|
* @returns Records with expanded lookup fields (IDs replaced by full objects)
|
|
2419
2460
|
*/
|
|
2420
2461
|
private expandRelatedRecords;
|
|
2421
|
-
find(object: string, query?: EngineQueryOptions): Promise<any[]>;
|
|
2422
|
-
findOne(objectName: string, query?: EngineQueryOptions): Promise<any>;
|
|
2462
|
+
find(object: string, query?: EngineQueryOptions, options?: EngineReadOptions): Promise<any[]>;
|
|
2463
|
+
findOne(objectName: string, query?: EngineQueryOptions, options?: EngineReadOptions): Promise<any>;
|
|
2423
2464
|
insert(object: string, data: any | any[], options?: DataEngineInsertOptions): Promise<any>;
|
|
2424
2465
|
update(object: string, data: any, options?: EngineUpdateOptions): Promise<any>;
|
|
2425
2466
|
/**
|
|
@@ -2436,8 +2477,8 @@ declare class ObjectQL implements IDataEngine {
|
|
|
2436
2477
|
*/
|
|
2437
2478
|
private cascadeDeleteRelations;
|
|
2438
2479
|
delete(object: string, options?: EngineDeleteOptions): Promise<any>;
|
|
2439
|
-
count(object: string, query?: EngineCountOptions): Promise<number>;
|
|
2440
|
-
aggregate(object: string, query: EngineAggregateOptions): Promise<any[]>;
|
|
2480
|
+
count(object: string, query?: EngineCountOptions, options?: EngineReadOptions): Promise<number>;
|
|
2481
|
+
aggregate(object: string, query: EngineAggregateOptions, options?: EngineReadOptions): Promise<any[]>;
|
|
2441
2482
|
/**
|
|
2442
2483
|
* Run raw driver-specific commands (SQL for SqlDriver, REST for RestDriver, …).
|
|
2443
2484
|
*
|
package/dist/index.js
CHANGED
|
@@ -2832,6 +2832,13 @@ function normaliseVersionToken(v) {
|
|
|
2832
2832
|
}
|
|
2833
2833
|
return s;
|
|
2834
2834
|
}
|
|
2835
|
+
var CLONE_STRIP_FIELDS = [
|
|
2836
|
+
"id",
|
|
2837
|
+
"created_at",
|
|
2838
|
+
"created_by",
|
|
2839
|
+
"updated_at",
|
|
2840
|
+
"updated_by"
|
|
2841
|
+
];
|
|
2835
2842
|
var SERVICE_CONFIG = {
|
|
2836
2843
|
auth: { route: "/api/v1/auth", plugin: "plugin-auth" },
|
|
2837
2844
|
automation: { route: "/api/v1/automation", plugin: "plugin-automation" },
|
|
@@ -4087,6 +4094,68 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
4087
4094
|
record: result
|
|
4088
4095
|
};
|
|
4089
4096
|
}
|
|
4097
|
+
/**
|
|
4098
|
+
* Clone a record — read the source, drop engine-owned columns, and
|
|
4099
|
+
* insert a fresh copy. Gated by the object's `enable.clone` capability
|
|
4100
|
+
* (default `true`; only an explicit `enable.clone === false` disables it).
|
|
4101
|
+
*
|
|
4102
|
+
* Shallow by design: it duplicates the record's own scalar/business field
|
|
4103
|
+
* values, not its related child records. The insert path re-stamps audit
|
|
4104
|
+
* columns, regenerates `autonumber` fields, and recomputes derived
|
|
4105
|
+
* (`formula`/`summary`) fields, so the copy is a valid new row rather than
|
|
4106
|
+
* a byte-identical twin. Caller-supplied `overrides` are applied last and
|
|
4107
|
+
* win over the copied values — the natural place to set a new `name`,
|
|
4108
|
+
* clear a unique field, or reset status before insert.
|
|
4109
|
+
*/
|
|
4110
|
+
async cloneData(request) {
|
|
4111
|
+
const schema = this.engine.registry.getObject(request.object);
|
|
4112
|
+
if (!schema) {
|
|
4113
|
+
const err = new Error(`Object '${request.object}' not found`);
|
|
4114
|
+
err.code = "OBJECT_NOT_FOUND";
|
|
4115
|
+
err.status = 404;
|
|
4116
|
+
err.object = request.object;
|
|
4117
|
+
throw err;
|
|
4118
|
+
}
|
|
4119
|
+
if (schema.enable?.clone === false) {
|
|
4120
|
+
const err = new Error(`Cloning is disabled for object '${request.object}'`);
|
|
4121
|
+
err.code = "CLONE_DISABLED";
|
|
4122
|
+
err.status = 403;
|
|
4123
|
+
err.object = request.object;
|
|
4124
|
+
throw err;
|
|
4125
|
+
}
|
|
4126
|
+
const ctx = request.context;
|
|
4127
|
+
const ctxOpt = ctx !== void 0 ? { context: ctx } : void 0;
|
|
4128
|
+
const source = await this.engine.findOne(
|
|
4129
|
+
request.object,
|
|
4130
|
+
{ where: { id: request.id }, ...ctxOpt }
|
|
4131
|
+
);
|
|
4132
|
+
if (!source) {
|
|
4133
|
+
const err = new Error(`Record ${request.id} not found in ${request.object}`);
|
|
4134
|
+
err.code = "RECORD_NOT_FOUND";
|
|
4135
|
+
err.status = 404;
|
|
4136
|
+
err.object = request.object;
|
|
4137
|
+
throw err;
|
|
4138
|
+
}
|
|
4139
|
+
const data = { ...source };
|
|
4140
|
+
for (const f of CLONE_STRIP_FIELDS) delete data[f];
|
|
4141
|
+
const fields = schema.fields || {};
|
|
4142
|
+
for (const [name, def] of Object.entries(fields)) {
|
|
4143
|
+
if (!def) continue;
|
|
4144
|
+
if (def.system === true || def.type === "autonumber" || def.type === "formula" || def.type === "summary") {
|
|
4145
|
+
delete data[name];
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4148
|
+
if (request.overrides && typeof request.overrides === "object") {
|
|
4149
|
+
Object.assign(data, request.overrides);
|
|
4150
|
+
}
|
|
4151
|
+
const result = await this.engine.insert(request.object, data, ctxOpt);
|
|
4152
|
+
return {
|
|
4153
|
+
object: request.object,
|
|
4154
|
+
id: result.id,
|
|
4155
|
+
sourceId: request.id,
|
|
4156
|
+
record: result
|
|
4157
|
+
};
|
|
4158
|
+
}
|
|
4090
4159
|
async updateData(request) {
|
|
4091
4160
|
await this.assertVersionMatch(request.object, request.id, request.expectedVersion, request.context);
|
|
4092
4161
|
const opts = { where: { id: request.id } };
|
|
@@ -7281,6 +7350,11 @@ function applyFormulaPlan(plan, records) {
|
|
|
7281
7350
|
}
|
|
7282
7351
|
}
|
|
7283
7352
|
}
|
|
7353
|
+
function mergeReadContext(fromQuery, fromOptions) {
|
|
7354
|
+
if (fromOptions == null) return fromQuery;
|
|
7355
|
+
if (fromQuery == null) return fromOptions;
|
|
7356
|
+
return { ...fromQuery, ...fromOptions };
|
|
7357
|
+
}
|
|
7284
7358
|
function resolveMetadataItemName(key, item) {
|
|
7285
7359
|
if (!item) return void 0;
|
|
7286
7360
|
if (item.name) return item.name;
|
|
@@ -8594,7 +8668,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
8594
8668
|
// ============================================
|
|
8595
8669
|
// Data Access Methods (IDataEngine Interface)
|
|
8596
8670
|
// ============================================
|
|
8597
|
-
async find(object, query) {
|
|
8671
|
+
async find(object, query, options) {
|
|
8598
8672
|
object = this.resolveObjectName(object);
|
|
8599
8673
|
this.logger.debug("Find operation starting", { object, query });
|
|
8600
8674
|
const driver = this.getDriver(object);
|
|
@@ -8623,7 +8697,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
8623
8697
|
operation: "find",
|
|
8624
8698
|
ast,
|
|
8625
8699
|
options: query,
|
|
8626
|
-
context: query?.context
|
|
8700
|
+
context: mergeReadContext(query?.context, options?.context)
|
|
8627
8701
|
};
|
|
8628
8702
|
await this.executeWithMiddleware(opCtx, async () => {
|
|
8629
8703
|
const hookContext = {
|
|
@@ -8655,7 +8729,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
8655
8729
|
});
|
|
8656
8730
|
return opCtx.result;
|
|
8657
8731
|
}
|
|
8658
|
-
async findOne(objectName, query) {
|
|
8732
|
+
async findOne(objectName, query, options) {
|
|
8659
8733
|
objectName = this.resolveObjectName(objectName);
|
|
8660
8734
|
this.logger.debug("FindOne operation", { objectName });
|
|
8661
8735
|
const driver = this.getDriver(objectName);
|
|
@@ -8678,7 +8752,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
8678
8752
|
operation: "findOne",
|
|
8679
8753
|
ast,
|
|
8680
8754
|
options: query,
|
|
8681
|
-
context: query?.context
|
|
8755
|
+
context: mergeReadContext(query?.context, options?.context)
|
|
8682
8756
|
};
|
|
8683
8757
|
await this.executeWithMiddleware(opCtx, async () => {
|
|
8684
8758
|
const findOneOpts = this.buildDriverOptions(opCtx.context);
|
|
@@ -9024,14 +9098,14 @@ var _ObjectQL = class _ObjectQL {
|
|
|
9024
9098
|
});
|
|
9025
9099
|
return opCtx.result;
|
|
9026
9100
|
}
|
|
9027
|
-
async count(object, query) {
|
|
9101
|
+
async count(object, query, options) {
|
|
9028
9102
|
object = this.resolveObjectName(object);
|
|
9029
9103
|
const driver = this.getDriver(object);
|
|
9030
9104
|
const opCtx = {
|
|
9031
9105
|
object,
|
|
9032
9106
|
operation: "count",
|
|
9033
9107
|
options: query,
|
|
9034
|
-
context: query?.context
|
|
9108
|
+
context: mergeReadContext(query?.context, options?.context)
|
|
9035
9109
|
};
|
|
9036
9110
|
await this.executeWithMiddleware(opCtx, async () => {
|
|
9037
9111
|
const countOpts = this.buildDriverOptions(opCtx.context);
|
|
@@ -9044,7 +9118,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
9044
9118
|
});
|
|
9045
9119
|
return opCtx.result;
|
|
9046
9120
|
}
|
|
9047
|
-
async aggregate(object, query) {
|
|
9121
|
+
async aggregate(object, query, options) {
|
|
9048
9122
|
object = this.resolveObjectName(object);
|
|
9049
9123
|
const driver = this.getDriver(object);
|
|
9050
9124
|
this.logger.debug(`Aggregate on ${object} using ${driver.name}`, query);
|
|
@@ -9052,7 +9126,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
9052
9126
|
object,
|
|
9053
9127
|
operation: "aggregate",
|
|
9054
9128
|
options: query,
|
|
9055
|
-
context: query?.context
|
|
9129
|
+
context: mergeReadContext(query?.context, options?.context)
|
|
9056
9130
|
};
|
|
9057
9131
|
await this.executeWithMiddleware(opCtx, async () => {
|
|
9058
9132
|
const ast = {
|