agent-cms 0.1.0 → 0.3.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.
@@ -1,4 +1,4 @@
1
- import { A as isSearchable, B as encodeJson, C as findUniqueConstraintViolations, D as getLinksTargets, E as getLinkTargets, F as parseMediaGalleryReferences, G as ReferenceConflictError, H as getFieldTypeDef, I as decodeJsonIfString, J as ValidationError, L as decodeJsonRecordStringOr, M as supportsUniqueValidation, O as getSlugSource, P as parseMediaFieldReference, R as decodeJsonString, S as computeIsValid, T as getBlocksOnly, U as DuplicateError, W as NotFoundError, a as materializeStructuredTextValue, b as isContentRow, c as FIELD_TYPES, i as materializeRecordStructuredTextFields, j as isUnique, k as isRequired, l as isFieldType, n as deleteBlocksForField, q as UnauthorizedError, r as getStructuredTextStorageKey, s as writeStructuredText, t as deleteBlockSubtrees, v as pruneBlockNodes, w as getBlockWhitelist, x as parseFieldValidators, y as StructuredTextWriteInput } from "./structured-text-service-B4xSlUg_.mjs";
1
+ import { A as isSearchable, B as encodeJson, C as findUniqueConstraintViolations, D as getLinksTargets, E as getLinkTargets, F as parseMediaGalleryReferences, G as ReferenceConflictError, H as getFieldTypeDef, I as decodeJsonIfString, J as ValidationError, L as decodeJsonRecordStringOr, M as supportsUniqueValidation, O as getSlugSource, P as parseMediaFieldReference, R as decodeJsonString, S as computeIsValid, T as getBlocksOnly, U as DuplicateError, W as NotFoundError, a as materializeStructuredTextValue, b as isContentRow, c as FIELD_TYPES, i as materializeRecordStructuredTextFields, j as isUnique, k as isRequired, l as isFieldType, m as expandStructuredTextShorthand, n as deleteBlocksForField, q as UnauthorizedError, r as getStructuredTextStorageKey, s as writeStructuredText, t as deleteBlockSubtrees, v as pruneBlockNodes, w as getBlockWhitelist, x as parseFieldValidators, y as StructuredTextWriteInput } from "./structured-text-service-BJkqWRkq.mjs";
2
2
  import { Context, Data, Effect, Option, Schema } from "effect";
3
3
  import { SqlClient } from "@effect/sql";
4
4
  import { customAlphabet } from "nanoid";
@@ -746,6 +746,21 @@ function getModel(id) {
746
746
  };
747
747
  });
748
748
  }
