@dyrected/core 2.1.0 → 2.4.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/chunk-7GGHK75W.js +1835 -0
- package/dist/chunk-GZODLJ3C.js +1861 -0
- package/dist/index.cjs +252 -7
- package/dist/index.d.cts +64 -1
- package/dist/index.d.ts +64 -1
- package/dist/index.js +171 -5
- package/dist/server.cjs +79 -2
- package/dist/server.d.cts +12 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -36,7 +36,9 @@ __export(index_exports, {
|
|
|
36
36
|
defineGlobal: () => defineGlobal,
|
|
37
37
|
generateAIPrompt: () => generateAIPrompt,
|
|
38
38
|
generateFreshSetupPrompt: () => generateFreshSetupPrompt,
|
|
39
|
-
normalizeConfig: () => normalizeConfig
|
|
39
|
+
normalizeConfig: () => normalizeConfig,
|
|
40
|
+
parseMongoWhere: () => parseMongoWhere,
|
|
41
|
+
parseSqlWhere: () => parseSqlWhere
|
|
40
42
|
});
|
|
41
43
|
module.exports = __toCommonJS(index_exports);
|
|
42
44
|
|
|
@@ -136,7 +138,10 @@ function buildConstraintsSection() {
|
|
|
136
138
|
- RESILIENCE : If Dyrected backend is unreachable, fall back to
|
|
137
139
|
initialData and show stale content \u2014 never an error page.
|
|
138
140
|
All relationship fields must handle null gracefully.
|
|
139
|
-
Every block renderer must have a default fallback case
|
|
141
|
+
Every block renderer must have a default fallback case.
|
|
142
|
+
- AUTO-SEEDING : Use initialData: [...] in CollectionConfig or GlobalConfig
|
|
143
|
+
to automatically populate the database on first run.
|
|
144
|
+
This is great for demo content or default settings.`;
|
|
140
145
|
}
|
|
141
146
|
function buildSchemaRulesSection() {
|
|
142
147
|
return `
|
|
@@ -145,8 +150,10 @@ function buildSchemaRulesSection() {
|
|
|
145
150
|
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
146
151
|
- Never drop existing fields from the schema. Mark unused fields as deprecated only.
|
|
147
152
|
- All new fields must have a defaultValue.
|
|
148
|
-
-
|
|
149
|
-
-
|
|
153
|
+
- Use renameTo: 'oldName' to lazily migrate data when renaming fields.
|
|
154
|
+
- Use promoted: true for fields that need high-performance SQL indexing or unique constraints.
|
|
155
|
+
- For Cloud deployments, run npx @dyrected/cli sync:schema after every config change. Self-hosted deployments sync automatically on startup.
|
|
156
|
+
`;
|
|
150
157
|
}
|
|
151
158
|
function buildDoNotSection() {
|
|
152
159
|
return `
|
|
@@ -193,6 +200,8 @@ FIELD OPTIONS:
|
|
|
193
200
|
- unique \u2014 database-level uniqueness constraint
|
|
194
201
|
- hasMany \u2014 allow multiple values (for relationship, select, image)
|
|
195
202
|
- defaultValue \u2014 fallback value (required on all new fields added to existing schemas)
|
|
203
|
+
- promoted \u2014 extracts field to a real SQL column for native indexing (SQL adapters only)
|
|
204
|
+
- renameTo \u2014 name of the old field key to migrate data from (lazy migration)
|
|
196
205
|
- admin.condition \u2014 Jexl expression string to conditionally show/hide field
|
|
197
206
|
e.g. "status == \\"published\\""
|
|
198
207
|
- admin.readOnly \u2014 display only, not editable
|
|
@@ -291,6 +300,7 @@ const settings = defineGlobal({
|
|
|
291
300
|
export default defineConfig({
|
|
292
301
|
collections: [media, pages],
|
|
293
302
|
globals: [settings],
|
|
303
|
+
// Use SqliteAdapter for local, PostgresAdapter or MySqlAdapter for production
|
|
294
304
|
db: new SqliteAdapter({ filename: './dyrected.db' }),
|
|
295
305
|
})
|
|
296
306
|
\`\`\``;
|
|
@@ -915,6 +925,162 @@ function normalizeConfig(config) {
|
|
|
915
925
|
};
|
|
916
926
|
}
|
|
917
927
|
|
|
928
|
+
// src/utils/parse-where.ts
|
|
929
|
+
function assertNever(op, context) {
|
|
930
|
+
throw new Error(`[dyrected/core] Unhandled where operator "${op}" in ${context}`);
|
|
931
|
+
}
|
|
932
|
+
function parseSqlWhere(where, getJsonField, placeholder = "?") {
|
|
933
|
+
const params = [];
|
|
934
|
+
let pgIndex = 1;
|
|
935
|
+
function next() {
|
|
936
|
+
return placeholder === "pg" ? `$${pgIndex++}` : "?";
|
|
937
|
+
}
|
|
938
|
+
function col(field) {
|
|
939
|
+
return field === "id" ? "id" : getJsonField(field);
|
|
940
|
+
}
|
|
941
|
+
function buildOperator(field, value) {
|
|
942
|
+
const c = col(field);
|
|
943
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
944
|
+
params.push(value);
|
|
945
|
+
return `${c} = ${next()}`;
|
|
946
|
+
}
|
|
947
|
+
const entries = Object.entries(value);
|
|
948
|
+
if (entries.length !== 1) {
|
|
949
|
+
return entries.map(([op2, operand2]) => buildSingleOp(c, op2, operand2)).join(" AND ");
|
|
950
|
+
}
|
|
951
|
+
const [op, operand] = entries[0];
|
|
952
|
+
return buildSingleOp(c, op, operand);
|
|
953
|
+
}
|
|
954
|
+
function buildSingleOp(c, op, operand) {
|
|
955
|
+
switch (op) {
|
|
956
|
+
case "equals":
|
|
957
|
+
params.push(operand);
|
|
958
|
+
return `${c} = ${next()}`;
|
|
959
|
+
case "not_equals":
|
|
960
|
+
params.push(operand);
|
|
961
|
+
return `${c} != ${next()}`;
|
|
962
|
+
case "in": {
|
|
963
|
+
const vals = Array.isArray(operand) ? operand : [operand];
|
|
964
|
+
if (vals.length === 0) return "1=0";
|
|
965
|
+
const placeholders = vals.map((v) => {
|
|
966
|
+
params.push(v);
|
|
967
|
+
return next();
|
|
968
|
+
});
|
|
969
|
+
return `${c} IN (${placeholders.join(", ")})`;
|
|
970
|
+
}
|
|
971
|
+
case "not_in": {
|
|
972
|
+
const vals = Array.isArray(operand) ? operand : [operand];
|
|
973
|
+
if (vals.length === 0) return "1=1";
|
|
974
|
+
const placeholders = vals.map((v) => {
|
|
975
|
+
params.push(v);
|
|
976
|
+
return next();
|
|
977
|
+
});
|
|
978
|
+
return `${c} NOT IN (${placeholders.join(", ")})`;
|
|
979
|
+
}
|
|
980
|
+
case "gt":
|
|
981
|
+
params.push(operand);
|
|
982
|
+
return `${c} > ${next()}`;
|
|
983
|
+
case "gte":
|
|
984
|
+
params.push(operand);
|
|
985
|
+
return `${c} >= ${next()}`;
|
|
986
|
+
case "lt":
|
|
987
|
+
params.push(operand);
|
|
988
|
+
return `${c} < ${next()}`;
|
|
989
|
+
case "lte":
|
|
990
|
+
params.push(operand);
|
|
991
|
+
return `${c} <= ${next()}`;
|
|
992
|
+
case "contains":
|
|
993
|
+
params.push(`%${operand}%`);
|
|
994
|
+
return `${c} LIKE ${next()}`;
|
|
995
|
+
case "starts_with":
|
|
996
|
+
params.push(`${operand}%`);
|
|
997
|
+
return `${c} LIKE ${next()}`;
|
|
998
|
+
case "exists":
|
|
999
|
+
return operand ? `${c} IS NOT NULL` : `${c} IS NULL`;
|
|
1000
|
+
default:
|
|
1001
|
+
return assertNever(op, "parseSqlWhere");
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
function buildClause(w) {
|
|
1005
|
+
const parts = [];
|
|
1006
|
+
for (const [field, value] of Object.entries(w)) {
|
|
1007
|
+
if (field === "OR" && Array.isArray(value)) {
|
|
1008
|
+
const sub = value.map((v) => `(${buildClause(v)})`).join(" OR ");
|
|
1009
|
+
parts.push(`(${sub})`);
|
|
1010
|
+
} else if (field === "AND" && Array.isArray(value)) {
|
|
1011
|
+
const sub = value.map((v) => `(${buildClause(v)})`).join(" AND ");
|
|
1012
|
+
parts.push(`(${sub})`);
|
|
1013
|
+
} else {
|
|
1014
|
+
parts.push(buildOperator(field, value));
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
return parts.length ? parts.join(" AND ") : "1=1";
|
|
1018
|
+
}
|
|
1019
|
+
const sql = buildClause(where);
|
|
1020
|
+
return { sql, params };
|
|
1021
|
+
}
|
|
1022
|
+
function parseMongoWhere(where) {
|
|
1023
|
+
function buildOperator(field, value) {
|
|
1024
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1025
|
+
return { [field]: { $eq: value } };
|
|
1026
|
+
}
|
|
1027
|
+
const entries = Object.entries(value);
|
|
1028
|
+
if (entries.length !== 1) {
|
|
1029
|
+
const merged = {};
|
|
1030
|
+
for (const [op2, operand2] of entries) {
|
|
1031
|
+
Object.assign(merged, buildSingleOp(field, op2, operand2)[field]);
|
|
1032
|
+
}
|
|
1033
|
+
return { [field]: merged };
|
|
1034
|
+
}
|
|
1035
|
+
const [op, operand] = entries[0];
|
|
1036
|
+
return buildSingleOp(field, op, operand);
|
|
1037
|
+
}
|
|
1038
|
+
function buildSingleOp(field, op, operand) {
|
|
1039
|
+
switch (op) {
|
|
1040
|
+
case "equals":
|
|
1041
|
+
return { [field]: { $eq: operand } };
|
|
1042
|
+
case "not_equals":
|
|
1043
|
+
return { [field]: { $ne: operand } };
|
|
1044
|
+
case "in":
|
|
1045
|
+
return { [field]: { $in: Array.isArray(operand) ? operand : [operand] } };
|
|
1046
|
+
case "not_in":
|
|
1047
|
+
return { [field]: { $nin: Array.isArray(operand) ? operand : [operand] } };
|
|
1048
|
+
case "gt":
|
|
1049
|
+
return { [field]: { $gt: operand } };
|
|
1050
|
+
case "gte":
|
|
1051
|
+
return { [field]: { $gte: operand } };
|
|
1052
|
+
case "lt":
|
|
1053
|
+
return { [field]: { $lt: operand } };
|
|
1054
|
+
case "lte":
|
|
1055
|
+
return { [field]: { $lte: operand } };
|
|
1056
|
+
case "contains":
|
|
1057
|
+
return { [field]: { $regex: operand, $options: "i" } };
|
|
1058
|
+
case "starts_with":
|
|
1059
|
+
return { [field]: { $regex: `^${operand}`, $options: "i" } };
|
|
1060
|
+
case "exists":
|
|
1061
|
+
return { [field]: { $exists: operand } };
|
|
1062
|
+
default:
|
|
1063
|
+
return assertNever(op, "parseMongoWhere");
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
function buildClause(w) {
|
|
1067
|
+
const conditions = [];
|
|
1068
|
+
for (const [field, value] of Object.entries(w)) {
|
|
1069
|
+
if (field === "OR" && Array.isArray(value)) {
|
|
1070
|
+
conditions.push({ $or: value.map(buildClause) });
|
|
1071
|
+
} else if (field === "AND" && Array.isArray(value)) {
|
|
1072
|
+
conditions.push({ $and: value.map(buildClause) });
|
|
1073
|
+
} else {
|
|
1074
|
+
conditions.push(buildOperator(field, value));
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
if (conditions.length === 0) return {};
|
|
1078
|
+
if (conditions.length === 1) return conditions[0];
|
|
1079
|
+
return { $and: conditions };
|
|
1080
|
+
}
|
|
1081
|
+
return buildClause(where);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
918
1084
|
// src/app.ts
|
|
919
1085
|
var import_hono = require("hono");
|
|
920
1086
|
var import_cors = require("hono/cors");
|
|
@@ -1029,7 +1195,14 @@ var DefaultsService = class {
|
|
|
1029
1195
|
static apply(fields, data = {}) {
|
|
1030
1196
|
const result = { ...data || {} };
|
|
1031
1197
|
fields.forEach((field) => {
|
|
1032
|
-
|
|
1198
|
+
let value = result[field.name];
|
|
1199
|
+
if ((value === void 0 || value === null) && field.renameTo) {
|
|
1200
|
+
const legacyValue = result[field.renameTo];
|
|
1201
|
+
if (legacyValue !== void 0 && legacyValue !== null) {
|
|
1202
|
+
value = legacyValue;
|
|
1203
|
+
result[field.name] = legacyValue;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1033
1206
|
if (value === void 0 || value === null) {
|
|
1034
1207
|
if (field.defaultValue !== void 0) {
|
|
1035
1208
|
result[field.name] = field.defaultValue;
|
|
@@ -1108,6 +1281,19 @@ var CollectionController = class {
|
|
|
1108
1281
|
sort,
|
|
1109
1282
|
where
|
|
1110
1283
|
});
|
|
1284
|
+
if (result.total === 0 && this.collection.initialData && !where && page === 1) {
|
|
1285
|
+
console.log(`[dyrected/core] Auto-seeding collection "${this.collection.slug}" from config.initialData`);
|
|
1286
|
+
for (const data of this.collection.initialData) {
|
|
1287
|
+
await db.create({ collection: this.collection.slug, data });
|
|
1288
|
+
}
|
|
1289
|
+
result = await db.find({
|
|
1290
|
+
collection: this.collection.slug,
|
|
1291
|
+
limit,
|
|
1292
|
+
page,
|
|
1293
|
+
sort,
|
|
1294
|
+
where
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1111
1297
|
result.docs = result.docs.map((doc) => DefaultsService.apply(this.collection.fields, doc));
|
|
1112
1298
|
if (depth > 0) {
|
|
1113
1299
|
const populationService = new PopulationService(db, config.collections);
|
|
@@ -1260,6 +1446,54 @@ var CollectionController = class {
|
|
|
1260
1446
|
}
|
|
1261
1447
|
return c.json({ message: "Deleted" });
|
|
1262
1448
|
}
|
|
1449
|
+
async deleteMany(c) {
|
|
1450
|
+
const config = c.get("config");
|
|
1451
|
+
const db = config.db;
|
|
1452
|
+
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
1453
|
+
const user = c.get("user");
|
|
1454
|
+
let ids = [];
|
|
1455
|
+
try {
|
|
1456
|
+
const body = await c.req.json().catch(() => null);
|
|
1457
|
+
if (body?.ids && Array.isArray(body.ids)) {
|
|
1458
|
+
ids = body.ids;
|
|
1459
|
+
}
|
|
1460
|
+
} catch {
|
|
1461
|
+
}
|
|
1462
|
+
if (!ids.length) {
|
|
1463
|
+
const raw = c.req.queries("ids") ?? c.req.queries("ids[]") ?? [];
|
|
1464
|
+
ids = raw.filter(Boolean);
|
|
1465
|
+
}
|
|
1466
|
+
if (!ids.length) return c.json({ message: "No IDs provided" }, 400);
|
|
1467
|
+
const deleted = [];
|
|
1468
|
+
const failed = [];
|
|
1469
|
+
for (const id of ids) {
|
|
1470
|
+
try {
|
|
1471
|
+
let before = null;
|
|
1472
|
+
if (this.collection.audit) {
|
|
1473
|
+
before = await db.findOne({ collection: this.collection.slug, id });
|
|
1474
|
+
}
|
|
1475
|
+
await db.delete({ collection: this.collection.slug, id });
|
|
1476
|
+
deleted.push(id);
|
|
1477
|
+
if (this.collection.audit) {
|
|
1478
|
+
AuditService.log(db, {
|
|
1479
|
+
operation: "delete",
|
|
1480
|
+
collection: this.collection.slug,
|
|
1481
|
+
documentId: id,
|
|
1482
|
+
user: user ? { id: user.sub, collection: user.collection, email: user.email } : void 0,
|
|
1483
|
+
before,
|
|
1484
|
+
after: null
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
} catch (err) {
|
|
1488
|
+
failed.push({ id, error: err?.message ?? "Unknown error" });
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
return c.json({
|
|
1492
|
+
message: `Deleted ${deleted.length} document(s)`,
|
|
1493
|
+
deleted,
|
|
1494
|
+
...failed.length ? { failed } : {}
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1263
1497
|
async seed(c) {
|
|
1264
1498
|
const config = c.get("config");
|
|
1265
1499
|
const db = config.db;
|
|
@@ -1294,7 +1528,13 @@ var GlobalController = class {
|
|
|
1294
1528
|
const db = config.db;
|
|
1295
1529
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
1296
1530
|
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
|
|
1297
|
-
|
|
1531
|
+
let data = await db.getGlobal({ slug: this.global.slug });
|
|
1532
|
+
const isEmpty = !data || Object.keys(data).length === 0;
|
|
1533
|
+
if (isEmpty && this.global.initialData) {
|
|
1534
|
+
console.log(`[dyrected/core] Auto-seeding global "${this.global.slug}" from config.initialData`);
|
|
1535
|
+
await db.updateGlobal({ slug: this.global.slug, data: this.global.initialData });
|
|
1536
|
+
data = this.global.initialData;
|
|
1537
|
+
}
|
|
1298
1538
|
const dataWithDefaults = DefaultsService.apply(this.global.fields, data);
|
|
1299
1539
|
if (depth > 0 && dataWithDefaults) {
|
|
1300
1540
|
const populationService = new PopulationService(db, config.collections);
|
|
@@ -2509,6 +2749,7 @@ function registerRoutes(app, config) {
|
|
|
2509
2749
|
app.get(path, accessGate(collection, "read"), (c) => controller.find(c));
|
|
2510
2750
|
app.post(path, accessGate(collection, "create"), (c) => controller.create(c));
|
|
2511
2751
|
app.post(`${path}/media`, accessGate(collection, "create"), (c) => controller.create(c));
|
|
2752
|
+
app.delete(`${path}/delete-many`, accessGate(collection, "delete"), (c) => controller.deleteMany(c));
|
|
2512
2753
|
app.get(`${path}/:id`, accessGate(collection, "read"), (c) => controller.findOne(c));
|
|
2513
2754
|
app.patch(`${path}/:id`, accessGate(collection, "update"), (c) => controller.update(c));
|
|
2514
2755
|
app.delete(`${path}/:id`, accessGate(collection, "delete"), (c) => controller.delete(c));
|
|
@@ -2559,8 +2800,10 @@ function registerRoutes(app, config) {
|
|
|
2559
2800
|
if (id) {
|
|
2560
2801
|
if (method === "GET") return controller.findOne(c);
|
|
2561
2802
|
if (method === "PATCH") return controller.update(c);
|
|
2803
|
+
if (method === "DELETE" && id === "delete-many") return controller.deleteMany(c);
|
|
2562
2804
|
if (method === "DELETE") return controller.delete(c);
|
|
2563
2805
|
if (method === "POST" && id === "media") return controller.create(c);
|
|
2806
|
+
if (method === "POST" && id === "seed") return controller.seed(c);
|
|
2564
2807
|
} else {
|
|
2565
2808
|
if (method === "GET") return controller.find(c);
|
|
2566
2809
|
if (method === "POST") return controller.create(c);
|
|
@@ -2646,5 +2889,7 @@ function defineConfig(config) {
|
|
|
2646
2889
|
defineGlobal,
|
|
2647
2890
|
generateAIPrompt,
|
|
2648
2891
|
generateFreshSetupPrompt,
|
|
2649
|
-
normalizeConfig
|
|
2892
|
+
normalizeConfig,
|
|
2893
|
+
parseMongoWhere,
|
|
2894
|
+
parseSqlWhere
|
|
2650
2895
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -21,6 +21,69 @@ declare function generateAIPrompt(activeTab: "next" | "nuxt" | "react" | "vue",
|
|
|
21
21
|
*/
|
|
22
22
|
declare function normalizeConfig(config: DyrectedConfig): DyrectedConfig;
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Dyrected Where Clause DSL — shared query language across all database adapters.
|
|
26
|
+
*
|
|
27
|
+
* Adapters translate this DSL into their native format:
|
|
28
|
+
* - SQL adapters (SQLite, Postgres, MySQL): use parseSqlWhere()
|
|
29
|
+
* - MongoDB adapter: use parseMongoWhere()
|
|
30
|
+
* - Future adapters: implement their own translator against WhereClause
|
|
31
|
+
*
|
|
32
|
+
* Exhaustive operator handling is enforced at compile time via `assertNever`,
|
|
33
|
+
* so adding a new operator without handling it will produce a TypeScript error
|
|
34
|
+
* in every adapter translator.
|
|
35
|
+
*/
|
|
36
|
+
type WhereOperatorName = 'equals' | 'not_equals' | 'in' | 'not_in' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'starts_with' | 'exists';
|
|
37
|
+
type WhereOperator = {
|
|
38
|
+
equals: any;
|
|
39
|
+
} | {
|
|
40
|
+
not_equals: any;
|
|
41
|
+
} | {
|
|
42
|
+
in: any[];
|
|
43
|
+
} | {
|
|
44
|
+
not_in: any[];
|
|
45
|
+
} | {
|
|
46
|
+
gt: any;
|
|
47
|
+
} | {
|
|
48
|
+
gte: any;
|
|
49
|
+
} | {
|
|
50
|
+
lt: any;
|
|
51
|
+
} | {
|
|
52
|
+
lte: any;
|
|
53
|
+
} | {
|
|
54
|
+
contains: string;
|
|
55
|
+
} | {
|
|
56
|
+
starts_with: string;
|
|
57
|
+
} | {
|
|
58
|
+
exists: boolean;
|
|
59
|
+
};
|
|
60
|
+
/** Top-level where clause. Fields map to operator objects or shorthand scalar values. */
|
|
61
|
+
type WhereClause = {
|
|
62
|
+
[field: string]: WhereOperator | any;
|
|
63
|
+
OR?: WhereClause[];
|
|
64
|
+
AND?: WhereClause[];
|
|
65
|
+
};
|
|
66
|
+
interface SqlWhereResult {
|
|
67
|
+
sql: string;
|
|
68
|
+
params: any[];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Translates a WhereClause into a parameterized SQL WHERE fragment.
|
|
72
|
+
*
|
|
73
|
+
* @param where The Dyrected where DSL object
|
|
74
|
+
* @param getJsonField Returns the SQL expression for a JSON-stored field (dialect-specific):
|
|
75
|
+
* SQLite: (f) => `json_extract(data, '$.${f}')`
|
|
76
|
+
* Postgres: (f) => `data->>'${f}'`
|
|
77
|
+
* MySQL: (f) => `JSON_UNQUOTE(JSON_EXTRACT(data, '$.${f}'))`
|
|
78
|
+
* @param placeholder '?' for SQLite/MySQL, 'pg' for auto-incrementing Postgres $1/$2/…
|
|
79
|
+
*/
|
|
80
|
+
declare function parseSqlWhere(where: WhereClause, getJsonField: (field: string) => string, placeholder?: '?' | 'pg'): SqlWhereResult;
|
|
81
|
+
/**
|
|
82
|
+
* Translates a WhereClause into a MongoDB filter object.
|
|
83
|
+
* Handles nested OR/AND via $or/$and, and all operators via $eq/$in/$gt etc.
|
|
84
|
+
*/
|
|
85
|
+
declare function parseMongoWhere(where: WhereClause): Record<string, any>;
|
|
86
|
+
|
|
24
87
|
/**
|
|
25
88
|
* Define a collection configuration with full type safety.
|
|
26
89
|
*/
|
|
@@ -34,4 +97,4 @@ declare function defineGlobal(config: GlobalConfig): GlobalConfig;
|
|
|
34
97
|
*/
|
|
35
98
|
declare function defineConfig(config: DyrectedConfig): DyrectedConfig;
|
|
36
99
|
|
|
37
|
-
export { CollectionConfig, DyrectedConfig, GlobalConfig, type SetupPromptConfig, defineCollection, defineConfig, defineGlobal, generateAIPrompt, generateFreshSetupPrompt, normalizeConfig };
|
|
100
|
+
export { CollectionConfig, DyrectedConfig, GlobalConfig, type SetupPromptConfig, type SqlWhereResult, type WhereClause, type WhereOperator, type WhereOperatorName, defineCollection, defineConfig, defineGlobal, generateAIPrompt, generateFreshSetupPrompt, normalizeConfig, parseMongoWhere, parseSqlWhere };
|
package/dist/index.d.ts
CHANGED
|
@@ -21,6 +21,69 @@ declare function generateAIPrompt(activeTab: "next" | "nuxt" | "react" | "vue",
|
|
|
21
21
|
*/
|
|
22
22
|
declare function normalizeConfig(config: DyrectedConfig): DyrectedConfig;
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Dyrected Where Clause DSL — shared query language across all database adapters.
|
|
26
|
+
*
|
|
27
|
+
* Adapters translate this DSL into their native format:
|
|
28
|
+
* - SQL adapters (SQLite, Postgres, MySQL): use parseSqlWhere()
|
|
29
|
+
* - MongoDB adapter: use parseMongoWhere()
|
|
30
|
+
* - Future adapters: implement their own translator against WhereClause
|
|
31
|
+
*
|
|
32
|
+
* Exhaustive operator handling is enforced at compile time via `assertNever`,
|
|
33
|
+
* so adding a new operator without handling it will produce a TypeScript error
|
|
34
|
+
* in every adapter translator.
|
|
35
|
+
*/
|
|
36
|
+
type WhereOperatorName = 'equals' | 'not_equals' | 'in' | 'not_in' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'starts_with' | 'exists';
|
|
37
|
+
type WhereOperator = {
|
|
38
|
+
equals: any;
|
|
39
|
+
} | {
|
|
40
|
+
not_equals: any;
|
|
41
|
+
} | {
|
|
42
|
+
in: any[];
|
|
43
|
+
} | {
|
|
44
|
+
not_in: any[];
|
|
45
|
+
} | {
|
|
46
|
+
gt: any;
|
|
47
|
+
} | {
|
|
48
|
+
gte: any;
|
|
49
|
+
} | {
|
|
50
|
+
lt: any;
|
|
51
|
+
} | {
|
|
52
|
+
lte: any;
|
|
53
|
+
} | {
|
|
54
|
+
contains: string;
|
|
55
|
+
} | {
|
|
56
|
+
starts_with: string;
|
|
57
|
+
} | {
|
|
58
|
+
exists: boolean;
|
|
59
|
+
};
|
|
60
|
+
/** Top-level where clause. Fields map to operator objects or shorthand scalar values. */
|
|
61
|
+
type WhereClause = {
|
|
62
|
+
[field: string]: WhereOperator | any;
|
|
63
|
+
OR?: WhereClause[];
|
|
64
|
+
AND?: WhereClause[];
|
|
65
|
+
};
|
|
66
|
+
interface SqlWhereResult {
|
|
67
|
+
sql: string;
|
|
68
|
+
params: any[];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Translates a WhereClause into a parameterized SQL WHERE fragment.
|
|
72
|
+
*
|
|
73
|
+
* @param where The Dyrected where DSL object
|
|
74
|
+
* @param getJsonField Returns the SQL expression for a JSON-stored field (dialect-specific):
|
|
75
|
+
* SQLite: (f) => `json_extract(data, '$.${f}')`
|
|
76
|
+
* Postgres: (f) => `data->>'${f}'`
|
|
77
|
+
* MySQL: (f) => `JSON_UNQUOTE(JSON_EXTRACT(data, '$.${f}'))`
|
|
78
|
+
* @param placeholder '?' for SQLite/MySQL, 'pg' for auto-incrementing Postgres $1/$2/…
|
|
79
|
+
*/
|
|
80
|
+
declare function parseSqlWhere(where: WhereClause, getJsonField: (field: string) => string, placeholder?: '?' | 'pg'): SqlWhereResult;
|
|
81
|
+
/**
|
|
82
|
+
* Translates a WhereClause into a MongoDB filter object.
|
|
83
|
+
* Handles nested OR/AND via $or/$and, and all operators via $eq/$in/$gt etc.
|
|
84
|
+
*/
|
|
85
|
+
declare function parseMongoWhere(where: WhereClause): Record<string, any>;
|
|
86
|
+
|
|
24
87
|
/**
|
|
25
88
|
* Define a collection configuration with full type safety.
|
|
26
89
|
*/
|
|
@@ -34,4 +97,4 @@ declare function defineGlobal(config: GlobalConfig): GlobalConfig;
|
|
|
34
97
|
*/
|
|
35
98
|
declare function defineConfig(config: DyrectedConfig): DyrectedConfig;
|
|
36
99
|
|
|
37
|
-
export { CollectionConfig, DyrectedConfig, GlobalConfig, type SetupPromptConfig, defineCollection, defineConfig, defineGlobal, generateAIPrompt, generateFreshSetupPrompt, normalizeConfig };
|
|
100
|
+
export { CollectionConfig, DyrectedConfig, GlobalConfig, type SetupPromptConfig, type SqlWhereResult, type WhereClause, type WhereOperator, type WhereOperatorName, defineCollection, defineConfig, defineGlobal, generateAIPrompt, generateFreshSetupPrompt, normalizeConfig, parseMongoWhere, parseSqlWhere };
|