@dyrected/core 2.16.0 → 2.18.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/{app-KSDgfXsw.d.cts → app-Dd9cBbCs.d.cts} +1460 -1184
- package/dist/{app-KSDgfXsw.d.ts → app-Dd9cBbCs.d.ts} +1460 -1184
- package/dist/index.cjs +988 -6
- package/dist/index.d.cts +155 -4
- package/dist/index.d.ts +155 -4
- package/dist/index.js +969 -6
- package/dist/server.cjs +1079 -40
- package/dist/server.d.cts +20 -4
- package/dist/server.d.ts +20 -4
- package/dist/server.js +1078 -40
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1792,8 +1792,10 @@ __export(index_exports, {
|
|
|
1792
1792
|
BASE62_ALPHABET: () => BASE62_ALPHABET,
|
|
1793
1793
|
BUILTIN_JEXL_HELPERS: () => BUILTIN_JEXL_HELPERS,
|
|
1794
1794
|
CROCKFORD_BASE32: () => CROCKFORD_BASE32,
|
|
1795
|
+
CURRENT_NAV_PREFERENCES_VERSION: () => CURRENT_NAV_PREFERENCES_VERSION,
|
|
1795
1796
|
ConfigValidationError: () => ConfigValidationError,
|
|
1796
1797
|
ConflictError: () => ConflictError,
|
|
1798
|
+
DEFAULT_USER_NAV_PREFERENCES: () => DEFAULT_USER_NAV_PREFERENCES,
|
|
1797
1799
|
DuplicateKeyError: () => DuplicateKeyError,
|
|
1798
1800
|
DyrectedAIError: () => DyrectedAIError,
|
|
1799
1801
|
DyrectedError: () => DyrectedError,
|
|
@@ -1806,6 +1808,10 @@ __export(index_exports, {
|
|
|
1806
1808
|
NON_NEGOTIABLE_CREDENTIAL_FIELDS: () => NON_NEGOTIABLE_CREDENTIAL_FIELDS,
|
|
1807
1809
|
NotFoundError: () => NotFoundError,
|
|
1808
1810
|
PII_REGEX_PATTERNS: () => PII_REGEX_PATTERNS,
|
|
1811
|
+
RESERVED_NAVIGATION_SLUGS: () => RESERVED_NAVIGATION_SLUGS,
|
|
1812
|
+
TASK_LOCKS_COLLECTION: () => TASK_LOCKS_COLLECTION,
|
|
1813
|
+
TASK_LOCKS_COLLECTION_CONFIG: () => TASK_LOCKS_COLLECTION_CONFIG,
|
|
1814
|
+
UNASSIGNED_GROUP_KEY: () => UNASSIGNED_GROUP_KEY,
|
|
1809
1815
|
ValidationError: () => ValidationError,
|
|
1810
1816
|
WORKFLOW_HISTORY_COLLECTION: () => WORKFLOW_HISTORY_COLLECTION,
|
|
1811
1817
|
abs: () => abs,
|
|
@@ -1816,6 +1822,7 @@ __export(index_exports, {
|
|
|
1816
1822
|
assertValidDeclarativeHookExpression: () => assertValidDeclarativeHookExpression,
|
|
1817
1823
|
assertValidDeclarativeHookResult: () => assertValidDeclarativeHookResult,
|
|
1818
1824
|
assertValidDeclarativeHooksInConfig: () => assertValidDeclarativeHooksInConfig,
|
|
1825
|
+
assertValidNavigationSlugs: () => assertValidNavigationSlugs,
|
|
1819
1826
|
assertValidPreviewUrlsInConfig: () => assertValidPreviewUrlsInConfig,
|
|
1820
1827
|
availableWorkflowTransitions: () => availableWorkflowTransitions,
|
|
1821
1828
|
buildSqlOrderBy: () => buildSqlOrderBy,
|
|
@@ -1824,14 +1831,17 @@ __export(index_exports, {
|
|
|
1824
1831
|
ceil: () => ceil,
|
|
1825
1832
|
clamp: () => clamp,
|
|
1826
1833
|
coalesce: () => coalesce,
|
|
1834
|
+
coerceBooleanWhere: () => coerceBooleanWhere,
|
|
1827
1835
|
collectAdminConditionValidationIssues: () => collectAdminConditionValidationIssues,
|
|
1828
1836
|
collectConfigDiagnostics: () => collectConfigDiagnostics,
|
|
1829
1837
|
collectDeclarativeAccessValidationIssues: () => collectDeclarativeAccessValidationIssues,
|
|
1830
1838
|
collectDeclarativeHookValidationIssues: () => collectDeclarativeHookValidationIssues,
|
|
1831
1839
|
collectPreviewUrlValidationIssues: () => collectPreviewUrlValidationIssues,
|
|
1832
1840
|
compact: () => compact,
|
|
1841
|
+
compileNavigation: () => compileNavigation,
|
|
1833
1842
|
count: () => count,
|
|
1834
1843
|
createLifecycleEvent: () => createLifecycleEvent,
|
|
1844
|
+
createTaskRunner: () => createTaskRunner,
|
|
1835
1845
|
createWorkflowDocument: () => createWorkflowDocument,
|
|
1836
1846
|
defaultValue: () => defaultValue,
|
|
1837
1847
|
defineAction: () => defineAction,
|
|
@@ -1854,6 +1864,7 @@ __export(index_exports, {
|
|
|
1854
1864
|
defineJoinField: () => defineJoinField,
|
|
1855
1865
|
defineJsonField: () => defineJsonField,
|
|
1856
1866
|
defineKanbanView: () => defineKanbanView,
|
|
1867
|
+
defineMoneyField: () => defineMoneyField,
|
|
1857
1868
|
defineMultiSelectField: () => defineMultiSelectField,
|
|
1858
1869
|
defineNumberField: () => defineNumberField,
|
|
1859
1870
|
defineObjectField: () => defineObjectField,
|
|
@@ -1865,11 +1876,13 @@ __export(index_exports, {
|
|
|
1865
1876
|
defineSelectField: () => defineSelectField,
|
|
1866
1877
|
defineTab: () => defineTab,
|
|
1867
1878
|
defineTableView: () => defineTableView,
|
|
1879
|
+
defineTask: () => defineTask,
|
|
1868
1880
|
defineTextField: () => defineTextField,
|
|
1869
1881
|
defineTextareaField: () => defineTextareaField,
|
|
1870
1882
|
defineTimeField: () => defineTimeField,
|
|
1871
1883
|
defineUrlField: () => defineUrlField,
|
|
1872
1884
|
defineView: () => defineView,
|
|
1885
|
+
defineWorkspace: () => defineWorkspace,
|
|
1873
1886
|
diffDays: () => diffDays,
|
|
1874
1887
|
dispatchLifecycleEvent: () => dispatchLifecycleEvent,
|
|
1875
1888
|
dispatchPendingLifecycleEvents: () => dispatchPendingLifecycleEvents,
|
|
@@ -1927,22 +1940,28 @@ __export(index_exports, {
|
|
|
1927
1940
|
max: () => max,
|
|
1928
1941
|
min: () => min,
|
|
1929
1942
|
nanoid: () => nanoid,
|
|
1943
|
+
nextCronRun: () => nextCronRun,
|
|
1930
1944
|
normalizeConfig: () => normalizeConfig,
|
|
1931
1945
|
normalizeDetailItem: () => normalizeDetailItem,
|
|
1946
|
+
normalizeGroupKey: () => normalizeGroupKey,
|
|
1932
1947
|
now: () => now,
|
|
1933
1948
|
numericFieldNames: () => numericFieldNames,
|
|
1949
|
+
parseCron: () => parseCron,
|
|
1934
1950
|
parseMongoWhere: () => parseMongoWhere,
|
|
1935
1951
|
parseSort: () => parseSort,
|
|
1936
1952
|
parseSqlWhere: () => parseSqlWhere,
|
|
1937
1953
|
pow: () => pow,
|
|
1938
1954
|
prefixedId: () => prefixedId,
|
|
1955
|
+
pruneNavigationForUser: () => pruneNavigationForUser,
|
|
1939
1956
|
publishedStateName: () => publishedStateName,
|
|
1940
1957
|
publishingWorkflow: () => publishingWorkflow,
|
|
1941
1958
|
readingTime: () => readingTime,
|
|
1959
|
+
reconcileNavigation: () => reconcileNavigation,
|
|
1942
1960
|
registerJexlHelpers: () => registerJexlHelpers,
|
|
1943
1961
|
replace: () => replace,
|
|
1944
1962
|
resolveAdminAuthCollection: () => resolveAdminAuthCollection,
|
|
1945
1963
|
resolveAuthorizedSiteId: () => resolveAuthorizedSiteId,
|
|
1964
|
+
resolveIndexName: () => resolveIndexName,
|
|
1946
1965
|
resolvePrefix: () => resolvePrefix,
|
|
1947
1966
|
round: () => round,
|
|
1948
1967
|
runCollectionHooks: () => runCollectionHooks,
|
|
@@ -2042,12 +2061,317 @@ var DyrectedAIError = class _DyrectedAIError extends Error {
|
|
|
2042
2061
|
}
|
|
2043
2062
|
};
|
|
2044
2063
|
|
|
2064
|
+
// src/types/navigation.ts
|
|
2065
|
+
function defineWorkspace(options) {
|
|
2066
|
+
return options;
|
|
2067
|
+
}
|
|
2068
|
+
var CURRENT_NAV_PREFERENCES_VERSION = 1;
|
|
2069
|
+
var DEFAULT_USER_NAV_PREFERENCES = {
|
|
2070
|
+
_version: CURRENT_NAV_PREFERENCES_VERSION,
|
|
2071
|
+
pinned: [],
|
|
2072
|
+
hidden: [],
|
|
2073
|
+
groupOrder: [],
|
|
2074
|
+
itemOrder: {},
|
|
2075
|
+
groups: [],
|
|
2076
|
+
items: []
|
|
2077
|
+
};
|
|
2078
|
+
|
|
2045
2079
|
// src/auth/token.ts
|
|
2046
2080
|
var import_jose = require("jose");
|
|
2047
2081
|
|
|
2048
2082
|
// src/auth/sessions.ts
|
|
2049
2083
|
var AUTH_SESSIONS_COLLECTION = "__auth_sessions";
|
|
2050
2084
|
|
|
2085
|
+
// src/tasks.ts
|
|
2086
|
+
var TASK_LOCKS_COLLECTION = "__task_locks";
|
|
2087
|
+
var TASK_LOCKS_COLLECTION_CONFIG = {
|
|
2088
|
+
slug: TASK_LOCKS_COLLECTION,
|
|
2089
|
+
labels: { singular: "Task lock", plural: "Task locks" },
|
|
2090
|
+
fields: [
|
|
2091
|
+
{ name: "name", type: "text", required: true },
|
|
2092
|
+
// Numeric and promoted so the lock compare-and-set is a real column comparison.
|
|
2093
|
+
{ name: "lockedUntil", type: "number", required: true, promoted: true },
|
|
2094
|
+
{ name: "lockedBy", type: "text" },
|
|
2095
|
+
{ name: "lastScheduledFor", type: "date" },
|
|
2096
|
+
{ name: "lastStartedAt", type: "date" },
|
|
2097
|
+
{ name: "lastFinishedAt", type: "date" },
|
|
2098
|
+
{ name: "lastStatus", type: "text" },
|
|
2099
|
+
{ name: "lastError", type: "textarea" }
|
|
2100
|
+
],
|
|
2101
|
+
access: {
|
|
2102
|
+
read: ({ user }) => !!user?.roles?.includes("admin"),
|
|
2103
|
+
create: () => false,
|
|
2104
|
+
update: () => false,
|
|
2105
|
+
delete: () => false
|
|
2106
|
+
},
|
|
2107
|
+
admin: { hidden: true }
|
|
2108
|
+
};
|
|
2109
|
+
var DEFAULT_LOCK_TIMEOUT_SECONDS = 300;
|
|
2110
|
+
var DEFAULT_TICK_MS = 3e4;
|
|
2111
|
+
var MONTH_NAMES = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
|
|
2112
|
+
var DAY_NAMES = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
|
|
2113
|
+
function parseCronField(raw, min2, max2, names, nameOffset, label) {
|
|
2114
|
+
const values = /* @__PURE__ */ new Set();
|
|
2115
|
+
const toNumber = (token) => {
|
|
2116
|
+
const named = names?.indexOf(token.toLowerCase());
|
|
2117
|
+
if (named !== void 0 && named >= 0) return named + nameOffset;
|
|
2118
|
+
if (!/^\d+$/.test(token)) throw new Error(`Invalid cron ${label} "${token}".`);
|
|
2119
|
+
return Number(token);
|
|
2120
|
+
};
|
|
2121
|
+
for (const part of raw.split(",")) {
|
|
2122
|
+
const [rangePart, stepPart] = part.split("/");
|
|
2123
|
+
const step = stepPart === void 0 ? 1 : Number(stepPart);
|
|
2124
|
+
if (!Number.isInteger(step) || step < 1) throw new Error(`Invalid cron ${label} step "${part}".`);
|
|
2125
|
+
let start;
|
|
2126
|
+
let end;
|
|
2127
|
+
if (rangePart === "*") {
|
|
2128
|
+
start = min2;
|
|
2129
|
+
end = max2;
|
|
2130
|
+
} else if (rangePart.includes("-")) {
|
|
2131
|
+
const [a, b] = rangePart.split("-");
|
|
2132
|
+
start = toNumber(a);
|
|
2133
|
+
end = toNumber(b);
|
|
2134
|
+
} else {
|
|
2135
|
+
start = toNumber(rangePart);
|
|
2136
|
+
end = stepPart === void 0 ? start : max2;
|
|
2137
|
+
}
|
|
2138
|
+
if (start < min2 || end > max2 || start > end) {
|
|
2139
|
+
throw new Error(`Cron ${label} "${part}" is out of range (${min2}-${max2}).`);
|
|
2140
|
+
}
|
|
2141
|
+
for (let v = start; v <= end; v += step) values.add(v);
|
|
2142
|
+
}
|
|
2143
|
+
return values;
|
|
2144
|
+
}
|
|
2145
|
+
function parseCron(expression) {
|
|
2146
|
+
const fields = expression.trim().split(/\s+/);
|
|
2147
|
+
if (fields.length !== 5) {
|
|
2148
|
+
throw new Error(`Cron expression "${expression}" must have 5 fields (minute hour day-of-month month day-of-week).`);
|
|
2149
|
+
}
|
|
2150
|
+
const [min2, hour, dom, mon, dow] = fields;
|
|
2151
|
+
const daysOfWeek = parseCronField(dow, 0, 7, DAY_NAMES, 0, "day-of-week");
|
|
2152
|
+
if (daysOfWeek.has(7)) daysOfWeek.add(0);
|
|
2153
|
+
daysOfWeek.delete(7);
|
|
2154
|
+
return {
|
|
2155
|
+
minutes: parseCronField(min2, 0, 59, void 0, 0, "minute"),
|
|
2156
|
+
hours: parseCronField(hour, 0, 23, void 0, 0, "hour"),
|
|
2157
|
+
daysOfMonth: parseCronField(dom, 1, 31, void 0, 0, "day-of-month"),
|
|
2158
|
+
months: parseCronField(mon, 1, 12, MONTH_NAMES, 1, "month"),
|
|
2159
|
+
daysOfWeek,
|
|
2160
|
+
dayFieldsBothRestricted: dom !== "*" && dow !== "*"
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2163
|
+
function dayMatches(schedule, date) {
|
|
2164
|
+
const domOk = schedule.daysOfMonth.has(date.getUTCDate());
|
|
2165
|
+
const dowOk = schedule.daysOfWeek.has(date.getUTCDay());
|
|
2166
|
+
return schedule.dayFieldsBothRestricted ? domOk || dowOk : domOk && dowOk;
|
|
2167
|
+
}
|
|
2168
|
+
function nextCronRun(expression, after) {
|
|
2169
|
+
const schedule = typeof expression === "string" ? parseCron(expression) : expression;
|
|
2170
|
+
const cursor = new Date(after.getTime());
|
|
2171
|
+
cursor.setUTCSeconds(0, 0);
|
|
2172
|
+
cursor.setUTCMinutes(cursor.getUTCMinutes() + 1);
|
|
2173
|
+
const limit = after.getTime() + 5 * 366 * 24 * 60 * 60 * 1e3;
|
|
2174
|
+
while (cursor.getTime() <= limit) {
|
|
2175
|
+
if (!schedule.months.has(cursor.getUTCMonth() + 1)) {
|
|
2176
|
+
cursor.setUTCMonth(cursor.getUTCMonth() + 1, 1);
|
|
2177
|
+
cursor.setUTCHours(0, 0, 0, 0);
|
|
2178
|
+
continue;
|
|
2179
|
+
}
|
|
2180
|
+
if (!dayMatches(schedule, cursor)) {
|
|
2181
|
+
cursor.setUTCDate(cursor.getUTCDate() + 1);
|
|
2182
|
+
cursor.setUTCHours(0, 0, 0, 0);
|
|
2183
|
+
continue;
|
|
2184
|
+
}
|
|
2185
|
+
if (!schedule.hours.has(cursor.getUTCHours())) {
|
|
2186
|
+
cursor.setUTCHours(cursor.getUTCHours() + 1, 0, 0, 0);
|
|
2187
|
+
continue;
|
|
2188
|
+
}
|
|
2189
|
+
if (!schedule.minutes.has(cursor.getUTCMinutes())) {
|
|
2190
|
+
cursor.setUTCMinutes(cursor.getUTCMinutes() + 1, 0, 0);
|
|
2191
|
+
continue;
|
|
2192
|
+
}
|
|
2193
|
+
return cursor;
|
|
2194
|
+
}
|
|
2195
|
+
throw new Error(`Cron expression "${typeof expression === "string" ? expression : "schedule"}" never matches.`);
|
|
2196
|
+
}
|
|
2197
|
+
function defineTask(task) {
|
|
2198
|
+
if (!task.name || !task.name.trim()) throw new Error("A task needs a non-empty name.");
|
|
2199
|
+
if (task.cron !== void 0) parseCron(task.cron);
|
|
2200
|
+
if (task.lockTimeout !== void 0 && !(task.lockTimeout > 0)) {
|
|
2201
|
+
throw new Error(`Task "${task.name}" lockTimeout must be a positive number of seconds.`);
|
|
2202
|
+
}
|
|
2203
|
+
return task;
|
|
2204
|
+
}
|
|
2205
|
+
function resolveLogger(config, taskName) {
|
|
2206
|
+
const base = config.logger;
|
|
2207
|
+
if (base && typeof base.child === "function") return base.child({ component: "tasks", task: taskName });
|
|
2208
|
+
const prefix = `[dyrected/tasks] ${taskName}`;
|
|
2209
|
+
return {
|
|
2210
|
+
info: (obj, msg) => console.info(prefix, msg ?? "", obj),
|
|
2211
|
+
warn: (obj, msg) => console.warn(prefix, msg ?? "", obj),
|
|
2212
|
+
error: (obj, msg) => console.error(prefix, msg ?? "", obj)
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
function floorToMinute(date) {
|
|
2216
|
+
const d = new Date(date.getTime());
|
|
2217
|
+
d.setUTCSeconds(0, 0);
|
|
2218
|
+
return d;
|
|
2219
|
+
}
|
|
2220
|
+
function createTaskRunner(config) {
|
|
2221
|
+
const db = config.db;
|
|
2222
|
+
if (!db) throw new Error("createTaskRunner requires config.db.");
|
|
2223
|
+
const tasks = /* @__PURE__ */ new Map();
|
|
2224
|
+
for (const task of config.tasks ?? []) {
|
|
2225
|
+
if (tasks.has(task.name)) throw new Error(`Duplicate task name "${task.name}".`);
|
|
2226
|
+
tasks.set(task.name, { ...task, schedule: task.cron ? parseCron(task.cron) : void 0 });
|
|
2227
|
+
}
|
|
2228
|
+
const instanceId = globalThis.crypto?.randomUUID?.() ?? `inst_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
2229
|
+
let timer;
|
|
2230
|
+
let inFlight = Promise.resolve();
|
|
2231
|
+
let ticking = false;
|
|
2232
|
+
let ready;
|
|
2233
|
+
function ensureReady() {
|
|
2234
|
+
ready ??= Promise.resolve(db.sync?.([TASK_LOCKS_COLLECTION_CONFIG], [])).then(() => void 0);
|
|
2235
|
+
ready.catch(() => {
|
|
2236
|
+
ready = void 0;
|
|
2237
|
+
});
|
|
2238
|
+
return ready;
|
|
2239
|
+
}
|
|
2240
|
+
async function ensureLockRow(name, now2) {
|
|
2241
|
+
await ensureReady();
|
|
2242
|
+
const existing = await db.findOne({ collection: TASK_LOCKS_COLLECTION, id: name });
|
|
2243
|
+
if (existing) return existing;
|
|
2244
|
+
try {
|
|
2245
|
+
return await db.create({
|
|
2246
|
+
collection: TASK_LOCKS_COLLECTION,
|
|
2247
|
+
data: {
|
|
2248
|
+
id: name,
|
|
2249
|
+
name,
|
|
2250
|
+
lockedUntil: 0,
|
|
2251
|
+
lockedBy: null,
|
|
2252
|
+
lastScheduledFor: floorToMinute(now2).toISOString(),
|
|
2253
|
+
lastStartedAt: null,
|
|
2254
|
+
lastFinishedAt: null,
|
|
2255
|
+
lastStatus: null,
|
|
2256
|
+
lastError: null
|
|
2257
|
+
}
|
|
2258
|
+
});
|
|
2259
|
+
} catch (error) {
|
|
2260
|
+
const created = await db.findOne({ collection: TASK_LOCKS_COLLECTION, id: name });
|
|
2261
|
+
if (created) return created;
|
|
2262
|
+
throw error;
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
async function execute(task, row, scheduledFor) {
|
|
2266
|
+
const started = Date.now();
|
|
2267
|
+
const lockMs = (task.lockTimeout ?? DEFAULT_LOCK_TIMEOUT_SECONDS) * 1e3;
|
|
2268
|
+
const token = `${instanceId}:${started}`;
|
|
2269
|
+
const where = {
|
|
2270
|
+
id: { equals: task.name },
|
|
2271
|
+
lockedUntil: { less_than: started }
|
|
2272
|
+
};
|
|
2273
|
+
if (scheduledFor) where.lastScheduledFor = { equals: row.lastScheduledFor };
|
|
2274
|
+
const claimed = await db.update({
|
|
2275
|
+
collection: TASK_LOCKS_COLLECTION,
|
|
2276
|
+
where,
|
|
2277
|
+
data: {
|
|
2278
|
+
lockedUntil: started + lockMs,
|
|
2279
|
+
lockedBy: token,
|
|
2280
|
+
lastStartedAt: new Date(started).toISOString(),
|
|
2281
|
+
...scheduledFor ? { lastScheduledFor: scheduledFor.toISOString() } : {}
|
|
2282
|
+
}
|
|
2283
|
+
});
|
|
2284
|
+
if (claimed.affectedRows !== 1) {
|
|
2285
|
+
return { name: task.name, status: "skipped", reason: "locked", durationMs: Date.now() - started };
|
|
2286
|
+
}
|
|
2287
|
+
const logger = resolveLogger(config, task.name);
|
|
2288
|
+
const controller = new AbortController();
|
|
2289
|
+
let timeout;
|
|
2290
|
+
let status = "completed";
|
|
2291
|
+
let errorMessage;
|
|
2292
|
+
try {
|
|
2293
|
+
await Promise.race([
|
|
2294
|
+
Promise.resolve(task.run({ db, logger, signal: controller.signal, scheduledFor })),
|
|
2295
|
+
new Promise((_, reject) => {
|
|
2296
|
+
timeout = setTimeout(() => {
|
|
2297
|
+
reject(new Error(`Task exceeded its lockTimeout of ${lockMs / 1e3}s.`));
|
|
2298
|
+
controller.abort();
|
|
2299
|
+
}, lockMs);
|
|
2300
|
+
})
|
|
2301
|
+
]);
|
|
2302
|
+
} catch (error) {
|
|
2303
|
+
status = "failed";
|
|
2304
|
+
errorMessage = error instanceof Error ? error.message : String(error);
|
|
2305
|
+
logger.error({ err: error }, "Task failed");
|
|
2306
|
+
} finally {
|
|
2307
|
+
if (timeout) clearTimeout(timeout);
|
|
2308
|
+
}
|
|
2309
|
+
try {
|
|
2310
|
+
await db.update({
|
|
2311
|
+
collection: TASK_LOCKS_COLLECTION,
|
|
2312
|
+
where: { id: { equals: task.name }, lockedBy: { equals: token } },
|
|
2313
|
+
data: {
|
|
2314
|
+
lockedUntil: 0,
|
|
2315
|
+
lockedBy: null,
|
|
2316
|
+
lastFinishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2317
|
+
lastStatus: status,
|
|
2318
|
+
lastError: errorMessage ?? null
|
|
2319
|
+
}
|
|
2320
|
+
});
|
|
2321
|
+
} catch (error) {
|
|
2322
|
+
logger.warn({ err: error }, "Could not release task lock; it will expire on its own");
|
|
2323
|
+
}
|
|
2324
|
+
return { name: task.name, status, error: errorMessage, durationMs: Date.now() - started };
|
|
2325
|
+
}
|
|
2326
|
+
function track(work) {
|
|
2327
|
+
inFlight = inFlight.then(() => work).catch(() => void 0);
|
|
2328
|
+
return work;
|
|
2329
|
+
}
|
|
2330
|
+
const runner = {
|
|
2331
|
+
async runTask(name) {
|
|
2332
|
+
const task = tasks.get(name);
|
|
2333
|
+
if (!task) throw new Error(`Unknown task "${name}".`);
|
|
2334
|
+
const row = await ensureLockRow(name, /* @__PURE__ */ new Date());
|
|
2335
|
+
return track(execute(task, row, void 0));
|
|
2336
|
+
},
|
|
2337
|
+
async runDue(now2 = /* @__PURE__ */ new Date()) {
|
|
2338
|
+
const results = [];
|
|
2339
|
+
for (const task of tasks.values()) {
|
|
2340
|
+
if (!task.schedule) continue;
|
|
2341
|
+
const row = await ensureLockRow(task.name, now2);
|
|
2342
|
+
const reference = new Date(String(row.lastScheduledFor));
|
|
2343
|
+
const due = Number.isNaN(reference.getTime()) ? true : nextCronRun(task.schedule, reference).getTime() <= now2.getTime();
|
|
2344
|
+
if (!due) {
|
|
2345
|
+
results.push({ name: task.name, status: "skipped", reason: "not-due", durationMs: 0 });
|
|
2346
|
+
continue;
|
|
2347
|
+
}
|
|
2348
|
+
results.push(await track(execute(task, row, floorToMinute(now2))));
|
|
2349
|
+
}
|
|
2350
|
+
return results;
|
|
2351
|
+
},
|
|
2352
|
+
start(options) {
|
|
2353
|
+
if (timer) return;
|
|
2354
|
+
const intervalMs = options?.intervalMs ?? DEFAULT_TICK_MS;
|
|
2355
|
+
const tick = () => {
|
|
2356
|
+
if (ticking) return;
|
|
2357
|
+
ticking = true;
|
|
2358
|
+
runner.runDue().catch((error) => resolveLogger(config, "runner").error({ err: error }, "Task tick failed")).finally(() => {
|
|
2359
|
+
ticking = false;
|
|
2360
|
+
});
|
|
2361
|
+
};
|
|
2362
|
+
timer = setInterval(tick, intervalMs);
|
|
2363
|
+
timer.unref?.();
|
|
2364
|
+
setTimeout(tick, 0);
|
|
2365
|
+
},
|
|
2366
|
+
async stop() {
|
|
2367
|
+
if (timer) clearInterval(timer);
|
|
2368
|
+
timer = void 0;
|
|
2369
|
+
await inFlight;
|
|
2370
|
+
}
|
|
2371
|
+
};
|
|
2372
|
+
return runner;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2051
2375
|
// src/workflows.ts
|
|
2052
2376
|
var WORKFLOW_HISTORY_COLLECTION = "__workflow_history";
|
|
2053
2377
|
var LIFECYCLE_EVENTS_COLLECTION = "__lifecycle_events";
|
|
@@ -2263,7 +2587,7 @@ async function dispatchPendingLifecycleEvents(config, limit = 50) {
|
|
|
2263
2587
|
return due.length;
|
|
2264
2588
|
}
|
|
2265
2589
|
async function saveWorkflowDraft(args) {
|
|
2266
|
-
const { config, collection, id, originalDoc, data, user } = args;
|
|
2590
|
+
const { config, collection, id, originalDoc, data, user, beforeCommit } = args;
|
|
2267
2591
|
const db = config.db;
|
|
2268
2592
|
const workflow = collection.workflow;
|
|
2269
2593
|
if (!db.transaction) throw new Error(`The configured database adapter does not support workflow transactions.`);
|
|
@@ -2297,13 +2621,14 @@ async function saveWorkflowDraft(args) {
|
|
|
2297
2621
|
}
|
|
2298
2622
|
const updated = await tx.update({ collection: collection.slug, id, data: updateData });
|
|
2299
2623
|
await persistEvent(tx, event);
|
|
2624
|
+
await beforeCommit?.(tx, updated);
|
|
2300
2625
|
return updated;
|
|
2301
2626
|
});
|
|
2302
2627
|
void dispatchLifecycleEvent(config, event);
|
|
2303
2628
|
return { doc, event };
|
|
2304
2629
|
}
|
|
2305
2630
|
async function createWorkflowDocument(args) {
|
|
2306
|
-
const { config, collection, data, user } = args;
|
|
2631
|
+
const { config, collection, data, user, beforeCommit } = args;
|
|
2307
2632
|
const db = config.db;
|
|
2308
2633
|
if (!db.transaction) throw new Error(`The configured database adapter does not support workflow transactions.`);
|
|
2309
2634
|
let event;
|
|
@@ -2317,13 +2642,14 @@ async function createWorkflowDocument(args) {
|
|
|
2317
2642
|
payload: { revision: 1, previousRevision: null }
|
|
2318
2643
|
});
|
|
2319
2644
|
await persistEvent(tx, event);
|
|
2645
|
+
await beforeCommit?.(tx, created);
|
|
2320
2646
|
return created;
|
|
2321
2647
|
});
|
|
2322
2648
|
void dispatchLifecycleEvent(config, event);
|
|
2323
2649
|
return { doc, event };
|
|
2324
2650
|
}
|
|
2325
2651
|
async function transitionWorkflow(args) {
|
|
2326
|
-
const { config, collection, id, transitionName, expectedRevision, comment, user, req } = args;
|
|
2652
|
+
const { config, collection, id, transitionName, expectedRevision, comment, input, user, req } = args;
|
|
2327
2653
|
const db = config.db;
|
|
2328
2654
|
const workflow = collection.workflow;
|
|
2329
2655
|
if (!db.transaction) throw new Error(`The configured database adapter does not support workflow transactions.`);
|
|
@@ -2349,7 +2675,7 @@ async function transitionWorkflow(args) {
|
|
|
2349
2675
|
if (transition.requireComment && !comment?.trim()) {
|
|
2350
2676
|
throw Object.assign(new Error(`A comment is required for "${transition.label}".`), { statusCode: 400 });
|
|
2351
2677
|
}
|
|
2352
|
-
const hookContext = { transition, from: meta.state, to: transition.to, doc: original, user, comment, req, db };
|
|
2678
|
+
const hookContext = { transition, from: meta.state, to: transition.to, doc: original, user, comment, input, req, db };
|
|
2353
2679
|
for (const hook of workflow.hooks?.beforeTransition ?? []) await hook(hookContext);
|
|
2354
2680
|
const events = [
|
|
2355
2681
|
createLifecycleEvent({
|
|
@@ -2403,6 +2729,19 @@ async function transitionWorkflow(args) {
|
|
|
2403
2729
|
if (targetState.published) data.__published = working;
|
|
2404
2730
|
if (transition.unpublish) data.__published = null;
|
|
2405
2731
|
const next = await tx.update({ collection: collection.slug, id, data });
|
|
2732
|
+
if (transition.onTransition) {
|
|
2733
|
+
await transition.onTransition({
|
|
2734
|
+
transition,
|
|
2735
|
+
from: lockedMeta.state,
|
|
2736
|
+
to: transition.to,
|
|
2737
|
+
doc: locked,
|
|
2738
|
+
input,
|
|
2739
|
+
comment,
|
|
2740
|
+
user,
|
|
2741
|
+
req,
|
|
2742
|
+
tx
|
|
2743
|
+
});
|
|
2744
|
+
}
|
|
2406
2745
|
await tx.create({
|
|
2407
2746
|
collection: WORKFLOW_HISTORY_COLLECTION,
|
|
2408
2747
|
data: {
|
|
@@ -3839,6 +4178,7 @@ function normalizeConfig(config) {
|
|
|
3839
4178
|
const globals = schemaAwareConfig?.globals || [];
|
|
3840
4179
|
const needsAudit = collections.some((col) => col.audit);
|
|
3841
4180
|
const needsWorkflow = collections.some((col) => col.workflow || col.drafts);
|
|
4181
|
+
const needsTasks = (config.tasks?.length ?? 0) > 0;
|
|
3842
4182
|
const needsAuthSessions = collections.some((col) => !!col.auth);
|
|
3843
4183
|
const adminAuthCollectionSlug = getAdminAuthCollection({
|
|
3844
4184
|
collections,
|
|
@@ -3918,7 +4258,33 @@ function normalizeConfig(config) {
|
|
|
3918
4258
|
}
|
|
3919
4259
|
];
|
|
3920
4260
|
}
|
|
4261
|
+
if (!existingFieldNames.has("__preferences")) {
|
|
4262
|
+
fields = [
|
|
4263
|
+
...fields,
|
|
4264
|
+
{
|
|
4265
|
+
name: "__preferences",
|
|
4266
|
+
type: "json",
|
|
4267
|
+
label: "Preferences",
|
|
4268
|
+
promoted: true,
|
|
4269
|
+
admin: {
|
|
4270
|
+
hidden: true,
|
|
4271
|
+
readOnly: true
|
|
4272
|
+
}
|
|
4273
|
+
}
|
|
4274
|
+
];
|
|
4275
|
+
}
|
|
3921
4276
|
fields = fields.map((field) => {
|
|
4277
|
+
if (field.name === "__preferences") {
|
|
4278
|
+
return {
|
|
4279
|
+
...field,
|
|
4280
|
+
promoted: true,
|
|
4281
|
+
admin: {
|
|
4282
|
+
...field.admin || {},
|
|
4283
|
+
hidden: true,
|
|
4284
|
+
readOnly: true
|
|
4285
|
+
}
|
|
4286
|
+
};
|
|
4287
|
+
}
|
|
3922
4288
|
if (field.name === "email") {
|
|
3923
4289
|
return {
|
|
3924
4290
|
...field,
|
|
@@ -4045,6 +4411,9 @@ function normalizeConfig(config) {
|
|
|
4045
4411
|
if (needsWorkflow && !normalizedCollections.some((col) => col.slug === LIFECYCLE_EVENTS_COLLECTION)) {
|
|
4046
4412
|
systemCollections.push(LIFECYCLE_EVENTS_COLLECTION_CONFIG);
|
|
4047
4413
|
}
|
|
4414
|
+
if (needsTasks && !normalizedCollections.some((col) => col.slug === TASK_LOCKS_COLLECTION)) {
|
|
4415
|
+
systemCollections.push(TASK_LOCKS_COLLECTION_CONFIG);
|
|
4416
|
+
}
|
|
4048
4417
|
if (needsAuthSessions && !normalizedCollections.some((col) => col.slug === AUTH_SESSIONS_COLLECTION)) {
|
|
4049
4418
|
systemCollections.push(AUTH_SESSIONS_COLLECTION_CONFIG);
|
|
4050
4419
|
}
|
|
@@ -4129,7 +4498,11 @@ function parseSqlWhere(where, getJsonField, placeholder = "?", likeOperator = pl
|
|
|
4129
4498
|
params.push("");
|
|
4130
4499
|
return `(${c})::text = ${next()}`;
|
|
4131
4500
|
}
|
|
4132
|
-
|
|
4501
|
+
if (typeof value === "boolean") {
|
|
4502
|
+
params.push(placeholder === "?" ? value ? 1 : 0 : isJsonExtract ? String(value) : value);
|
|
4503
|
+
} else {
|
|
4504
|
+
params.push(value);
|
|
4505
|
+
}
|
|
4133
4506
|
return `${c} = ${next()}`;
|
|
4134
4507
|
}
|
|
4135
4508
|
const entries = Object.entries(value);
|
|
@@ -4329,6 +4702,38 @@ function parseMongoWhere(where) {
|
|
|
4329
4702
|
}
|
|
4330
4703
|
return buildClause(where);
|
|
4331
4704
|
}
|
|
4705
|
+
function coerceBooleanWhere(where, fields) {
|
|
4706
|
+
const booleanFields = new Set(
|
|
4707
|
+
(fields ?? []).filter((f) => f.type === "boolean" && f.name).map((f) => f.name)
|
|
4708
|
+
);
|
|
4709
|
+
if (booleanFields.size === 0) return where;
|
|
4710
|
+
const toBool = (v) => v === "true" ? true : v === "false" ? false : v;
|
|
4711
|
+
const walk = (node) => {
|
|
4712
|
+
if (Array.isArray(node)) return node.map(walk);
|
|
4713
|
+
if (!node || typeof node !== "object") return node;
|
|
4714
|
+
const out = {};
|
|
4715
|
+
for (const [key, value] of Object.entries(node)) {
|
|
4716
|
+
if (key.toUpperCase() === "AND" || key.toUpperCase() === "OR") {
|
|
4717
|
+
out[key] = walk(value);
|
|
4718
|
+
} else if (booleanFields.has(key)) {
|
|
4719
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
4720
|
+
out[key] = Object.fromEntries(
|
|
4721
|
+
Object.entries(value).map(([op, operand]) => [
|
|
4722
|
+
op,
|
|
4723
|
+
Array.isArray(operand) ? operand.map(toBool) : toBool(operand)
|
|
4724
|
+
])
|
|
4725
|
+
);
|
|
4726
|
+
} else {
|
|
4727
|
+
out[key] = toBool(value);
|
|
4728
|
+
}
|
|
4729
|
+
} else {
|
|
4730
|
+
out[key] = value;
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
return out;
|
|
4734
|
+
};
|
|
4735
|
+
return walk(where);
|
|
4736
|
+
}
|
|
4332
4737
|
|
|
4333
4738
|
// src/utils/parse-sort.ts
|
|
4334
4739
|
var SORT_FIELD_PATTERN = /^([A-Za-z_][A-Za-z0-9_]*)(?:\s+(ASC|DESC))?$/i;
|
|
@@ -4610,7 +5015,12 @@ function generateOpenApi(config) {
|
|
|
4610
5015
|
type: "object",
|
|
4611
5016
|
properties: {
|
|
4612
5017
|
expectedRevision: { type: "integer", minimum: 1 },
|
|
4613
|
-
comment: { type: "string" }
|
|
5018
|
+
comment: { type: "string" },
|
|
5019
|
+
input: {
|
|
5020
|
+
type: "object",
|
|
5021
|
+
additionalProperties: true,
|
|
5022
|
+
description: "Extra data passed to the transition's onTransition handler."
|
|
5023
|
+
}
|
|
4614
5024
|
}
|
|
4615
5025
|
},
|
|
4616
5026
|
WorkflowHistoryEntry: {
|
|
@@ -5773,6 +6183,9 @@ function fieldToSchema(field) {
|
|
|
5773
6183
|
case "number":
|
|
5774
6184
|
schema = { type: "number" };
|
|
5775
6185
|
break;
|
|
6186
|
+
case "money":
|
|
6187
|
+
schema = { type: "integer", description: "Amount in minor units (for example kobo or cents)." };
|
|
6188
|
+
break;
|
|
5776
6189
|
case "boolean":
|
|
5777
6190
|
schema = { type: "boolean" };
|
|
5778
6191
|
break;
|
|
@@ -6725,6 +7138,555 @@ function sanitizeDocForAI(options) {
|
|
|
6725
7138
|
return result;
|
|
6726
7139
|
}
|
|
6727
7140
|
|
|
7141
|
+
// src/utils/index-name.ts
|
|
7142
|
+
var import_node_crypto2 = require("crypto");
|
|
7143
|
+
var MAX_INDEX_NAME_LENGTH = 60;
|
|
7144
|
+
function resolveIndexName(prefix, table, fields) {
|
|
7145
|
+
const full = `${prefix}_${table}_${fields.join("_")}`;
|
|
7146
|
+
if (full.length <= MAX_INDEX_NAME_LENGTH) return full;
|
|
7147
|
+
const hash = (0, import_node_crypto2.createHash)("sha1").update(full).digest("hex").slice(0, 10);
|
|
7148
|
+
const tail = (fields[fields.length - 1] ?? "").slice(0, 30);
|
|
7149
|
+
return `${prefix}_${hash}_${tail}`;
|
|
7150
|
+
}
|
|
7151
|
+
|
|
7152
|
+
// src/utils/group-key.ts
|
|
7153
|
+
var UNASSIGNED_GROUP_KEY = "__unassigned__";
|
|
7154
|
+
var MYSQL_DATETIME = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d+)?$/;
|
|
7155
|
+
function normalizeGroupKey(raw, fieldType) {
|
|
7156
|
+
if (raw === null || raw === void 0) return UNASSIGNED_GROUP_KEY;
|
|
7157
|
+
if (fieldType === "boolean") {
|
|
7158
|
+
if (raw === true || raw === 1 || raw === "1" || raw === "true" || raw === "t") return "true";
|
|
7159
|
+
if (raw === false || raw === 0 || raw === "0" || raw === "false" || raw === "f") return "false";
|
|
7160
|
+
}
|
|
7161
|
+
if ((fieldType === "number" || fieldType === "money") && raw !== "" && !Number.isNaN(Number(raw))) {
|
|
7162
|
+
return String(Number(raw));
|
|
7163
|
+
}
|
|
7164
|
+
if (fieldType === "date" || fieldType === "datetime") {
|
|
7165
|
+
if (raw instanceof Date) return Number.isNaN(raw.getTime()) ? String(raw) : raw.toISOString();
|
|
7166
|
+
if (typeof raw === "string" && MYSQL_DATETIME.test(raw)) return `${raw.replace(" ", "T")}Z`;
|
|
7167
|
+
}
|
|
7168
|
+
return String(raw);
|
|
7169
|
+
}
|
|
7170
|
+
|
|
7171
|
+
// src/utils/navigation-reconciler.ts
|
|
7172
|
+
var normalizeKey = (val) => (val || "").toLowerCase().replace(/[^a-z0-9]/g, "-");
|
|
7173
|
+
function reconcileNavigation(baseTree, prefs, schemas, options) {
|
|
7174
|
+
if (!baseTree) {
|
|
7175
|
+
return {
|
|
7176
|
+
groups: [],
|
|
7177
|
+
ungrouped: [],
|
|
7178
|
+
pinnedItems: []
|
|
7179
|
+
};
|
|
7180
|
+
}
|
|
7181
|
+
const includeHidden = options?.includeHidden ?? false;
|
|
7182
|
+
const effectivePrefs = prefs || DEFAULT_USER_NAV_PREFERENCES;
|
|
7183
|
+
const hiddenSet = new Set(effectivePrefs.hidden || []);
|
|
7184
|
+
const knownCollectionSlugs = new Set((schemas?.collections || []).map((c) => c.slug));
|
|
7185
|
+
const knownGlobalSlugs = new Set((schemas?.globals || []).map((g) => g.slug));
|
|
7186
|
+
const groupsList = [];
|
|
7187
|
+
const findGroup = (query) => {
|
|
7188
|
+
const qNorm = normalizeKey(query);
|
|
7189
|
+
return groupsList.find(
|
|
7190
|
+
(g) => g.id === query || g.slug === query || g.name === query || normalizeKey(g.id) === qNorm || normalizeKey(g.slug) === qNorm || normalizeKey(g.name) === qNorm
|
|
7191
|
+
);
|
|
7192
|
+
};
|
|
7193
|
+
for (const g of baseTree.groups || []) {
|
|
7194
|
+
groupsList.push({
|
|
7195
|
+
...g,
|
|
7196
|
+
items: (g.items || []).map((item) => ({ ...item }))
|
|
7197
|
+
});
|
|
7198
|
+
}
|
|
7199
|
+
for (const ug of effectivePrefs.groups || []) {
|
|
7200
|
+
const existing = findGroup(ug.slug || ug.name);
|
|
7201
|
+
if (!existing) {
|
|
7202
|
+
groupsList.push({
|
|
7203
|
+
id: ug.slug ? `group_${ug.slug}` : `group_${normalizeKey(ug.name)}`,
|
|
7204
|
+
name: ug.name,
|
|
7205
|
+
slug: ug.slug || normalizeKey(ug.name),
|
|
7206
|
+
icon: ug.icon || "Folder",
|
|
7207
|
+
defaultExpanded: ug.defaultExpanded ?? true,
|
|
7208
|
+
order: ug.order ?? 100,
|
|
7209
|
+
items: []
|
|
7210
|
+
});
|
|
7211
|
+
}
|
|
7212
|
+
}
|
|
7213
|
+
for (const itemOpt of effectivePrefs.items || []) {
|
|
7214
|
+
const targetGroup = typeof itemOpt.group === "object" ? itemOpt.group?.name : itemOpt.group;
|
|
7215
|
+
const groupKey = targetGroup || "Custom";
|
|
7216
|
+
let group = findGroup(groupKey);
|
|
7217
|
+
if (!group) {
|
|
7218
|
+
group = {
|
|
7219
|
+
id: `group_${normalizeKey(groupKey)}`,
|
|
7220
|
+
name: groupKey,
|
|
7221
|
+
slug: normalizeKey(groupKey),
|
|
7222
|
+
icon: "Folder",
|
|
7223
|
+
defaultExpanded: true,
|
|
7224
|
+
order: 99,
|
|
7225
|
+
items: []
|
|
7226
|
+
};
|
|
7227
|
+
groupsList.push(group);
|
|
7228
|
+
}
|
|
7229
|
+
const itemId = itemOpt.slug ? `workspace_${itemOpt.slug}` : itemOpt.collection ? `collection_${itemOpt.collection}` : `custom_${Math.random()}`;
|
|
7230
|
+
for (const otherGroup of groupsList) {
|
|
7231
|
+
if (otherGroup !== group) {
|
|
7232
|
+
otherGroup.items = otherGroup.items.filter((i) => i.slug !== itemOpt.slug && i.id !== itemId);
|
|
7233
|
+
}
|
|
7234
|
+
}
|
|
7235
|
+
const existingIndex = group.items.findIndex((i) => i.id === itemId || i.slug === itemOpt.slug);
|
|
7236
|
+
const existingItem = existingIndex >= 0 ? group.items[existingIndex] : void 0;
|
|
7237
|
+
const col = schemas?.collections?.find((c) => c.slug === itemOpt.collection);
|
|
7238
|
+
const colViews = col?.views || [];
|
|
7239
|
+
const baseViews = existingItem?.views || [];
|
|
7240
|
+
const userViews = itemOpt.views || [];
|
|
7241
|
+
const userViewSlugs = new Set(userViews.map((v) => v.slug));
|
|
7242
|
+
let mergedViews = [
|
|
7243
|
+
...baseViews.filter((v) => !userViewSlugs.has(v.slug)),
|
|
7244
|
+
...userViews
|
|
7245
|
+
];
|
|
7246
|
+
if (mergedViews.length === 0 && itemOpt.collection) {
|
|
7247
|
+
if (colViews.length > 0) {
|
|
7248
|
+
mergedViews = [...colViews];
|
|
7249
|
+
} else {
|
|
7250
|
+
mergedViews = [
|
|
7251
|
+
{
|
|
7252
|
+
slug: "default",
|
|
7253
|
+
label: col?.label || col?.labels?.plural || itemOpt.label || "All Records",
|
|
7254
|
+
layout: "table",
|
|
7255
|
+
collection: itemOpt.collection
|
|
7256
|
+
}
|
|
7257
|
+
];
|
|
7258
|
+
}
|
|
7259
|
+
}
|
|
7260
|
+
mergedViews = mergedViews.map((v) => ({
|
|
7261
|
+
...v,
|
|
7262
|
+
collection: v.collection || itemOpt.collection || existingItem?.collection
|
|
7263
|
+
}));
|
|
7264
|
+
const compiledItem = {
|
|
7265
|
+
id: itemId,
|
|
7266
|
+
type: itemOpt.slug ? "workspace" : itemOpt.collection ? "collection" : itemOpt.global ? "global" : "workspace",
|
|
7267
|
+
slug: itemOpt.slug || itemOpt.collection || itemOpt.global || "item",
|
|
7268
|
+
label: itemOpt.label || itemOpt.slug || "Item",
|
|
7269
|
+
icon: itemOpt.icon || existingItem?.icon || col?.admin?.icon || "Briefcase",
|
|
7270
|
+
group: group.name,
|
|
7271
|
+
order: itemOpt.order ?? existingItem?.order ?? 100,
|
|
7272
|
+
views: mergedViews,
|
|
7273
|
+
collection: itemOpt.collection || existingItem?.collection,
|
|
7274
|
+
global: itemOpt.global || existingItem?.global,
|
|
7275
|
+
badge: itemOpt.badge || existingItem?.badge,
|
|
7276
|
+
access: itemOpt.access || existingItem?.access
|
|
7277
|
+
};
|
|
7278
|
+
if (existingIndex >= 0) {
|
|
7279
|
+
group.items[existingIndex] = compiledItem;
|
|
7280
|
+
} else {
|
|
7281
|
+
group.items.push(compiledItem);
|
|
7282
|
+
}
|
|
7283
|
+
}
|
|
7284
|
+
const allAvailableItems = [];
|
|
7285
|
+
const reconciledGroups = [];
|
|
7286
|
+
for (const group of groupsList) {
|
|
7287
|
+
const isGroupHidden = hiddenSet.has(group.id) || !!group.slug && hiddenSet.has(group.slug) || hiddenSet.has(group.name);
|
|
7288
|
+
if (!includeHidden && isGroupHidden) {
|
|
7289
|
+
continue;
|
|
7290
|
+
}
|
|
7291
|
+
const visibleItems = [];
|
|
7292
|
+
for (const item of group.items) {
|
|
7293
|
+
const isItemHidden = hiddenSet.has(item.id) || !!item.slug && hiddenSet.has(item.slug);
|
|
7294
|
+
if (!includeHidden && isItemHidden) {
|
|
7295
|
+
continue;
|
|
7296
|
+
}
|
|
7297
|
+
const allViews = item.views || [];
|
|
7298
|
+
const visibleViews = includeHidden ? allViews : allViews.filter((v) => {
|
|
7299
|
+
const compoundKey = `${item.slug}_${v.slug}`;
|
|
7300
|
+
return !hiddenSet.has(compoundKey) && !hiddenSet.has(v.slug);
|
|
7301
|
+
});
|
|
7302
|
+
let isTombstone = false;
|
|
7303
|
+
if (schemas && schemas.collections && schemas.collections.length > 0) {
|
|
7304
|
+
if (item.collection && !knownCollectionSlugs.has(item.collection)) {
|
|
7305
|
+
isTombstone = true;
|
|
7306
|
+
}
|
|
7307
|
+
}
|
|
7308
|
+
if (schemas && schemas.globals && schemas.globals.length > 0) {
|
|
7309
|
+
if (item.global && !knownGlobalSlugs.has(item.global)) {
|
|
7310
|
+
isTombstone = true;
|
|
7311
|
+
}
|
|
7312
|
+
}
|
|
7313
|
+
const processedItem = {
|
|
7314
|
+
...item,
|
|
7315
|
+
views: visibleViews,
|
|
7316
|
+
...isTombstone ? { isTombstone: true } : {}
|
|
7317
|
+
};
|
|
7318
|
+
visibleItems.push(processedItem);
|
|
7319
|
+
allAvailableItems.push(processedItem);
|
|
7320
|
+
}
|
|
7321
|
+
const groupKey = group.slug || group.name;
|
|
7322
|
+
const itemOrderList = effectivePrefs.itemOrder?.[group.id] || effectivePrefs.itemOrder?.[groupKey];
|
|
7323
|
+
if (itemOrderList && itemOrderList.length > 0) {
|
|
7324
|
+
visibleItems.sort((a, b) => {
|
|
7325
|
+
const aIdx = itemOrderList.indexOf(a.id) >= 0 ? itemOrderList.indexOf(a.id) : a.slug ? itemOrderList.indexOf(a.slug) : -1;
|
|
7326
|
+
const bIdx = itemOrderList.indexOf(b.id) >= 0 ? itemOrderList.indexOf(b.id) : b.slug ? itemOrderList.indexOf(b.slug) : -1;
|
|
7327
|
+
if (aIdx >= 0 && bIdx >= 0) return aIdx - bIdx;
|
|
7328
|
+
if (aIdx >= 0) return -1;
|
|
7329
|
+
if (bIdx >= 0) return 1;
|
|
7330
|
+
return (a.order ?? 100) - (b.order ?? 100);
|
|
7331
|
+
});
|
|
7332
|
+
} else {
|
|
7333
|
+
visibleItems.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
|
|
7334
|
+
}
|
|
7335
|
+
reconciledGroups.push({
|
|
7336
|
+
...group,
|
|
7337
|
+
items: visibleItems
|
|
7338
|
+
});
|
|
7339
|
+
}
|
|
7340
|
+
if (effectivePrefs.groupOrder && effectivePrefs.groupOrder.length > 0) {
|
|
7341
|
+
reconciledGroups.sort((a, b) => {
|
|
7342
|
+
const aIdx = effectivePrefs.groupOrder.indexOf(a.id) >= 0 ? effectivePrefs.groupOrder.indexOf(a.id) : a.slug ? effectivePrefs.groupOrder.indexOf(a.slug) : effectivePrefs.groupOrder.indexOf(a.name);
|
|
7343
|
+
const bIdx = effectivePrefs.groupOrder.indexOf(b.id) >= 0 ? effectivePrefs.groupOrder.indexOf(b.id) : b.slug ? effectivePrefs.groupOrder.indexOf(b.slug) : effectivePrefs.groupOrder.indexOf(b.name);
|
|
7344
|
+
if (aIdx >= 0 && bIdx >= 0) return aIdx - bIdx;
|
|
7345
|
+
if (aIdx >= 0) return -1;
|
|
7346
|
+
if (bIdx >= 0) return 1;
|
|
7347
|
+
return (a.order ?? 100) - (b.order ?? 100);
|
|
7348
|
+
});
|
|
7349
|
+
} else {
|
|
7350
|
+
reconciledGroups.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
|
|
7351
|
+
}
|
|
7352
|
+
const reconciledUngrouped = (baseTree.ungrouped || []).filter((item) => {
|
|
7353
|
+
const isItemHidden = hiddenSet.has(item.id) || !!item.slug && hiddenSet.has(item.slug);
|
|
7354
|
+
return includeHidden || !isItemHidden;
|
|
7355
|
+
}).map((item) => {
|
|
7356
|
+
const allViews = item.views || [];
|
|
7357
|
+
const visibleViews = includeHidden ? allViews : allViews.filter((v) => {
|
|
7358
|
+
const compoundKey = `${item.slug}_${v.slug}`;
|
|
7359
|
+
return !hiddenSet.has(compoundKey) && !hiddenSet.has(v.slug);
|
|
7360
|
+
});
|
|
7361
|
+
const processed = { ...item, views: visibleViews };
|
|
7362
|
+
allAvailableItems.push(processed);
|
|
7363
|
+
return processed;
|
|
7364
|
+
});
|
|
7365
|
+
const pinnedItems = [];
|
|
7366
|
+
for (const pin of effectivePrefs.pinned || []) {
|
|
7367
|
+
const match = allAvailableItems.find((i) => i.slug === pin.slug || i.id === pin.slug);
|
|
7368
|
+
if (match) {
|
|
7369
|
+
pinnedItems.push({
|
|
7370
|
+
...match,
|
|
7371
|
+
id: `pin_${match.id}`,
|
|
7372
|
+
label: pin.label || match.label,
|
|
7373
|
+
icon: pin.icon || match.icon
|
|
7374
|
+
});
|
|
7375
|
+
} else {
|
|
7376
|
+
pinnedItems.push({
|
|
7377
|
+
id: `pin_${pin.slug}`,
|
|
7378
|
+
type: pin.type,
|
|
7379
|
+
slug: pin.slug,
|
|
7380
|
+
label: pin.label || pin.slug,
|
|
7381
|
+
icon: pin.icon || "Star",
|
|
7382
|
+
order: 0,
|
|
7383
|
+
views: []
|
|
7384
|
+
});
|
|
7385
|
+
}
|
|
7386
|
+
}
|
|
7387
|
+
return {
|
|
7388
|
+
groups: reconciledGroups,
|
|
7389
|
+
ungrouped: reconciledUngrouped,
|
|
7390
|
+
pinnedItems
|
|
7391
|
+
};
|
|
7392
|
+
}
|
|
7393
|
+
|
|
7394
|
+
// src/utils/navigation.ts
|
|
7395
|
+
var RESERVED_NAVIGATION_SLUGS = /* @__PURE__ */ new Set([
|
|
7396
|
+
"collections",
|
|
7397
|
+
"globals",
|
|
7398
|
+
"setup",
|
|
7399
|
+
"api",
|
|
7400
|
+
"login",
|
|
7401
|
+
"logout",
|
|
7402
|
+
"settings",
|
|
7403
|
+
"admin",
|
|
7404
|
+
"preferences",
|
|
7405
|
+
"auth"
|
|
7406
|
+
]);
|
|
7407
|
+
function assertValidNavigationSlugs(navigation) {
|
|
7408
|
+
for (const item of navigation) {
|
|
7409
|
+
if (item.slug) {
|
|
7410
|
+
const normalizedSlug = item.slug.toLowerCase().trim().replace(/^\//, "");
|
|
7411
|
+
if (RESERVED_NAVIGATION_SLUGS.has(normalizedSlug)) {
|
|
7412
|
+
throw new Error(
|
|
7413
|
+
`[Dyrected Config] The slug '${item.slug}' in navigation item '${item.label || item.slug}' is a reserved system path and cannot be used as an operational workspace slug.`
|
|
7414
|
+
);
|
|
7415
|
+
}
|
|
7416
|
+
}
|
|
7417
|
+
}
|
|
7418
|
+
}
|
|
7419
|
+
function resolveGroupMeta(groupProp, defaultGroupName = "Collections") {
|
|
7420
|
+
if (!groupProp) {
|
|
7421
|
+
return { name: defaultGroupName };
|
|
7422
|
+
}
|
|
7423
|
+
if (typeof groupProp === "string") {
|
|
7424
|
+
return { name: groupProp };
|
|
7425
|
+
}
|
|
7426
|
+
return { name: groupProp.name, meta: groupProp };
|
|
7427
|
+
}
|
|
7428
|
+
function compileNavigation(config) {
|
|
7429
|
+
const collections = config.collections || [];
|
|
7430
|
+
const globals = config.globals || [];
|
|
7431
|
+
const explicitNav = config.admin?.navigation || [];
|
|
7432
|
+
assertValidNavigationSlugs(explicitNav);
|
|
7433
|
+
const collectionsBySlug = /* @__PURE__ */ new Map();
|
|
7434
|
+
for (const col of collections) {
|
|
7435
|
+
collectionsBySlug.set(col.slug, col);
|
|
7436
|
+
}
|
|
7437
|
+
const globalsBySlug = /* @__PURE__ */ new Map();
|
|
7438
|
+
for (const gl of globals) {
|
|
7439
|
+
globalsBySlug.set(gl.slug, gl);
|
|
7440
|
+
}
|
|
7441
|
+
const mentionedCollections = /* @__PURE__ */ new Set();
|
|
7442
|
+
const mentionedGlobals = /* @__PURE__ */ new Set();
|
|
7443
|
+
const injectedViewsByCollection = /* @__PURE__ */ new Map();
|
|
7444
|
+
for (const item of explicitNav) {
|
|
7445
|
+
if (item.addToCollection) {
|
|
7446
|
+
const existing = injectedViewsByCollection.get(item.addToCollection) || [];
|
|
7447
|
+
if (item.views && item.views.length > 0) {
|
|
7448
|
+
injectedViewsByCollection.set(item.addToCollection, [...existing, ...item.views]);
|
|
7449
|
+
}
|
|
7450
|
+
}
|
|
7451
|
+
if (item.collection) mentionedCollections.add(item.collection);
|
|
7452
|
+
if (item.global) mentionedGlobals.add(item.global);
|
|
7453
|
+
}
|
|
7454
|
+
const groupMetadataMap = /* @__PURE__ */ new Map();
|
|
7455
|
+
const itemsByGroup = /* @__PURE__ */ new Map();
|
|
7456
|
+
const ungroupedItems = [];
|
|
7457
|
+
const registerItem = (groupName, item) => {
|
|
7458
|
+
if (!groupName) {
|
|
7459
|
+
ungroupedItems.push(item);
|
|
7460
|
+
} else {
|
|
7461
|
+
const list = itemsByGroup.get(groupName) || [];
|
|
7462
|
+
list.push(item);
|
|
7463
|
+
itemsByGroup.set(groupName, list);
|
|
7464
|
+
}
|
|
7465
|
+
};
|
|
7466
|
+
for (const item of explicitNav) {
|
|
7467
|
+
if (item.addToCollection && !item.slug && !item.collection && !item.global && !item.dashboard && !item.href) {
|
|
7468
|
+
continue;
|
|
7469
|
+
}
|
|
7470
|
+
const { name: groupName, meta: groupMeta } = resolveGroupMeta(item.group, void 0);
|
|
7471
|
+
if (groupName && groupMeta) {
|
|
7472
|
+
groupMetadataMap.set(groupName, { ...groupMetadataMap.get(groupName) || {}, ...groupMeta });
|
|
7473
|
+
}
|
|
7474
|
+
if (item.dashboard) {
|
|
7475
|
+
registerItem(groupName, {
|
|
7476
|
+
id: "dashboard",
|
|
7477
|
+
type: "dashboard",
|
|
7478
|
+
slug: "dashboard",
|
|
7479
|
+
label: item.label || "Dashboard",
|
|
7480
|
+
icon: item.icon || "LayoutDashboard",
|
|
7481
|
+
group: groupName,
|
|
7482
|
+
order: item.order ?? 0,
|
|
7483
|
+
views: [],
|
|
7484
|
+
access: item.access
|
|
7485
|
+
});
|
|
7486
|
+
continue;
|
|
7487
|
+
}
|
|
7488
|
+
if (item.collection) {
|
|
7489
|
+
const col = collectionsBySlug.get(item.collection);
|
|
7490
|
+
const combinedViews = [
|
|
7491
|
+
...col?.views || [],
|
|
7492
|
+
...injectedViewsByCollection.get(item.collection) || [],
|
|
7493
|
+
...item.views || []
|
|
7494
|
+
];
|
|
7495
|
+
registerItem(groupName, {
|
|
7496
|
+
id: item.slug || `collection_${item.collection}`,
|
|
7497
|
+
type: item.slug ? "workspace" : "collection",
|
|
7498
|
+
slug: item.slug || item.collection,
|
|
7499
|
+
collection: item.collection,
|
|
7500
|
+
label: item.label || col?.labels?.plural || item.collection,
|
|
7501
|
+
icon: item.icon || col?.admin?.icon || "Folder",
|
|
7502
|
+
group: groupName,
|
|
7503
|
+
order: item.order ?? col?.admin?.order ?? 100,
|
|
7504
|
+
views: combinedViews,
|
|
7505
|
+
badge: item.badge,
|
|
7506
|
+
access: item.access || col?.access?.read
|
|
7507
|
+
});
|
|
7508
|
+
continue;
|
|
7509
|
+
}
|
|
7510
|
+
if (item.global) {
|
|
7511
|
+
const gl = globalsBySlug.get(item.global);
|
|
7512
|
+
registerItem(groupName, {
|
|
7513
|
+
id: item.slug || `global_${item.global}`,
|
|
7514
|
+
type: "global",
|
|
7515
|
+
slug: item.slug || item.global,
|
|
7516
|
+
global: item.global,
|
|
7517
|
+
label: item.label || gl?.label || item.global,
|
|
7518
|
+
icon: item.icon || "Settings",
|
|
7519
|
+
group: groupName,
|
|
7520
|
+
order: item.order ?? 100,
|
|
7521
|
+
views: [],
|
|
7522
|
+
badge: item.badge,
|
|
7523
|
+
access: item.access
|
|
7524
|
+
});
|
|
7525
|
+
continue;
|
|
7526
|
+
}
|
|
7527
|
+
if (item.href) {
|
|
7528
|
+
registerItem(groupName, {
|
|
7529
|
+
id: `link_${item.slug || item.href}`,
|
|
7530
|
+
type: "link",
|
|
7531
|
+
slug: item.slug || "link",
|
|
7532
|
+
label: item.label || "Link",
|
|
7533
|
+
href: item.href,
|
|
7534
|
+
icon: item.icon || "ExternalLink",
|
|
7535
|
+
group: groupName,
|
|
7536
|
+
order: item.order ?? 100,
|
|
7537
|
+
views: [],
|
|
7538
|
+
badge: item.badge,
|
|
7539
|
+
access: item.access
|
|
7540
|
+
});
|
|
7541
|
+
continue;
|
|
7542
|
+
}
|
|
7543
|
+
const workspaceSlug = item.slug || "workspace";
|
|
7544
|
+
registerItem(groupName, {
|
|
7545
|
+
id: `workspace_${workspaceSlug}`,
|
|
7546
|
+
type: "workspace",
|
|
7547
|
+
slug: workspaceSlug,
|
|
7548
|
+
label: item.label || workspaceSlug,
|
|
7549
|
+
icon: item.icon || "Briefcase",
|
|
7550
|
+
group: groupName,
|
|
7551
|
+
order: item.order ?? 100,
|
|
7552
|
+
views: item.views || [],
|
|
7553
|
+
badge: item.badge,
|
|
7554
|
+
access: item.access
|
|
7555
|
+
});
|
|
7556
|
+
}
|
|
7557
|
+
for (const col of collections) {
|
|
7558
|
+
if (mentionedCollections.has(col.slug)) continue;
|
|
7559
|
+
if (col.admin?.hidden) continue;
|
|
7560
|
+
const groupProp = col.admin?.group;
|
|
7561
|
+
const { name: groupName, meta: groupMeta } = resolveGroupMeta(groupProp, "Collections");
|
|
7562
|
+
if (groupMeta) {
|
|
7563
|
+
groupMetadataMap.set(groupName, { ...groupMetadataMap.get(groupName) || {}, ...groupMeta });
|
|
7564
|
+
}
|
|
7565
|
+
const combinedViews = [
|
|
7566
|
+
...col.views || [],
|
|
7567
|
+
...injectedViewsByCollection.get(col.slug) || []
|
|
7568
|
+
];
|
|
7569
|
+
registerItem(groupName, {
|
|
7570
|
+
id: `collection_${col.slug}`,
|
|
7571
|
+
type: "collection",
|
|
7572
|
+
slug: col.slug,
|
|
7573
|
+
collection: col.slug,
|
|
7574
|
+
label: col.labels?.plural || col.slug,
|
|
7575
|
+
icon: col.admin?.icon || "Folder",
|
|
7576
|
+
group: groupName,
|
|
7577
|
+
order: col?.admin?.order ?? 100,
|
|
7578
|
+
views: combinedViews,
|
|
7579
|
+
access: col.access?.read
|
|
7580
|
+
});
|
|
7581
|
+
}
|
|
7582
|
+
for (const gl of globals) {
|
|
7583
|
+
if (mentionedGlobals.has(gl.slug)) continue;
|
|
7584
|
+
const groupName = "Configuration";
|
|
7585
|
+
registerItem(groupName, {
|
|
7586
|
+
id: `global_${gl.slug}`,
|
|
7587
|
+
type: "global",
|
|
7588
|
+
slug: gl.slug,
|
|
7589
|
+
global: gl.slug,
|
|
7590
|
+
label: gl.label || gl.slug,
|
|
7591
|
+
icon: "Settings",
|
|
7592
|
+
group: groupName,
|
|
7593
|
+
order: 100,
|
|
7594
|
+
views: []
|
|
7595
|
+
});
|
|
7596
|
+
}
|
|
7597
|
+
const sortAndSpliceItems = (items) => {
|
|
7598
|
+
const sorted = [...items].sort((a, b) => a.order - b.order);
|
|
7599
|
+
const explicitMap = /* @__PURE__ */ new Map();
|
|
7600
|
+
for (const opt of explicitNav) {
|
|
7601
|
+
if (opt.slug) explicitMap.set(opt.slug, opt);
|
|
7602
|
+
if (opt.collection) explicitMap.set(opt.collection, opt);
|
|
7603
|
+
if (opt.global) explicitMap.set(opt.global, opt);
|
|
7604
|
+
}
|
|
7605
|
+
for (const opt of explicitNav) {
|
|
7606
|
+
const itemKey = opt.slug || opt.collection || opt.global;
|
|
7607
|
+
if (!itemKey) continue;
|
|
7608
|
+
if (opt.after) {
|
|
7609
|
+
const itemIdx = sorted.findIndex((i) => i.slug === itemKey);
|
|
7610
|
+
const targetIdx = sorted.findIndex((i) => i.slug === opt.after);
|
|
7611
|
+
if (itemIdx !== -1 && targetIdx !== -1 && itemIdx !== targetIdx) {
|
|
7612
|
+
const [moved] = sorted.splice(itemIdx, 1);
|
|
7613
|
+
const newTargetIdx = sorted.findIndex((i) => i.slug === opt.after);
|
|
7614
|
+
sorted.splice(newTargetIdx + 1, 0, moved);
|
|
7615
|
+
}
|
|
7616
|
+
} else if (opt.before) {
|
|
7617
|
+
const itemIdx = sorted.findIndex((i) => i.slug === itemKey);
|
|
7618
|
+
const targetIdx = sorted.findIndex((i) => i.slug === opt.before);
|
|
7619
|
+
if (itemIdx !== -1 && targetIdx !== -1 && itemIdx !== targetIdx) {
|
|
7620
|
+
const [moved] = sorted.splice(itemIdx, 1);
|
|
7621
|
+
const newTargetIdx = sorted.findIndex((i) => i.slug === opt.before);
|
|
7622
|
+
sorted.splice(newTargetIdx, 0, moved);
|
|
7623
|
+
}
|
|
7624
|
+
} else if (opt.position === "first") {
|
|
7625
|
+
const itemIdx = sorted.findIndex((i) => i.slug === itemKey);
|
|
7626
|
+
if (itemIdx > 0) {
|
|
7627
|
+
const [moved] = sorted.splice(itemIdx, 1);
|
|
7628
|
+
sorted.unshift(moved);
|
|
7629
|
+
}
|
|
7630
|
+
} else if (opt.position === "last") {
|
|
7631
|
+
const itemIdx = sorted.findIndex((i) => i.slug === itemKey);
|
|
7632
|
+
if (itemIdx !== -1 && itemIdx < sorted.length - 1) {
|
|
7633
|
+
const [moved] = sorted.splice(itemIdx, 1);
|
|
7634
|
+
sorted.push(moved);
|
|
7635
|
+
}
|
|
7636
|
+
}
|
|
7637
|
+
}
|
|
7638
|
+
return sorted;
|
|
7639
|
+
};
|
|
7640
|
+
const compiledGroups = [];
|
|
7641
|
+
for (const [groupName, rawItems] of itemsByGroup.entries()) {
|
|
7642
|
+
const meta = groupMetadataMap.get(groupName);
|
|
7643
|
+
const sortedItems = sortAndSpliceItems(rawItems);
|
|
7644
|
+
compiledGroups.push({
|
|
7645
|
+
id: `group_${groupName.toLowerCase().replace(/\s+/g, "_")}`,
|
|
7646
|
+
name: groupName,
|
|
7647
|
+
slug: meta?.slug || groupName.toLowerCase().replace(/\s+/g, "-"),
|
|
7648
|
+
icon: meta?.icon,
|
|
7649
|
+
defaultExpanded: meta?.defaultExpanded ?? true,
|
|
7650
|
+
order: meta?.order ?? 100,
|
|
7651
|
+
items: sortedItems
|
|
7652
|
+
});
|
|
7653
|
+
}
|
|
7654
|
+
compiledGroups.sort((a, b) => a.order - b.order);
|
|
7655
|
+
return {
|
|
7656
|
+
groups: compiledGroups,
|
|
7657
|
+
ungrouped: sortAndSpliceItems(ungroupedItems)
|
|
7658
|
+
};
|
|
7659
|
+
}
|
|
7660
|
+
function pruneNavigationForUser(tree, user) {
|
|
7661
|
+
const userRoles = /* @__PURE__ */ new Set();
|
|
7662
|
+
if (user?.role) userRoles.add(user.role);
|
|
7663
|
+
if (Array.isArray(user?.roles)) {
|
|
7664
|
+
for (const r of user.roles) userRoles.add(r);
|
|
7665
|
+
}
|
|
7666
|
+
const isAllowed = (item) => {
|
|
7667
|
+
if (!item.access) return true;
|
|
7668
|
+
if (Array.isArray(item.access)) {
|
|
7669
|
+
if (item.access.length === 0) return true;
|
|
7670
|
+
return item.access.some((role) => userRoles.has(role));
|
|
7671
|
+
}
|
|
7672
|
+
return true;
|
|
7673
|
+
};
|
|
7674
|
+
const prunedGroups = [];
|
|
7675
|
+
for (const group of tree.groups) {
|
|
7676
|
+
const visibleItems = group.items.filter(isAllowed);
|
|
7677
|
+
if (visibleItems.length > 0) {
|
|
7678
|
+
prunedGroups.push({
|
|
7679
|
+
...group,
|
|
7680
|
+
items: visibleItems
|
|
7681
|
+
});
|
|
7682
|
+
}
|
|
7683
|
+
}
|
|
7684
|
+
return {
|
|
7685
|
+
groups: prunedGroups,
|
|
7686
|
+
ungrouped: tree.ungrouped.filter(isAllowed)
|
|
7687
|
+
};
|
|
7688
|
+
}
|
|
7689
|
+
|
|
6728
7690
|
// src/index.ts
|
|
6729
7691
|
var import_zod = require("zod");
|
|
6730
7692
|
function defineCollection(config) {
|
|
@@ -6749,6 +7711,7 @@ var defineTextField = createFieldDefiner("text");
|
|
|
6749
7711
|
var defineTextareaField = createFieldDefiner("textarea");
|
|
6750
7712
|
var defineRichTextField = createFieldDefiner("richText");
|
|
6751
7713
|
var defineNumberField = createFieldDefiner("number");
|
|
7714
|
+
var defineMoneyField = createFieldDefiner("money");
|
|
6752
7715
|
var defineBooleanField = createFieldDefiner("boolean");
|
|
6753
7716
|
var defineDateField = createFieldDefiner("date");
|
|
6754
7717
|
var defineDateTimeField = createFieldDefiner("datetime");
|
|
@@ -6792,8 +7755,10 @@ function defineTab(args) {
|
|
|
6792
7755
|
BASE62_ALPHABET,
|
|
6793
7756
|
BUILTIN_JEXL_HELPERS,
|
|
6794
7757
|
CROCKFORD_BASE32,
|
|
7758
|
+
CURRENT_NAV_PREFERENCES_VERSION,
|
|
6795
7759
|
ConfigValidationError,
|
|
6796
7760
|
ConflictError,
|
|
7761
|
+
DEFAULT_USER_NAV_PREFERENCES,
|
|
6797
7762
|
DuplicateKeyError,
|
|
6798
7763
|
DyrectedAIError,
|
|
6799
7764
|
DyrectedError,
|
|
@@ -6806,6 +7771,10 @@ function defineTab(args) {
|
|
|
6806
7771
|
NON_NEGOTIABLE_CREDENTIAL_FIELDS,
|
|
6807
7772
|
NotFoundError,
|
|
6808
7773
|
PII_REGEX_PATTERNS,
|
|
7774
|
+
RESERVED_NAVIGATION_SLUGS,
|
|
7775
|
+
TASK_LOCKS_COLLECTION,
|
|
7776
|
+
TASK_LOCKS_COLLECTION_CONFIG,
|
|
7777
|
+
UNASSIGNED_GROUP_KEY,
|
|
6809
7778
|
ValidationError,
|
|
6810
7779
|
WORKFLOW_HISTORY_COLLECTION,
|
|
6811
7780
|
abs,
|
|
@@ -6816,6 +7785,7 @@ function defineTab(args) {
|
|
|
6816
7785
|
assertValidDeclarativeHookExpression,
|
|
6817
7786
|
assertValidDeclarativeHookResult,
|
|
6818
7787
|
assertValidDeclarativeHooksInConfig,
|
|
7788
|
+
assertValidNavigationSlugs,
|
|
6819
7789
|
assertValidPreviewUrlsInConfig,
|
|
6820
7790
|
availableWorkflowTransitions,
|
|
6821
7791
|
buildSqlOrderBy,
|
|
@@ -6824,14 +7794,17 @@ function defineTab(args) {
|
|
|
6824
7794
|
ceil,
|
|
6825
7795
|
clamp,
|
|
6826
7796
|
coalesce,
|
|
7797
|
+
coerceBooleanWhere,
|
|
6827
7798
|
collectAdminConditionValidationIssues,
|
|
6828
7799
|
collectConfigDiagnostics,
|
|
6829
7800
|
collectDeclarativeAccessValidationIssues,
|
|
6830
7801
|
collectDeclarativeHookValidationIssues,
|
|
6831
7802
|
collectPreviewUrlValidationIssues,
|
|
6832
7803
|
compact,
|
|
7804
|
+
compileNavigation,
|
|
6833
7805
|
count,
|
|
6834
7806
|
createLifecycleEvent,
|
|
7807
|
+
createTaskRunner,
|
|
6835
7808
|
createWorkflowDocument,
|
|
6836
7809
|
defaultValue,
|
|
6837
7810
|
defineAction,
|
|
@@ -6854,6 +7827,7 @@ function defineTab(args) {
|
|
|
6854
7827
|
defineJoinField,
|
|
6855
7828
|
defineJsonField,
|
|
6856
7829
|
defineKanbanView,
|
|
7830
|
+
defineMoneyField,
|
|
6857
7831
|
defineMultiSelectField,
|
|
6858
7832
|
defineNumberField,
|
|
6859
7833
|
defineObjectField,
|
|
@@ -6865,11 +7839,13 @@ function defineTab(args) {
|
|
|
6865
7839
|
defineSelectField,
|
|
6866
7840
|
defineTab,
|
|
6867
7841
|
defineTableView,
|
|
7842
|
+
defineTask,
|
|
6868
7843
|
defineTextField,
|
|
6869
7844
|
defineTextareaField,
|
|
6870
7845
|
defineTimeField,
|
|
6871
7846
|
defineUrlField,
|
|
6872
7847
|
defineView,
|
|
7848
|
+
defineWorkspace,
|
|
6873
7849
|
diffDays,
|
|
6874
7850
|
dispatchLifecycleEvent,
|
|
6875
7851
|
dispatchPendingLifecycleEvents,
|
|
@@ -6927,22 +7903,28 @@ function defineTab(args) {
|
|
|
6927
7903
|
max,
|
|
6928
7904
|
min,
|
|
6929
7905
|
nanoid,
|
|
7906
|
+
nextCronRun,
|
|
6930
7907
|
normalizeConfig,
|
|
6931
7908
|
normalizeDetailItem,
|
|
7909
|
+
normalizeGroupKey,
|
|
6932
7910
|
now,
|
|
6933
7911
|
numericFieldNames,
|
|
7912
|
+
parseCron,
|
|
6934
7913
|
parseMongoWhere,
|
|
6935
7914
|
parseSort,
|
|
6936
7915
|
parseSqlWhere,
|
|
6937
7916
|
pow,
|
|
6938
7917
|
prefixedId,
|
|
7918
|
+
pruneNavigationForUser,
|
|
6939
7919
|
publishedStateName,
|
|
6940
7920
|
publishingWorkflow,
|
|
6941
7921
|
readingTime,
|
|
7922
|
+
reconcileNavigation,
|
|
6942
7923
|
registerJexlHelpers,
|
|
6943
7924
|
replace,
|
|
6944
7925
|
resolveAdminAuthCollection,
|
|
6945
7926
|
resolveAuthorizedSiteId,
|
|
7927
|
+
resolveIndexName,
|
|
6946
7928
|
resolvePrefix,
|
|
6947
7929
|
round,
|
|
6948
7930
|
runCollectionHooks,
|