dineway 0.1.3
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/LICENSE +9 -0
- package/README.md +89 -0
- package/dist/adapters-BlzWJG82.d.mts +106 -0
- package/dist/apply-CAPvMfoU.mjs +1339 -0
- package/dist/astro/index.d.mts +50 -0
- package/dist/astro/index.mjs +1326 -0
- package/dist/astro/middleware/auth.d.mts +30 -0
- package/dist/astro/middleware/auth.mjs +708 -0
- package/dist/astro/middleware/redirect.d.mts +21 -0
- package/dist/astro/middleware/redirect.mjs +62 -0
- package/dist/astro/middleware/request-context.d.mts +17 -0
- package/dist/astro/middleware/request-context.mjs +1371 -0
- package/dist/astro/middleware/setup.d.mts +19 -0
- package/dist/astro/middleware/setup.mjs +46 -0
- package/dist/astro/middleware.d.mts +12 -0
- package/dist/astro/middleware.mjs +1716 -0
- package/dist/astro/types.d.mts +269 -0
- package/dist/astro/types.mjs +1 -0
- package/dist/base64-F8-DUraK.mjs +58 -0
- package/dist/byline-DeWCMU_i.mjs +234 -0
- package/dist/bylines-DyqBV9EQ.mjs +137 -0
- package/dist/chunk-ClPoSABd.mjs +21 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +3987 -0
- package/dist/client/external-auth-headers.d.mts +38 -0
- package/dist/client/external-auth-headers.mjs +101 -0
- package/dist/client/index.d.mts +397 -0
- package/dist/client/index.mjs +345 -0
- package/dist/config-Cq8H0SfX.mjs +46 -0
- package/dist/connection-C9pxzuag.mjs +52 -0
- package/dist/content-zSgdNmnt.mjs +836 -0
- package/dist/db/index.d.mts +4 -0
- package/dist/db/index.mjs +62 -0
- package/dist/db/libsql.d.mts +10 -0
- package/dist/db/libsql.mjs +21 -0
- package/dist/db/postgres.d.mts +10 -0
- package/dist/db/postgres.mjs +29 -0
- package/dist/db/sqlite.d.mts +10 -0
- package/dist/db/sqlite.mjs +15 -0
- package/dist/default-WYlzADZL.mjs +80 -0
- package/dist/dialect-helpers-B9uSp2GJ.mjs +89 -0
- package/dist/error-DrxtnGPg.mjs +26 -0
- package/dist/index-C-jx21qs.d.mts +4771 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.mjs +30 -0
- package/dist/load-C6FCD1FU.mjs +27 -0
- package/dist/loader-qKmo0wAY.mjs +446 -0
- package/dist/manifest-schema-CTSEyIJ3.mjs +186 -0
- package/dist/media/index.d.mts +25 -0
- package/dist/media/index.mjs +54 -0
- package/dist/media/local-runtime.d.mts +38 -0
- package/dist/media/local-runtime.mjs +132 -0
- package/dist/media-DMTr80Gv.mjs +199 -0
- package/dist/mode-BlyYtIFO.mjs +22 -0
- package/dist/page/index.d.mts +148 -0
- package/dist/page/index.mjs +419 -0
- package/dist/placeholder-B3knXwNc.mjs +267 -0
- package/dist/placeholder-bOx1xCTY.d.mts +283 -0
- package/dist/plugin-utils.d.mts +57 -0
- package/dist/plugin-utils.mjs +77 -0
- package/dist/plugins/adapt-sandbox-entry.d.mts +21 -0
- package/dist/plugins/adapt-sandbox-entry.mjs +112 -0
- package/dist/query-BiaPl_g2.mjs +459 -0
- package/dist/redirect-JPqLAbxa.mjs +328 -0
- package/dist/registry-DSd1GWB8.mjs +851 -0
- package/dist/request-context.d.mts +49 -0
- package/dist/request-context.mjs +42 -0
- package/dist/runner-B5l1JfOj.d.mts +26 -0
- package/dist/runner-BGUGywgG.mjs +1529 -0
- package/dist/runtime.d.mts +25 -0
- package/dist/runtime.mjs +41 -0
- package/dist/search-BNruJHDL.mjs +11054 -0
- package/dist/seed/index.d.mts +3 -0
- package/dist/seed/index.mjs +15 -0
- package/dist/seo/index.d.mts +69 -0
- package/dist/seo/index.mjs +69 -0
- package/dist/storage/local.d.mts +38 -0
- package/dist/storage/local.mjs +165 -0
- package/dist/storage/s3.d.mts +31 -0
- package/dist/storage/s3.mjs +174 -0
- package/dist/tokens-4vgYuXsZ.mjs +170 -0
- package/dist/transport-C5FYnid7.mjs +417 -0
- package/dist/transport-gIL-e43D.d.mts +41 -0
- package/dist/types-BawVha09.mjs +30 -0
- package/dist/types-BgQeVaPj.d.mts +192 -0
- package/dist/types-CLLdsG3g.d.mts +103 -0
- package/dist/types-D38djUXv.d.mts +1196 -0
- package/dist/types-DShnjzb6.mjs +15 -0
- package/dist/types-DkvMXalq.d.mts +425 -0
- package/dist/types-DuNbGKjF.mjs +74 -0
- package/dist/types-ju-_ORz7.d.mts +182 -0
- package/dist/validate-CXnRKfJK.mjs +327 -0
- package/dist/validate-CqRJb_xU.mjs +96 -0
- package/dist/validate-DVKJJ-M_.d.mts +377 -0
- package/locals.d.ts +47 -0
- package/package.json +313 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { n as PLUGIN_CAPABILITIES, t as HOOK_NAMES } from "../manifest-schema-CTSEyIJ3.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/plugins/adapt-sandbox-entry.ts
|
|
4
|
+
/**
|
|
5
|
+
* Default hook configuration values
|
|
6
|
+
*/
|
|
7
|
+
const DEFAULT_PRIORITY = 100;
|
|
8
|
+
const DEFAULT_TIMEOUT = 5e3;
|
|
9
|
+
const DEFAULT_ERROR_POLICY = "abort";
|
|
10
|
+
/**
|
|
11
|
+
* Check if a standard hook entry is a config object (has a `handler` property)
|
|
12
|
+
*/
|
|
13
|
+
function isHookConfig(entry) {
|
|
14
|
+
return typeof entry === "object" && entry !== null && "handler" in entry;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolve a single standard hook entry to a ResolvedHook.
|
|
18
|
+
*
|
|
19
|
+
* Standard-format hooks use the sandbox entry convention:
|
|
20
|
+
* handler(event, ctx) -- two args
|
|
21
|
+
*
|
|
22
|
+
* The HookPipeline dispatch methods also call handlers with (event, ctx),
|
|
23
|
+
* so the handler is compatible as-is. We just need to wrap it for type safety.
|
|
24
|
+
*/
|
|
25
|
+
function resolveStandardHook(entry, pluginId) {
|
|
26
|
+
if (isHookConfig(entry)) return {
|
|
27
|
+
priority: entry.priority ?? DEFAULT_PRIORITY,
|
|
28
|
+
timeout: entry.timeout ?? DEFAULT_TIMEOUT,
|
|
29
|
+
dependencies: entry.dependencies ?? [],
|
|
30
|
+
errorPolicy: entry.errorPolicy ?? DEFAULT_ERROR_POLICY,
|
|
31
|
+
exclusive: entry.exclusive ?? false,
|
|
32
|
+
handler: entry.handler,
|
|
33
|
+
pluginId
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
priority: DEFAULT_PRIORITY,
|
|
37
|
+
timeout: DEFAULT_TIMEOUT,
|
|
38
|
+
dependencies: [],
|
|
39
|
+
errorPolicy: DEFAULT_ERROR_POLICY,
|
|
40
|
+
exclusive: false,
|
|
41
|
+
handler: entry,
|
|
42
|
+
pluginId
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const VALID_CAPABILITIES_SET = new Set(PLUGIN_CAPABILITIES);
|
|
46
|
+
const VALID_HOOK_NAMES_SET = new Set(HOOK_NAMES);
|
|
47
|
+
/**
|
|
48
|
+
* Adapt a standard-format plugin definition into a ResolvedPlugin.
|
|
49
|
+
*
|
|
50
|
+
* This is the core of the unified plugin format. It takes the `{ hooks, routes }`
|
|
51
|
+
* export from a standard plugin and produces a ResolvedPlugin that can enter the
|
|
52
|
+
* HookPipeline alongside native plugins.
|
|
53
|
+
*
|
|
54
|
+
* @param definition - The standard plugin definition (from definePlugin() or raw export)
|
|
55
|
+
* @param descriptor - The plugin descriptor with id, version, capabilities, etc.
|
|
56
|
+
* @returns A ResolvedPlugin compatible with HookPipeline
|
|
57
|
+
*/
|
|
58
|
+
function adaptSandboxEntry(definition, descriptor) {
|
|
59
|
+
const pluginId = descriptor.id;
|
|
60
|
+
const version = descriptor.version;
|
|
61
|
+
const resolvedHooks = {};
|
|
62
|
+
if (definition.hooks) for (const [hookName, entry] of Object.entries(definition.hooks)) {
|
|
63
|
+
if (!VALID_HOOK_NAMES_SET.has(hookName)) throw new Error(`Plugin "${pluginId}" declares unknown hook "${hookName}". Valid hooks: ${[...VALID_HOOK_NAMES_SET].join(", ")}`);
|
|
64
|
+
resolvedHooks[hookName] = resolveStandardHook(entry, pluginId);
|
|
65
|
+
}
|
|
66
|
+
const resolvedRoutes = {};
|
|
67
|
+
if (definition.routes) for (const [routeName, routeEntry] of Object.entries(definition.routes)) {
|
|
68
|
+
const standardHandler = routeEntry.handler;
|
|
69
|
+
resolvedRoutes[routeName] = {
|
|
70
|
+
input: routeEntry.input,
|
|
71
|
+
public: routeEntry.public,
|
|
72
|
+
handler: async (ctx) => {
|
|
73
|
+
const routeCtx = {
|
|
74
|
+
input: ctx.input,
|
|
75
|
+
request: ctx.request,
|
|
76
|
+
requestMeta: ctx.requestMeta
|
|
77
|
+
};
|
|
78
|
+
const { input: _, request: __, requestMeta: ___, ...pluginCtx } = ctx;
|
|
79
|
+
return standardHandler(routeCtx, pluginCtx);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const rawCapabilities = descriptor.capabilities ?? [];
|
|
84
|
+
for (const cap of rawCapabilities) if (!VALID_CAPABILITIES_SET.has(cap)) throw new Error(`Invalid capability "${cap}" in plugin "${pluginId}". Valid capabilities: ${[...VALID_CAPABILITIES_SET].join(", ")}`);
|
|
85
|
+
const capabilities = [...rawCapabilities];
|
|
86
|
+
const allowedHosts = descriptor.allowedHosts ?? [];
|
|
87
|
+
if (capabilities.includes("write:content") && !capabilities.includes("read:content")) capabilities.push("read:content");
|
|
88
|
+
if (capabilities.includes("write:media") && !capabilities.includes("read:media")) capabilities.push("read:media");
|
|
89
|
+
if (capabilities.includes("network:fetch:any") && !capabilities.includes("network:fetch")) capabilities.push("network:fetch");
|
|
90
|
+
const rawStorage = descriptor.storage ?? {};
|
|
91
|
+
const storage = {};
|
|
92
|
+
for (const [name, config] of Object.entries(rawStorage)) storage[name] = {
|
|
93
|
+
indexes: config.indexes ?? [],
|
|
94
|
+
uniqueIndexes: config.uniqueIndexes
|
|
95
|
+
};
|
|
96
|
+
const admin = {};
|
|
97
|
+
if (descriptor.adminPages) admin.pages = descriptor.adminPages;
|
|
98
|
+
if (descriptor.adminWidgets) admin.widgets = descriptor.adminWidgets;
|
|
99
|
+
return {
|
|
100
|
+
id: pluginId,
|
|
101
|
+
version,
|
|
102
|
+
capabilities,
|
|
103
|
+
allowedHosts,
|
|
104
|
+
storage,
|
|
105
|
+
hooks: resolvedHooks,
|
|
106
|
+
routes: resolvedRoutes,
|
|
107
|
+
admin
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
112
|
+
export { adaptSandboxEntry };
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-ClPoSABd.mjs";
|
|
2
|
+
import { n as getI18nConfig, r as isI18nEnabled, t as getFallbackChain } from "./config-Cq8H0SfX.mjs";
|
|
3
|
+
import { getRequestContext } from "./request-context.mjs";
|
|
4
|
+
|
|
5
|
+
//#region src/visual-editing/editable.ts
|
|
6
|
+
/**
|
|
7
|
+
* Create an editable proxy for an entry.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* - `{...entry.edit}` - entry-level annotation (includes status/hasDraft)
|
|
11
|
+
* - `{...entry.edit.title}` - field-level annotation
|
|
12
|
+
* - `{...entry.edit['nested.field']}` - nested field (bracket notation)
|
|
13
|
+
*/
|
|
14
|
+
function createEditable(collection, id, options) {
|
|
15
|
+
const base = {
|
|
16
|
+
collection,
|
|
17
|
+
id,
|
|
18
|
+
...options?.status && { status: options.status },
|
|
19
|
+
...options?.hasDraft && { hasDraft: true }
|
|
20
|
+
};
|
|
21
|
+
return new Proxy({}, {
|
|
22
|
+
get(_, prop) {
|
|
23
|
+
if (prop === "toJSON") return () => ({ "data-dineway-ref": JSON.stringify(base) });
|
|
24
|
+
if (typeof prop === "symbol") return void 0;
|
|
25
|
+
if (prop === "data-dineway-ref") return JSON.stringify(base);
|
|
26
|
+
return { "data-dineway-ref": JSON.stringify({
|
|
27
|
+
...base,
|
|
28
|
+
field: String(prop)
|
|
29
|
+
}) };
|
|
30
|
+
},
|
|
31
|
+
ownKeys() {
|
|
32
|
+
return ["data-dineway-ref"];
|
|
33
|
+
},
|
|
34
|
+
getOwnPropertyDescriptor(_, prop) {
|
|
35
|
+
if (prop === "data-dineway-ref") return {
|
|
36
|
+
configurable: true,
|
|
37
|
+
enumerable: true,
|
|
38
|
+
value: JSON.stringify(base)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Create a noop proxy for production mode.
|
|
45
|
+
* Spreading this produces no attributes.
|
|
46
|
+
*/
|
|
47
|
+
function createNoop() {
|
|
48
|
+
return new Proxy({}, {
|
|
49
|
+
get(_, prop) {
|
|
50
|
+
if (typeof prop === "symbol") return void 0;
|
|
51
|
+
},
|
|
52
|
+
ownKeys() {
|
|
53
|
+
return [];
|
|
54
|
+
},
|
|
55
|
+
getOwnPropertyDescriptor() {}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/query.ts
|
|
61
|
+
/**
|
|
62
|
+
* Query functions for Dineway content
|
|
63
|
+
*
|
|
64
|
+
* These wrap Astro's getLiveCollection/getLiveEntry with type filtering.
|
|
65
|
+
* Use these instead of calling Astro's functions directly.
|
|
66
|
+
*
|
|
67
|
+
* Error handling follows Astro's pattern - returns { entries/entry, error }
|
|
68
|
+
* so callers can gracefully handle errors (including 404s).
|
|
69
|
+
*
|
|
70
|
+
* Preview mode is handled implicitly via ALS request context —
|
|
71
|
+
* no parameters needed. The middleware verifies the preview token
|
|
72
|
+
* and sets the context; query functions read it automatically.
|
|
73
|
+
*/
|
|
74
|
+
var query_exports = /* @__PURE__ */ __exportAll({
|
|
75
|
+
getDinewayCollection: () => getDinewayCollection,
|
|
76
|
+
getDinewayEntry: () => getDinewayEntry,
|
|
77
|
+
getEditMeta: () => getEditMeta,
|
|
78
|
+
getTranslations: () => getTranslations,
|
|
79
|
+
resolveDinewayPath: () => resolveDinewayPath
|
|
80
|
+
});
|
|
81
|
+
const COLLECTION_NAME = "_dineway";
|
|
82
|
+
/** Symbol key for edit metadata on PT arrays — avoids collision with user data */
|
|
83
|
+
const DINEWAY_EDIT = Symbol.for("__dineway");
|
|
84
|
+
/** Type guard for EditFieldMeta */
|
|
85
|
+
function isEditFieldMeta(value) {
|
|
86
|
+
if (typeof value !== "object" || value === null) return false;
|
|
87
|
+
if (!("collection" in value) || !("id" in value) || !("field" in value)) return false;
|
|
88
|
+
const { collection, id, field } = value;
|
|
89
|
+
return typeof collection === "string" && typeof id === "string" && typeof field === "string";
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Read edit metadata from a value (returns undefined if not tagged).
|
|
93
|
+
* Uses Object.getOwnPropertyDescriptor to access Symbol-keyed property
|
|
94
|
+
* without an unsafe type assertion.
|
|
95
|
+
*/
|
|
96
|
+
function getEditMeta(value) {
|
|
97
|
+
if (value && typeof value === "object") {
|
|
98
|
+
const meta = Object.getOwnPropertyDescriptor(value, DINEWAY_EDIT)?.value;
|
|
99
|
+
if (isEditFieldMeta(meta)) return meta;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Tag PT-like arrays in entry data with edit metadata (non-enumerable).
|
|
104
|
+
* A PT array is identified by: is an array, first element has _type property.
|
|
105
|
+
*/
|
|
106
|
+
function tagEditableFields(data, collection, id) {
|
|
107
|
+
for (const [field, value] of Object.entries(data)) if (Array.isArray(value) && value.length > 0 && value[0] && typeof value[0] === "object" && "_type" in value[0]) Object.defineProperty(value, DINEWAY_EDIT, {
|
|
108
|
+
value: {
|
|
109
|
+
collection,
|
|
110
|
+
id,
|
|
111
|
+
field
|
|
112
|
+
},
|
|
113
|
+
enumerable: false,
|
|
114
|
+
configurable: true
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/** Safely read a string field from a Record, with optional fallback */
|
|
118
|
+
function dataStr(data, key, fallback = "") {
|
|
119
|
+
const val = data[key];
|
|
120
|
+
return typeof val === "string" ? val : fallback;
|
|
121
|
+
}
|
|
122
|
+
/** Type guard for Record<string, unknown> */
|
|
123
|
+
function isRecord(value) {
|
|
124
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
125
|
+
}
|
|
126
|
+
/** Extract data as Record from an Astro entry (which is any-typed) */
|
|
127
|
+
function entryData(entry) {
|
|
128
|
+
return isRecord(entry.data) ? entry.data : {};
|
|
129
|
+
}
|
|
130
|
+
/** Extract the database ID from entry data (data.id is the ULID, entry.id is the slug) */
|
|
131
|
+
function entryDatabaseId(entry) {
|
|
132
|
+
return dataStr(entryData(entry), "id") || entry.id;
|
|
133
|
+
}
|
|
134
|
+
/** Extract edit options from entry data for the proxy */
|
|
135
|
+
function entryEditOptions(entry) {
|
|
136
|
+
const data = entryData(entry);
|
|
137
|
+
const status = dataStr(data, "status", "draft");
|
|
138
|
+
const draftRevisionId = dataStr(data, "draftRevisionId") || void 0;
|
|
139
|
+
const liveRevisionId = dataStr(data, "liveRevisionId") || void 0;
|
|
140
|
+
return {
|
|
141
|
+
status,
|
|
142
|
+
hasDraft: !!draftRevisionId && draftRevisionId !== liveRevisionId
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get all entries of a content type
|
|
147
|
+
*
|
|
148
|
+
* Returns { entries, error } for graceful error handling.
|
|
149
|
+
*
|
|
150
|
+
* When dineway-env.d.ts is generated, the collection name will be
|
|
151
|
+
* type-checked and the return type will be inferred automatically.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* import { getDinewayCollection } from "dineway";
|
|
156
|
+
*
|
|
157
|
+
* const { entries: posts, error } = await getDinewayCollection("posts");
|
|
158
|
+
* if (error) {
|
|
159
|
+
* console.error("Failed to load posts:", error);
|
|
160
|
+
* return;
|
|
161
|
+
* }
|
|
162
|
+
* // posts[0].data.title is typed (if dineway-env.d.ts exists)
|
|
163
|
+
*
|
|
164
|
+
* // With filters
|
|
165
|
+
* const { entries: drafts } = await getDinewayCollection("posts", { status: "draft" });
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
async function getDinewayCollection(type, filter) {
|
|
169
|
+
const { getLiveCollection } = await import("astro:content");
|
|
170
|
+
const ctx = getRequestContext();
|
|
171
|
+
const i18nConfig = getI18nConfig();
|
|
172
|
+
const resolvedLocale = filter?.locale ?? ctx?.locale ?? (isI18nEnabled() ? i18nConfig.defaultLocale : void 0);
|
|
173
|
+
const result = await getLiveCollection(COLLECTION_NAME, {
|
|
174
|
+
type,
|
|
175
|
+
status: filter?.status,
|
|
176
|
+
limit: filter?.limit,
|
|
177
|
+
cursor: filter?.cursor,
|
|
178
|
+
where: filter?.where,
|
|
179
|
+
orderBy: filter?.orderBy,
|
|
180
|
+
locale: resolvedLocale
|
|
181
|
+
});
|
|
182
|
+
const { entries, error, cacheHint } = result;
|
|
183
|
+
const rawCursor = Object.getOwnPropertyDescriptor(result, "nextCursor")?.value;
|
|
184
|
+
const nextCursor = typeof rawCursor === "string" ? rawCursor : void 0;
|
|
185
|
+
if (error) return {
|
|
186
|
+
entries: [],
|
|
187
|
+
error,
|
|
188
|
+
cacheHint: {}
|
|
189
|
+
};
|
|
190
|
+
const isEditMode = ctx?.editMode ?? false;
|
|
191
|
+
const entriesWithEdit = entries.map((entry) => {
|
|
192
|
+
const dbId = entryDatabaseId(entry);
|
|
193
|
+
if (isEditMode) tagEditableFields(entryData(entry), type, dbId);
|
|
194
|
+
return {
|
|
195
|
+
...entry,
|
|
196
|
+
edit: isEditMode ? createEditable(type, dbId, entryEditOptions(entry)) : createNoop()
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
await hydrateEntryBylines(type, entriesWithEdit);
|
|
200
|
+
return {
|
|
201
|
+
entries: entriesWithEdit,
|
|
202
|
+
nextCursor,
|
|
203
|
+
cacheHint: cacheHint ?? {}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Get a single entry by type and ID/slug
|
|
208
|
+
*
|
|
209
|
+
* Returns { entry, error, isPreview } for graceful error handling.
|
|
210
|
+
* - entry is null if not found (not an error)
|
|
211
|
+
* - error is set only for actual errors (db issues, etc.)
|
|
212
|
+
*
|
|
213
|
+
* Preview mode is detected automatically from request context (ALS).
|
|
214
|
+
* When the URL has a valid `_preview` token, the middleware sets preview
|
|
215
|
+
* context and this function serves draft revision data if available.
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```ts
|
|
219
|
+
* import { getDinewayEntry } from "dineway";
|
|
220
|
+
*
|
|
221
|
+
* // Simple usage — preview just works via middleware
|
|
222
|
+
* const { entry: post, isPreview, error } = await getDinewayEntry("posts", "my-slug");
|
|
223
|
+
* if (!post) return Astro.redirect("/404");
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
async function getDinewayEntry(type, id, options) {
|
|
227
|
+
const { getLiveEntry } = await import("astro:content");
|
|
228
|
+
const ctx = getRequestContext();
|
|
229
|
+
const preview = ctx?.preview;
|
|
230
|
+
const isEditMode = ctx?.editMode ?? false;
|
|
231
|
+
const serveDrafts = !!preview && preview.collection === type || isEditMode;
|
|
232
|
+
const requestedLocale = options?.locale ?? ctx?.locale;
|
|
233
|
+
/** Wrap a raw Astro entry with edit proxy, tagging editable fields if needed */
|
|
234
|
+
function wrapEntry(raw) {
|
|
235
|
+
const dbId = entryDatabaseId(raw);
|
|
236
|
+
if (isEditMode) tagEditableFields(entryData(raw), type, dbId);
|
|
237
|
+
return {
|
|
238
|
+
...raw,
|
|
239
|
+
edit: isEditMode ? createEditable(type, dbId, entryEditOptions(raw)) : createNoop()
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/** Check if an entry is publicly visible (published or scheduled past its time) */
|
|
243
|
+
function isVisible(entry) {
|
|
244
|
+
const data = entryData(entry);
|
|
245
|
+
const status = dataStr(data, "status");
|
|
246
|
+
const scheduledAt = dataStr(data, "scheduledAt") || void 0;
|
|
247
|
+
return status === "published" || !!(status === "scheduled" && scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date());
|
|
248
|
+
}
|
|
249
|
+
const localeChain = requestedLocale && isI18nEnabled() ? getFallbackChain(requestedLocale) : [requestedLocale];
|
|
250
|
+
/** Return a successful EntryResult with bylines hydrated */
|
|
251
|
+
async function successResult(wrapped, opts) {
|
|
252
|
+
await hydrateEntryBylines(type, [wrapped]);
|
|
253
|
+
return {
|
|
254
|
+
entry: wrapped,
|
|
255
|
+
isPreview: opts.isPreview,
|
|
256
|
+
fallbackLocale: opts.fallbackLocale,
|
|
257
|
+
cacheHint: opts.cacheHint
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
if (serveDrafts) {
|
|
261
|
+
for (let i = 0; i < localeChain.length; i++) {
|
|
262
|
+
const locale = localeChain[i];
|
|
263
|
+
const fallbackLocale = i > 0 ? locale : void 0;
|
|
264
|
+
const { entry: baseEntry, error: baseError, cacheHint } = await getLiveEntry(COLLECTION_NAME, {
|
|
265
|
+
type,
|
|
266
|
+
id,
|
|
267
|
+
locale
|
|
268
|
+
});
|
|
269
|
+
if (baseError) return {
|
|
270
|
+
entry: null,
|
|
271
|
+
error: baseError,
|
|
272
|
+
isPreview: serveDrafts,
|
|
273
|
+
cacheHint: {}
|
|
274
|
+
};
|
|
275
|
+
if (!baseEntry) continue;
|
|
276
|
+
const draftRevisionId = dataStr(entryData(baseEntry), "draftRevisionId") || void 0;
|
|
277
|
+
if (draftRevisionId) {
|
|
278
|
+
const { entry: draftEntry, error: draftError } = await getLiveEntry(COLLECTION_NAME, {
|
|
279
|
+
type,
|
|
280
|
+
id,
|
|
281
|
+
revisionId: draftRevisionId,
|
|
282
|
+
locale
|
|
283
|
+
});
|
|
284
|
+
if (!draftError && draftEntry) return successResult(wrapEntry(draftEntry), {
|
|
285
|
+
isPreview: serveDrafts,
|
|
286
|
+
fallbackLocale,
|
|
287
|
+
cacheHint: cacheHint ?? {}
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
return successResult(wrapEntry(baseEntry), {
|
|
291
|
+
isPreview: serveDrafts,
|
|
292
|
+
fallbackLocale,
|
|
293
|
+
cacheHint: cacheHint ?? {}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
entry: null,
|
|
298
|
+
isPreview: serveDrafts,
|
|
299
|
+
cacheHint: {}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
for (let i = 0; i < localeChain.length; i++) {
|
|
303
|
+
const locale = localeChain[i];
|
|
304
|
+
const fallbackLocale = i > 0 ? locale : void 0;
|
|
305
|
+
const { entry, error, cacheHint } = await getLiveEntry(COLLECTION_NAME, {
|
|
306
|
+
type,
|
|
307
|
+
id,
|
|
308
|
+
locale
|
|
309
|
+
});
|
|
310
|
+
if (error) return {
|
|
311
|
+
entry: null,
|
|
312
|
+
error,
|
|
313
|
+
isPreview: false,
|
|
314
|
+
cacheHint: {}
|
|
315
|
+
};
|
|
316
|
+
if (entry && isVisible(entry)) return successResult(wrapEntry(entry), {
|
|
317
|
+
isPreview: false,
|
|
318
|
+
fallbackLocale,
|
|
319
|
+
cacheHint: cacheHint ?? {}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
entry: null,
|
|
324
|
+
isPreview: false,
|
|
325
|
+
cacheHint: {}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Eagerly hydrate byline data onto entry.data for one or more entries.
|
|
330
|
+
*
|
|
331
|
+
* Attaches `bylines` (array of ContentBylineCredit) and `byline`
|
|
332
|
+
* (primary BylineSummary or null) to each entry's data object.
|
|
333
|
+
* Uses batch queries to avoid N+1.
|
|
334
|
+
*
|
|
335
|
+
* Fails silently if the byline tables don't exist yet (pre-migration).
|
|
336
|
+
*/
|
|
337
|
+
async function hydrateEntryBylines(type, entries) {
|
|
338
|
+
if (entries.length === 0) return;
|
|
339
|
+
try {
|
|
340
|
+
const { getBylinesForEntries } = await import("./bylines-DyqBV9EQ.mjs").then((n) => n.t);
|
|
341
|
+
const ids = entries.map((e) => dataStr(entryData(e), "id")).filter(Boolean);
|
|
342
|
+
if (ids.length === 0) return;
|
|
343
|
+
const bylinesMap = await getBylinesForEntries(type, ids);
|
|
344
|
+
for (const entry of entries) {
|
|
345
|
+
const data = entryData(entry);
|
|
346
|
+
const dbId = dataStr(data, "id");
|
|
347
|
+
if (!dbId) continue;
|
|
348
|
+
const credits = bylinesMap.get(dbId) ?? [];
|
|
349
|
+
data.bylines = credits;
|
|
350
|
+
data.byline = credits[0]?.byline ?? null;
|
|
351
|
+
}
|
|
352
|
+
} catch (err) {
|
|
353
|
+
const msg = err instanceof Error ? err.message : "";
|
|
354
|
+
if (!msg.includes("no such table")) console.warn("[dineway] Failed to hydrate bylines:", msg);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Get all translations of a content item.
|
|
359
|
+
*
|
|
360
|
+
* Given a content entry, returns all locale variants that share the same
|
|
361
|
+
* translation group. This is useful for building language switcher UI.
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* ```ts
|
|
365
|
+
* import { getDinewayEntry, getTranslations } from "dineway";
|
|
366
|
+
*
|
|
367
|
+
* const { entry: post } = await getDinewayEntry("posts", "hello-world", { locale: "en" });
|
|
368
|
+
* const { translations } = await getTranslations("posts", post.data.id);
|
|
369
|
+
* // translations = [{ id: "...", locale: "en", slug: "hello-world", status: "published" }, ...]
|
|
370
|
+
* ```
|
|
371
|
+
*/
|
|
372
|
+
async function getTranslations(type, id) {
|
|
373
|
+
try {
|
|
374
|
+
const db = (await import("./loader-qKmo0wAY.mjs").then((n) => n.r)).getDb;
|
|
375
|
+
const dbInstance = await db();
|
|
376
|
+
const { ContentRepository } = await import("./content-zSgdNmnt.mjs").then((n) => n.n);
|
|
377
|
+
const repo = new ContentRepository(dbInstance);
|
|
378
|
+
const item = await repo.findByIdOrSlug(type, id);
|
|
379
|
+
if (!item) return {
|
|
380
|
+
translationGroup: "",
|
|
381
|
+
translations: [],
|
|
382
|
+
error: /* @__PURE__ */ new Error(`Content item not found: ${id}`)
|
|
383
|
+
};
|
|
384
|
+
const group = item.translationGroup || item.id;
|
|
385
|
+
return {
|
|
386
|
+
translationGroup: group,
|
|
387
|
+
translations: (await repo.findTranslations(type, group)).map((t) => ({
|
|
388
|
+
id: t.id,
|
|
389
|
+
locale: t.locale || "en",
|
|
390
|
+
slug: t.slug,
|
|
391
|
+
status: t.status
|
|
392
|
+
}))
|
|
393
|
+
};
|
|
394
|
+
} catch (error) {
|
|
395
|
+
return {
|
|
396
|
+
translationGroup: "",
|
|
397
|
+
translations: [],
|
|
398
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/** Matches `{paramName}` placeholders in URL patterns */
|
|
403
|
+
const URL_PARAM_PATTERN = /\{(\w+)\}/g;
|
|
404
|
+
/** Convert a URL pattern like "/blog/{slug}" to a regex and param name list */
|
|
405
|
+
function patternToRegex(pattern) {
|
|
406
|
+
const paramNames = [];
|
|
407
|
+
const regexStr = pattern.replace(URL_PARAM_PATTERN, (_match, name) => {
|
|
408
|
+
paramNames.push(name);
|
|
409
|
+
return "([^/]+)";
|
|
410
|
+
});
|
|
411
|
+
return {
|
|
412
|
+
regex: new RegExp(`^${regexStr}$`),
|
|
413
|
+
paramNames
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Resolve a URL path to a content entry by matching against collection URL patterns.
|
|
418
|
+
*
|
|
419
|
+
* Loads all collections with a `urlPattern` set, converts each pattern to a regex,
|
|
420
|
+
* and tests the given path. On match, extracts the slug and fetches the entry.
|
|
421
|
+
*
|
|
422
|
+
* @example
|
|
423
|
+
* ```ts
|
|
424
|
+
* import { resolveDinewayPath } from "dineway";
|
|
425
|
+
*
|
|
426
|
+
* // Given pages with urlPattern "/{slug}" and posts with "/blog/{slug}":
|
|
427
|
+
* const result = await resolveDinewayPath("/blog/hello-world");
|
|
428
|
+
* if (result) {
|
|
429
|
+
* console.log(result.collection); // "posts"
|
|
430
|
+
* console.log(result.params.slug); // "hello-world"
|
|
431
|
+
* console.log(result.entry.data); // post data
|
|
432
|
+
* }
|
|
433
|
+
* ```
|
|
434
|
+
*/
|
|
435
|
+
async function resolveDinewayPath(path) {
|
|
436
|
+
const { getDb } = await import("./loader-qKmo0wAY.mjs").then((n) => n.r);
|
|
437
|
+
const { SchemaRegistry } = await import("./registry-DSd1GWB8.mjs").then((n) => n.r);
|
|
438
|
+
const collections = await new SchemaRegistry(await getDb()).listCollections();
|
|
439
|
+
for (const collection of collections) {
|
|
440
|
+
if (!collection.urlPattern) continue;
|
|
441
|
+
const { regex, paramNames } = patternToRegex(collection.urlPattern);
|
|
442
|
+
const match = path.match(regex);
|
|
443
|
+
if (!match) continue;
|
|
444
|
+
const params = {};
|
|
445
|
+
for (let i = 0; i < paramNames.length; i++) params[paramNames[i]] = match[i + 1];
|
|
446
|
+
const slug = params.slug;
|
|
447
|
+
if (!slug) continue;
|
|
448
|
+
const { entry } = await getDinewayEntry(collection.slug, slug);
|
|
449
|
+
if (entry) return {
|
|
450
|
+
entry,
|
|
451
|
+
collection: collection.slug,
|
|
452
|
+
params
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
//#endregion
|
|
459
|
+
export { query_exports as a, createNoop as c, getTranslations as i, getDinewayEntry as n, resolveDinewayPath as o, getEditMeta as r, createEditable as s, getDinewayCollection as t };
|