@objectstack/objectql 9.11.0 → 10.0.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.mjs
CHANGED
|
@@ -3870,7 +3870,9 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
3870
3870
|
["$skip", "skip"],
|
|
3871
3871
|
["$orderby", "orderBy"],
|
|
3872
3872
|
["$select", "select"],
|
|
3873
|
-
["$count", "count"]
|
|
3873
|
+
["$count", "count"],
|
|
3874
|
+
["$search", "search"],
|
|
3875
|
+
["$searchFields", "searchFields"]
|
|
3874
3876
|
]) {
|
|
3875
3877
|
if (options[dollar] != null && options[bare] == null) {
|
|
3876
3878
|
options[bare] = options[dollar];
|
|
@@ -3968,6 +3970,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
3968
3970
|
"aggregations",
|
|
3969
3971
|
"groupBy",
|
|
3970
3972
|
"search",
|
|
3973
|
+
"searchFields",
|
|
3971
3974
|
"context",
|
|
3972
3975
|
"cursor"
|
|
3973
3976
|
]);
|
|
@@ -6162,6 +6165,118 @@ function collectSecretFields(schema) {
|
|
|
6162
6165
|
|
|
6163
6166
|
// src/engine.ts
|
|
6164
6167
|
import { pluralToSingular, ExternalWriteForbiddenError } from "@objectstack/spec/shared";
|
|
6168
|
+
|
|
6169
|
+
// src/search-filter.ts
|
|
6170
|
+
var TEXTUAL_TYPES = /* @__PURE__ */ new Set(["text", "email", "phone", "url", "autonumber", "textarea", "markdown"]);
|
|
6171
|
+
var ENUM_TYPES = /* @__PURE__ */ new Set(["select", "status"]);
|
|
6172
|
+
var EXCLUDED_FIELDS = /* @__PURE__ */ new Set([
|
|
6173
|
+
"id",
|
|
6174
|
+
"_id",
|
|
6175
|
+
"created",
|
|
6176
|
+
"modified",
|
|
6177
|
+
"created_at",
|
|
6178
|
+
"updated_at",
|
|
6179
|
+
"created_by",
|
|
6180
|
+
"updated_by",
|
|
6181
|
+
"owner_id",
|
|
6182
|
+
"organization_id",
|
|
6183
|
+
"space",
|
|
6184
|
+
"company_id"
|
|
6185
|
+
]);
|
|
6186
|
+
var EXCLUDED_TYPES = /* @__PURE__ */ new Set([
|
|
6187
|
+
"json",
|
|
6188
|
+
"object",
|
|
6189
|
+
"grid",
|
|
6190
|
+
"image",
|
|
6191
|
+
"file",
|
|
6192
|
+
"avatar",
|
|
6193
|
+
"vector",
|
|
6194
|
+
"location",
|
|
6195
|
+
"geometry",
|
|
6196
|
+
"secret",
|
|
6197
|
+
"password",
|
|
6198
|
+
"encrypted",
|
|
6199
|
+
"boolean",
|
|
6200
|
+
"lookup",
|
|
6201
|
+
"master_detail"
|
|
6202
|
+
]);
|
|
6203
|
+
function normalizeSearch(raw) {
|
|
6204
|
+
if (raw == null) return { query: "" };
|
|
6205
|
+
if (typeof raw === "string") return { query: raw };
|
|
6206
|
+
if (typeof raw === "object") {
|
|
6207
|
+
const o = raw;
|
|
6208
|
+
const q = typeof o.query === "string" ? o.query : typeof o.q === "string" ? o.q : "";
|
|
6209
|
+
const fields = Array.isArray(o.fields) ? o.fields : void 0;
|
|
6210
|
+
return { query: q, fields };
|
|
6211
|
+
}
|
|
6212
|
+
return { query: "" };
|
|
6213
|
+
}
|
|
6214
|
+
function autoDefaultFields(fields, displayField) {
|
|
6215
|
+
const names = Object.keys(fields).filter((f) => {
|
|
6216
|
+
if (EXCLUDED_FIELDS.has(f)) return false;
|
|
6217
|
+
const meta = fields[f];
|
|
6218
|
+
if (!meta || meta.hidden) return false;
|
|
6219
|
+
const t = meta.type;
|
|
6220
|
+
if (!t) return false;
|
|
6221
|
+
if (EXCLUDED_TYPES.has(t)) return false;
|
|
6222
|
+
return TEXTUAL_TYPES.has(t) || ENUM_TYPES.has(t);
|
|
6223
|
+
});
|
|
6224
|
+
const lead = displayField && fields[displayField] ? displayField : fields.name ? "name" : fields.title ? "title" : void 0;
|
|
6225
|
+
if (!lead) return names;
|
|
6226
|
+
return [lead, ...names.filter((f) => f !== lead)];
|
|
6227
|
+
}
|
|
6228
|
+
function resolveSearchFields(opts) {
|
|
6229
|
+
const all = opts.fields || {};
|
|
6230
|
+
const declared = opts.searchableFields?.filter((f) => all[f]);
|
|
6231
|
+
const allowed = declared && declared.length > 0 ? declared : autoDefaultFields(all, opts.displayField);
|
|
6232
|
+
const requested = typeof opts.requestedFields === "string" ? opts.requestedFields.split(",").map((f) => f.trim()).filter(Boolean) : opts.requestedFields;
|
|
6233
|
+
if (requested && requested.length > 0) {
|
|
6234
|
+
const allowSet = new Set(allowed);
|
|
6235
|
+
const validated = requested.filter((f) => allowSet.has(f));
|
|
6236
|
+
if (validated.length > 0) return validated;
|
|
6237
|
+
}
|
|
6238
|
+
return allowed;
|
|
6239
|
+
}
|
|
6240
|
+
function optionValuesMatching(meta, term) {
|
|
6241
|
+
if (!Array.isArray(meta.options)) return [];
|
|
6242
|
+
const lc = term.toLowerCase();
|
|
6243
|
+
const out = [];
|
|
6244
|
+
for (const opt of meta.options) {
|
|
6245
|
+
if (opt == null) continue;
|
|
6246
|
+
if (typeof opt === "string") {
|
|
6247
|
+
if (opt.toLowerCase().includes(lc)) out.push(opt);
|
|
6248
|
+
continue;
|
|
6249
|
+
}
|
|
6250
|
+
const label = String(opt.label ?? opt.value ?? "");
|
|
6251
|
+
if (label.toLowerCase().includes(lc)) out.push(opt.value);
|
|
6252
|
+
}
|
|
6253
|
+
return out;
|
|
6254
|
+
}
|
|
6255
|
+
function fieldClausesForTerm(field, term, meta) {
|
|
6256
|
+
if (ENUM_TYPES.has(meta?.type ?? "")) {
|
|
6257
|
+
const values = optionValuesMatching(meta, term);
|
|
6258
|
+
if (values.length > 0) return [{ [field]: { $in: values } }];
|
|
6259
|
+
return [{ [field]: { $contains: term } }];
|
|
6260
|
+
}
|
|
6261
|
+
return [{ [field]: { $contains: term } }];
|
|
6262
|
+
}
|
|
6263
|
+
function expandSearchToFilter(raw, opts) {
|
|
6264
|
+
const { query, fields: requested } = normalizeSearch(raw);
|
|
6265
|
+
if (!query || !query.trim()) return null;
|
|
6266
|
+
const searchFields = resolveSearchFields({
|
|
6267
|
+
...opts,
|
|
6268
|
+
requestedFields: requested ?? opts.requestedFields
|
|
6269
|
+
});
|
|
6270
|
+
if (searchFields.length === 0) return null;
|
|
6271
|
+
const terms = query.trim().split(/\s+/).filter(Boolean);
|
|
6272
|
+
const andClauses = terms.map((term) => ({
|
|
6273
|
+
$or: searchFields.flatMap((f) => fieldClausesForTerm(f, term, opts.fields[f] || {}))
|
|
6274
|
+
}));
|
|
6275
|
+
if (andClauses.length === 0) return null;
|
|
6276
|
+
return andClauses.length === 1 ? andClauses[0] : { $and: andClauses };
|
|
6277
|
+
}
|
|
6278
|
+
|
|
6279
|
+
// src/engine.ts
|
|
6165
6280
|
import { ExpressionEngine as ExpressionEngine3 } from "@objectstack/formula";
|
|
6166
6281
|
import { isAggregatedViewContainer as isAggregatedViewContainer2, expandViewContainer } from "@objectstack/spec";
|
|
6167
6282
|
|
|
@@ -8475,11 +8590,18 @@ var _ObjectQL = class _ObjectQL {
|
|
|
8475
8590
|
const uniqueIds = [...new Set(allIds)];
|
|
8476
8591
|
if (uniqueIds.length === 0) continue;
|
|
8477
8592
|
try {
|
|
8593
|
+
const idFilter = { id: { $in: uniqueIds } };
|
|
8594
|
+
const where = nestedAST.where ? { $and: [idFilter, nestedAST.where] } : idFilter;
|
|
8478
8595
|
const relatedQuery = {
|
|
8479
8596
|
object: referenceObject,
|
|
8480
|
-
where
|
|
8597
|
+
where,
|
|
8481
8598
|
...nestedAST.fields ? { fields: nestedAST.fields } : {},
|
|
8482
8599
|
...nestedAST.orderBy ? { orderBy: nestedAST.orderBy } : {}
|
|
8600
|
+
// NOTE: nestedAST.limit/offset are intentionally NOT forwarded here.
|
|
8601
|
+
// This path batch-loads every parent's related records in a single
|
|
8602
|
+
// $in query, so a *per-parent* limit/offset can't be expressed — a
|
|
8603
|
+
// global cap on the batch would silently drop records other parents
|
|
8604
|
+
// need. Paginate by querying the related object directly instead.
|
|
8483
8605
|
};
|
|
8484
8606
|
const driver = this.getDriver(referenceObject);
|
|
8485
8607
|
const expandOpts = this.buildDriverOptions(execCtx);
|
|
@@ -8532,11 +8654,34 @@ var _ObjectQL = class _ObjectQL {
|
|
|
8532
8654
|
const driver = this.getDriver(object);
|
|
8533
8655
|
const ast = { object, ...query };
|
|
8534
8656
|
delete ast.context;
|
|
8657
|
+
if (ast.filter != null && ast.where == null) {
|
|
8658
|
+
ast.where = ast.filter;
|
|
8659
|
+
}
|
|
8660
|
+
delete ast.filter;
|
|
8535
8661
|
if (ast.top != null && ast.limit == null) {
|
|
8536
8662
|
ast.limit = ast.top;
|
|
8537
8663
|
}
|
|
8538
8664
|
delete ast.top;
|
|
8539
8665
|
const _findSchema = this._registry.getObject(object);
|
|
8666
|
+
{
|
|
8667
|
+
const _searchRaw = ast.search ?? ast.$search;
|
|
8668
|
+
if (_searchRaw != null && _findSchema?.fields) {
|
|
8669
|
+
const _reqFields = ast.searchFields ?? ast.$searchFields ?? (typeof ast.search === "object" ? ast.search?.fields : void 0);
|
|
8670
|
+
const _searchFilter = expandSearchToFilter(_searchRaw, {
|
|
8671
|
+
fields: _findSchema.fields,
|
|
8672
|
+
searchableFields: _findSchema.searchableFields,
|
|
8673
|
+
requestedFields: _reqFields,
|
|
8674
|
+
displayField: _findSchema.displayNameField
|
|
8675
|
+
});
|
|
8676
|
+
if (_searchFilter) {
|
|
8677
|
+
ast.where = ast.where ? { $and: [ast.where, _searchFilter] } : _searchFilter;
|
|
8678
|
+
}
|
|
8679
|
+
}
|
|
8680
|
+
delete ast.search;
|
|
8681
|
+
delete ast.$search;
|
|
8682
|
+
delete ast.searchFields;
|
|
8683
|
+
delete ast.$searchFields;
|
|
8684
|
+
}
|
|
8540
8685
|
const _findFormula = planFormulaProjection(_findSchema, ast.fields);
|
|
8541
8686
|
if (_findFormula.projected) ast.fields = _findFormula.projected;
|
|
8542
8687
|
if (_findSchema?.fields && Array.isArray(ast.fields) && ast.fields.length > 0) {
|
|
@@ -9241,7 +9386,14 @@ var _ObjectQL = class _ObjectQL {
|
|
|
9241
9386
|
const obj = this._registry.getObject(objectName);
|
|
9242
9387
|
if (!obj) return;
|
|
9243
9388
|
const driver = this.getDriverForObject(objectName);
|
|
9244
|
-
if (!driver
|
|
9389
|
+
if (!driver) return;
|
|
9390
|
+
if (obj.external != null) {
|
|
9391
|
+
if (typeof driver.registerExternalObject === "function") {
|
|
9392
|
+
await driver.registerExternalObject(obj);
|
|
9393
|
+
}
|
|
9394
|
+
return;
|
|
9395
|
+
}
|
|
9396
|
+
if (typeof driver.syncSchema !== "function") return;
|
|
9245
9397
|
const tableName = StorageNameMapping.resolveTableName(obj);
|
|
9246
9398
|
await driver.syncSchema(tableName, obj);
|
|
9247
9399
|
}
|
|
@@ -10075,6 +10227,27 @@ var ObjectQLPlugin = class {
|
|
|
10075
10227
|
skipped++;
|
|
10076
10228
|
continue;
|
|
10077
10229
|
}
|
|
10230
|
+
if (obj.external != null) {
|
|
10231
|
+
if (typeof driver.registerExternalObject === "function") {
|
|
10232
|
+
try {
|
|
10233
|
+
await driver.registerExternalObject(obj);
|
|
10234
|
+
synced++;
|
|
10235
|
+
} catch (e) {
|
|
10236
|
+
ctx.logger.warn("Failed to register external object metadata", {
|
|
10237
|
+
object: obj.name,
|
|
10238
|
+
driver: driver.name,
|
|
10239
|
+
error: e instanceof Error ? e.message : String(e)
|
|
10240
|
+
});
|
|
10241
|
+
}
|
|
10242
|
+
} else {
|
|
10243
|
+
ctx.logger.debug("Driver does not support registerExternalObject, skipping external object", {
|
|
10244
|
+
object: obj.name,
|
|
10245
|
+
driver: driver.name
|
|
10246
|
+
});
|
|
10247
|
+
skipped++;
|
|
10248
|
+
}
|
|
10249
|
+
continue;
|
|
10250
|
+
}
|
|
10078
10251
|
if (typeof driver.syncSchema !== "function") {
|
|
10079
10252
|
ctx.logger.debug("Driver does not support syncSchema, skipping", {
|
|
10080
10253
|
object: obj.name,
|