@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/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
- })).doc : await db.create({ collection: this.collection.slug, data });
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
- })).doc : await db.update({ collection: this.collection.slug, id, data });
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 resetUrl = body?.resetUrl;
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 inviteUrl = body?.inviteUrl;
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 Promise.all(
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 { M as DyrectedContext, D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig, bw as DatabaseAdapter, b as AuthenticatedUser, bR as DyrectedAIContext, as as AIThread, ao as AIMessage, cN as RAGSearchResult, ar as AIRateLimitStore, aq as AIRateLimitResult } from './app-CLdT_0se.cjs';
2
- export { dv as CollectionTokenPayload, dw as DEFAULT_SESSION_EXPIRY, dx as DyrectedObservabilityRuntime, dy as RequestBodyCapture, dz as RequestTraceContext, dA as ResolvedObservabilityConfig, dB as attachRequestMetrics, dC as bindObservabilityRuntime, dD as buildRequestLogger, dE as captureRequestBody, dF as createDyrectedApp, dG as createObservabilityRuntime, dH as createRequestSpan, dI as decodeCollectionToken, dJ as endRequestSpan, dK as getConfigLogger, dL as getObservabilityRuntime, dM as getRequestLogger, dN as redactBody, dO as redactHeaders, dP as renderPrometheusMetrics, dQ as resolveObservabilityConfig, dR as resolveSessionTokenExpiry, dS as shouldSampleRequest, dT as signCollectionToken, dU as verifyCollectionToken } from './app-CLdT_0se.cjs';
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 | undefined;
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 { M as DyrectedContext, D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig, bw as DatabaseAdapter, b as AuthenticatedUser, bR as DyrectedAIContext, as as AIThread, ao as AIMessage, cN as RAGSearchResult, ar as AIRateLimitStore, aq as AIRateLimitResult } from './app-CLdT_0se.js';
2
- export { dv as CollectionTokenPayload, dw as DEFAULT_SESSION_EXPIRY, dx as DyrectedObservabilityRuntime, dy as RequestBodyCapture, dz as RequestTraceContext, dA as ResolvedObservabilityConfig, dB as attachRequestMetrics, dC as bindObservabilityRuntime, dD as buildRequestLogger, dE as captureRequestBody, dF as createDyrectedApp, dG as createObservabilityRuntime, dH as createRequestSpan, dI as decodeCollectionToken, dJ as endRequestSpan, dK as getConfigLogger, dL as getObservabilityRuntime, dM as getRequestLogger, dN as redactBody, dO as redactHeaders, dP as renderPrometheusMetrics, dQ as resolveObservabilityConfig, dR as resolveSessionTokenExpiry, dS as shouldSampleRequest, dT as signCollectionToken, dU as verifyCollectionToken } from './app-CLdT_0se.js';
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 | undefined;
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 };