@dyrected/core 2.17.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-CLdT_0se.d.cts → app-Dd9cBbCs.d.cts} +171 -5
- package/dist/{app-CLdT_0se.d.ts → app-Dd9cBbCs.d.ts} +171 -5
- package/dist/index.cjs +412 -6
- package/dist/index.d.cts +98 -4
- package/dist/index.d.ts +98 -4
- package/dist/index.js +401 -6
- package/dist/server.cjs +217 -27
- package/dist/server.d.cts +20 -4
- package/dist/server.d.ts +20 -4
- package/dist/server.js +216 -27
- package/package.json +1 -1
package/dist/server.cjs
CHANGED
|
@@ -4013,6 +4013,7 @@ __export(server_exports, {
|
|
|
4013
4013
|
revokeAllAuthSessions: () => revokeAllAuthSessions,
|
|
4014
4014
|
revokeAuthSession: () => revokeAuthSession,
|
|
4015
4015
|
sendEmail: () => sendEmail,
|
|
4016
|
+
serializeActionsForApi: () => serializeActionsForApi,
|
|
4016
4017
|
serializeDetailForApi: () => serializeDetailForApi,
|
|
4017
4018
|
serializeViewForApi: () => serializeViewForApi,
|
|
4018
4019
|
shouldSampleRequest: () => shouldSampleRequest,
|
|
@@ -6144,6 +6145,72 @@ function createReadonlyDb(db) {
|
|
|
6144
6145
|
});
|
|
6145
6146
|
}
|
|
6146
6147
|
|
|
6148
|
+
// src/utils/errors.ts
|
|
6149
|
+
var DyrectedError = class _DyrectedError extends Error {
|
|
6150
|
+
statusCode;
|
|
6151
|
+
constructor(message, statusCode = 500) {
|
|
6152
|
+
super(message);
|
|
6153
|
+
this.name = "DyrectedError";
|
|
6154
|
+
this.statusCode = statusCode;
|
|
6155
|
+
Object.setPrototypeOf(this, _DyrectedError.prototype);
|
|
6156
|
+
}
|
|
6157
|
+
};
|
|
6158
|
+
var ValidationError = class _ValidationError extends DyrectedError {
|
|
6159
|
+
constructor(message) {
|
|
6160
|
+
super(message, 400);
|
|
6161
|
+
this.name = "ValidationError";
|
|
6162
|
+
Object.setPrototypeOf(this, _ValidationError.prototype);
|
|
6163
|
+
}
|
|
6164
|
+
};
|
|
6165
|
+
|
|
6166
|
+
// src/utils/field-integrity.ts
|
|
6167
|
+
function* storedFields(fields) {
|
|
6168
|
+
for (const field of fields ?? []) {
|
|
6169
|
+
if (field.type === "row") {
|
|
6170
|
+
yield* storedFields(field.fields);
|
|
6171
|
+
} else if (field.name) {
|
|
6172
|
+
yield field;
|
|
6173
|
+
}
|
|
6174
|
+
}
|
|
6175
|
+
}
|
|
6176
|
+
function sameValue(a, b) {
|
|
6177
|
+
if (a === b) return true;
|
|
6178
|
+
if (a === null || b === null || a === void 0 || b === void 0) return a == b;
|
|
6179
|
+
if (typeof a === "object" || typeof b === "object") {
|
|
6180
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
6181
|
+
}
|
|
6182
|
+
return false;
|
|
6183
|
+
}
|
|
6184
|
+
function assertImmutableFieldsUnchanged(fields, data, original) {
|
|
6185
|
+
for (const field of storedFields(fields)) {
|
|
6186
|
+
if (!field.immutable || data[field.name] === void 0) continue;
|
|
6187
|
+
if (!sameValue(data[field.name], original[field.name])) {
|
|
6188
|
+
throw new ValidationError(
|
|
6189
|
+
`Field "${field.label ?? field.name}" is immutable and cannot be modified.`
|
|
6190
|
+
);
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
}
|
|
6194
|
+
function normalizeMoneyFields(fields, data) {
|
|
6195
|
+
let next = data;
|
|
6196
|
+
for (const field of storedFields(fields)) {
|
|
6197
|
+
if (field.type !== "money") continue;
|
|
6198
|
+
const value = data[field.name];
|
|
6199
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
6200
|
+
let minor;
|
|
6201
|
+
if (typeof value === "number") minor = value;
|
|
6202
|
+
else if (typeof value === "string" && /^-?\d+$/.test(value.trim())) minor = Number(value.trim());
|
|
6203
|
+
if (minor === void 0 || !Number.isSafeInteger(minor)) {
|
|
6204
|
+
throw new ValidationError(
|
|
6205
|
+
`Field "${field.label ?? field.name}" must be a whole number of minor units (for example kobo or cents).`
|
|
6206
|
+
);
|
|
6207
|
+
}
|
|
6208
|
+
if (next === data) next = { ...data };
|
|
6209
|
+
next[field.name] = minor;
|
|
6210
|
+
}
|
|
6211
|
+
return next;
|
|
6212
|
+
}
|
|
6213
|
+
|
|
6147
6214
|
// src/utils/upload-validation.ts
|
|
6148
6215
|
function isMimeAllowed(mimeType, allowed) {
|
|
6149
6216
|
const type = (mimeType || "").toLowerCase().trim();
|
|
@@ -6739,7 +6806,7 @@ async function dispatchLifecycleEvent(config, event) {
|
|
|
6739
6806
|
}
|
|
6740
6807
|
}
|
|
6741
6808
|
async function saveWorkflowDraft(args) {
|
|
6742
|
-
const { config, collection, id, originalDoc, data, user } = args;
|
|
6809
|
+
const { config, collection, id, originalDoc, data, user, beforeCommit } = args;
|
|
6743
6810
|
const db = config.db;
|
|
6744
6811
|
const workflow = collection.workflow;
|
|
6745
6812
|
if (!db.transaction) throw new Error(`The configured database adapter does not support workflow transactions.`);
|
|
@@ -6773,13 +6840,14 @@ async function saveWorkflowDraft(args) {
|
|
|
6773
6840
|
}
|
|
6774
6841
|
const updated = await tx.update({ collection: collection.slug, id, data: updateData });
|
|
6775
6842
|
await persistEvent(tx, event);
|
|
6843
|
+
await beforeCommit?.(tx, updated);
|
|
6776
6844
|
return updated;
|
|
6777
6845
|
});
|
|
6778
6846
|
void dispatchLifecycleEvent(config, event);
|
|
6779
6847
|
return { doc, event };
|
|
6780
6848
|
}
|
|
6781
6849
|
async function createWorkflowDocument(args) {
|
|
6782
|
-
const { config, collection, data, user } = args;
|
|
6850
|
+
const { config, collection, data, user, beforeCommit } = args;
|
|
6783
6851
|
const db = config.db;
|
|
6784
6852
|
if (!db.transaction) throw new Error(`The configured database adapter does not support workflow transactions.`);
|
|
6785
6853
|
let event;
|
|
@@ -6793,13 +6861,14 @@ async function createWorkflowDocument(args) {
|
|
|
6793
6861
|
payload: { revision: 1, previousRevision: null }
|
|
6794
6862
|
});
|
|
6795
6863
|
await persistEvent(tx, event);
|
|
6864
|
+
await beforeCommit?.(tx, created);
|
|
6796
6865
|
return created;
|
|
6797
6866
|
});
|
|
6798
6867
|
void dispatchLifecycleEvent(config, event);
|
|
6799
6868
|
return { doc, event };
|
|
6800
6869
|
}
|
|
6801
6870
|
async function transitionWorkflow(args) {
|
|
6802
|
-
const { config, collection, id, transitionName, expectedRevision, comment, user, req } = args;
|
|
6871
|
+
const { config, collection, id, transitionName, expectedRevision, comment, input, user, req } = args;
|
|
6803
6872
|
const db = config.db;
|
|
6804
6873
|
const workflow = collection.workflow;
|
|
6805
6874
|
if (!db.transaction) throw new Error(`The configured database adapter does not support workflow transactions.`);
|
|
@@ -6825,7 +6894,7 @@ async function transitionWorkflow(args) {
|
|
|
6825
6894
|
if (transition.requireComment && !comment?.trim()) {
|
|
6826
6895
|
throw Object.assign(new Error(`A comment is required for "${transition.label}".`), { statusCode: 400 });
|
|
6827
6896
|
}
|
|
6828
|
-
const hookContext = { transition, from: meta.state, to: transition.to, doc: original, user, comment, req, db };
|
|
6897
|
+
const hookContext = { transition, from: meta.state, to: transition.to, doc: original, user, comment, input, req, db };
|
|
6829
6898
|
for (const hook of workflow.hooks?.beforeTransition ?? []) await hook(hookContext);
|
|
6830
6899
|
const events = [
|
|
6831
6900
|
createLifecycleEvent({
|
|
@@ -6879,6 +6948,19 @@ async function transitionWorkflow(args) {
|
|
|
6879
6948
|
if (targetState.published) data.__published = working;
|
|
6880
6949
|
if (transition.unpublish) data.__published = null;
|
|
6881
6950
|
const next = await tx.update({ collection: collection.slug, id, data });
|
|
6951
|
+
if (transition.onTransition) {
|
|
6952
|
+
await transition.onTransition({
|
|
6953
|
+
transition,
|
|
6954
|
+
from: lockedMeta.state,
|
|
6955
|
+
to: transition.to,
|
|
6956
|
+
doc: locked,
|
|
6957
|
+
input,
|
|
6958
|
+
comment,
|
|
6959
|
+
user,
|
|
6960
|
+
req,
|
|
6961
|
+
tx
|
|
6962
|
+
});
|
|
6963
|
+
}
|
|
6882
6964
|
await tx.create({
|
|
6883
6965
|
collection: WORKFLOW_HISTORY_COLLECTION,
|
|
6884
6966
|
data: {
|
|
@@ -7518,6 +7600,7 @@ var CollectionController = class {
|
|
|
7518
7600
|
updatedBy: user?.sub ?? null
|
|
7519
7601
|
};
|
|
7520
7602
|
data = DefaultsService.apply(this.collection.fields, data);
|
|
7603
|
+
data = normalizeMoneyFields(this.collection.fields, data);
|
|
7521
7604
|
if (this.collection.workflow) {
|
|
7522
7605
|
data = initializeWorkflowDocument(data, this.collection.workflow);
|
|
7523
7606
|
}
|
|
@@ -7565,12 +7648,18 @@ var CollectionController = class {
|
|
|
7565
7648
|
if (!data.id) {
|
|
7566
7649
|
data = { ...data, id: generateDocumentId(this.collection) };
|
|
7567
7650
|
}
|
|
7651
|
+
const beforeCommit = this.buildBeforeCommit(c, "create", user);
|
|
7568
7652
|
const doc = this.collection.workflow ? (await createWorkflowDocument({
|
|
7569
7653
|
config,
|
|
7570
7654
|
collection: this.collection,
|
|
7571
7655
|
data,
|
|
7572
|
-
user
|
|
7573
|
-
|
|
7656
|
+
user,
|
|
7657
|
+
beforeCommit
|
|
7658
|
+
})).doc : await this.persistInTransaction(
|
|
7659
|
+
db,
|
|
7660
|
+
beforeCommit,
|
|
7661
|
+
(writer) => writer.create({ collection: this.collection.slug, data })
|
|
7662
|
+
);
|
|
7574
7663
|
if (this.collection.audit && db) {
|
|
7575
7664
|
AuditService.log(db, {
|
|
7576
7665
|
operation: "create",
|
|
@@ -7804,6 +7893,36 @@ var CollectionController = class {
|
|
|
7804
7893
|
const body = await c.req.json();
|
|
7805
7894
|
return this.performUpdate(c, id, body);
|
|
7806
7895
|
}
|
|
7896
|
+
/**
|
|
7897
|
+
* Builds the callback that runs the collection's `beforeCommit` hooks inside
|
|
7898
|
+
* the write transaction, or `undefined` when the collection has none.
|
|
7899
|
+
*/
|
|
7900
|
+
buildBeforeCommit(c, operation, user, previousDoc) {
|
|
7901
|
+
const hooks = this.collection.hooks?.beforeCommit;
|
|
7902
|
+
if (!hooks?.length) return void 0;
|
|
7903
|
+
return async (tx, doc) => {
|
|
7904
|
+
for (const hook of hooks) {
|
|
7905
|
+
await hook({ doc, previousDoc, req: this.toHookRequestContext(c), user, operation, tx });
|
|
7906
|
+
}
|
|
7907
|
+
};
|
|
7908
|
+
}
|
|
7909
|
+
/**
|
|
7910
|
+
* Runs a single write. When `beforeCommit` hooks exist the write and the
|
|
7911
|
+
* hooks share one transaction, so a throwing hook rolls the write back.
|
|
7912
|
+
*/
|
|
7913
|
+
async persistInTransaction(db, beforeCommit, write) {
|
|
7914
|
+
if (!beforeCommit) return write(db);
|
|
7915
|
+
if (!db.transaction) {
|
|
7916
|
+
throw new Error(
|
|
7917
|
+
`Collection "${this.collection.slug}" defines beforeCommit hooks but the configured database adapter does not support transactions.`
|
|
7918
|
+
);
|
|
7919
|
+
}
|
|
7920
|
+
return db.transaction(async (tx) => {
|
|
7921
|
+
const doc = await write(tx);
|
|
7922
|
+
await beforeCommit(tx, doc);
|
|
7923
|
+
return doc;
|
|
7924
|
+
});
|
|
7925
|
+
}
|
|
7807
7926
|
/**
|
|
7808
7927
|
* Core update pipeline shared by PATCH requests and operational actions:
|
|
7809
7928
|
* auth-field stripping, timestamps, access enforcement, field write access,
|
|
@@ -7845,6 +7964,7 @@ var CollectionController = class {
|
|
|
7845
7964
|
403
|
|
7846
7965
|
);
|
|
7847
7966
|
}
|
|
7967
|
+
data = normalizeMoneyFields(this.collection.fields, data);
|
|
7848
7968
|
let before = null;
|
|
7849
7969
|
if (this.collection.audit) {
|
|
7850
7970
|
before = originalDoc;
|
|
@@ -7878,14 +7998,21 @@ var CollectionController = class {
|
|
|
7878
7998
|
surface: "collection.beforeChange",
|
|
7879
7999
|
path: `collection:${this.collection.slug}.hooks.beforeChange`
|
|
7880
8000
|
});
|
|
8001
|
+
assertImmutableFieldsUnchanged(this.collection.fields, data, originalDoc);
|
|
8002
|
+
const beforeCommit = this.buildBeforeCommit(c, "update", user, originalDoc);
|
|
7881
8003
|
const doc = this.collection.workflow ? (await saveWorkflowDraft({
|
|
7882
8004
|
config,
|
|
7883
8005
|
collection: this.collection,
|
|
7884
8006
|
id,
|
|
7885
8007
|
originalDoc,
|
|
7886
8008
|
data,
|
|
7887
|
-
user
|
|
7888
|
-
|
|
8009
|
+
user,
|
|
8010
|
+
beforeCommit
|
|
8011
|
+
})).doc : await this.persistInTransaction(
|
|
8012
|
+
db,
|
|
8013
|
+
beforeCommit,
|
|
8014
|
+
(writer) => writer.update({ collection: this.collection.slug, id, data })
|
|
8015
|
+
);
|
|
7889
8016
|
if (this.collection.audit && db) {
|
|
7890
8017
|
AuditService.log(db, {
|
|
7891
8018
|
operation: "update",
|
|
@@ -8112,6 +8239,7 @@ var CollectionController = class {
|
|
|
8112
8239
|
transitionName,
|
|
8113
8240
|
expectedRevision: body.expectedRevision,
|
|
8114
8241
|
comment: body.comment,
|
|
8242
|
+
input: body.input,
|
|
8115
8243
|
user: c.get("user"),
|
|
8116
8244
|
req: { query: c.req.query(), headers: c.req.header(), raw: c.req.raw }
|
|
8117
8245
|
});
|
|
@@ -9443,6 +9571,20 @@ var MediaFolderController = class {
|
|
|
9443
9571
|
}
|
|
9444
9572
|
};
|
|
9445
9573
|
|
|
9574
|
+
// src/utils/admin-url.ts
|
|
9575
|
+
var DEFAULT_ADMIN_PATH = "/admin";
|
|
9576
|
+
function resolveAdminUrl(c, config, clientUrl) {
|
|
9577
|
+
const configured = config.admin?.adminUrl?.trim() || process.env.DYRECTED_ADMIN_URL?.trim() || (typeof clientUrl === "string" ? clientUrl.trim() : "") || DEFAULT_ADMIN_PATH;
|
|
9578
|
+
try {
|
|
9579
|
+
return new URL(configured, c.req.url).toString();
|
|
9580
|
+
} catch {
|
|
9581
|
+
return configured;
|
|
9582
|
+
}
|
|
9583
|
+
}
|
|
9584
|
+
function appendQueryParam(url, key, value) {
|
|
9585
|
+
return `${url}${url.includes("?") ? "&" : "?"}${key}=${encodeURIComponent(value)}`;
|
|
9586
|
+
}
|
|
9587
|
+
|
|
9446
9588
|
// src/controllers/auth.controller.ts
|
|
9447
9589
|
var import_node_crypto3 = require("crypto");
|
|
9448
9590
|
|
|
@@ -10118,8 +10260,7 @@ var AuthController = class {
|
|
|
10118
10260
|
},
|
|
10119
10261
|
"1h"
|
|
10120
10262
|
);
|
|
10121
|
-
const
|
|
10122
|
-
const url = resetUrl ? `${resetUrl}${resetUrl.includes("?") ? "&" : "?"}token=${encodeURIComponent(resetToken)}` : void 0;
|
|
10263
|
+
const url = appendQueryParam(resolveAdminUrl(c, config, body?.resetUrl), "token", resetToken);
|
|
10123
10264
|
try {
|
|
10124
10265
|
const { subject, html } = buildResetPasswordEmail(config, {
|
|
10125
10266
|
token: resetToken,
|
|
@@ -10251,8 +10392,7 @@ var AuthController = class {
|
|
|
10251
10392
|
},
|
|
10252
10393
|
"7d"
|
|
10253
10394
|
);
|
|
10254
|
-
const
|
|
10255
|
-
const url = inviteUrl ? `${inviteUrl}${inviteUrl.includes("?") ? "&" : "?"}inviteToken=${encodeURIComponent(inviteToken)}` : void 0;
|
|
10395
|
+
const url = appendQueryParam(resolveAdminUrl(c, config, body?.inviteUrl), "inviteToken", inviteToken);
|
|
10256
10396
|
try {
|
|
10257
10397
|
const { subject, html } = buildInviteEmail(config, {
|
|
10258
10398
|
token: inviteToken,
|
|
@@ -12526,7 +12666,12 @@ function generateOpenApi(config) {
|
|
|
12526
12666
|
type: "object",
|
|
12527
12667
|
properties: {
|
|
12528
12668
|
expectedRevision: { type: "integer", minimum: 1 },
|
|
12529
|
-
comment: { type: "string" }
|
|
12669
|
+
comment: { type: "string" },
|
|
12670
|
+
input: {
|
|
12671
|
+
type: "object",
|
|
12672
|
+
additionalProperties: true,
|
|
12673
|
+
description: "Extra data passed to the transition's onTransition handler."
|
|
12674
|
+
}
|
|
12530
12675
|
}
|
|
12531
12676
|
},
|
|
12532
12677
|
WorkflowHistoryEntry: {
|
|
@@ -13689,6 +13834,9 @@ function fieldToSchema(field) {
|
|
|
13689
13834
|
case "number":
|
|
13690
13835
|
schema = { type: "number" };
|
|
13691
13836
|
break;
|
|
13837
|
+
case "money":
|
|
13838
|
+
schema = { type: "integer", description: "Amount in minor units (for example kobo or cents)." };
|
|
13839
|
+
break;
|
|
13692
13840
|
case "boolean":
|
|
13693
13841
|
schema = { type: "boolean" };
|
|
13694
13842
|
break;
|
|
@@ -14508,6 +14656,30 @@ function serializeDetailForApi(detail) {
|
|
|
14508
14656
|
if (!detail || !Array.isArray(detail)) return void 0;
|
|
14509
14657
|
return detail.map(serializeDetailItemForApi);
|
|
14510
14658
|
}
|
|
14659
|
+
async function serializeActionsForApi(actions, serializeAccess) {
|
|
14660
|
+
const serializeAccessConfig = async (access) => {
|
|
14661
|
+
if (!access) return void 0;
|
|
14662
|
+
const serialized = {};
|
|
14663
|
+
for (const key of ["read", "create", "update", "delete"]) {
|
|
14664
|
+
if (access[key] !== void 0) serialized[key] = await serializeAccess(access[key]);
|
|
14665
|
+
}
|
|
14666
|
+
return serialized;
|
|
14667
|
+
};
|
|
14668
|
+
return Promise.all(
|
|
14669
|
+
(actions || []).map(async (action) => ({
|
|
14670
|
+
name: action.name,
|
|
14671
|
+
label: action.label,
|
|
14672
|
+
icon: action.icon,
|
|
14673
|
+
type: action.type ?? "row",
|
|
14674
|
+
confirm: action.confirm,
|
|
14675
|
+
submitLabel: action.submitLabel,
|
|
14676
|
+
fields: action.fields?.map(serializeFieldForApi),
|
|
14677
|
+
mutation: action.mutation,
|
|
14678
|
+
// Self-hosted handlers are intentionally omitted: they never leave the server.
|
|
14679
|
+
access: action.access ? await serializeAccessConfig(action.access) : void 0
|
|
14680
|
+
}))
|
|
14681
|
+
);
|
|
14682
|
+
}
|
|
14511
14683
|
async function serializeViewForApi(view, serializeAccess) {
|
|
14512
14684
|
if (!view) return view;
|
|
14513
14685
|
const serializeAccessConfig = async (access) => {
|
|
@@ -14534,20 +14706,7 @@ async function serializeViewForApi(view, serializeAccess) {
|
|
|
14534
14706
|
features: view.features,
|
|
14535
14707
|
actionOrder: view.actionOrder,
|
|
14536
14708
|
components: view.components,
|
|
14537
|
-
actions: await
|
|
14538
|
-
(view.actions || []).map(async (action) => ({
|
|
14539
|
-
name: action.name,
|
|
14540
|
-
label: action.label,
|
|
14541
|
-
icon: action.icon,
|
|
14542
|
-
type: action.type ?? "row",
|
|
14543
|
-
confirm: action.confirm,
|
|
14544
|
-
submitLabel: action.submitLabel,
|
|
14545
|
-
fields: action.fields?.map(serializeFieldForApi),
|
|
14546
|
-
mutation: action.mutation,
|
|
14547
|
-
// Self-hosted handlers are intentionally omitted: they never leave the server.
|
|
14548
|
-
access: action.access ? await serializeAccessConfig(action.access) : void 0
|
|
14549
|
-
}))
|
|
14550
|
-
),
|
|
14709
|
+
actions: await serializeActionsForApi(view.actions, serializeAccess),
|
|
14551
14710
|
access: view.access ? await serializeAccessConfig(view.access) : void 0
|
|
14552
14711
|
};
|
|
14553
14712
|
}
|
|
@@ -14629,6 +14788,7 @@ function registerRoutes(app, config) {
|
|
|
14629
14788
|
drafts: !!col.drafts,
|
|
14630
14789
|
defaultView: col.defaultView ?? col.admin?.defaultView,
|
|
14631
14790
|
views: await Promise.all((col.views || []).map((view) => serializeViewForApi(view, serializeAccess))),
|
|
14791
|
+
actions: await serializeActionsForApi(col.actions, serializeAccess),
|
|
14632
14792
|
admin: col.admin,
|
|
14633
14793
|
detail: serializeDetailForApi(col.detail),
|
|
14634
14794
|
workflow: col.workflow ? {
|
|
@@ -15509,6 +15669,31 @@ function registerRoutes(app, config) {
|
|
|
15509
15669
|
});
|
|
15510
15670
|
}
|
|
15511
15671
|
|
|
15672
|
+
// src/tasks.ts
|
|
15673
|
+
var TASK_LOCKS_COLLECTION = "__task_locks";
|
|
15674
|
+
var TASK_LOCKS_COLLECTION_CONFIG = {
|
|
15675
|
+
slug: TASK_LOCKS_COLLECTION,
|
|
15676
|
+
labels: { singular: "Task lock", plural: "Task locks" },
|
|
15677
|
+
fields: [
|
|
15678
|
+
{ name: "name", type: "text", required: true },
|
|
15679
|
+
// Numeric and promoted so the lock compare-and-set is a real column comparison.
|
|
15680
|
+
{ name: "lockedUntil", type: "number", required: true, promoted: true },
|
|
15681
|
+
{ name: "lockedBy", type: "text" },
|
|
15682
|
+
{ name: "lastScheduledFor", type: "date" },
|
|
15683
|
+
{ name: "lastStartedAt", type: "date" },
|
|
15684
|
+
{ name: "lastFinishedAt", type: "date" },
|
|
15685
|
+
{ name: "lastStatus", type: "text" },
|
|
15686
|
+
{ name: "lastError", type: "textarea" }
|
|
15687
|
+
],
|
|
15688
|
+
access: {
|
|
15689
|
+
read: ({ user }) => !!user?.roles?.includes("admin"),
|
|
15690
|
+
create: () => false,
|
|
15691
|
+
update: () => false,
|
|
15692
|
+
delete: () => false
|
|
15693
|
+
},
|
|
15694
|
+
admin: { hidden: true }
|
|
15695
|
+
};
|
|
15696
|
+
|
|
15512
15697
|
// src/utils/config.ts
|
|
15513
15698
|
var AUDIT_COLLECTION_SLUG = "__audit";
|
|
15514
15699
|
var SYSTEM_FIELDS = [
|
|
@@ -15660,6 +15845,7 @@ function normalizeConfig(config) {
|
|
|
15660
15845
|
const globals = schemaAwareConfig?.globals || [];
|
|
15661
15846
|
const needsAudit = collections.some((col) => col.audit);
|
|
15662
15847
|
const needsWorkflow = collections.some((col) => col.workflow || col.drafts);
|
|
15848
|
+
const needsTasks = (config.tasks?.length ?? 0) > 0;
|
|
15663
15849
|
const needsAuthSessions = collections.some((col) => !!col.auth);
|
|
15664
15850
|
const adminAuthCollectionSlug = getAdminAuthCollection({
|
|
15665
15851
|
collections,
|
|
@@ -15892,6 +16078,9 @@ function normalizeConfig(config) {
|
|
|
15892
16078
|
if (needsWorkflow && !normalizedCollections.some((col) => col.slug === LIFECYCLE_EVENTS_COLLECTION)) {
|
|
15893
16079
|
systemCollections.push(LIFECYCLE_EVENTS_COLLECTION_CONFIG);
|
|
15894
16080
|
}
|
|
16081
|
+
if (needsTasks && !normalizedCollections.some((col) => col.slug === TASK_LOCKS_COLLECTION)) {
|
|
16082
|
+
systemCollections.push(TASK_LOCKS_COLLECTION_CONFIG);
|
|
16083
|
+
}
|
|
15895
16084
|
if (needsAuthSessions && !normalizedCollections.some((col) => col.slug === AUTH_SESSIONS_COLLECTION)) {
|
|
15896
16085
|
systemCollections.push(AUTH_SESSIONS_COLLECTION_CONFIG);
|
|
15897
16086
|
}
|
|
@@ -16262,6 +16451,7 @@ init_embedding_service();
|
|
|
16262
16451
|
revokeAllAuthSessions,
|
|
16263
16452
|
revokeAuthSession,
|
|
16264
16453
|
sendEmail,
|
|
16454
|
+
serializeActionsForApi,
|
|
16265
16455
|
serializeDetailForApi,
|
|
16266
16456
|
serializeViewForApi,
|
|
16267
16457
|
shouldSampleRequest,
|
package/dist/server.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { O as DyrectedContext, D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig, e as DatabaseAdapter, b as AuthenticatedUser, bV as DyrectedAIContext, aw as AIThread, as as AIMessage, cT as RAGSearchResult, av as AIRateLimitStore, au as AIRateLimitResult } from './app-Dd9cBbCs.cjs';
|
|
2
|
+
export { dH as CollectionTokenPayload, dI as DEFAULT_SESSION_EXPIRY, dJ as DyrectedObservabilityRuntime, dK as RequestBodyCapture, dL as RequestTraceContext, dM as ResolvedObservabilityConfig, dN as attachRequestMetrics, dO as bindObservabilityRuntime, dP as buildRequestLogger, dQ as captureRequestBody, dR as createDyrectedApp, dS as createObservabilityRuntime, dT as createRequestSpan, dU as decodeCollectionToken, dV as endRequestSpan, dW as getConfigLogger, dX as getObservabilityRuntime, dY as getRequestLogger, dZ as redactBody, d_ as redactHeaders, d$ as renderPrometheusMetrics, e0 as resolveObservabilityConfig, e1 as resolveSessionTokenExpiry, e2 as shouldSampleRequest, e3 as signCollectionToken, e4 as verifyCollectionToken } from './app-Dd9cBbCs.cjs';
|
|
3
3
|
import * as hono from 'hono';
|
|
4
4
|
import { Hono, Context, Next } from 'hono';
|
|
5
5
|
import * as hono_utils_http_status from 'hono/utils/http-status';
|
|
@@ -14,6 +14,12 @@ import '@opentelemetry/api';
|
|
|
14
14
|
import '@opentelemetry/exporter-prometheus';
|
|
15
15
|
|
|
16
16
|
declare function serializeDetailForApi(detail?: any): any;
|
|
17
|
+
/**
|
|
18
|
+
* Serializes a list of operational actions (view-level or collection-level)
|
|
19
|
+
* for the Admin API. Function properties (self-hosted handlers) are stripped so
|
|
20
|
+
* the payload stays JSON-safe; access rules are resolved to static values.
|
|
21
|
+
*/
|
|
22
|
+
declare function serializeActionsForApi(actions: any[] | undefined, serializeAccess: (access: any) => Promise<any>): Promise<any[]>;
|
|
17
23
|
/**
|
|
18
24
|
* Serializes an operational view config for the Admin API.
|
|
19
25
|
* Function properties (action handlers) are stripped so the payload stays
|
|
@@ -153,7 +159,7 @@ declare class AuthController {
|
|
|
153
159
|
success: true;
|
|
154
160
|
message: string;
|
|
155
161
|
token: string;
|
|
156
|
-
inviteUrl: string
|
|
162
|
+
inviteUrl: string;
|
|
157
163
|
}, hono_utils_http_status.ContentfulStatusCode, "json">)>;
|
|
158
164
|
acceptInvite(c: Context<DyrectedContext>): Promise<(Response & hono.TypedResponse<{
|
|
159
165
|
message: string;
|
|
@@ -186,6 +192,16 @@ declare class CollectionController {
|
|
|
186
192
|
create(c: Context<DyrectedContext>): Promise<any>;
|
|
187
193
|
upload(c: Context<DyrectedContext>): Promise<any>;
|
|
188
194
|
update(c: Context<DyrectedContext>): Promise<any>;
|
|
195
|
+
/**
|
|
196
|
+
* Builds the callback that runs the collection's `beforeCommit` hooks inside
|
|
197
|
+
* the write transaction, or `undefined` when the collection has none.
|
|
198
|
+
*/
|
|
199
|
+
private buildBeforeCommit;
|
|
200
|
+
/**
|
|
201
|
+
* Runs a single write. When `beforeCommit` hooks exist the write and the
|
|
202
|
+
* hooks share one transaction, so a throwing hook rolls the write back.
|
|
203
|
+
*/
|
|
204
|
+
private persistInTransaction;
|
|
189
205
|
/**
|
|
190
206
|
* Core update pipeline shared by PATCH requests and operational actions:
|
|
191
207
|
* auth-field stripping, timestamps, access enforcement, field write access,
|
|
@@ -745,4 +761,4 @@ declare class InMemoryRateLimitStore implements AIRateLimitStore {
|
|
|
745
761
|
}
|
|
746
762
|
declare function aiRateLimit(config?: DyrectedConfig): (c: Context<DyrectedContext>, next: Next) => Promise<void>;
|
|
747
763
|
|
|
748
|
-
export { AIAgent, type AIAgentOptions, AIController, AUTH_SESSIONS_COLLECTION, AuthController, type AuthSessionRecord, type ChunkOptions, type ChunkResult, CollectionController, DEFAULT_LOCK_TIME_MS, DEFAULT_MAX_LOGIN_ATTEMPTS, type DeleteDocumentChunksOptions, DyrectedContext, EmbeddingService, GlobalController, InMemoryRateLimitStore, type IndexDocumentOptions, MediaController, PreviewController, type RAGSearchOptions, RAGService, type ReindexAllOptions, type ReindexCollectionOptions, type ResolvedAuthLockoutConfig, aiRateLimit, buildDyrectedSystemPrompt, buildInviteEmail, buildPasswordChangedEmail, buildResetPasswordEmail, buildWelcomeEmail, chunkText, cosineSimilarity, createDyrectedAITools, estimateTokens, extractTextFromDoc, formatAIErrorMessage, getAIModel, getAuthSession, getEmbeddingModel, getLockedUntilMs, hasEmbeddingApiKey, hashContent, hashPassword, isAuthSessionActive, issueAuthSessionToken, normalizeFieldValue, registerRoutes, resolveAuthLockoutConfig, revokeAllAuthSessions, revokeAuthSession, sendEmail, serializeDetailForApi, serializeViewForApi, touchAuthSession, validatePayloadAgainstFields, verifyPassword };
|
|
764
|
+
export { AIAgent, type AIAgentOptions, AIController, AUTH_SESSIONS_COLLECTION, AuthController, type AuthSessionRecord, type ChunkOptions, type ChunkResult, CollectionController, DEFAULT_LOCK_TIME_MS, DEFAULT_MAX_LOGIN_ATTEMPTS, type DeleteDocumentChunksOptions, DyrectedContext, EmbeddingService, GlobalController, InMemoryRateLimitStore, type IndexDocumentOptions, MediaController, PreviewController, type RAGSearchOptions, RAGService, type ReindexAllOptions, type ReindexCollectionOptions, type ResolvedAuthLockoutConfig, aiRateLimit, buildDyrectedSystemPrompt, buildInviteEmail, buildPasswordChangedEmail, buildResetPasswordEmail, buildWelcomeEmail, chunkText, cosineSimilarity, createDyrectedAITools, estimateTokens, extractTextFromDoc, formatAIErrorMessage, getAIModel, getAuthSession, getEmbeddingModel, getLockedUntilMs, hasEmbeddingApiKey, hashContent, hashPassword, isAuthSessionActive, issueAuthSessionToken, normalizeFieldValue, registerRoutes, resolveAuthLockoutConfig, revokeAllAuthSessions, revokeAuthSession, sendEmail, serializeActionsForApi, serializeDetailForApi, serializeViewForApi, touchAuthSession, validatePayloadAgainstFields, verifyPassword };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { O as DyrectedContext, D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig, e as DatabaseAdapter, b as AuthenticatedUser, bV as DyrectedAIContext, aw as AIThread, as as AIMessage, cT as RAGSearchResult, av as AIRateLimitStore, au as AIRateLimitResult } from './app-Dd9cBbCs.js';
|
|
2
|
+
export { dH as CollectionTokenPayload, dI as DEFAULT_SESSION_EXPIRY, dJ as DyrectedObservabilityRuntime, dK as RequestBodyCapture, dL as RequestTraceContext, dM as ResolvedObservabilityConfig, dN as attachRequestMetrics, dO as bindObservabilityRuntime, dP as buildRequestLogger, dQ as captureRequestBody, dR as createDyrectedApp, dS as createObservabilityRuntime, dT as createRequestSpan, dU as decodeCollectionToken, dV as endRequestSpan, dW as getConfigLogger, dX as getObservabilityRuntime, dY as getRequestLogger, dZ as redactBody, d_ as redactHeaders, d$ as renderPrometheusMetrics, e0 as resolveObservabilityConfig, e1 as resolveSessionTokenExpiry, e2 as shouldSampleRequest, e3 as signCollectionToken, e4 as verifyCollectionToken } from './app-Dd9cBbCs.js';
|
|
3
3
|
import * as hono from 'hono';
|
|
4
4
|
import { Hono, Context, Next } from 'hono';
|
|
5
5
|
import * as hono_utils_http_status from 'hono/utils/http-status';
|
|
@@ -14,6 +14,12 @@ import '@opentelemetry/api';
|
|
|
14
14
|
import '@opentelemetry/exporter-prometheus';
|
|
15
15
|
|
|
16
16
|
declare function serializeDetailForApi(detail?: any): any;
|
|
17
|
+
/**
|
|
18
|
+
* Serializes a list of operational actions (view-level or collection-level)
|
|
19
|
+
* for the Admin API. Function properties (self-hosted handlers) are stripped so
|
|
20
|
+
* the payload stays JSON-safe; access rules are resolved to static values.
|
|
21
|
+
*/
|
|
22
|
+
declare function serializeActionsForApi(actions: any[] | undefined, serializeAccess: (access: any) => Promise<any>): Promise<any[]>;
|
|
17
23
|
/**
|
|
18
24
|
* Serializes an operational view config for the Admin API.
|
|
19
25
|
* Function properties (action handlers) are stripped so the payload stays
|
|
@@ -153,7 +159,7 @@ declare class AuthController {
|
|
|
153
159
|
success: true;
|
|
154
160
|
message: string;
|
|
155
161
|
token: string;
|
|
156
|
-
inviteUrl: string
|
|
162
|
+
inviteUrl: string;
|
|
157
163
|
}, hono_utils_http_status.ContentfulStatusCode, "json">)>;
|
|
158
164
|
acceptInvite(c: Context<DyrectedContext>): Promise<(Response & hono.TypedResponse<{
|
|
159
165
|
message: string;
|
|
@@ -186,6 +192,16 @@ declare class CollectionController {
|
|
|
186
192
|
create(c: Context<DyrectedContext>): Promise<any>;
|
|
187
193
|
upload(c: Context<DyrectedContext>): Promise<any>;
|
|
188
194
|
update(c: Context<DyrectedContext>): Promise<any>;
|
|
195
|
+
/**
|
|
196
|
+
* Builds the callback that runs the collection's `beforeCommit` hooks inside
|
|
197
|
+
* the write transaction, or `undefined` when the collection has none.
|
|
198
|
+
*/
|
|
199
|
+
private buildBeforeCommit;
|
|
200
|
+
/**
|
|
201
|
+
* Runs a single write. When `beforeCommit` hooks exist the write and the
|
|
202
|
+
* hooks share one transaction, so a throwing hook rolls the write back.
|
|
203
|
+
*/
|
|
204
|
+
private persistInTransaction;
|
|
189
205
|
/**
|
|
190
206
|
* Core update pipeline shared by PATCH requests and operational actions:
|
|
191
207
|
* auth-field stripping, timestamps, access enforcement, field write access,
|
|
@@ -745,4 +761,4 @@ declare class InMemoryRateLimitStore implements AIRateLimitStore {
|
|
|
745
761
|
}
|
|
746
762
|
declare function aiRateLimit(config?: DyrectedConfig): (c: Context<DyrectedContext>, next: Next) => Promise<void>;
|
|
747
763
|
|
|
748
|
-
export { AIAgent, type AIAgentOptions, AIController, AUTH_SESSIONS_COLLECTION, AuthController, type AuthSessionRecord, type ChunkOptions, type ChunkResult, CollectionController, DEFAULT_LOCK_TIME_MS, DEFAULT_MAX_LOGIN_ATTEMPTS, type DeleteDocumentChunksOptions, DyrectedContext, EmbeddingService, GlobalController, InMemoryRateLimitStore, type IndexDocumentOptions, MediaController, PreviewController, type RAGSearchOptions, RAGService, type ReindexAllOptions, type ReindexCollectionOptions, type ResolvedAuthLockoutConfig, aiRateLimit, buildDyrectedSystemPrompt, buildInviteEmail, buildPasswordChangedEmail, buildResetPasswordEmail, buildWelcomeEmail, chunkText, cosineSimilarity, createDyrectedAITools, estimateTokens, extractTextFromDoc, formatAIErrorMessage, getAIModel, getAuthSession, getEmbeddingModel, getLockedUntilMs, hasEmbeddingApiKey, hashContent, hashPassword, isAuthSessionActive, issueAuthSessionToken, normalizeFieldValue, registerRoutes, resolveAuthLockoutConfig, revokeAllAuthSessions, revokeAuthSession, sendEmail, serializeDetailForApi, serializeViewForApi, touchAuthSession, validatePayloadAgainstFields, verifyPassword };
|
|
764
|
+
export { AIAgent, type AIAgentOptions, AIController, AUTH_SESSIONS_COLLECTION, AuthController, type AuthSessionRecord, type ChunkOptions, type ChunkResult, CollectionController, DEFAULT_LOCK_TIME_MS, DEFAULT_MAX_LOGIN_ATTEMPTS, type DeleteDocumentChunksOptions, DyrectedContext, EmbeddingService, GlobalController, InMemoryRateLimitStore, type IndexDocumentOptions, MediaController, PreviewController, type RAGSearchOptions, RAGService, type ReindexAllOptions, type ReindexCollectionOptions, type ResolvedAuthLockoutConfig, aiRateLimit, buildDyrectedSystemPrompt, buildInviteEmail, buildPasswordChangedEmail, buildResetPasswordEmail, buildWelcomeEmail, chunkText, cosineSimilarity, createDyrectedAITools, estimateTokens, extractTextFromDoc, formatAIErrorMessage, getAIModel, getAuthSession, getEmbeddingModel, getLockedUntilMs, hasEmbeddingApiKey, hashContent, hashPassword, isAuthSessionActive, issueAuthSessionToken, normalizeFieldValue, registerRoutes, resolveAuthLockoutConfig, revokeAllAuthSessions, revokeAuthSession, sendEmail, serializeActionsForApi, serializeDetailForApi, serializeViewForApi, touchAuthSession, validatePayloadAgainstFields, verifyPassword };
|