@newcms/query-engine 0.1.1 → 0.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.cjs ADDED
@@ -0,0 +1,351 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc2) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc2 = __getOwnPropDesc(from, key)) || desc2.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ QueryEngine: () => QueryEngine
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/query-engine.ts
28
+ var import_drizzle_orm = require("drizzle-orm");
29
+ var import_database = require("@newcms/database");
30
+ var DEFAULT_PER_PAGE = 10;
31
+ var QueryEngine = class {
32
+ constructor(db) {
33
+ this.db = db;
34
+ }
35
+ db;
36
+ /**
37
+ * Execute a content query.
38
+ */
39
+ async query(params = {}) {
40
+ const perPage = params.perPage === -1 ? 0 : params.perPage ?? DEFAULT_PER_PAGE;
41
+ const page = params.page ?? 1;
42
+ const offset = perPage > 0 ? (page - 1) * perPage : 0;
43
+ const conditions = this.buildConditions(params);
44
+ const orderClause = this.buildOrderBy(params);
45
+ const [countResult] = await this.db.select({ count: import_drizzle_orm.sql`count(DISTINCT ${import_database.posts.id})::int` }).from(import_database.posts).leftJoin(import_database.postmeta, this.needsMetaJoin(params) ? (0, import_drizzle_orm.eq)(import_database.posts.id, import_database.postmeta.postId) : import_drizzle_orm.sql`false`).leftJoin(
46
+ import_database.termRelationships,
47
+ this.needsTaxJoin(params) ? (0, import_drizzle_orm.eq)(import_database.posts.id, import_database.termRelationships.objectId) : import_drizzle_orm.sql`false`
48
+ ).where(conditions.length > 0 ? (0, import_drizzle_orm.and)(...conditions) : void 0);
49
+ const total = countResult.count;
50
+ const totalPages = perPage > 0 ? Math.ceil(total / perPage) : total > 0 ? 1 : 0;
51
+ let query = this.db.selectDistinct({
52
+ id: import_database.posts.id,
53
+ postAuthor: import_database.posts.postAuthor,
54
+ postDate: import_database.posts.postDate,
55
+ postDateGmt: import_database.posts.postDateGmt,
56
+ postContent: import_database.posts.postContent,
57
+ postTitle: import_database.posts.postTitle,
58
+ postExcerpt: import_database.posts.postExcerpt,
59
+ postStatus: import_database.posts.postStatus,
60
+ commentStatus: import_database.posts.commentStatus,
61
+ pingStatus: import_database.posts.pingStatus,
62
+ postPassword: import_database.posts.postPassword,
63
+ postName: import_database.posts.postName,
64
+ toPing: import_database.posts.toPing,
65
+ pinged: import_database.posts.pinged,
66
+ postModified: import_database.posts.postModified,
67
+ postModifiedGmt: import_database.posts.postModifiedGmt,
68
+ postContentFiltered: import_database.posts.postContentFiltered,
69
+ postParent: import_database.posts.postParent,
70
+ guid: import_database.posts.guid,
71
+ menuOrder: import_database.posts.menuOrder,
72
+ postType: import_database.posts.postType,
73
+ postMimeType: import_database.posts.postMimeType,
74
+ commentCount: import_database.posts.commentCount
75
+ }).from(import_database.posts).leftJoin(import_database.postmeta, this.needsMetaJoin(params) ? (0, import_drizzle_orm.eq)(import_database.posts.id, import_database.postmeta.postId) : import_drizzle_orm.sql`false`).leftJoin(
76
+ import_database.termRelationships,
77
+ this.needsTaxJoin(params) ? (0, import_drizzle_orm.eq)(import_database.posts.id, import_database.termRelationships.objectId) : import_drizzle_orm.sql`false`
78
+ ).where(conditions.length > 0 ? (0, import_drizzle_orm.and)(...conditions) : void 0).orderBy(orderClause).$dynamic();
79
+ if (perPage > 0) {
80
+ query = query.limit(perPage).offset(offset);
81
+ }
82
+ const rows = await query;
83
+ const flags = this.buildFlags(params, rows.length, total);
84
+ return {
85
+ posts: rows,
86
+ total,
87
+ totalPages,
88
+ page,
89
+ perPage: perPage || total,
90
+ flags
91
+ };
92
+ }
93
+ /**
94
+ * Shorthand: query a single post by ID.
95
+ */
96
+ async querySingle(id, postType) {
97
+ return this.query({
98
+ include: [id],
99
+ postType: postType ?? "post",
100
+ postStatus: ["publish", "private", "draft"],
101
+ perPage: 1
102
+ });
103
+ }
104
+ // ─── Private: condition builders ─────────────────────────────
105
+ buildConditions(params) {
106
+ const conditions = [];
107
+ const postType = params.postType ?? "post";
108
+ if (Array.isArray(postType)) {
109
+ conditions.push((0, import_drizzle_orm.inArray)(import_database.posts.postType, postType));
110
+ } else {
111
+ conditions.push((0, import_drizzle_orm.eq)(import_database.posts.postType, postType));
112
+ }
113
+ const status = params.postStatus ?? "publish";
114
+ if (Array.isArray(status)) {
115
+ conditions.push((0, import_drizzle_orm.inArray)(import_database.posts.postStatus, status));
116
+ } else {
117
+ conditions.push((0, import_drizzle_orm.eq)(import_database.posts.postStatus, status));
118
+ }
119
+ if (params.author !== void 0) {
120
+ if (Array.isArray(params.author)) {
121
+ conditions.push((0, import_drizzle_orm.inArray)(import_database.posts.postAuthor, params.author));
122
+ } else {
123
+ conditions.push((0, import_drizzle_orm.eq)(import_database.posts.postAuthor, params.author));
124
+ }
125
+ }
126
+ if (params.include && params.include.length > 0) {
127
+ conditions.push((0, import_drizzle_orm.inArray)(import_database.posts.id, params.include));
128
+ }
129
+ if (params.exclude && params.exclude.length > 0) {
130
+ conditions.push((0, import_drizzle_orm.notInArray)(import_database.posts.id, params.exclude));
131
+ }
132
+ if (params.parent !== void 0) {
133
+ conditions.push((0, import_drizzle_orm.eq)(import_database.posts.postParent, params.parent));
134
+ }
135
+ if (params.slug !== void 0) {
136
+ if (Array.isArray(params.slug)) {
137
+ conditions.push((0, import_drizzle_orm.inArray)(import_database.posts.postName, params.slug));
138
+ } else {
139
+ conditions.push((0, import_drizzle_orm.eq)(import_database.posts.postName, params.slug));
140
+ }
141
+ }
142
+ if (params.search) {
143
+ const searchTerm = params.search.replace(/[^\w\s]/g, "").trim();
144
+ if (searchTerm) {
145
+ conditions.push(
146
+ (0, import_drizzle_orm.or)(
147
+ (0, import_drizzle_orm.like)(import_database.posts.postTitle, `%${searchTerm}%`),
148
+ (0, import_drizzle_orm.like)(import_database.posts.postContent, `%${searchTerm}%`)
149
+ )
150
+ );
151
+ }
152
+ }
153
+ if (params.tax) {
154
+ const taxConditions = this.buildTaxConditions(params.tax);
155
+ if (taxConditions) {
156
+ conditions.push(taxConditions);
157
+ }
158
+ }
159
+ if (params.meta) {
160
+ const metaConditions = this.buildMetaConditions(params.meta);
161
+ if (metaConditions) {
162
+ conditions.push(metaConditions);
163
+ }
164
+ }
165
+ if (params.date) {
166
+ const dateConditions = this.buildDateConditions(params.date);
167
+ if (dateConditions) {
168
+ conditions.push(dateConditions);
169
+ }
170
+ }
171
+ return conditions;
172
+ }
173
+ buildTaxConditions(tax) {
174
+ const clauseConditions = tax.clauses.map((clause) => {
175
+ const subConditions = [];
176
+ if (clause.termIds && clause.termIds.length > 0) {
177
+ const ttIdSubquery = import_drizzle_orm.sql`${import_database.termRelationships.termTaxonomyId} IN (
178
+ SELECT ${import_database.termTaxonomy.termTaxonomyId} FROM ${import_database.termTaxonomy}
179
+ WHERE ${import_database.termTaxonomy.taxonomy} = ${clause.taxonomy}
180
+ AND ${import_database.termTaxonomy.termId} IN (${import_drizzle_orm.sql.join(clause.termIds.map((id) => import_drizzle_orm.sql`${id}`), import_drizzle_orm.sql`, `)})
181
+ )`;
182
+ if (clause.operator === "NOT IN") {
183
+ subConditions.push(import_drizzle_orm.sql`${import_database.posts.id} NOT IN (
184
+ SELECT ${import_database.termRelationships.objectId} FROM ${import_database.termRelationships}
185
+ WHERE ${ttIdSubquery}
186
+ )`);
187
+ } else {
188
+ subConditions.push(ttIdSubquery);
189
+ }
190
+ }
191
+ if (clause.termSlugs && clause.termSlugs.length > 0) {
192
+ const ttIdSubquery = import_drizzle_orm.sql`${import_database.termRelationships.termTaxonomyId} IN (
193
+ SELECT ${import_database.termTaxonomy.termTaxonomyId} FROM ${import_database.termTaxonomy}
194
+ INNER JOIN ${import_database.terms} ON ${import_database.terms.termId} = ${import_database.termTaxonomy.termId}
195
+ WHERE ${import_database.termTaxonomy.taxonomy} = ${clause.taxonomy}
196
+ AND ${import_database.terms.slug} IN (${import_drizzle_orm.sql.join(clause.termSlugs.map((s) => import_drizzle_orm.sql`${s}`), import_drizzle_orm.sql`, `)})
197
+ )`;
198
+ if (clause.operator === "NOT IN") {
199
+ subConditions.push(import_drizzle_orm.sql`${import_database.posts.id} NOT IN (
200
+ SELECT ${import_database.termRelationships.objectId} FROM ${import_database.termRelationships}
201
+ WHERE ${ttIdSubquery}
202
+ )`);
203
+ } else {
204
+ subConditions.push(ttIdSubquery);
205
+ }
206
+ }
207
+ return subConditions.length > 0 ? (0, import_drizzle_orm.and)(...subConditions) : void 0;
208
+ }).filter(Boolean);
209
+ if (clauseConditions.length === 0) return void 0;
210
+ const relation = tax.relation ?? "AND";
211
+ if (relation === "OR") {
212
+ return (0, import_drizzle_orm.or)(...clauseConditions);
213
+ }
214
+ return (0, import_drizzle_orm.and)(...clauseConditions);
215
+ }
216
+ buildMetaConditions(meta) {
217
+ const clauseConditions = meta.clauses.map((clause) => {
218
+ if (clause.compare === "EXISTS") {
219
+ return import_drizzle_orm.sql`${import_database.posts.id} IN (
220
+ SELECT ${import_database.postmeta.postId} FROM ${import_database.postmeta}
221
+ WHERE ${import_database.postmeta.metaKey} = ${clause.key}
222
+ )`;
223
+ }
224
+ if (clause.compare === "NOT EXISTS") {
225
+ return import_drizzle_orm.sql`${import_database.posts.id} NOT IN (
226
+ SELECT ${import_database.postmeta.postId} FROM ${import_database.postmeta}
227
+ WHERE ${import_database.postmeta.metaKey} = ${clause.key}
228
+ )`;
229
+ }
230
+ const valueStr = String(clause.value ?? "");
231
+ const compare = clause.compare ?? "=";
232
+ const castCol = clause.type === "NUMERIC" ? import_drizzle_orm.sql`CAST(${import_database.postmeta.metaValue} AS NUMERIC)` : import_database.postmeta.metaValue;
233
+ const compareValue = clause.type === "NUMERIC" ? import_drizzle_orm.sql`CAST(${valueStr} AS NUMERIC)` : import_drizzle_orm.sql`${valueStr}`;
234
+ let condition;
235
+ switch (compare) {
236
+ case "=":
237
+ condition = import_drizzle_orm.sql`${castCol} = ${compareValue}`;
238
+ break;
239
+ case "!=":
240
+ condition = import_drizzle_orm.sql`${castCol} != ${compareValue}`;
241
+ break;
242
+ case ">":
243
+ condition = import_drizzle_orm.sql`${castCol} > ${compareValue}`;
244
+ break;
245
+ case "<":
246
+ condition = import_drizzle_orm.sql`${castCol} < ${compareValue}`;
247
+ break;
248
+ case ">=":
249
+ condition = import_drizzle_orm.sql`${castCol} >= ${compareValue}`;
250
+ break;
251
+ case "<=":
252
+ condition = import_drizzle_orm.sql`${castCol} <= ${compareValue}`;
253
+ break;
254
+ case "LIKE":
255
+ condition = import_drizzle_orm.sql`${import_database.postmeta.metaValue} LIKE ${valueStr}`;
256
+ break;
257
+ case "NOT LIKE":
258
+ condition = import_drizzle_orm.sql`${import_database.postmeta.metaValue} NOT LIKE ${valueStr}`;
259
+ break;
260
+ default:
261
+ condition = import_drizzle_orm.sql`${castCol} = ${compareValue}`;
262
+ }
263
+ return import_drizzle_orm.sql`${import_database.posts.id} IN (
264
+ SELECT ${import_database.postmeta.postId} FROM ${import_database.postmeta}
265
+ WHERE ${import_database.postmeta.metaKey} = ${clause.key} AND ${condition}
266
+ )`;
267
+ }).filter(Boolean);
268
+ if (clauseConditions.length === 0) return void 0;
269
+ const relation = meta.relation ?? "AND";
270
+ if (relation === "OR") {
271
+ return (0, import_drizzle_orm.or)(...clauseConditions);
272
+ }
273
+ return (0, import_drizzle_orm.and)(...clauseConditions);
274
+ }
275
+ buildDateConditions(date) {
276
+ const clauseConditions = date.clauses.map((clause) => {
277
+ const col = clause.column === "post_modified" ? import_database.posts.postModified : import_database.posts.postDate;
278
+ const conditions = [];
279
+ if (clause.year !== void 0) {
280
+ conditions.push(import_drizzle_orm.sql`EXTRACT(YEAR FROM ${col}) = ${clause.year}`);
281
+ }
282
+ if (clause.month !== void 0) {
283
+ conditions.push(import_drizzle_orm.sql`EXTRACT(MONTH FROM ${col}) = ${clause.month}`);
284
+ }
285
+ if (clause.day !== void 0) {
286
+ conditions.push(import_drizzle_orm.sql`EXTRACT(DAY FROM ${col}) = ${clause.day}`);
287
+ }
288
+ if (clause.after !== void 0) {
289
+ const afterDate = clause.after instanceof Date ? clause.after : new Date(clause.after);
290
+ conditions.push((0, import_drizzle_orm.gt)(col, afterDate));
291
+ }
292
+ if (clause.before !== void 0) {
293
+ const beforeDate = clause.before instanceof Date ? clause.before : new Date(clause.before);
294
+ conditions.push((0, import_drizzle_orm.lt)(col, beforeDate));
295
+ }
296
+ return conditions.length > 0 ? (0, import_drizzle_orm.and)(...conditions) : void 0;
297
+ }).filter(Boolean);
298
+ if (clauseConditions.length === 0) return void 0;
299
+ const relation = date.relation ?? "AND";
300
+ if (relation === "OR") {
301
+ return (0, import_drizzle_orm.or)(...clauseConditions);
302
+ }
303
+ return (0, import_drizzle_orm.and)(...clauseConditions);
304
+ }
305
+ // ─── Private: order by ───────────────────────────────────────
306
+ buildOrderBy(params) {
307
+ const direction = params.order === "asc" ? import_drizzle_orm.asc : import_drizzle_orm.desc;
308
+ switch (params.orderBy) {
309
+ case "title":
310
+ return direction(import_database.posts.postTitle);
311
+ case "name":
312
+ return direction(import_database.posts.postName);
313
+ case "modified":
314
+ return direction(import_database.posts.postModified);
315
+ case "id":
316
+ return direction(import_database.posts.id);
317
+ case "author":
318
+ return direction(import_database.posts.postAuthor);
319
+ case "menu_order":
320
+ return direction(import_database.posts.menuOrder);
321
+ case "date":
322
+ default:
323
+ return direction(import_database.posts.postDate);
324
+ }
325
+ }
326
+ // ─── Private: join detection ─────────────────────────────────
327
+ needsMetaJoin(params) {
328
+ return params.meta !== void 0 && params.meta.clauses.length > 0;
329
+ }
330
+ needsTaxJoin(params) {
331
+ return params.tax !== void 0 && params.tax.clauses.length > 0;
332
+ }
333
+ // ─── Private: query flags ────────────────────────────────────
334
+ buildFlags(params, resultCount, _total) {
335
+ const isSearch = Boolean(params.search);
336
+ const isSingle = (params.include?.length === 1 || params.slug !== void 0) && !Array.isArray(params.slug);
337
+ const isAuthor = params.author !== void 0;
338
+ const isTaxonomy = params.tax !== void 0;
339
+ const isDate = params.date !== void 0;
340
+ const isPage = params.postType === "page";
341
+ const is404 = resultCount === 0;
342
+ const isHome = !isSearch && !isSingle && !isAuthor && !isTaxonomy && !isDate && params.postType === "post";
343
+ const isArchive = !isSingle && (isAuthor || isTaxonomy || isDate);
344
+ return { isSingle, isArchive, isSearch, is404, isHome, isPage, isAuthor, isTaxonomy, isDate };
345
+ }
346
+ };
347
+ // Annotate the CommonJS export names for ESM import in node:
348
+ 0 && (module.exports = {
349
+ QueryEngine
350
+ });
351
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/query-engine.ts"],"sourcesContent":["export { QueryEngine } from './query-engine';\nexport type {\n\tQueryParams,\n\tQueryResult,\n\tQueryFlags,\n\tTaxQuery,\n\tTaxQueryClause,\n\tMetaQuery,\n\tMetaQueryClause,\n\tDateQuery,\n\tDateQueryClause,\n} from './types';\n","import { eq, and, or, desc, asc, sql, inArray, like, gt, lt, notInArray } from 'drizzle-orm';\nimport {\n\tposts,\n\tpostmeta,\n\ttermRelationships,\n\ttermTaxonomy,\n\tterms,\n} from '@newcms/database';\nimport type { Database } from '@newcms/database';\nimport type {\n\tQueryParams,\n\tQueryResult,\n\tQueryFlags,\n\tTaxQuery,\n\tMetaQuery,\n\tDateQuery,\n} from './types';\n\nconst DEFAULT_PER_PAGE = 10;\n\n/**\n * Declarative query engine for content.\n *\n * Accepts a `QueryParams` object and builds a type-safe SQL query\n * via Drizzle ORM. Supports taxonomy sub-queries, meta sub-queries,\n * date filters, full-text search, and pagination.\n */\nexport class QueryEngine {\n\tconstructor(private db: Database) {}\n\n\t/**\n\t * Execute a content query.\n\t */\n\tasync query(params: QueryParams = {}): Promise<QueryResult> {\n\t\tconst perPage = params.perPage === -1 ? 0 : (params.perPage ?? DEFAULT_PER_PAGE);\n\t\tconst page = params.page ?? 1;\n\t\tconst offset = perPage > 0 ? (page - 1) * perPage : 0;\n\n\t\tconst conditions = this.buildConditions(params);\n\t\tconst orderClause = this.buildOrderBy(params);\n\n\t\t// Count total\n\t\tconst [countResult] = await this.db\n\t\t\t.select({ count: sql<number>`count(DISTINCT ${posts.id})::int` })\n\t\t\t.from(posts)\n\t\t\t.leftJoin(postmeta, this.needsMetaJoin(params) ? eq(posts.id, postmeta.postId) : sql`false`)\n\t\t\t.leftJoin(\n\t\t\t\ttermRelationships,\n\t\t\t\tthis.needsTaxJoin(params) ? eq(posts.id, termRelationships.objectId) : sql`false`,\n\t\t\t)\n\t\t\t.where(conditions.length > 0 ? and(...conditions) : undefined);\n\n\t\tconst total = countResult.count;\n\t\tconst totalPages = perPage > 0 ? Math.ceil(total / perPage) : (total > 0 ? 1 : 0);\n\n\t\t// Fetch posts\n\t\tlet query = this.db\n\t\t\t.selectDistinct({\n\t\t\t\tid: posts.id,\n\t\t\t\tpostAuthor: posts.postAuthor,\n\t\t\t\tpostDate: posts.postDate,\n\t\t\t\tpostDateGmt: posts.postDateGmt,\n\t\t\t\tpostContent: posts.postContent,\n\t\t\t\tpostTitle: posts.postTitle,\n\t\t\t\tpostExcerpt: posts.postExcerpt,\n\t\t\t\tpostStatus: posts.postStatus,\n\t\t\t\tcommentStatus: posts.commentStatus,\n\t\t\t\tpingStatus: posts.pingStatus,\n\t\t\t\tpostPassword: posts.postPassword,\n\t\t\t\tpostName: posts.postName,\n\t\t\t\ttoPing: posts.toPing,\n\t\t\t\tpinged: posts.pinged,\n\t\t\t\tpostModified: posts.postModified,\n\t\t\t\tpostModifiedGmt: posts.postModifiedGmt,\n\t\t\t\tpostContentFiltered: posts.postContentFiltered,\n\t\t\t\tpostParent: posts.postParent,\n\t\t\t\tguid: posts.guid,\n\t\t\t\tmenuOrder: posts.menuOrder,\n\t\t\t\tpostType: posts.postType,\n\t\t\t\tpostMimeType: posts.postMimeType,\n\t\t\t\tcommentCount: posts.commentCount,\n\t\t\t})\n\t\t\t.from(posts)\n\t\t\t.leftJoin(postmeta, this.needsMetaJoin(params) ? eq(posts.id, postmeta.postId) : sql`false`)\n\t\t\t.leftJoin(\n\t\t\t\ttermRelationships,\n\t\t\t\tthis.needsTaxJoin(params) ? eq(posts.id, termRelationships.objectId) : sql`false`,\n\t\t\t)\n\t\t\t.where(conditions.length > 0 ? and(...conditions) : undefined)\n\t\t\t.orderBy(orderClause)\n\t\t\t.$dynamic();\n\n\t\tif (perPage > 0) {\n\t\t\tquery = query.limit(perPage).offset(offset);\n\t\t}\n\n\t\tconst rows = await query;\n\n\t\tconst flags = this.buildFlags(params, rows.length, total);\n\n\t\treturn {\n\t\t\tposts: rows,\n\t\t\ttotal,\n\t\t\ttotalPages,\n\t\t\tpage,\n\t\t\tperPage: perPage || total,\n\t\t\tflags,\n\t\t};\n\t}\n\n\t/**\n\t * Shorthand: query a single post by ID.\n\t */\n\tasync querySingle(id: number, postType?: string): Promise<QueryResult> {\n\t\treturn this.query({\n\t\t\tinclude: [id],\n\t\t\tpostType: postType ?? 'post',\n\t\t\tpostStatus: ['publish', 'private', 'draft'],\n\t\t\tperPage: 1,\n\t\t});\n\t}\n\n\t// ─── Private: condition builders ─────────────────────────────\n\n\tprivate buildConditions(params: QueryParams): ReturnType<typeof eq>[] {\n\t\tconst conditions: ReturnType<typeof eq>[] = [];\n\n\t\t// Post type\n\t\tconst postType = params.postType ?? 'post';\n\t\tif (Array.isArray(postType)) {\n\t\t\tconditions.push(inArray(posts.postType, postType));\n\t\t} else {\n\t\t\tconditions.push(eq(posts.postType, postType));\n\t\t}\n\n\t\t// Post status\n\t\tconst status = params.postStatus ?? 'publish';\n\t\tif (Array.isArray(status)) {\n\t\t\tconditions.push(inArray(posts.postStatus, status));\n\t\t} else {\n\t\t\tconditions.push(eq(posts.postStatus, status));\n\t\t}\n\n\t\t// Author\n\t\tif (params.author !== undefined) {\n\t\t\tif (Array.isArray(params.author)) {\n\t\t\t\tconditions.push(inArray(posts.postAuthor, params.author));\n\t\t\t} else {\n\t\t\t\tconditions.push(eq(posts.postAuthor, params.author));\n\t\t\t}\n\t\t}\n\n\t\t// Include/exclude IDs\n\t\tif (params.include && params.include.length > 0) {\n\t\t\tconditions.push(inArray(posts.id, params.include));\n\t\t}\n\t\tif (params.exclude && params.exclude.length > 0) {\n\t\t\tconditions.push(notInArray(posts.id, params.exclude));\n\t\t}\n\n\t\t// Parent\n\t\tif (params.parent !== undefined) {\n\t\t\tconditions.push(eq(posts.postParent, params.parent));\n\t\t}\n\n\t\t// Slug\n\t\tif (params.slug !== undefined) {\n\t\t\tif (Array.isArray(params.slug)) {\n\t\t\t\tconditions.push(inArray(posts.postName, params.slug));\n\t\t\t} else {\n\t\t\t\tconditions.push(eq(posts.postName, params.slug));\n\t\t\t}\n\t\t}\n\n\t\t// Search (full-text)\n\t\tif (params.search) {\n\t\t\tconst searchTerm = params.search.replace(/[^\\w\\s]/g, '').trim();\n\t\t\tif (searchTerm) {\n\t\t\t\tconditions.push(\n\t\t\t\t\tor(\n\t\t\t\t\t\tlike(posts.postTitle, `%${searchTerm}%`),\n\t\t\t\t\t\tlike(posts.postContent, `%${searchTerm}%`),\n\t\t\t\t\t)!,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Taxonomy sub-query\n\t\tif (params.tax) {\n\t\t\tconst taxConditions = this.buildTaxConditions(params.tax);\n\t\t\tif (taxConditions) {\n\t\t\t\tconditions.push(taxConditions);\n\t\t\t}\n\t\t}\n\n\t\t// Meta sub-query\n\t\tif (params.meta) {\n\t\t\tconst metaConditions = this.buildMetaConditions(params.meta);\n\t\t\tif (metaConditions) {\n\t\t\t\tconditions.push(metaConditions);\n\t\t\t}\n\t\t}\n\n\t\t// Date sub-query\n\t\tif (params.date) {\n\t\t\tconst dateConditions = this.buildDateConditions(params.date);\n\t\t\tif (dateConditions) {\n\t\t\t\tconditions.push(dateConditions);\n\t\t\t}\n\t\t}\n\n\t\treturn conditions;\n\t}\n\n\tprivate buildTaxConditions(tax: TaxQuery): ReturnType<typeof eq> | undefined {\n\t\tconst clauseConditions = tax.clauses.map((clause) => {\n\t\t\tconst subConditions: ReturnType<typeof eq>[] = [];\n\n\t\t\tif (clause.termIds && clause.termIds.length > 0) {\n\t\t\t\t// Sub-select to find term_taxonomy_ids for these term IDs\n\t\t\t\tconst ttIdSubquery = sql`${termRelationships.termTaxonomyId} IN (\n\t\t\t\t\tSELECT ${termTaxonomy.termTaxonomyId} FROM ${termTaxonomy}\n\t\t\t\t\tWHERE ${termTaxonomy.taxonomy} = ${clause.taxonomy}\n\t\t\t\t\tAND ${termTaxonomy.termId} IN (${sql.join(clause.termIds.map((id) => sql`${id}`), sql`, `)})\n\t\t\t\t)`;\n\n\t\t\t\tif (clause.operator === 'NOT IN') {\n\t\t\t\t\tsubConditions.push(sql`${posts.id} NOT IN (\n\t\t\t\t\t\tSELECT ${termRelationships.objectId} FROM ${termRelationships}\n\t\t\t\t\t\tWHERE ${ttIdSubquery}\n\t\t\t\t\t)`);\n\t\t\t\t} else {\n\t\t\t\t\tsubConditions.push(ttIdSubquery);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (clause.termSlugs && clause.termSlugs.length > 0) {\n\t\t\t\tconst ttIdSubquery = sql`${termRelationships.termTaxonomyId} IN (\n\t\t\t\t\tSELECT ${termTaxonomy.termTaxonomyId} FROM ${termTaxonomy}\n\t\t\t\t\tINNER JOIN ${terms} ON ${terms.termId} = ${termTaxonomy.termId}\n\t\t\t\t\tWHERE ${termTaxonomy.taxonomy} = ${clause.taxonomy}\n\t\t\t\t\tAND ${terms.slug} IN (${sql.join(clause.termSlugs.map((s) => sql`${s}`), sql`, `)})\n\t\t\t\t)`;\n\n\t\t\t\tif (clause.operator === 'NOT IN') {\n\t\t\t\t\tsubConditions.push(sql`${posts.id} NOT IN (\n\t\t\t\t\t\tSELECT ${termRelationships.objectId} FROM ${termRelationships}\n\t\t\t\t\t\tWHERE ${ttIdSubquery}\n\t\t\t\t\t)`);\n\t\t\t\t} else {\n\t\t\t\t\tsubConditions.push(ttIdSubquery);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn subConditions.length > 0 ? and(...subConditions) : undefined;\n\t\t}).filter(Boolean);\n\n\t\tif (clauseConditions.length === 0) return undefined;\n\n\t\tconst relation = tax.relation ?? 'AND';\n\t\tif (relation === 'OR') {\n\t\t\treturn or(...clauseConditions)!;\n\t\t}\n\t\treturn and(...clauseConditions)!;\n\t}\n\n\tprivate buildMetaConditions(meta: MetaQuery): ReturnType<typeof eq> | undefined {\n\t\tconst clauseConditions = meta.clauses.map((clause) => {\n\t\t\tif (clause.compare === 'EXISTS') {\n\t\t\t\treturn sql`${posts.id} IN (\n\t\t\t\t\tSELECT ${postmeta.postId} FROM ${postmeta}\n\t\t\t\t\tWHERE ${postmeta.metaKey} = ${clause.key}\n\t\t\t\t)`;\n\t\t\t}\n\t\t\tif (clause.compare === 'NOT EXISTS') {\n\t\t\t\treturn sql`${posts.id} NOT IN (\n\t\t\t\t\tSELECT ${postmeta.postId} FROM ${postmeta}\n\t\t\t\t\tWHERE ${postmeta.metaKey} = ${clause.key}\n\t\t\t\t)`;\n\t\t\t}\n\n\t\t\tconst valueStr = String(clause.value ?? '');\n\t\t\tconst compare = clause.compare ?? '=';\n\t\t\tconst castCol = clause.type === 'NUMERIC'\n\t\t\t\t? sql`CAST(${postmeta.metaValue} AS NUMERIC)`\n\t\t\t\t: postmeta.metaValue;\n\n\t\t\tconst compareValue = clause.type === 'NUMERIC'\n\t\t\t\t? sql`CAST(${valueStr} AS NUMERIC)`\n\t\t\t\t: sql`${valueStr}`;\n\n\t\t\tlet condition;\n\t\t\tswitch (compare) {\n\t\t\t\tcase '=': condition = sql`${castCol} = ${compareValue}`; break;\n\t\t\t\tcase '!=': condition = sql`${castCol} != ${compareValue}`; break;\n\t\t\t\tcase '>': condition = sql`${castCol} > ${compareValue}`; break;\n\t\t\t\tcase '<': condition = sql`${castCol} < ${compareValue}`; break;\n\t\t\t\tcase '>=': condition = sql`${castCol} >= ${compareValue}`; break;\n\t\t\t\tcase '<=': condition = sql`${castCol} <= ${compareValue}`; break;\n\t\t\t\tcase 'LIKE': condition = sql`${postmeta.metaValue} LIKE ${valueStr}`; break;\n\t\t\t\tcase 'NOT LIKE': condition = sql`${postmeta.metaValue} NOT LIKE ${valueStr}`; break;\n\t\t\t\tdefault: condition = sql`${castCol} = ${compareValue}`;\n\t\t\t}\n\n\t\t\treturn sql`${posts.id} IN (\n\t\t\t\tSELECT ${postmeta.postId} FROM ${postmeta}\n\t\t\t\tWHERE ${postmeta.metaKey} = ${clause.key} AND ${condition}\n\t\t\t)`;\n\t\t}).filter(Boolean);\n\n\t\tif (clauseConditions.length === 0) return undefined;\n\n\t\tconst relation = meta.relation ?? 'AND';\n\t\tif (relation === 'OR') {\n\t\t\treturn or(...clauseConditions)!;\n\t\t}\n\t\treturn and(...clauseConditions)!;\n\t}\n\n\tprivate buildDateConditions(date: DateQuery): ReturnType<typeof eq> | undefined {\n\t\tconst clauseConditions = date.clauses.map((clause) => {\n\t\t\tconst col = clause.column === 'post_modified' ? posts.postModified : posts.postDate;\n\t\t\tconst conditions: ReturnType<typeof eq>[] = [];\n\n\t\t\tif (clause.year !== undefined) {\n\t\t\t\tconditions.push(sql`EXTRACT(YEAR FROM ${col}) = ${clause.year}`);\n\t\t\t}\n\t\t\tif (clause.month !== undefined) {\n\t\t\t\tconditions.push(sql`EXTRACT(MONTH FROM ${col}) = ${clause.month}`);\n\t\t\t}\n\t\t\tif (clause.day !== undefined) {\n\t\t\t\tconditions.push(sql`EXTRACT(DAY FROM ${col}) = ${clause.day}`);\n\t\t\t}\n\t\t\tif (clause.after !== undefined) {\n\t\t\t\tconst afterDate = clause.after instanceof Date ? clause.after : new Date(clause.after);\n\t\t\t\tconditions.push(gt(col, afterDate));\n\t\t\t}\n\t\t\tif (clause.before !== undefined) {\n\t\t\t\tconst beforeDate = clause.before instanceof Date ? clause.before : new Date(clause.before);\n\t\t\t\tconditions.push(lt(col, beforeDate));\n\t\t\t}\n\n\t\t\treturn conditions.length > 0 ? and(...conditions) : undefined;\n\t\t}).filter(Boolean);\n\n\t\tif (clauseConditions.length === 0) return undefined;\n\n\t\tconst relation = date.relation ?? 'AND';\n\t\tif (relation === 'OR') {\n\t\t\treturn or(...clauseConditions)!;\n\t\t}\n\t\treturn and(...clauseConditions)!;\n\t}\n\n\t// ─── Private: order by ───────────────────────────────────────\n\n\tprivate buildOrderBy(params: QueryParams) {\n\t\tconst direction = params.order === 'asc' ? asc : desc;\n\t\tswitch (params.orderBy) {\n\t\t\tcase 'title': return direction(posts.postTitle);\n\t\t\tcase 'name': return direction(posts.postName);\n\t\t\tcase 'modified': return direction(posts.postModified);\n\t\t\tcase 'id': return direction(posts.id);\n\t\t\tcase 'author': return direction(posts.postAuthor);\n\t\t\tcase 'menu_order': return direction(posts.menuOrder);\n\t\t\tcase 'date':\n\t\t\tdefault: return direction(posts.postDate);\n\t\t}\n\t}\n\n\t// ─── Private: join detection ─────────────────────────────────\n\n\tprivate needsMetaJoin(params: QueryParams): boolean {\n\t\treturn params.meta !== undefined && params.meta.clauses.length > 0;\n\t}\n\n\tprivate needsTaxJoin(params: QueryParams): boolean {\n\t\treturn params.tax !== undefined && params.tax.clauses.length > 0;\n\t}\n\n\t// ─── Private: query flags ────────────────────────────────────\n\n\tprivate buildFlags(params: QueryParams, resultCount: number, _total: number): QueryFlags {\n\t\tconst isSearch = Boolean(params.search);\n\t\tconst isSingle = (params.include?.length === 1 || params.slug !== undefined) && !Array.isArray(params.slug);\n\t\tconst isAuthor = params.author !== undefined;\n\t\tconst isTaxonomy = params.tax !== undefined;\n\t\tconst isDate = params.date !== undefined;\n\t\tconst isPage = params.postType === 'page';\n\t\tconst is404 = resultCount === 0;\n\t\tconst isHome = !isSearch && !isSingle && !isAuthor && !isTaxonomy && !isDate && params.postType === 'post';\n\t\tconst isArchive = !isSingle && (isAuthor || isTaxonomy || isDate);\n\n\t\treturn { isSingle, isArchive, isSearch, is404, isHome, isPage, isAuthor, isTaxonomy, isDate };\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,9 @@
1
+ import { Database } from '@newcms/database';
2
+
1
3
  /**
2
4
  * Parameters for a content query.
3
5
  */
4
- export interface QueryParams {
6
+ interface QueryParams {
5
7
  /** Post type(s) to query. Default: 'post' */
6
8
  postType?: string | string[];
7
9
  /** Post status(es). Default: 'publish' */
@@ -40,12 +42,12 @@ export interface QueryParams {
40
42
  /**
41
43
  * Taxonomy sub-query — filter posts by term assignments.
42
44
  */
43
- export interface TaxQuery {
45
+ interface TaxQuery {
44
46
  /** How to combine multiple clauses: AND = all must match, OR = any can match */
45
47
  relation?: 'AND' | 'OR';
46
48
  clauses: TaxQueryClause[];
47
49
  }
48
- export interface TaxQueryClause {
50
+ interface TaxQueryClause {
49
51
  taxonomy: string;
50
52
  /** Term IDs to match */
51
53
  termIds?: number[];
@@ -59,11 +61,11 @@ export interface TaxQueryClause {
59
61
  /**
60
62
  * Meta sub-query — filter posts by metadata values.
61
63
  */
62
- export interface MetaQuery {
64
+ interface MetaQuery {
63
65
  relation?: 'AND' | 'OR';
64
66
  clauses: MetaQueryClause[];
65
67
  }
66
- export interface MetaQueryClause {
68
+ interface MetaQueryClause {
67
69
  key: string;
68
70
  value?: string | number | boolean;
69
71
  compare?: '=' | '!=' | '>' | '<' | '>=' | '<=' | 'LIKE' | 'NOT LIKE' | 'IN' | 'NOT IN' | 'EXISTS' | 'NOT EXISTS';
@@ -73,11 +75,11 @@ export interface MetaQueryClause {
73
75
  /**
74
76
  * Date sub-query — filter posts by date ranges.
75
77
  */
76
- export interface DateQuery {
78
+ interface DateQuery {
77
79
  relation?: 'AND' | 'OR';
78
80
  clauses: DateQueryClause[];
79
81
  }
80
- export interface DateQueryClause {
82
+ interface DateQueryClause {
81
83
  year?: number;
82
84
  month?: number;
83
85
  day?: number;
@@ -88,7 +90,7 @@ export interface DateQueryClause {
88
90
  /**
89
91
  * Result of a content query.
90
92
  */
91
- export interface QueryResult<T = unknown> {
93
+ interface QueryResult<T = unknown> {
92
94
  /** The matched posts */
93
95
  posts: T[];
94
96
  /** Total number of matching posts (ignoring pagination) */
@@ -105,7 +107,7 @@ export interface QueryResult<T = unknown> {
105
107
  /**
106
108
  * Flags indicating the type of query result.
107
109
  */
108
- export interface QueryFlags {
110
+ interface QueryFlags {
109
111
  isSingle: boolean;
110
112
  isArchive: boolean;
111
113
  isSearch: boolean;
@@ -116,4 +118,33 @@ export interface QueryFlags {
116
118
  isTaxonomy: boolean;
117
119
  isDate: boolean;
118
120
  }
119
- //# sourceMappingURL=types.d.ts.map
121
+
122
+ /**
123
+ * Declarative query engine for content.
124
+ *
125
+ * Accepts a `QueryParams` object and builds a type-safe SQL query
126
+ * via Drizzle ORM. Supports taxonomy sub-queries, meta sub-queries,
127
+ * date filters, full-text search, and pagination.
128
+ */
129
+ declare class QueryEngine {
130
+ private db;
131
+ constructor(db: Database);
132
+ /**
133
+ * Execute a content query.
134
+ */
135
+ query(params?: QueryParams): Promise<QueryResult>;
136
+ /**
137
+ * Shorthand: query a single post by ID.
138
+ */
139
+ querySingle(id: number, postType?: string): Promise<QueryResult>;
140
+ private buildConditions;
141
+ private buildTaxConditions;
142
+ private buildMetaConditions;
143
+ private buildDateConditions;
144
+ private buildOrderBy;
145
+ private needsMetaJoin;
146
+ private needsTaxJoin;
147
+ private buildFlags;
148
+ }
149
+
150
+ export { type DateQuery, type DateQueryClause, type MetaQuery, type MetaQueryClause, QueryEngine, type QueryFlags, type QueryParams, type QueryResult, type TaxQuery, type TaxQueryClause };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,150 @@
1
- export { QueryEngine } from './query-engine.js';
2
- export type { QueryParams, QueryResult, QueryFlags, TaxQuery, TaxQueryClause, MetaQuery, MetaQueryClause, DateQuery, DateQueryClause, } from './types.js';
3
- //# sourceMappingURL=index.d.ts.map
1
+ import { Database } from '@newcms/database';
2
+
3
+ /**
4
+ * Parameters for a content query.
5
+ */
6
+ interface QueryParams {
7
+ /** Post type(s) to query. Default: 'post' */
8
+ postType?: string | string[];
9
+ /** Post status(es). Default: 'publish' */
10
+ postStatus?: string | string[];
11
+ /** Filter by author ID(s) */
12
+ author?: number | number[];
13
+ /** Search term (full-text search) */
14
+ search?: string;
15
+ /** Number of results per page. Default: 10. Use -1 for all. */
16
+ perPage?: number;
17
+ /** Page number (1-based). Default: 1 */
18
+ page?: number;
19
+ /** Order by field. Default: 'date' */
20
+ orderBy?: 'date' | 'title' | 'name' | 'modified' | 'id' | 'author' | 'menu_order' | 'relevance';
21
+ /** Order direction. Default: 'desc' */
22
+ order?: 'asc' | 'desc';
23
+ /** Include only these post IDs */
24
+ include?: number[];
25
+ /** Exclude these post IDs */
26
+ exclude?: number[];
27
+ /** Filter by parent ID (for hierarchical types) */
28
+ parent?: number;
29
+ /** Filter by slug(s) */
30
+ slug?: string | string[];
31
+ /** Only sticky posts */
32
+ stickyOnly?: boolean;
33
+ /** Taxonomy sub-query */
34
+ tax?: TaxQuery;
35
+ /** Meta sub-query */
36
+ meta?: MetaQuery;
37
+ /** Date sub-query */
38
+ date?: DateQuery;
39
+ /** Specific fields to return (optimization) */
40
+ fields?: 'all' | 'ids';
41
+ }
42
+ /**
43
+ * Taxonomy sub-query — filter posts by term assignments.
44
+ */
45
+ interface TaxQuery {
46
+ /** How to combine multiple clauses: AND = all must match, OR = any can match */
47
+ relation?: 'AND' | 'OR';
48
+ clauses: TaxQueryClause[];
49
+ }
50
+ interface TaxQueryClause {
51
+ taxonomy: string;
52
+ /** Term IDs to match */
53
+ termIds?: number[];
54
+ /** Term slugs to match */
55
+ termSlugs?: string[];
56
+ /** Operator: IN = has any of these terms, NOT IN = doesn't have these */
57
+ operator?: 'IN' | 'NOT IN' | 'AND';
58
+ /** Include children of hierarchical terms */
59
+ includeChildren?: boolean;
60
+ }
61
+ /**
62
+ * Meta sub-query — filter posts by metadata values.
63
+ */
64
+ interface MetaQuery {
65
+ relation?: 'AND' | 'OR';
66
+ clauses: MetaQueryClause[];
67
+ }
68
+ interface MetaQueryClause {
69
+ key: string;
70
+ value?: string | number | boolean;
71
+ compare?: '=' | '!=' | '>' | '<' | '>=' | '<=' | 'LIKE' | 'NOT LIKE' | 'IN' | 'NOT IN' | 'EXISTS' | 'NOT EXISTS';
72
+ /** Cast type for comparison */
73
+ type?: 'CHAR' | 'NUMERIC' | 'DATE' | 'DATETIME';
74
+ }
75
+ /**
76
+ * Date sub-query — filter posts by date ranges.
77
+ */
78
+ interface DateQuery {
79
+ relation?: 'AND' | 'OR';
80
+ clauses: DateQueryClause[];
81
+ }
82
+ interface DateQueryClause {
83
+ year?: number;
84
+ month?: number;
85
+ day?: number;
86
+ after?: string | Date;
87
+ before?: string | Date;
88
+ column?: 'post_date' | 'post_modified';
89
+ }
90
+ /**
91
+ * Result of a content query.
92
+ */
93
+ interface QueryResult<T = unknown> {
94
+ /** The matched posts */
95
+ posts: T[];
96
+ /** Total number of matching posts (ignoring pagination) */
97
+ total: number;
98
+ /** Total number of pages */
99
+ totalPages: number;
100
+ /** Current page */
101
+ page: number;
102
+ /** Posts per page */
103
+ perPage: number;
104
+ /** Query type flags */
105
+ flags: QueryFlags;
106
+ }
107
+ /**
108
+ * Flags indicating the type of query result.
109
+ */
110
+ interface QueryFlags {
111
+ isSingle: boolean;
112
+ isArchive: boolean;
113
+ isSearch: boolean;
114
+ is404: boolean;
115
+ isHome: boolean;
116
+ isPage: boolean;
117
+ isAuthor: boolean;
118
+ isTaxonomy: boolean;
119
+ isDate: boolean;
120
+ }
121
+
122
+ /**
123
+ * Declarative query engine for content.
124
+ *
125
+ * Accepts a `QueryParams` object and builds a type-safe SQL query
126
+ * via Drizzle ORM. Supports taxonomy sub-queries, meta sub-queries,
127
+ * date filters, full-text search, and pagination.
128
+ */
129
+ declare class QueryEngine {
130
+ private db;
131
+ constructor(db: Database);
132
+ /**
133
+ * Execute a content query.
134
+ */
135
+ query(params?: QueryParams): Promise<QueryResult>;
136
+ /**
137
+ * Shorthand: query a single post by ID.
138
+ */
139
+ querySingle(id: number, postType?: string): Promise<QueryResult>;
140
+ private buildConditions;
141
+ private buildTaxConditions;
142
+ private buildMetaConditions;
143
+ private buildDateConditions;
144
+ private buildOrderBy;
145
+ private needsMetaJoin;
146
+ private needsTaxJoin;
147
+ private buildFlags;
148
+ }
149
+
150
+ export { type DateQuery, type DateQueryClause, type MetaQuery, type MetaQueryClause, QueryEngine, type QueryFlags, type QueryParams, type QueryResult, type TaxQuery, type TaxQueryClause };