@objectstack/objectql 9.11.0 → 10.2.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.js +176 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +176 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -3929,7 +3929,9 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
3929
3929
|
["$skip", "skip"],
|
|
3930
3930
|
["$orderby", "orderBy"],
|
|
3931
3931
|
["$select", "select"],
|
|
3932
|
-
["$count", "count"]
|
|
3932
|
+
["$count", "count"],
|
|
3933
|
+
["$search", "search"],
|
|
3934
|
+
["$searchFields", "searchFields"]
|
|
3933
3935
|
]) {
|
|
3934
3936
|
if (options[dollar] != null && options[bare] == null) {
|
|
3935
3937
|
options[bare] = options[dollar];
|
|
@@ -4027,6 +4029,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
4027
4029
|
"aggregations",
|
|
4028
4030
|
"groupBy",
|
|
4029
4031
|
"search",
|
|
4032
|
+
"searchFields",
|
|
4030
4033
|
"context",
|
|
4031
4034
|
"cursor"
|
|
4032
4035
|
]);
|
|
@@ -6221,6 +6224,118 @@ function collectSecretFields(schema) {
|
|
|
6221
6224
|
|
|
6222
6225
|
// src/engine.ts
|
|
6223
6226
|
var import_shared5 = require("@objectstack/spec/shared");
|
|
6227
|
+
|
|
6228
|
+
// src/search-filter.ts
|
|
6229
|
+
var TEXTUAL_TYPES = /* @__PURE__ */ new Set(["text", "email", "phone", "url", "autonumber", "textarea", "markdown"]);
|
|
6230
|
+
var ENUM_TYPES = /* @__PURE__ */ new Set(["select", "status"]);
|
|
6231
|
+
var EXCLUDED_FIELDS = /* @__PURE__ */ new Set([
|
|
6232
|
+
"id",
|
|
6233
|
+
"_id",
|
|
6234
|
+
"created",
|
|
6235
|
+
"modified",
|
|
6236
|
+
"created_at",
|
|
6237
|
+
"updated_at",
|
|
6238
|
+
"created_by",
|
|
6239
|
+
"updated_by",
|
|
6240
|
+
"owner_id",
|
|
6241
|
+
"organization_id",
|
|
6242
|
+
"space",
|
|
6243
|
+
"company_id"
|
|
6244
|
+
]);
|
|
6245
|
+
var EXCLUDED_TYPES = /* @__PURE__ */ new Set([
|
|
6246
|
+
"json",
|
|
6247
|
+
"object",
|
|
6248
|
+
"grid",
|
|
6249
|
+
"image",
|
|
6250
|
+
"file",
|
|
6251
|
+
"avatar",
|
|
6252
|
+
"vector",
|
|
6253
|
+
"location",
|
|
6254
|
+
"geometry",
|
|
6255
|
+
"secret",
|
|
6256
|
+
"password",
|
|
6257
|
+
"encrypted",
|
|
6258
|
+
"boolean",
|
|
6259
|
+
"lookup",
|
|
6260
|
+
"master_detail"
|
|
6261
|
+
]);
|
|
6262
|
+
function normalizeSearch(raw) {
|
|
6263
|
+
if (raw == null) return { query: "" };
|
|
6264
|
+
if (typeof raw === "string") return { query: raw };
|
|
6265
|
+
if (typeof raw === "object") {
|
|
6266
|
+
const o = raw;
|
|
6267
|
+
const q = typeof o.query === "string" ? o.query : typeof o.q === "string" ? o.q : "";
|
|
6268
|
+
const fields = Array.isArray(o.fields) ? o.fields : void 0;
|
|
6269
|
+
return { query: q, fields };
|
|
6270
|
+
}
|
|
6271
|
+
return { query: "" };
|
|
6272
|
+
}
|
|
6273
|
+
function autoDefaultFields(fields, displayField) {
|
|
6274
|
+
const names = Object.keys(fields).filter((f) => {
|
|
6275
|
+
if (EXCLUDED_FIELDS.has(f)) return false;
|
|
6276
|
+
const meta = fields[f];
|
|
6277
|
+
if (!meta || meta.hidden) return false;
|
|
6278
|
+
const t = meta.type;
|
|
6279
|
+
if (!t) return false;
|
|
6280
|
+
if (EXCLUDED_TYPES.has(t)) return false;
|
|
6281
|
+
return TEXTUAL_TYPES.has(t) || ENUM_TYPES.has(t);
|
|
6282
|
+
});
|
|
6283
|
+
const lead = displayField && fields[displayField] ? displayField : fields.name ? "name" : fields.title ? "title" : void 0;
|
|
6284
|
+
if (!lead) return names;
|
|
6285
|
+
return [lead, ...names.filter((f) => f !== lead)];
|
|
6286
|
+
}
|
|
6287
|
+
function resolveSearchFields(opts) {
|
|
6288
|
+
const all = opts.fields || {};
|
|
6289
|
+
const declared = opts.searchableFields?.filter((f) => all[f]);
|
|
6290
|
+
const allowed = declared && declared.length > 0 ? declared : autoDefaultFields(all, opts.displayField);
|
|
6291
|
+
const requested = typeof opts.requestedFields === "string" ? opts.requestedFields.split(",").map((f) => f.trim()).filter(Boolean) : opts.requestedFields;
|
|
6292
|
+
if (requested && requested.length > 0) {
|
|
6293
|
+
const allowSet = new Set(allowed);
|
|
6294
|
+
const validated = requested.filter((f) => allowSet.has(f));
|
|
6295
|
+
if (validated.length > 0) return validated;
|
|
6296
|
+
}
|
|
6297
|
+
return allowed;
|
|
6298
|
+
}
|
|
6299
|
+
function optionValuesMatching(meta, term) {
|
|
6300
|
+
if (!Array.isArray(meta.options)) return [];
|
|
6301
|
+
const lc = term.toLowerCase();
|
|
6302
|
+
const out = [];
|
|
6303
|
+
for (const opt of meta.options) {
|
|
6304
|
+
if (opt == null) continue;
|
|
6305
|
+
if (typeof opt === "string") {
|
|
6306
|
+
if (opt.toLowerCase().includes(lc)) out.push(opt);
|
|
6307
|
+
continue;
|
|
6308
|
+
}
|
|
6309
|
+
const label = String(opt.label ?? opt.value ?? "");
|
|
6310
|
+
if (label.toLowerCase().includes(lc)) out.push(opt.value);
|
|
6311
|
+
}
|
|
6312
|
+
return out;
|
|
6313
|
+
}
|
|
6314
|
+
function fieldClausesForTerm(field, term, meta) {
|
|
6315
|
+
if (ENUM_TYPES.has(meta?.type ?? "")) {
|
|
6316
|
+
const values = optionValuesMatching(meta, term);
|
|
6317
|
+
if (values.length > 0) return [{ [field]: { $in: values } }];
|
|
6318
|
+
return [{ [field]: { $contains: term } }];
|
|
6319
|
+
}
|
|
6320
|
+
return [{ [field]: { $contains: term } }];
|
|
6321
|
+
}
|
|
6322
|
+
function expandSearchToFilter(raw, opts) {
|
|
6323
|
+
const { query, fields: requested } = normalizeSearch(raw);
|
|
6324
|
+
if (!query || !query.trim()) return null;
|
|
6325
|
+
const searchFields = resolveSearchFields({
|
|
6326
|
+
...opts,
|
|
6327
|
+
requestedFields: requested ?? opts.requestedFields
|
|
6328
|
+
});
|
|
6329
|
+
if (searchFields.length === 0) return null;
|
|
6330
|
+
const terms = query.trim().split(/\s+/).filter(Boolean);
|
|
6331
|
+
const andClauses = terms.map((term) => ({
|
|
6332
|
+
$or: searchFields.flatMap((f) => fieldClausesForTerm(f, term, opts.fields[f] || {}))
|
|
6333
|
+
}));
|
|
6334
|
+
if (andClauses.length === 0) return null;
|
|
6335
|
+
return andClauses.length === 1 ? andClauses[0] : { $and: andClauses };
|
|
6336
|
+
}
|
|
6337
|
+
|
|
6338
|
+
// src/engine.ts
|
|
6224
6339
|
var import_formula4 = require("@objectstack/formula");
|
|
6225
6340
|
var import_spec = require("@objectstack/spec");
|
|
6226
6341
|
|
|
@@ -8534,11 +8649,18 @@ var _ObjectQL = class _ObjectQL {
|
|
|
8534
8649
|
const uniqueIds = [...new Set(allIds)];
|
|
8535
8650
|
if (uniqueIds.length === 0) continue;
|
|
8536
8651
|
try {
|
|
8652
|
+
const idFilter = { id: { $in: uniqueIds } };
|
|
8653
|
+
const where = nestedAST.where ? { $and: [idFilter, nestedAST.where] } : idFilter;
|
|
8537
8654
|
const relatedQuery = {
|
|
8538
8655
|
object: referenceObject,
|
|
8539
|
-
where
|
|
8656
|
+
where,
|
|
8540
8657
|
...nestedAST.fields ? { fields: nestedAST.fields } : {},
|
|
8541
8658
|
...nestedAST.orderBy ? { orderBy: nestedAST.orderBy } : {}
|
|
8659
|
+
// NOTE: nestedAST.limit/offset are intentionally NOT forwarded here.
|
|
8660
|
+
// This path batch-loads every parent's related records in a single
|
|
8661
|
+
// $in query, so a *per-parent* limit/offset can't be expressed — a
|
|
8662
|
+
// global cap on the batch would silently drop records other parents
|
|
8663
|
+
// need. Paginate by querying the related object directly instead.
|
|
8542
8664
|
};
|
|
8543
8665
|
const driver = this.getDriver(referenceObject);
|
|
8544
8666
|
const expandOpts = this.buildDriverOptions(execCtx);
|
|
@@ -8591,11 +8713,34 @@ var _ObjectQL = class _ObjectQL {
|
|
|
8591
8713
|
const driver = this.getDriver(object);
|
|
8592
8714
|
const ast = { object, ...query };
|
|
8593
8715
|
delete ast.context;
|
|
8716
|
+
if (ast.filter != null && ast.where == null) {
|
|
8717
|
+
ast.where = ast.filter;
|
|
8718
|
+
}
|
|
8719
|
+
delete ast.filter;
|
|
8594
8720
|
if (ast.top != null && ast.limit == null) {
|
|
8595
8721
|
ast.limit = ast.top;
|
|
8596
8722
|
}
|
|
8597
8723
|
delete ast.top;
|
|
8598
8724
|
const _findSchema = this._registry.getObject(object);
|
|
8725
|
+
{
|
|
8726
|
+
const _searchRaw = ast.search ?? ast.$search;
|
|
8727
|
+
if (_searchRaw != null && _findSchema?.fields) {
|
|
8728
|
+
const _reqFields = ast.searchFields ?? ast.$searchFields ?? (typeof ast.search === "object" ? ast.search?.fields : void 0);
|
|
8729
|
+
const _searchFilter = expandSearchToFilter(_searchRaw, {
|
|
8730
|
+
fields: _findSchema.fields,
|
|
8731
|
+
searchableFields: _findSchema.searchableFields,
|
|
8732
|
+
requestedFields: _reqFields,
|
|
8733
|
+
displayField: _findSchema.displayNameField
|
|
8734
|
+
});
|
|
8735
|
+
if (_searchFilter) {
|
|
8736
|
+
ast.where = ast.where ? { $and: [ast.where, _searchFilter] } : _searchFilter;
|
|
8737
|
+
}
|
|
8738
|
+
}
|
|
8739
|
+
delete ast.search;
|
|
8740
|
+
delete ast.$search;
|
|
8741
|
+
delete ast.searchFields;
|
|
8742
|
+
delete ast.$searchFields;
|
|
8743
|
+
}
|
|
8599
8744
|
const _findFormula = planFormulaProjection(_findSchema, ast.fields);
|
|
8600
8745
|
if (_findFormula.projected) ast.fields = _findFormula.projected;
|
|
8601
8746
|
if (_findSchema?.fields && Array.isArray(ast.fields) && ast.fields.length > 0) {
|
|
@@ -9300,7 +9445,14 @@ var _ObjectQL = class _ObjectQL {
|
|
|
9300
9445
|
const obj = this._registry.getObject(objectName);
|
|
9301
9446
|
if (!obj) return;
|
|
9302
9447
|
const driver = this.getDriverForObject(objectName);
|
|
9303
|
-
if (!driver
|
|
9448
|
+
if (!driver) return;
|
|
9449
|
+
if (obj.external != null) {
|
|
9450
|
+
if (typeof driver.registerExternalObject === "function") {
|
|
9451
|
+
await driver.registerExternalObject(obj);
|
|
9452
|
+
}
|
|
9453
|
+
return;
|
|
9454
|
+
}
|
|
9455
|
+
if (typeof driver.syncSchema !== "function") return;
|
|
9304
9456
|
const tableName = import_system2.StorageNameMapping.resolveTableName(obj);
|
|
9305
9457
|
await driver.syncSchema(tableName, obj);
|
|
9306
9458
|
}
|
|
@@ -10129,6 +10281,27 @@ var ObjectQLPlugin = class {
|
|
|
10129
10281
|
skipped++;
|
|
10130
10282
|
continue;
|
|
10131
10283
|
}
|
|
10284
|
+
if (obj.external != null) {
|
|
10285
|
+
if (typeof driver.registerExternalObject === "function") {
|
|
10286
|
+
try {
|
|
10287
|
+
await driver.registerExternalObject(obj);
|
|
10288
|
+
synced++;
|
|
10289
|
+
} catch (e) {
|
|
10290
|
+
ctx.logger.warn("Failed to register external object metadata", {
|
|
10291
|
+
object: obj.name,
|
|
10292
|
+
driver: driver.name,
|
|
10293
|
+
error: e instanceof Error ? e.message : String(e)
|
|
10294
|
+
});
|
|
10295
|
+
}
|
|
10296
|
+
} else {
|
|
10297
|
+
ctx.logger.debug("Driver does not support registerExternalObject, skipping external object", {
|
|
10298
|
+
object: obj.name,
|
|
10299
|
+
driver: driver.name
|
|
10300
|
+
});
|
|
10301
|
+
skipped++;
|
|
10302
|
+
}
|
|
10303
|
+
continue;
|
|
10304
|
+
}
|
|
10132
10305
|
if (typeof driver.syncSchema !== "function") {
|
|
10133
10306
|
ctx.logger.debug("Driver does not support syncSchema, skipping", {
|
|
10134
10307
|
object: obj.name,
|