dineway 0.1.4 → 0.1.5
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/README.md +6 -3
- package/dist/{apply-CAPvMfoU.mjs → apply-iVSqz2qs.mjs} +132 -39
- package/dist/astro/index.d.mts +18 -9
- package/dist/astro/index.mjs +238 -16
- package/dist/astro/middleware/auth.d.mts +16 -5
- package/dist/astro/middleware/auth.mjs +74 -37
- package/dist/astro/middleware/redirect.mjs +24 -8
- package/dist/astro/middleware/request-context.mjs +18 -5
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.mjs +411 -169
- package/dist/astro/types.d.mts +25 -8
- package/dist/{byline-DeWCMU_i.mjs → byline-OhH2dlRu.mjs} +6 -21
- package/dist/{bylines-DyqBV9EQ.mjs → bylines-BGpD9_hy.mjs} +16 -6
- package/dist/cache-BdSY-gQN.mjs +42 -0
- package/dist/chunks--4F8ddV4.mjs +18 -0
- package/dist/cli/index.mjs +935 -15
- package/dist/client/external-auth-headers.d.mts +1 -1
- package/dist/client/index.d.mts +11 -3
- package/dist/client/index.mjs +4 -3
- package/dist/{connection-C9pxzuag.mjs → connection-BCNICDWN.mjs} +22 -5
- package/dist/{content-zSgdNmnt.mjs → content-DWi4d0rT.mjs} +41 -2
- package/dist/database/instrumentation.d.mts +34 -0
- package/dist/database/instrumentation.mjs +53 -0
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +2 -2
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/libsql.mjs +11 -5
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db/sqlite.mjs +7 -1
- package/dist/db-errors-CEqD7qH9.mjs +23 -0
- package/dist/{default-WYlzADZL.mjs → default-VjJyuuG9.mjs} +2 -0
- package/dist/{dialect-helpers-B9uSp2GJ.mjs → dialect-helpers-DhTzaUxP.mjs} +3 -0
- package/dist/{error-DrxtnGPg.mjs → error-BmL6QipT.mjs} +7 -3
- package/dist/{index-C-jx21qs.d.mts → index-yvc6E_17.d.mts} +157 -30
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +24 -22
- package/dist/{loader-qKmo0wAY.mjs → loader-sMG4TZ-u.mjs} +9 -3
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +7 -7
- package/dist/page/index.d.mts +10 -2
- package/dist/page/index.mjs +22 -1
- package/dist/patterns-CrCYkMBb.mjs +92 -0
- package/dist/{placeholder-bOx1xCTY.d.mts → placeholder--wOi4TbO.d.mts} +1 -1
- package/dist/{placeholder-B3knXwNc.mjs → placeholder-Cp8g5Emj.mjs} +1 -1
- package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
- package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
- package/dist/{query-BiaPl_g2.mjs → query-kDmwCsHh.mjs} +118 -50
- package/dist/{redirect-JPqLAbxa.mjs → redirect-DnEWAkVg.mjs} +43 -99
- package/dist/{registry-DSd1GWB8.mjs → registry-C0zjeB9P.mjs} +191 -123
- package/dist/request-cache-Dk5qPSOx.mjs +66 -0
- package/dist/request-context.d.mts +4 -16
- package/dist/{runner-B5l1JfOj.d.mts → runner-CFI6B6J2.d.mts} +1 -1
- package/dist/{runner-BGUGywgG.mjs → runner-DWZm2KQm.mjs} +589 -137
- package/dist/runtime.d.mts +6 -6
- package/dist/runtime.mjs +2 -2
- package/dist/{search-BNruJHDL.mjs → search-ByRGV2pq.mjs} +570 -424
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +11 -10
- package/dist/seo/index.d.mts +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +11 -3
- package/dist/storage/s3.mjs +78 -15
- package/dist/taxonomies-1s5PaS_8.mjs +266 -0
- package/dist/transaction-Cn2rjY78.mjs +27 -0
- package/dist/{types-BgQeVaPj.d.mts → types-BuMDPy5C.d.mts} +52 -3
- package/dist/{types-DuNbGKjF.mjs → types-COeOq9nK.mjs} +6 -1
- package/dist/{types-ju-_ORz7.d.mts → types-CWbdtiux.d.mts} +13 -5
- package/dist/{types-D38djUXv.d.mts → types-Cj0KMIZV.d.mts} +16 -3
- package/dist/{types-DkvMXalq.d.mts → types-DOrVigru.d.mts} +159 -0
- package/dist/{validate-CXnRKfJK.mjs → validate-BZ5wnLLp.mjs} +2 -1
- package/dist/{validate-DVKJJ-M_.d.mts → validate-IPf8n4Fj.d.mts} +4 -51
- package/dist/{validate-CqRJb_xU.mjs → validate-VPnKoIzW.mjs} +10 -10
- package/dist/version-BKXPsfmJ.mjs +6 -0
- package/package.json +49 -38
- package/src/astro/routes/admin.astro +25 -9
- package/src/astro/routes/api/admin/api-tokens/[id].ts +4 -0
- package/src/astro/routes/api/admin/api-tokens/index.ts +24 -2
- package/src/astro/routes/api/admin/briefing.ts +76 -0
- package/src/astro/routes/api/admin/bylines/[id]/index.ts +3 -0
- package/src/astro/routes/api/admin/bylines/index.ts +2 -0
- package/src/astro/routes/api/admin/context/[id]/history.ts +35 -0
- package/src/astro/routes/api/admin/context/[id]/index.ts +35 -0
- package/src/astro/routes/api/admin/context/[id]/review.ts +57 -0
- package/src/astro/routes/api/admin/context/[id]/supersede.ts +58 -0
- package/src/astro/routes/api/admin/context/diff.ts +35 -0
- package/src/astro/routes/api/admin/context/index.ts +69 -0
- package/src/astro/routes/api/admin/context/stale.ts +35 -0
- package/src/astro/routes/api/admin/hitl-requests/[id]/index.ts +38 -0
- package/src/astro/routes/api/admin/hitl-requests/[id]/resolve.ts +54 -0
- package/src/astro/routes/api/admin/hitl-requests/index.ts +38 -0
- package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +58 -17
- package/src/astro/routes/api/admin/oauth-clients/[id].ts +28 -1
- package/src/astro/routes/api/admin/oauth-clients/index.ts +25 -1
- package/src/astro/routes/api/admin/plugins/[id]/disable.ts +54 -2
- package/src/astro/routes/api/admin/plugins/[id]/enable.ts +54 -2
- package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +51 -1
- package/src/astro/routes/api/admin/plugins/[id]/update.ts +98 -3
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +72 -1
- package/src/astro/routes/api/admin/review-requests/[id]/index.ts +35 -0
- package/src/astro/routes/api/admin/review-requests/[id]/resolve.ts +52 -0
- package/src/astro/routes/api/admin/review-requests/index.ts +35 -0
- package/src/astro/routes/api/admin/users/[id]/disable.ts +26 -23
- package/src/astro/routes/api/admin/users/[id]/index.ts +41 -21
- package/src/astro/routes/api/auth/invite/register-options.ts +73 -0
- package/src/astro/routes/api/auth/magic-link/send.ts +2 -1
- package/src/astro/routes/api/auth/passkey/options.ts +2 -1
- package/src/astro/routes/api/auth/passkey/verify.ts +5 -1
- package/src/astro/routes/api/auth/signup/request.ts +20 -8
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +3 -4
- package/src/astro/routes/api/content/[collection]/[id]/compare.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +16 -2
- package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +16 -0
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +9 -0
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +45 -1
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +12 -2
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +24 -0
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +3 -0
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +20 -0
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +13 -0
- package/src/astro/routes/api/content/[collection]/[id].ts +36 -0
- package/src/astro/routes/api/content/[collection]/index.ts +48 -4
- package/src/astro/routes/api/content/[collection]/trash.ts +1 -1
- package/src/astro/routes/api/health.ts +54 -0
- package/src/astro/routes/api/import/wordpress/analyze.ts +2 -10
- package/src/astro/routes/api/import/wordpress/execute.ts +40 -6
- package/src/astro/routes/api/import/wordpress/prepare.ts +36 -5
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +33 -1
- package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +3 -3
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +57 -15
- package/src/astro/routes/api/manifest.ts +13 -1
- package/src/astro/routes/api/mcp.ts +1 -0
- package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +7 -2
- package/src/astro/routes/api/media/upload-url.ts +11 -2
- package/src/astro/routes/api/media.ts +9 -7
- package/src/astro/routes/api/menus/[name]/items.ts +124 -5
- package/src/astro/routes/api/menus/[name]/reorder.ts +47 -1
- package/src/astro/routes/api/menus/[name].ts +84 -4
- package/src/astro/routes/api/menus/index.ts +46 -2
- package/src/astro/routes/api/oauth/authorize.ts +21 -8
- package/src/astro/routes/api/oauth/device/code.ts +2 -1
- package/src/astro/routes/api/oauth/device/token.ts +2 -1
- package/src/astro/routes/api/oauth/register.ts +182 -0
- package/src/astro/routes/api/oauth/token.ts +18 -7
- package/src/astro/routes/api/openapi.json.ts +3 -2
- package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +21 -4
- package/src/astro/routes/api/redirects/[id].ts +103 -4
- package/src/astro/routes/api/redirects/index.ts +50 -2
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +28 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +15 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +13 -0
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +27 -0
- package/src/astro/routes/api/schema/collections/index.ts +14 -0
- package/src/astro/routes/api/search/index.ts +1 -0
- package/src/astro/routes/api/search/suggest.ts +1 -0
- package/src/astro/routes/api/sections/[slug].ts +123 -4
- package/src/astro/routes/api/sections/index.ts +57 -2
- package/src/astro/routes/api/settings.ts +51 -2
- package/src/astro/routes/api/setup/admin-verify.ts +25 -5
- package/src/astro/routes/api/setup/admin.ts +16 -8
- package/src/astro/routes/api/setup/index.ts +3 -2
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +141 -4
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +64 -2
- package/src/astro/routes/api/taxonomies/index.ts +57 -2
- package/src/astro/routes/api/well-known/auth.ts +3 -1
- package/src/astro/routes/api/well-known/oauth-authorization-server.ts +8 -5
- package/src/astro/routes/api/well-known/oauth-protected-resource.ts +3 -2
- package/src/astro/routes/api/widget-areas/[name]/reorder.ts +58 -16
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +124 -38
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +66 -20
- package/src/astro/routes/api/widget-areas/[name].ts +55 -7
- package/src/astro/routes/api/widget-areas/index.ts +56 -6
- package/src/components/DinewayHead.astro +15 -7
- package/src/components/DinewayMedia.astro +1 -1
- package/src/components/InlinePortableTextEditor.tsx +1 -1
- package/src/components/Table.astro +68 -41
- package/src/components/index.ts +2 -12
- package/src/components/marks.ts +19 -0
- package/LICENSE +0 -9
- /package/dist/{adapters-BlzWJG82.d.mts → adapters-C2ypTrZZ.d.mts} +0 -0
- /package/dist/{config-Cq8H0SfX.mjs → config-BXwuX8Bx.mjs} +0 -0
- /package/dist/{load-C6FCD1FU.mjs → load-Coc9HpHH.mjs} +0 -0
- /package/dist/{manifest-schema-CTSEyIJ3.mjs → manifest-schema-D1MSVnoI.mjs} +0 -0
- /package/dist/{mode-BlyYtIFO.mjs → mode-47goXBBK.mjs} +0 -0
- /package/dist/{tokens-4vgYuXsZ.mjs → tokens-CJz9ubV6.mjs} +0 -0
- /package/dist/{transport-C5FYnid7.mjs → transport-DB5eDN4x.mjs} +0 -0
- /package/dist/{transport-gIL-e43D.d.mts → transport-Wge_IzKl.d.mts} +0 -0
- /package/dist/{types-CLLdsG3g.d.mts → types-BzcUjoqg.d.mts} +0 -0
- /package/dist/{types-DShnjzb6.mjs → types-griIBQOQ.mjs} +0 -0
|
@@ -1,36 +1,11 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-ClPoSABd.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
|
|
3
|
+
import { a as isSqlite, c as tableExists, n as currentTimestamp, s as listTablesLike } from "./dialect-helpers-DhTzaUxP.mjs";
|
|
4
|
+
import { t as withTransaction } from "./transaction-Cn2rjY78.mjs";
|
|
5
|
+
import { i as RESERVED_FIELD_SLUGS, n as FIELD_TYPE_TO_COLUMN, r as RESERVED_COLLECTION_SLUGS } from "./types-COeOq9nK.mjs";
|
|
5
6
|
import { sql } from "kysely";
|
|
6
7
|
import { ulid } from "ulidx";
|
|
7
8
|
|
|
8
|
-
//#region src/database/transaction.ts
|
|
9
|
-
/**
|
|
10
|
-
* Run a callback inside a transaction if supported, or directly if not.
|
|
11
|
-
*
|
|
12
|
-
* Probes the database once on first call to determine if transactions work.
|
|
13
|
-
* The result is cached for the lifetime of the process/worker.
|
|
14
|
-
*/
|
|
15
|
-
let transactionsSupported = null;
|
|
16
|
-
const TRANSACTIONS_NOT_SUPPORTED_RE = /transactions are not supported/i;
|
|
17
|
-
async function withTransaction(db, fn) {
|
|
18
|
-
if (transactionsSupported === true) return db.transaction().execute(fn);
|
|
19
|
-
if (transactionsSupported === false) return fn(db);
|
|
20
|
-
try {
|
|
21
|
-
const result = await db.transaction().execute(fn);
|
|
22
|
-
transactionsSupported = true;
|
|
23
|
-
return result;
|
|
24
|
-
} catch (error) {
|
|
25
|
-
if (error instanceof Error && TRANSACTIONS_NOT_SUPPORTED_RE.test(error.message)) {
|
|
26
|
-
transactionsSupported = false;
|
|
27
|
-
return fn(db);
|
|
28
|
-
}
|
|
29
|
-
throw error;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
//#endregion
|
|
34
9
|
//#region src/search/fts-manager.ts
|
|
35
10
|
/**
|
|
36
11
|
* FTS5 Manager
|
|
@@ -55,12 +30,14 @@ var FTSManager = class {
|
|
|
55
30
|
* Uses _dineway_ prefix to clearly mark as internal/system table
|
|
56
31
|
*/
|
|
57
32
|
getFtsTableName(collectionSlug) {
|
|
33
|
+
validateIdentifier(collectionSlug, "collection slug");
|
|
58
34
|
return `_dineway_fts_${collectionSlug}`;
|
|
59
35
|
}
|
|
60
36
|
/**
|
|
61
37
|
* Get the content table name for a collection
|
|
62
38
|
*/
|
|
63
39
|
getContentTableName(collectionSlug) {
|
|
40
|
+
validateIdentifier(collectionSlug, "collection slug");
|
|
64
41
|
return `ec_${collectionSlug}`;
|
|
65
42
|
}
|
|
66
43
|
/**
|
|
@@ -99,9 +76,14 @@ var FTSManager = class {
|
|
|
99
76
|
await this.createTriggers(collectionSlug, searchableFields);
|
|
100
77
|
}
|
|
101
78
|
/**
|
|
102
|
-
* Create triggers to keep FTS table in sync with content table
|
|
79
|
+
* Create triggers to keep FTS table in sync with content table.
|
|
80
|
+
*
|
|
81
|
+
* Insert and update triggers only add rows to the FTS index when
|
|
82
|
+
* `deleted_at IS NULL`. This keeps soft-deleted content out of the
|
|
83
|
+
* index and keeps repair counts aligned with searchable content.
|
|
103
84
|
*/
|
|
104
85
|
async createTriggers(collectionSlug, searchableFields) {
|
|
86
|
+
this.validateInputs(collectionSlug, searchableFields);
|
|
105
87
|
const ftsTable = this.getFtsTableName(collectionSlug);
|
|
106
88
|
const contentTable = this.getContentTableName(collectionSlug);
|
|
107
89
|
const fieldList = searchableFields.join(", ");
|
|
@@ -109,6 +91,7 @@ var FTSManager = class {
|
|
|
109
91
|
await sql.raw(`
|
|
110
92
|
CREATE TRIGGER IF NOT EXISTS "${ftsTable}_insert"
|
|
111
93
|
AFTER INSERT ON "${contentTable}"
|
|
94
|
+
WHEN NEW.deleted_at IS NULL
|
|
112
95
|
BEGIN
|
|
113
96
|
INSERT INTO "${ftsTable}"(rowid, id, locale, ${fieldList})
|
|
114
97
|
VALUES (NEW.rowid, NEW.id, NEW.locale, ${newFieldList});
|
|
@@ -120,7 +103,8 @@ var FTSManager = class {
|
|
|
120
103
|
BEGIN
|
|
121
104
|
DELETE FROM "${ftsTable}" WHERE rowid = OLD.rowid;
|
|
122
105
|
INSERT INTO "${ftsTable}"(rowid, id, locale, ${fieldList})
|
|
123
|
-
|
|
106
|
+
SELECT NEW.rowid, NEW.id, NEW.locale, ${newFieldList}
|
|
107
|
+
WHERE NEW.deleted_at IS NULL;
|
|
124
108
|
END
|
|
125
109
|
`).execute(this.db);
|
|
126
110
|
await sql.raw(`
|
|
@@ -135,6 +119,7 @@ var FTSManager = class {
|
|
|
135
119
|
* Drop triggers for a collection
|
|
136
120
|
*/
|
|
137
121
|
async dropTriggers(collectionSlug) {
|
|
122
|
+
this.validateInputs(collectionSlug);
|
|
138
123
|
const ftsTable = this.getFtsTableName(collectionSlug);
|
|
139
124
|
await sql.raw(`DROP TRIGGER IF EXISTS "${ftsTable}_insert"`).execute(this.db);
|
|
140
125
|
await sql.raw(`DROP TRIGGER IF EXISTS "${ftsTable}_update"`).execute(this.db);
|
|
@@ -211,16 +196,16 @@ var FTSManager = class {
|
|
|
211
196
|
return (await this.db.selectFrom("_dineway_fields").select("slug").where("collection_id", "=", collection.id).where("searchable", "=", 1).execute()).map((f) => f.slug);
|
|
212
197
|
}
|
|
213
198
|
/**
|
|
214
|
-
* Enable search for a collection
|
|
199
|
+
* Enable search for a collection.
|
|
215
200
|
*
|
|
216
|
-
*
|
|
201
|
+
* Rebuilds from scratch to ensure stale tables or triggers do not survive
|
|
202
|
+
* repeated enablement.
|
|
217
203
|
*/
|
|
218
204
|
async enableSearch(collectionSlug, options) {
|
|
219
205
|
if (!isSqlite(this.db)) throw new Error("Full-text search is only available with SQLite databases");
|
|
220
206
|
const searchableFields = await this.getSearchableFields(collectionSlug);
|
|
221
207
|
if (searchableFields.length === 0) throw new Error(`No searchable fields defined for collection "${collectionSlug}". Mark at least one field as searchable before enabling search.`);
|
|
222
|
-
await this.
|
|
223
|
-
await this.populateFromContent(collectionSlug, searchableFields);
|
|
208
|
+
await this.rebuildIndex(collectionSlug, searchableFields, options?.weights);
|
|
224
209
|
await this.setSearchConfig(collectionSlug, {
|
|
225
210
|
enabled: true,
|
|
226
211
|
weights: options?.weights
|
|
@@ -233,8 +218,12 @@ var FTSManager = class {
|
|
|
233
218
|
*/
|
|
234
219
|
async disableSearch(collectionSlug) {
|
|
235
220
|
if (!isSqlite(this.db)) return;
|
|
221
|
+
const existing = await this.getSearchConfig(collectionSlug);
|
|
236
222
|
await this.dropFtsTable(collectionSlug);
|
|
237
|
-
await this.setSearchConfig(collectionSlug, {
|
|
223
|
+
await this.setSearchConfig(collectionSlug, {
|
|
224
|
+
enabled: false,
|
|
225
|
+
weights: existing?.weights
|
|
226
|
+
});
|
|
238
227
|
}
|
|
239
228
|
/**
|
|
240
229
|
* Get index statistics for a collection
|
|
@@ -242,10 +231,10 @@ var FTSManager = class {
|
|
|
242
231
|
async getIndexStats(collectionSlug) {
|
|
243
232
|
if (!isSqlite(this.db)) return null;
|
|
244
233
|
this.validateInputs(collectionSlug);
|
|
245
|
-
const
|
|
234
|
+
const ftsDocsizeTable = `${this.getFtsTableName(collectionSlug)}_docsize`;
|
|
246
235
|
if (!await this.ftsTableExists(collectionSlug)) return null;
|
|
247
236
|
return { indexed: (await sql`
|
|
248
|
-
SELECT COUNT(*) as count FROM "${sql.raw(
|
|
237
|
+
SELECT COUNT(*) as count FROM "${sql.raw(ftsDocsizeTable)}"
|
|
249
238
|
`.execute(this.db)).rows[0]?.count ?? 0 };
|
|
250
239
|
}
|
|
251
240
|
/**
|
|
@@ -258,22 +247,27 @@ var FTSManager = class {
|
|
|
258
247
|
async verifyAndRepairIndex(collectionSlug) {
|
|
259
248
|
if (!isSqlite(this.db)) return false;
|
|
260
249
|
this.validateInputs(collectionSlug);
|
|
261
|
-
const
|
|
250
|
+
const ftsDocsizeTable = `${this.getFtsTableName(collectionSlug)}_docsize`;
|
|
262
251
|
const contentTable = this.getContentTableName(collectionSlug);
|
|
263
|
-
|
|
252
|
+
const fields = await this.getSearchableFields(collectionSlug);
|
|
253
|
+
const config = await this.getSearchConfig(collectionSlug);
|
|
254
|
+
if (!await this.ftsTableExists(collectionSlug)) {
|
|
255
|
+
if (!config?.enabled || fields.length === 0) return false;
|
|
256
|
+
console.warn(`FTS index for "${collectionSlug}" is missing. Rebuilding.`);
|
|
257
|
+
await this.rebuildIndex(collectionSlug, fields, config.weights);
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
264
260
|
const contentCount = await sql`
|
|
265
261
|
SELECT COUNT(*) as count FROM ${sql.ref(contentTable)}
|
|
266
262
|
WHERE deleted_at IS NULL
|
|
267
263
|
`.execute(this.db);
|
|
268
264
|
const ftsCount = await sql`
|
|
269
|
-
SELECT COUNT(*) as count FROM "${sql.raw(
|
|
265
|
+
SELECT COUNT(*) as count FROM "${sql.raw(ftsDocsizeTable)}"
|
|
270
266
|
`.execute(this.db);
|
|
271
267
|
const contentRows = contentCount.rows[0]?.count ?? 0;
|
|
272
268
|
const ftsRows = ftsCount.rows[0]?.count ?? 0;
|
|
273
269
|
if (contentRows !== ftsRows) {
|
|
274
270
|
console.warn(`FTS index for "${collectionSlug}" has ${ftsRows} rows but content table has ${contentRows}. Rebuilding.`);
|
|
275
|
-
const fields = await this.getSearchableFields(collectionSlug);
|
|
276
|
-
const config = await this.getSearchConfig(collectionSlug);
|
|
277
271
|
if (fields.length > 0) await this.rebuildIndex(collectionSlug, fields, config?.weights);
|
|
278
272
|
return true;
|
|
279
273
|
}
|
|
@@ -334,6 +328,16 @@ function isFieldType(value) {
|
|
|
334
328
|
function isColumnType(value) {
|
|
335
329
|
return COLUMN_TYPES.has(value);
|
|
336
330
|
}
|
|
331
|
+
function parseSupports(value) {
|
|
332
|
+
if (!value) return [];
|
|
333
|
+
try {
|
|
334
|
+
const parsed = JSON.parse(value);
|
|
335
|
+
if (!Array.isArray(parsed)) return [];
|
|
336
|
+
return parsed.filter((item) => typeof item === "string");
|
|
337
|
+
} catch {
|
|
338
|
+
return [];
|
|
339
|
+
}
|
|
340
|
+
}
|
|
337
341
|
/**
|
|
338
342
|
* Error thrown when a schema operation fails
|
|
339
343
|
*/
|
|
@@ -387,6 +391,9 @@ var SchemaRegistry = class {
|
|
|
387
391
|
this.validateSlug(input.slug, "collection");
|
|
388
392
|
if (RESERVED_COLLECTION_SLUGS.includes(input.slug)) throw new SchemaError(`Collection slug "${input.slug}" is reserved`, "RESERVED_SLUG");
|
|
389
393
|
if (await this.getCollection(input.slug)) throw new SchemaError(`Collection "${input.slug}" already exists`, "COLLECTION_EXISTS");
|
|
394
|
+
await this.validateUrlPattern(input.urlPattern);
|
|
395
|
+
await this.checkLabelUniqueness(input.label, input.labelSingular);
|
|
396
|
+
await this.checkUrlPatternUniqueness(input.urlPattern);
|
|
390
397
|
const id = ulid();
|
|
391
398
|
const hasSeo = input.hasSeo ?? input.supports?.includes("seo") ?? false;
|
|
392
399
|
await withTransaction(this.db, async (trx) => {
|
|
@@ -415,26 +422,67 @@ var SchemaRegistry = class {
|
|
|
415
422
|
async updateCollection(slug, input) {
|
|
416
423
|
const existing = await this.getCollection(slug);
|
|
417
424
|
if (!existing) throw new SchemaError(`Collection "${slug}" not found`, "COLLECTION_NOT_FOUND");
|
|
425
|
+
const labelChanged = input.label !== void 0 && input.label !== existing.label;
|
|
426
|
+
const labelSingularChanged = input.labelSingular !== void 0 && input.labelSingular !== (existing.labelSingular ?? void 0);
|
|
427
|
+
if (labelChanged || labelSingularChanged) await this.checkLabelUniqueness(labelChanged ? input.label : void 0, labelSingularChanged ? input.labelSingular : void 0, slug);
|
|
428
|
+
if (input.urlPattern !== void 0 && input.urlPattern !== (existing.urlPattern ?? null)) {
|
|
429
|
+
await this.validateUrlPattern(input.urlPattern);
|
|
430
|
+
await this.checkUrlPatternUniqueness(input.urlPattern, slug);
|
|
431
|
+
}
|
|
418
432
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
419
433
|
const supportsArray = input.supports ?? existing.supports;
|
|
420
434
|
const hasSeo = input.hasSeo !== void 0 ? input.hasSeo : input.supports !== void 0 ? supportsArray.includes("seo") : existing.hasSeo;
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
435
|
+
return withTransaction(this.db, async (trx) => {
|
|
436
|
+
await trx.updateTable("_dineway_collections").set({
|
|
437
|
+
label: input.label ?? existing.label,
|
|
438
|
+
label_singular: input.labelSingular ?? existing.labelSingular ?? null,
|
|
439
|
+
description: input.description ?? existing.description ?? null,
|
|
440
|
+
icon: input.icon ?? existing.icon ?? null,
|
|
441
|
+
supports: input.supports ? JSON.stringify(input.supports) : JSON.stringify(existing.supports),
|
|
442
|
+
url_pattern: input.urlPattern !== void 0 ? input.urlPattern ?? null : existing.urlPattern ?? null,
|
|
443
|
+
has_seo: hasSeo ? 1 : 0,
|
|
444
|
+
comments_enabled: input.commentsEnabled !== void 0 ? input.commentsEnabled ? 1 : 0 : existing.commentsEnabled ? 1 : 0,
|
|
445
|
+
comments_moderation: input.commentsModeration ?? existing.commentsModeration,
|
|
446
|
+
comments_closed_after_days: input.commentsClosedAfterDays !== void 0 ? input.commentsClosedAfterDays : existing.commentsClosedAfterDays,
|
|
447
|
+
comments_auto_approve_users: input.commentsAutoApproveUsers !== void 0 ? input.commentsAutoApproveUsers ? 1 : 0 : existing.commentsAutoApproveUsers ? 1 : 0,
|
|
448
|
+
updated_at: now
|
|
449
|
+
}).where("slug", "=", slug).execute();
|
|
450
|
+
const row = await trx.selectFrom("_dineway_collections").where("slug", "=", slug).selectAll().executeTakeFirst();
|
|
451
|
+
if (!row) throw new SchemaError("Failed to update collection", "UPDATE_FAILED");
|
|
452
|
+
if (input.supports !== void 0) {
|
|
453
|
+
if (existing.supports.includes("search") !== parseSupports(row.supports).includes("search")) await this.syncSearchState(slug, trx);
|
|
454
|
+
}
|
|
455
|
+
return this.mapCollectionRow(row);
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
async validateUrlPattern(urlPattern) {
|
|
459
|
+
if (!urlPattern) return;
|
|
460
|
+
if (!urlPattern.includes("{slug}")) throw new SchemaError("URL pattern must include a {slug} placeholder", "INVALID_URL_PATTERN");
|
|
461
|
+
}
|
|
462
|
+
async checkLabelUniqueness(label, labelSingular, excludeSlug) {
|
|
463
|
+
if (!label && !labelSingular) return;
|
|
464
|
+
let query = this.db.selectFrom("_dineway_collections").select([
|
|
465
|
+
"slug",
|
|
466
|
+
"label",
|
|
467
|
+
"label_singular"
|
|
468
|
+
]);
|
|
469
|
+
if (excludeSlug) query = query.where("slug", "!=", excludeSlug);
|
|
470
|
+
const others = await query.execute();
|
|
471
|
+
for (const other of others) {
|
|
472
|
+
if (label && other.label === label) throw new SchemaError(`A content type with the label "${label}" already exists`, "LABEL_EXISTS");
|
|
473
|
+
if (labelSingular && other.label_singular === labelSingular) throw new SchemaError(`A content type with the singular label "${labelSingular}" already exists`, "LABEL_EXISTS");
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
async checkUrlPatternUniqueness(urlPattern, excludeSlug) {
|
|
477
|
+
if (!urlPattern) return;
|
|
478
|
+
let query = this.db.selectFrom("_dineway_collections").select([
|
|
479
|
+
"slug",
|
|
480
|
+
"label",
|
|
481
|
+
"url_pattern"
|
|
482
|
+
]);
|
|
483
|
+
if (excludeSlug) query = query.where("slug", "!=", excludeSlug);
|
|
484
|
+
const others = await query.execute();
|
|
485
|
+
for (const other of others) if (other.url_pattern === urlPattern) throw new SchemaError(`The URL pattern "${urlPattern}" is already used by "${other.label}"`, "URL_PATTERN_EXISTS");
|
|
438
486
|
}
|
|
439
487
|
/**
|
|
440
488
|
* Delete a collection
|
|
@@ -445,8 +493,11 @@ var SchemaRegistry = class {
|
|
|
445
493
|
if (!options?.force) {
|
|
446
494
|
if (await this.collectionHasContent(slug)) throw new SchemaError(`Collection "${slug}" has content. Use force: true to delete.`, "COLLECTION_HAS_CONTENT");
|
|
447
495
|
}
|
|
448
|
-
await this.
|
|
449
|
-
|
|
496
|
+
await withTransaction(this.db, async (trx) => {
|
|
497
|
+
await new FTSManager(trx).dropFtsTable(slug);
|
|
498
|
+
await this.dropContentTable(slug, trx);
|
|
499
|
+
await trx.deleteFrom("_dineway_collections").where("id", "=", existing.id).execute();
|
|
500
|
+
});
|
|
450
501
|
}
|
|
451
502
|
/**
|
|
452
503
|
* List fields for a collection
|
|
@@ -476,30 +527,34 @@ var SchemaRegistry = class {
|
|
|
476
527
|
const columnType = FIELD_TYPE_TO_COLUMN[input.type];
|
|
477
528
|
const maxSort = await this.db.selectFrom("_dineway_fields").where("collection_id", "=", collection.id).select((eb) => eb.fn.max("sort_order").as("max")).executeTakeFirst();
|
|
478
529
|
const sortOrder = input.sortOrder ?? (maxSort?.max ?? -1) + 1;
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
530
|
+
return withTransaction(this.db, async (trx) => {
|
|
531
|
+
await trx.insertInto("_dineway_fields").values({
|
|
532
|
+
id,
|
|
533
|
+
collection_id: collection.id,
|
|
534
|
+
slug: input.slug,
|
|
535
|
+
label: input.label,
|
|
536
|
+
type: input.type,
|
|
537
|
+
column_type: columnType,
|
|
538
|
+
required: input.required ? 1 : 0,
|
|
539
|
+
unique: input.unique ? 1 : 0,
|
|
540
|
+
default_value: input.defaultValue !== void 0 ? JSON.stringify(input.defaultValue) : null,
|
|
541
|
+
validation: input.validation ? JSON.stringify(input.validation) : null,
|
|
542
|
+
widget: input.widget ?? null,
|
|
543
|
+
options: input.options ? JSON.stringify(input.options) : null,
|
|
544
|
+
sort_order: sortOrder,
|
|
545
|
+
searchable: input.searchable ? 1 : 0,
|
|
546
|
+
translatable: input.translatable === false ? 0 : 1
|
|
547
|
+
}).execute();
|
|
548
|
+
await this.addColumn(collectionSlug, input.slug, input.type, {
|
|
549
|
+
required: input.required,
|
|
550
|
+
defaultValue: input.defaultValue
|
|
551
|
+
}, trx);
|
|
552
|
+
const fieldRow = await trx.selectFrom("_dineway_fields").where("collection_id", "=", collection.id).where("slug", "=", input.slug).selectAll().executeTakeFirst();
|
|
553
|
+
if (!fieldRow) throw new SchemaError("Failed to create field", "CREATE_FAILED");
|
|
554
|
+
const field = this.mapFieldRow(fieldRow);
|
|
555
|
+
if (input.searchable) await this.syncSearchState(collectionSlug, trx);
|
|
556
|
+
return field;
|
|
499
557
|
});
|
|
500
|
-
const field = await this.getField(collectionSlug, input.slug);
|
|
501
|
-
if (!field) throw new SchemaError("Failed to create field", "CREATE_FAILED");
|
|
502
|
-
return field;
|
|
503
558
|
}
|
|
504
559
|
/**
|
|
505
560
|
* Update a field
|
|
@@ -507,36 +562,43 @@ var SchemaRegistry = class {
|
|
|
507
562
|
async updateField(collectionSlug, fieldSlug, input) {
|
|
508
563
|
const field = await this.getField(collectionSlug, fieldSlug);
|
|
509
564
|
if (!field) throw new SchemaError(`Field "${fieldSlug}" not found in collection "${collectionSlug}"`, "FIELD_NOT_FOUND");
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
565
|
+
return withTransaction(this.db, async (trx) => {
|
|
566
|
+
await trx.updateTable("_dineway_fields").set({
|
|
567
|
+
label: input.label ?? field.label,
|
|
568
|
+
required: input.required !== void 0 ? input.required ? 1 : 0 : field.required ? 1 : 0,
|
|
569
|
+
unique: input.unique !== void 0 ? input.unique ? 1 : 0 : field.unique ? 1 : 0,
|
|
570
|
+
searchable: input.searchable !== void 0 ? input.searchable ? 1 : 0 : field.searchable ? 1 : 0,
|
|
571
|
+
translatable: input.translatable !== void 0 ? input.translatable ? 1 : 0 : field.translatable ? 1 : 0,
|
|
572
|
+
default_value: input.defaultValue !== void 0 ? JSON.stringify(input.defaultValue) : field.defaultValue !== void 0 ? JSON.stringify(field.defaultValue) : null,
|
|
573
|
+
validation: input.validation ? JSON.stringify(input.validation) : field.validation ? JSON.stringify(field.validation) : null,
|
|
574
|
+
widget: input.widget ?? field.widget ?? null,
|
|
575
|
+
options: input.options ? JSON.stringify(input.options) : field.options ? JSON.stringify(field.options) : null,
|
|
576
|
+
sort_order: input.sortOrder ?? field.sortOrder
|
|
577
|
+
}).where("id", "=", field.id).execute();
|
|
578
|
+
const updatedRow = await trx.selectFrom("_dineway_fields").where("collection_id", "=", field.collectionId).where("slug", "=", fieldSlug).selectAll().executeTakeFirst();
|
|
579
|
+
if (!updatedRow) throw new SchemaError("Failed to update field", "UPDATE_FAILED");
|
|
580
|
+
const updated = this.mapFieldRow(updatedRow);
|
|
581
|
+
if (input.searchable !== void 0 && input.searchable !== field.searchable) await this.syncSearchState(collectionSlug, trx);
|
|
582
|
+
return updated;
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Synchronize an existing FTS index with the collection's current state.
|
|
529
587
|
*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
588
|
+
* Schema writes must not first-time enable FTS. Initial enablement stays in
|
|
589
|
+
* explicit seed/import/admin flows where FTS failures can be handled deliberately.
|
|
532
590
|
*/
|
|
533
|
-
async
|
|
534
|
-
const
|
|
535
|
-
const
|
|
536
|
-
|
|
591
|
+
async syncSearchState(collectionSlug, db) {
|
|
592
|
+
const conn = db ?? this.db;
|
|
593
|
+
const ftsManager = new FTSManager(conn);
|
|
594
|
+
const row = await conn.selectFrom("_dineway_collections").where("slug", "=", collectionSlug).select("supports").executeTakeFirst();
|
|
595
|
+
if (!row) return;
|
|
596
|
+
const wantsSearch = parseSupports(row.supports).includes("search");
|
|
537
597
|
const searchableFields = await ftsManager.getSearchableFields(collectionSlug);
|
|
538
|
-
|
|
539
|
-
|
|
598
|
+
const config = await ftsManager.getSearchConfig(collectionSlug);
|
|
599
|
+
const ftsActive = config?.enabled === true;
|
|
600
|
+
if (wantsSearch && searchableFields.length > 0 && ftsActive) await ftsManager.rebuildIndex(collectionSlug, searchableFields, config?.weights);
|
|
601
|
+
else if (ftsActive && (!wantsSearch || searchableFields.length === 0)) await ftsManager.disableSearch(collectionSlug);
|
|
540
602
|
}
|
|
541
603
|
/**
|
|
542
604
|
* Delete a field
|
|
@@ -544,8 +606,11 @@ var SchemaRegistry = class {
|
|
|
544
606
|
async deleteField(collectionSlug, fieldSlug) {
|
|
545
607
|
const field = await this.getField(collectionSlug, fieldSlug);
|
|
546
608
|
if (!field) throw new SchemaError(`Field "${fieldSlug}" not found in collection "${collectionSlug}"`, "FIELD_NOT_FOUND");
|
|
547
|
-
await this.
|
|
548
|
-
|
|
609
|
+
await withTransaction(this.db, async (trx) => {
|
|
610
|
+
await trx.deleteFrom("_dineway_fields").where("id", "=", field.id).execute();
|
|
611
|
+
if (field.searchable) await this.syncSearchState(collectionSlug, trx);
|
|
612
|
+
await this.dropColumn(collectionSlug, fieldSlug, trx);
|
|
613
|
+
});
|
|
549
614
|
}
|
|
550
615
|
/**
|
|
551
616
|
* Reorder fields
|
|
@@ -615,14 +680,15 @@ var SchemaRegistry = class {
|
|
|
615
680
|
/**
|
|
616
681
|
* Drop a content table
|
|
617
682
|
*/
|
|
618
|
-
async dropContentTable(slug) {
|
|
683
|
+
async dropContentTable(slug, db) {
|
|
619
684
|
const tableName = this.getTableName(slug);
|
|
620
|
-
await sql`DROP TABLE IF EXISTS ${sql.ref(tableName)}`.execute(this.db);
|
|
685
|
+
await sql`DROP TABLE IF EXISTS ${sql.ref(tableName)}`.execute(db ?? this.db);
|
|
621
686
|
}
|
|
622
687
|
/**
|
|
623
688
|
* Add a column to a content table
|
|
624
689
|
*/
|
|
625
|
-
async addColumn(collectionSlug, fieldSlug, fieldType, options) {
|
|
690
|
+
async addColumn(collectionSlug, fieldSlug, fieldType, options, db) {
|
|
691
|
+
const conn = db ?? this.db;
|
|
626
692
|
const tableName = this.getTableName(collectionSlug);
|
|
627
693
|
const columnType = FIELD_TYPE_TO_COLUMN[fieldType];
|
|
628
694
|
const columnName = this.getColumnName(fieldSlug);
|
|
@@ -631,28 +697,28 @@ var SchemaRegistry = class {
|
|
|
631
697
|
await sql`
|
|
632
698
|
ALTER TABLE ${sql.ref(tableName)}
|
|
633
699
|
ADD COLUMN ${sql.ref(columnName)} ${sql.raw(columnType)} NOT NULL DEFAULT ${sql.raw(defaultVal)}
|
|
634
|
-
`.execute(
|
|
700
|
+
`.execute(conn);
|
|
635
701
|
} else if (options?.required) {
|
|
636
702
|
const defaultVal = this.getEmptyDefault(fieldType);
|
|
637
703
|
await sql`
|
|
638
704
|
ALTER TABLE ${sql.ref(tableName)}
|
|
639
705
|
ADD COLUMN ${sql.ref(columnName)} ${sql.raw(columnType)} NOT NULL DEFAULT ${sql.raw(defaultVal)}
|
|
640
|
-
`.execute(
|
|
706
|
+
`.execute(conn);
|
|
641
707
|
} else await sql`
|
|
642
708
|
ALTER TABLE ${sql.ref(tableName)}
|
|
643
709
|
ADD COLUMN ${sql.ref(columnName)} ${sql.raw(columnType)}
|
|
644
|
-
`.execute(
|
|
710
|
+
`.execute(conn);
|
|
645
711
|
}
|
|
646
712
|
/**
|
|
647
713
|
* Drop a column from a content table
|
|
648
714
|
*/
|
|
649
|
-
async dropColumn(collectionSlug, fieldSlug) {
|
|
715
|
+
async dropColumn(collectionSlug, fieldSlug, db) {
|
|
650
716
|
const tableName = this.getTableName(collectionSlug);
|
|
651
717
|
const columnName = this.getColumnName(fieldSlug);
|
|
652
718
|
await sql`
|
|
653
719
|
ALTER TABLE ${sql.ref(tableName)}
|
|
654
720
|
DROP COLUMN ${sql.ref(columnName)}
|
|
655
|
-
`.execute(this.db);
|
|
721
|
+
`.execute(db ?? this.db);
|
|
656
722
|
}
|
|
657
723
|
/**
|
|
658
724
|
* Check if a collection has any content
|
|
@@ -672,12 +738,14 @@ var SchemaRegistry = class {
|
|
|
672
738
|
* Get table name for a collection
|
|
673
739
|
*/
|
|
674
740
|
getTableName(slug) {
|
|
741
|
+
validateIdentifier(slug, "collection slug");
|
|
675
742
|
return `ec_${slug}`;
|
|
676
743
|
}
|
|
677
744
|
/**
|
|
678
745
|
* Get column name for a field
|
|
679
746
|
*/
|
|
680
747
|
getColumnName(slug) {
|
|
748
|
+
validateIdentifier(slug, "field slug");
|
|
681
749
|
return slug;
|
|
682
750
|
}
|
|
683
751
|
/**
|
|
@@ -848,4 +916,4 @@ var SchemaRegistry = class {
|
|
|
848
916
|
};
|
|
849
917
|
|
|
850
918
|
//#endregion
|
|
851
|
-
export {
|
|
919
|
+
export { FTSManager as i, SchemaRegistry as n, registry_exports as r, SchemaError as t };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { getRequestContext } from "./request-context.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/request-cache.ts
|
|
4
|
+
const STORE_KEY = Symbol.for("dineway:request-cache");
|
|
5
|
+
function getStore() {
|
|
6
|
+
const existing = Reflect.get(globalThis, STORE_KEY);
|
|
7
|
+
if (existing instanceof WeakMap) return existing;
|
|
8
|
+
const weakMap = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
Reflect.set(globalThis, STORE_KEY, weakMap);
|
|
10
|
+
return weakMap;
|
|
11
|
+
}
|
|
12
|
+
function asTypedPromise(promise) {
|
|
13
|
+
return promise;
|
|
14
|
+
}
|
|
15
|
+
const store = getStore();
|
|
16
|
+
/**
|
|
17
|
+
* Return a cached promise for `key` in the current request scope.
|
|
18
|
+
*
|
|
19
|
+
* Rejected promises are evicted so later retries can re-run the query.
|
|
20
|
+
*/
|
|
21
|
+
function requestCached(key, fn) {
|
|
22
|
+
const ctx = getRequestContext();
|
|
23
|
+
if (!ctx) return fn();
|
|
24
|
+
let cache = store.get(ctx);
|
|
25
|
+
if (!cache) {
|
|
26
|
+
cache = /* @__PURE__ */ new Map();
|
|
27
|
+
store.set(ctx, cache);
|
|
28
|
+
}
|
|
29
|
+
const existing = cache.get(key);
|
|
30
|
+
if (existing) return asTypedPromise(existing);
|
|
31
|
+
const promise = Promise.resolve().then(fn).catch((error) => {
|
|
32
|
+
cache.delete(key);
|
|
33
|
+
throw error;
|
|
34
|
+
});
|
|
35
|
+
cache.set(key, promise);
|
|
36
|
+
return promise;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Peek at a cached promise without inserting a new one.
|
|
40
|
+
*/
|
|
41
|
+
function peekRequestCache(key) {
|
|
42
|
+
const ctx = getRequestContext();
|
|
43
|
+
if (!ctx) return void 0;
|
|
44
|
+
const existing = store.get(ctx)?.get(key);
|
|
45
|
+
return existing ? asTypedPromise(existing) : void 0;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Pre-populate the request cache with a resolved value.
|
|
49
|
+
*
|
|
50
|
+
* Used by hydration paths that already loaded the data in bulk and want
|
|
51
|
+
* narrower downstream helper calls to short-circuit without re-querying.
|
|
52
|
+
*/
|
|
53
|
+
function setRequestCacheEntry(key, value) {
|
|
54
|
+
const ctx = getRequestContext();
|
|
55
|
+
if (!ctx) return;
|
|
56
|
+
let cache = store.get(ctx);
|
|
57
|
+
if (!cache) {
|
|
58
|
+
cache = /* @__PURE__ */ new Map();
|
|
59
|
+
store.set(ctx, cache);
|
|
60
|
+
}
|
|
61
|
+
if (cache.has(key)) return;
|
|
62
|
+
cache.set(key, Promise.resolve(value));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
export { requestCached as n, setRequestCacheEntry as r, peekRequestCache as t };
|
|
@@ -1,20 +1,6 @@
|
|
|
1
|
+
import { QueryRecorder } from "./database/instrumentation.mjs";
|
|
2
|
+
|
|
1
3
|
//#region src/request-context.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Dineway Request Context
|
|
4
|
-
*
|
|
5
|
-
* Uses AsyncLocalStorage to provide request-scoped state to query functions
|
|
6
|
-
* without requiring explicit parameter passing. The middleware wraps next()
|
|
7
|
-
* in als.run(), making the context available to all code during rendering.
|
|
8
|
-
*
|
|
9
|
-
* For logged-out users with no CMS signals (no edit cookie, no preview param),
|
|
10
|
-
* the middleware skips ALS entirely — zero overhead for normal traffic.
|
|
11
|
-
*
|
|
12
|
-
* The AsyncLocalStorage instance is stored on globalThis with a Symbol key
|
|
13
|
-
* to guarantee a singleton even when bundlers duplicate this module across
|
|
14
|
-
* code-split chunks. Without this, Rollup/Vite may inline the module into
|
|
15
|
-
* multiple chunks (e.g. middleware and page components), each with its own
|
|
16
|
-
* ALS instance — breaking request-scoped state propagation.
|
|
17
|
-
*/
|
|
18
4
|
interface DinewayRequestContext {
|
|
19
5
|
/** Whether the current request is in visual editing mode */
|
|
20
6
|
editMode: boolean;
|
|
@@ -34,6 +20,8 @@ interface DinewayRequestContext {
|
|
|
34
20
|
* playground sidecar flows.
|
|
35
21
|
*/
|
|
36
22
|
db?: unknown;
|
|
23
|
+
/** Optional query recorder for per-request instrumentation. */
|
|
24
|
+
queryRecorder?: QueryRecorder;
|
|
37
25
|
}
|
|
38
26
|
/**
|
|
39
27
|
* Run a function within a Dineway request context.
|