749
+ function getModelByApiKey$1(apiKey) {
750
+ return Effect.gen(function* () {
751
+ const sql = yield* SqlClient.SqlClient;
752
+ const models = yield* sql.unsafe("SELECT * FROM models WHERE api_key = ?", [apiKey]);
753
+ if (models.length === 0) return yield* new NotFoundError({
754
+ entity: "Model",
755
+ id: apiKey
756
+ });
757
+ const fields = yield* sql.unsafe("SELECT * FROM fields WHERE model_id = ? ORDER BY position", [models[0].id]);
758
+ return {
759
+ ...models[0],
760
+ fields: fields.map(parseFieldValidators)
761
+ };
762
+ });
763
+ }
749
764
  function createModel(body) {
750
765
  return Effect.gen(function* () {
751
766
  const sql = yield* SqlClient.SqlClient;
@@ -753,8 +768,8 @@ function createModel(body) {
753
768
  if ((yield* sql.unsafe("SELECT id FROM models WHERE api_key = ?", [body.apiKey])).length > 0) return yield* new DuplicateError({ message: `Model with apiKey '${body.apiKey}' already exists` });
754
769
  const now = (/* @__PURE__ */ new Date()).toISOString();
755
770
  const id = generateId();
756
- yield* sql.unsafe(`INSERT INTO models (id, name, api_key, is_block, singleton, sortable, tree, has_draft, all_locales_required, ordering, created_at, updated_at)
757
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
771
+ yield* sql.unsafe(`INSERT INTO models (id, name, api_key, is_block, singleton, sortable, tree, has_draft, all_locales_required, ordering, canonical_path_template, created_at, updated_at)
772
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
758
773
  id,
759
774
  body.name,
760
775
  body.apiKey,
@@ -765,6 +780,7 @@ function createModel(body) {
765
780
  body.hasDraft ? 1 : 0,
766
781
  body.allLocalesRequired ? 1 : 0,
767
782
  body.ordering ?? null,
783
+ body.canonicalPathTemplate ?? null,
768
784
  now,
769
785
  now
770
786
  ]);
@@ -784,6 +800,7 @@ function createModel(body) {
784
800
  hasDraft: body.hasDraft,
785
801
  allLocalesRequired: body.allLocalesRequired,
786
802
  ordering: body.ordering ?? null,
803
+ canonicalPathTemplate: body.canonicalPathTemplate ?? null,
787
804
  createdAt: now,
788
805
  updatedAt: now
789
806
  };
@@ -825,6 +842,10 @@ function updateModel(id, body) {
825
842
  sets.push("ordering = ?");
826
843
  values.push(body.ordering);
827
844
  }
845
+ if (body.canonicalPathTemplate !== void 0) {
846
+ sets.push("canonical_path_template = ?");
847
+ values.push(body.canonicalPathTemplate);
848
+ }
828
849
  if (body.apiKey !== void 0 && body.apiKey !== model.api_key) {
829
850
  const newApiKey = body.apiKey;
830
851
  if (!/^[a-z][a-z0-9_]*$/.test(newApiKey)) return yield* new ValidationError({ message: "apiKey must start with a lowercase letter and contain only lowercase letters, numbers, and underscores" });
@@ -1794,7 +1815,8 @@ function processCreateLikeRecordFields({ modelApiKey, tableName, recordId, data,
1794
1815
  localizedDast[localeCode] = null;
1795
1816
  continue;
1796
1817
  }
1797
- const stInput = yield* Schema.decodeUnknown(StructuredTextWriteInput)(scopeStructuredTextIds(localeValue, `${field.api_key}:${localeCode}`)).pipe(Effect.mapError((e) => new ValidationError({
1818
+ const expandedLocale = expandStructuredTextShorthand(localeValue);
1819
+ const stInput = yield* Schema.decodeUnknown(StructuredTextWriteInput)(scopeStructuredTextIds(expandedLocale, `${field.api_key}:${localeCode}`)).pipe(Effect.mapError((e) => new ValidationError({
1798
1820
  message: createFieldErrorMessage(errorPrefix, `Invalid StructuredText for field '${field.api_key}' locale '${localeCode}': ${e.message}`),
1799
1821
  field: field.api_key
1800
1822
  })));
@@ -1815,7 +1837,8 @@ function processCreateLikeRecordFields({ modelApiKey, tableName, recordId, data,
1815
1837
  record[field.api_key] = localizedDast;
1816
1838
  continue;
1817
1839
  }
1818
- const stInput = yield* Schema.decodeUnknown(StructuredTextWriteInput)(data[field.api_key]).pipe(Effect.mapError((e) => new ValidationError({
1840
+ const expanded = expandStructuredTextShorthand(data[field.api_key]);
1841
+ const stInput = yield* Schema.decodeUnknown(StructuredTextWriteInput)(expanded).pipe(Effect.mapError((e) => new ValidationError({
1819
1842
  message: createFieldErrorMessage(errorPrefix, `Invalid StructuredText for field '${field.api_key}': ${e.message}`),
1820
1843
  field: field.api_key
1821
1844
  })));
@@ -2022,7 +2045,12 @@ function getRecord(modelApiKey, id) {
2022
2045
  entity: "Record",
2023
2046
  id
2024
2047
  });
2025
- return normalizeBooleanFields(record, yield* getModelFields(model.id));
2048
+ const fields = yield* getModelFields(model.id);
2049
+ return yield* materializeRecordStructuredTextFields({
2050
+ modelApiKey: model.api_key,
2051
+ record: normalizeBooleanFields(record, fields),
2052
+ fields
2053
+ });
2026
2054
  });
2027
2055
  }
2028
2056
  function updateSingletonRecord(modelApiKey, data, actor) {
@@ -2106,7 +2134,8 @@ function patchRecord(id, body, actor) {
2106
2134
  nextLocaleMap[localeCode] = null;
2107
2135
  continue;
2108
2136
  }
2109
- const stInput = yield* Schema.decodeUnknown(StructuredTextWriteInput)(scopeStructuredTextIds(localeValue, `${field.api_key}:${localeCode}`)).pipe(Effect.mapError((e) => new ValidationError({
2137
+ const expandedLocale = expandStructuredTextShorthand(localeValue);
2138
+ const stInput = yield* Schema.decodeUnknown(StructuredTextWriteInput)(scopeStructuredTextIds(expandedLocale, `${field.api_key}:${localeCode}`)).pipe(Effect.mapError((e) => new ValidationError({
2110
2139
  message: `Invalid StructuredText for field '${field.api_key}' locale '${localeCode}': ${e.message}`,
2111
2140
  field: field.api_key
2112
2141
  })));
@@ -2127,7 +2156,8 @@ function patchRecord(id, body, actor) {
2127
2156
  updates[field.api_key] = nextLocaleMap;
2128
2157
  continue;
2129
2158
  }
2130
- const stInput = yield* Schema.decodeUnknown(StructuredTextWriteInput)(data[field.api_key]).pipe(Effect.mapError((e) => new ValidationError({
2159
+ const expanded = expandStructuredTextShorthand(data[field.api_key]);
2160
+ const stInput = yield* Schema.decodeUnknown(StructuredTextWriteInput)(expanded).pipe(Effect.mapError((e) => new ValidationError({
2131
2161
  message: `Invalid StructuredText for field '${field.api_key}': ${e.message}`,
2132
2162
  field: field.api_key
2133
2163
  })));
@@ -2484,23 +2514,11 @@ function patchBlocksForField(body, actor) {
2484
2514
  message: `Block '${blockId}' does not exist in field '${body.fieldApiKey}'.`,
2485
2515
  field: body.fieldApiKey
2486
2516
  });
2487
- } else if (typeof patchValue === "string") {
2488
- if (!Object.hasOwn(existingBlocks, blockId)) {
2489
- let nestedMatched = false;
2490
- for (const topLevelBlock of Object.values(mergedBlocks)) {
2491
- const result = applyPatchToNestedStructuredText(topLevelBlock, blockId, patchValue);
2492
- if (result.ambiguous) return yield* new ValidationError({
2493
- message: `Block '${blockId}' matched multiple nested structured_text locations in field '${body.fieldApiKey}'. Patch the parent block explicitly instead.`,
2494
- field: body.fieldApiKey
2495
- });
2496
- nestedMatched = nestedMatched || result.applied;
2497
- }
2498
- if (!nestedMatched) return yield* new ValidationError({
2499
- message: `Block '${blockId}' does not exist in field '${body.fieldApiKey}'.`,
2500
- field: body.fieldApiKey
2501
- });
2502
- }
2503
- } else if (typeof patchValue === "object" && !Array.isArray(patchValue)) {
2517
+ } else if (typeof patchValue === "string") return yield* new ValidationError({
2518
+ message: `Invalid patch value for block '${blockId}': use an object to update fields, null to delete, or omit the key to keep unchanged.`,
2519
+ field: body.fieldApiKey
2520
+ });
2521
+ else if (typeof patchValue === "object" && !Array.isArray(patchValue)) {
2504
2522
  if (!Object.hasOwn(existingBlocks, blockId)) {
2505
2523
  let nestedMatched = false;
2506
2524
  for (const topLevelBlock of Object.values(mergedBlocks)) {
@@ -2530,11 +2548,53 @@ function patchBlocksForField(body, actor) {
2530
2548
  message: `Invalid patch value for block '${blockId}': expected string, object, or null`,
2531
2549
  field: body.fieldApiKey
2532
2550
  });
2551
+ const appendedIds = [];
2552
+ for (const entry of body.append ?? []) {
2553
+ const id = generateId();
2554
+ appendedIds.push(id);
2555
+ mergedBlocks[id] = entry;
2556
+ }
2533
2557
  let finalDastValue;
2534
- if (body.value !== void 0) finalDastValue = body.value;
2535
- else if (blockIdsToDelete.size > 0) {
2558
+ if (body.order) {
2559
+ if (!getBlocksOnly(field.validators)) return yield* new ValidationError({
2560
+ message: "The 'order' parameter is only supported on blocks_only structured_text fields. Use the 'value' parameter to provide a custom DAST for mixed prose+block fields.",
2561
+ field: body.fieldApiKey
2562
+ });
2563
+ if (body.value !== void 0) return yield* new ValidationError({
2564
+ message: "Cannot use both 'order' and 'value' — they both control the DAST document structure.",
2565
+ field: body.fieldApiKey
2566
+ });
2567
+ finalDastValue = {
2568
+ schema: "dast",
2569
+ document: {
2570
+ type: "root",
2571
+ children: body.order.map((id) => ({
2572
+ type: "block",
2573
+ item: id
2574
+ }))
2575
+ }
2576
+ };
2577
+ } else if (body.value !== void 0 && appendedIds.length > 0) return yield* new ValidationError({
2578
+ message: "Cannot use both 'value' and 'append' — appended blocks need auto-generated DAST nodes which conflict with a custom DAST value.",
2579
+ field: body.fieldApiKey
2580
+ });
2581
+ else if (body.value !== void 0) finalDastValue = body.value;
2582
+ else if (blockIdsToDelete.size > 0 || appendedIds.length > 0) {
2536
2583
  const existingDast = existingEnvelope.value;
2537
- finalDastValue = pruneBlockNodes(existingDast, blockIdsToDelete);
2584
+ const pruned = blockIdsToDelete.size > 0 ? pruneBlockNodes(existingDast, blockIdsToDelete) : existingDast;
2585
+ if (appendedIds.length > 0) {
2586
+ const prunedDoc = pruned;
2587
+ finalDastValue = {
2588
+ ...prunedDoc,
2589
+ document: {
2590
+ ...prunedDoc.document,
2591
+ children: [...prunedDoc.document.children, ...appendedIds.map((id) => ({
2592
+ type: "block",
2593
+ item: id
2594
+ }))]
2595
+ }
2596
+ };
2597
+ } else finalDastValue = pruned;
2538
2598
  } else finalDastValue = existingEnvelope.value;
2539
2599
  yield* deleteBlocksForField({
2540
2600
  rootRecordId: body.recordId,
@@ -2587,11 +2647,16 @@ function patchBlocksForField(body, actor) {
2587
2647
  });
2588
2648
  const updatedRecord = yield* selectById(tableName, body.recordId);
2589
2649
  if (!updatedRecord) return null;
2590
- return yield* materializeRecordStructuredTextFields({
2650
+ const materialized = yield* materializeRecordStructuredTextFields({
2591
2651
  modelApiKey: model.api_key,
2592
2652
  record: normalizeBooleanFields(updatedRecord, modelFields),
2593
2653
  fields: modelFields
2594
2654
  });
2655
+ if (materialized && appendedIds.length > 0) return {
2656
+ ...materialized,
2657
+ _appendedIds: appendedIds
2658
+ };
2659
+ return materialized;
2595
2660
  });
2596
2661
  }
2597
2662
  /**
@@ -3419,7 +3484,8 @@ const CreateModelInput = Schema.Struct({
3419
3484
  tree: Schema.optionalWith(Schema.Boolean, { default: () => false }),
3420
3485
  hasDraft: Schema.optionalWith(Schema.Boolean, { default: () => true }),
3421
3486
  allLocalesRequired: Schema.optionalWith(Schema.Boolean, { default: () => false }),
3422
- ordering: Schema.optional(Schema.String)
3487
+ ordering: Schema.optional(Schema.String),
3488
+ canonicalPathTemplate: Schema.optional(Schema.NullOr(Schema.String))
3423
3489
  });
3424
3490
  const CreateFieldInput = Schema.Struct({
3425
3491
  label: Schema.NonEmptyString,
@@ -3530,7 +3596,8 @@ const UpdateModelInput = Schema.Struct({
3530
3596
  sortable: Schema.optional(Schema.Boolean),
3531
3597
  hasDraft: Schema.optional(Schema.Boolean),
3532
3598
  allLocalesRequired: Schema.optional(Schema.Boolean),
3533
- ordering: Schema.optional(Schema.NullOr(Schema.String))
3599
+ ordering: Schema.optional(Schema.NullOr(Schema.String)),
3600
+ canonicalPathTemplate: Schema.optional(Schema.NullOr(Schema.String))
3534
3601
  });
3535
3602
  const UpdateFieldInput = Schema.Struct({
3536
3603
  label: Schema.optional(Schema.NonEmptyString),
@@ -3573,6 +3640,7 @@ const SchemaExportModelSchema = Schema.Struct({
3573
3640
  tree: Schema.Boolean,
3574
3641
  hasDraft: Schema.Boolean,
3575
3642
  ordering: Schema.optionalWith(Schema.NullOr(Schema.String), { default: () => null }),
3643
+ canonicalPathTemplate: Schema.optionalWith(Schema.NullOr(Schema.String), { default: () => null }),
3576
3644
  fields: Schema.Array(SchemaExportFieldSchema)
3577
3645
  });
3578
3646
  const SchemaExportLocaleSchema = Schema.Struct({
@@ -3585,10 +3653,15 @@ const PatchBlocksInput = Schema.Struct({
3585
3653
  modelApiKey: Schema.NonEmptyString,
3586
3654
  fieldApiKey: Schema.NonEmptyString,
3587
3655
  value: Schema.optional(Schema.Unknown),
3656
+ order: Schema.optional(Schema.Array(Schema.NonEmptyString)),
3588
3657
  blocks: Schema.Record({
3589
3658
  key: Schema.String,
3590
3659
  value: Schema.NullOr(Schema.Unknown)
3591
- })
3660
+ }),
3661
+ append: Schema.optional(Schema.Array(Schema.Record({
3662
+ key: Schema.String,
3663
+ value: Schema.Unknown
3664
+ })))
3592
3665
  });
3593
3666
  const ImportSchemaInput = Schema.Struct({
3594
3667
  version: Schema.Literal(1),
@@ -3642,6 +3715,7 @@ function exportSchema() {
3642
3715
  tree: !!m.tree,
3643
3716
  hasDraft: !!m.has_draft,
3644
3717
  ordering: m.ordering,
3718
+ canonicalPathTemplate: m.canonical_path_template ?? null,
3645
3719
  fields: (fieldsByModelId.get(m.id) ?? []).map((f) => ({
3646
3720
  label: f.label,
3647
3721
  apiKey: f.api_key,
@@ -3693,7 +3767,8 @@ function importSchema(s) {
3693
3767
  tree: model.tree,
3694
3768
  hasDraft: model.hasDraft,
3695
3769
  allLocalesRequired: false,
3696
- ordering: model.ordering ?? void 0
3770
+ ordering: model.ordering ?? void 0,
3771
+ canonicalPathTemplate: model.canonicalPathTemplate ?? void 0
3697
3772
  });
3698
3773
  modelApiKeyToId.set(model.apiKey, result.id);
3699
3774
  stats.models++;
@@ -3733,7 +3808,7 @@ function getTokenPrefix(token) {
3733
3808
  function isLegacyStoredToken(row) {
3734
3809
  return row.secret_hash === null;
3735
3810
  }
3736
- function hashToken(token) {
3811
+ function hashToken$1(token) {
3737
3812
  return Effect.tryPromise({
3738
3813
  try: async () => {
3739
3814
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token));
@@ -3752,7 +3827,7 @@ function createEditorToken(input) {
3752
3827
  if (expiresIn !== void 0 && expiresIn <= 0) return yield* new ValidationError({ message: "expiresIn must be a positive integer" });
3753
3828
  const expiresAt = expiresIn !== void 0 ? new Date(Date.now() + expiresIn * 1e3).toISOString() : null;
3754
3829
  const tokenPrefix = getTokenPrefix(token);
3755
- const secretHash = yield* hashToken(token);
3830
+ const secretHash = yield* hashToken$1(token);
3756
3831
  yield* sql.unsafe(`INSERT INTO editor_tokens (id, name, token_prefix, secret_hash, created_at, expires_at) VALUES (?, ?, ?, ?, ?, ?)`, [
3757
3832
  id,
3758
3833
  input.name,
@@ -3795,7 +3870,7 @@ function revokeEditorToken(id) {
3795
3870
  function validateEditorToken(token) {
3796
3871
  return Effect.gen(function* () {
3797
3872
  const sql = yield* SqlClient.SqlClient;
3798
- const secretHash = yield* hashToken(token);
3873
+ const secretHash = yield* hashToken$1(token);
3799
3874
  const hashedRows = yield* sql.unsafe("SELECT * FROM editor_tokens WHERE secret_hash = ?", [secretHash]);
3800
3875
  const legacyRows = hashedRows.length === 0 ? yield* sql.unsafe("SELECT * FROM editor_tokens WHERE id = ?", [token]) : [];
3801
3876
  if (hashedRows.length === 0 && legacyRows.length === 0) return yield* new UnauthorizedError({ message: "Invalid editor token" });
@@ -3815,6 +3890,70 @@ function validateEditorToken(token) {
3815
3890
  });
3816
3891
  }
3817
3892
  //#endregion
3818
- export { listRecords as $, scheduleUnpublish as A, getAsset as B, SearchInput as C, clearSchedule as D, UpdateModelInput as E, removeBlockType as F, updateAssetMetadata as G, listAssets as H, removeLocale as I, publishRecord as J, bulkPublishRecords as K, AssetImportContext as L, deleteLocale as M, listLocales as N, runScheduledTransitions as O, removeBlockFromWhitelist as P, getRecord as Q, createAsset as R, SearchAssetsInput as S, UpdateFieldInput as T, replaceAsset as U, importAssetFromUrl as V, searchAssets as W, bulkCreateRecords as X, unpublishRecord as Y, createRecord as Z, PatchBlocksInput as _, updateModel as _t, exportSchema as a, getVersion as at, ReorderInput as b, VectorizeContext as bt, CreateAssetInput as c, HooksContext as ct, CreateLocaleInput as d, listFields as dt, patchBlocksForField as et, CreateModelInput as f, updateField as ft, ImportSchemaInput as g, listModels as gt, ImportAssetFromUrlInput as h, getModel as ht, validateEditorToken as i, updateSingletonRecord as it, createLocale as j, schedulePublish as k, CreateEditorTokenInput as l, createField as lt, CreateUploadUrlInput as m, deleteModel as mt, listEditorTokens as n, removeRecord as nt, importSchema as o, listVersions as ot, CreateRecordInput as p, createModel as pt, bulkUnpublishRecords as q, revokeEditorToken as r, reorderRecords as rt, BulkCreateRecordsInput as s, restoreVersion as st, createEditorToken as t, patchRecord as tt, CreateFieldInput as u, deleteField as ut, PatchRecordInput as v, reindexAll as vt, UpdateAssetMetadataInput as w, ScheduleRecordInput as x, ReindexSearchInput as y, search as yt, deleteAsset as z };
3893
+ //#region src/services/preview-service.ts
3894
+ /**
3895
+ * Preview token service — short-lived tokens for draft preview access.
3896
+ * Follows the same SHA-256 hashing pattern as token-service.ts.
3897
+ */
3898
+ function hashToken(token) {
3899
+ return Effect.tryPromise({
3900
+ try: async () => {
3901
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token));
3902
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
3903
+ },
3904
+ catch: (cause) => new ValidationError({ message: `Failed to hash preview token: ${cause instanceof Error ? cause.message : String(cause)}` })
3905
+ });
3906
+ }
3907
+ function generatePreviewToken() {
3908
+ const bytes = new Uint8Array(32);
3909
+ crypto.getRandomValues(bytes);
3910
+ return `pvt_${btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "")}`;
3911
+ }
3912
+ const DEFAULT_EXPIRY_SECONDS = 1440 * 60;
3913
+ function createPreviewToken(expiresIn) {
3914
+ return Effect.gen(function* () {
3915
+ const sql = yield* SqlClient.SqlClient;
3916
+ const seconds = expiresIn ?? DEFAULT_EXPIRY_SECONDS;
3917
+ if (seconds <= 0) return yield* new ValidationError({ message: "expiresIn must be a positive number" });
3918
+ const id = generateId();
3919
+ const token = generatePreviewToken();
3920
+ const tokenHash = yield* hashToken(token);
3921
+ const expiresAt = new Date(Date.now() + seconds * 1e3).toISOString();
3922
+ yield* sql.unsafe(`INSERT INTO preview_tokens (id, token_hash, expires_at) VALUES (?, ?, ?)`, [
3923
+ id,
3924
+ tokenHash,
3925
+ expiresAt
3926
+ ]);
3927
+ return {
3928
+ id,
3929
+ token,
3930
+ expiresAt
3931
+ };
3932
+ });
3933
+ }
3934
+ function validatePreviewToken(token) {
3935
+ return Effect.gen(function* () {
3936
+ const sql = yield* SqlClient.SqlClient;
3937
+ const tokenHash = yield* hashToken(token);
3938
+ const rows = yield* sql.unsafe("SELECT id, expires_at FROM preview_tokens WHERE token_hash = ?", [tokenHash]);
3939
+ if (rows.length === 0) return { valid: false };
3940
+ const row = rows[0];
3941
+ if (new Date(row.expires_at) < /* @__PURE__ */ new Date()) return { valid: false };
3942
+ yield* Effect.fork(sql.unsafe(`DELETE FROM preview_tokens WHERE id IN (SELECT id FROM preview_tokens WHERE expires_at < datetime('now') LIMIT 100)`).pipe(Effect.ignore));
3943
+ return {
3944
+ valid: true,
3945
+ expiresAt: row.expires_at
3946
+ };
3947
+ });
3948
+ }
3949
+ function resolvePreviewPath(canonicalPathTemplate, recordData) {
3950
+ return canonicalPathTemplate.replace(/\{([^}]+)\}/g, (_match, fieldName) => {
3951
+ const value = recordData[fieldName];
3952
+ if (value === void 0 || value === null) return "";
3953
+ return encodeURIComponent(String(value));
3954
+ });
3955
+ }
3956
+ //#endregion
3957
+ export { bulkCreateRecords as $, clearSchedule as A, AssetImportContext as B, ReorderInput as C, search as Ct, UpdateAssetMetadataInput as D, SearchInput as E, deleteLocale as F, listAssets as G, deleteAsset as H, listLocales as I, updateAssetMetadata as J, replaceAsset as K, removeBlockFromWhitelist as L, schedulePublish as M, scheduleUnpublish as N, UpdateFieldInput as O, createLocale as P, unpublishRecord as Q, removeBlockType as R, ReindexSearchInput as S, reindexAll as St, SearchAssetsInput as T, getAsset as U, createAsset as V, importAssetFromUrl as W, bulkUnpublishRecords as X, bulkPublishRecords as Y, publishRecord as Z, CreateUploadUrlInput as _, deleteModel as _t, listEditorTokens as a, removeRecord as at, PatchBlocksInput as b, listModels as bt, exportSchema as c, getVersion as ct, CreateAssetInput as d, HooksContext as dt, createRecord as et, CreateEditorTokenInput as f, createField as ft, CreateRecordInput as g, createModel as gt, CreateModelInput as h, updateField as ht, createEditorToken as i, patchRecord as it, runScheduledTransitions as j, UpdateModelInput as k, importSchema as l, listVersions as lt, CreateLocaleInput as m, listFields as mt, resolvePreviewPath as n, listRecords as nt, revokeEditorToken as o, reorderRecords as ot, CreateFieldInput as p, deleteField as pt, searchAssets as q, validatePreviewToken as r, patchBlocksForField as rt, validateEditorToken as s, updateSingletonRecord as st, createPreviewToken as t, getRecord as tt, BulkCreateRecordsInput as u, restoreVersion as ut, ImportAssetFromUrlInput as v, getModel as vt, ScheduleRecordInput as w, VectorizeContext as wt, PatchRecordInput as x, updateModel as xt, ImportSchemaInput as y, getModelByApiKey$1 as yt, removeLocale as z };
3819
3958
 
3820
- //# sourceMappingURL=token-service-BDjccMmz.mjs.map
3959
+ //# sourceMappingURL=preview-service-C9Tmhdye.mjs.map