@dalgoridim/headless-cms 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +126 -0
- package/README.md +238 -48
- package/REDESIGN.md +250 -0
- package/dist/adapters/firestore/index.cjs +24 -3
- package/dist/adapters/firestore/index.cjs.map +1 -1
- package/dist/adapters/firestore/index.js +24 -3
- package/dist/adapters/firestore/index.js.map +1 -1
- package/dist/adapters/postgres/index.cjs +37 -11
- package/dist/adapters/postgres/index.cjs.map +1 -1
- package/dist/adapters/postgres/index.js +37 -11
- package/dist/adapters/postgres/index.js.map +1 -1
- package/dist/client/index.cjs +94 -543
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +89 -26
- package/dist/client/index.d.ts +89 -26
- package/dist/client/index.js +96 -547
- package/dist/client/index.js.map +1 -1
- package/dist/index.cjs +16 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -16
- package/dist/index.d.ts +81 -16
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/server/index.cjs +63 -9
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +35 -5
- package/dist/server/index.d.ts +35 -5
- package/dist/server/index.js +61 -8
- package/dist/server/index.js.map +1 -1
- package/package.json +5 -10
package/dist/server/index.cjs
CHANGED
|
@@ -22,7 +22,8 @@ var server_exports = {};
|
|
|
22
22
|
__export(server_exports, {
|
|
23
23
|
UnauthorizedError: () => UnauthorizedError,
|
|
24
24
|
createAdminGate: () => createAdminGate,
|
|
25
|
-
createCmsHandlers: () => createCmsHandlers
|
|
25
|
+
createCmsHandlers: () => createCmsHandlers,
|
|
26
|
+
resolveRelations: () => resolveRelations
|
|
26
27
|
});
|
|
27
28
|
module.exports = __toCommonJS(server_exports);
|
|
28
29
|
|
|
@@ -33,13 +34,15 @@ var UnauthorizedError = class extends Error {
|
|
|
33
34
|
this.name = "UnauthorizedError";
|
|
34
35
|
}
|
|
35
36
|
};
|
|
36
|
-
|
|
37
|
+
var defaultAuthorize = (identity) => identity.isAdmin === true;
|
|
38
|
+
function createAdminGate(auth, authorize = defaultAuthorize) {
|
|
37
39
|
return async function requireAdmin(req) {
|
|
38
40
|
const identity = await auth.verifyRequest(req);
|
|
39
|
-
if (!identity
|
|
40
|
-
throw new UnauthorizedError(
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
if (!identity) {
|
|
42
|
+
throw new UnauthorizedError("Unauthorized");
|
|
43
|
+
}
|
|
44
|
+
if (!await authorize(identity, req)) {
|
|
45
|
+
throw new UnauthorizedError("Forbidden - Not an authorized admin");
|
|
43
46
|
}
|
|
44
47
|
return identity;
|
|
45
48
|
};
|
|
@@ -60,8 +63,8 @@ function errorResponse(error) {
|
|
|
60
63
|
return json({ error: message }, 403);
|
|
61
64
|
}
|
|
62
65
|
function createCmsHandlers(deps) {
|
|
63
|
-
const { data, auth, storage } = deps;
|
|
64
|
-
const requireAdmin = createAdminGate(auth);
|
|
66
|
+
const { data, auth, storage, authorize } = deps;
|
|
67
|
+
const requireAdmin = createAdminGate(auth, authorize);
|
|
65
68
|
const GET = async (req, ctx) => {
|
|
66
69
|
try {
|
|
67
70
|
await requireAdmin(req);
|
|
@@ -119,10 +122,61 @@ function createCmsHandlers(deps) {
|
|
|
119
122
|
};
|
|
120
123
|
return { GET, PATCH, PUT, DELETE, sign };
|
|
121
124
|
}
|
|
125
|
+
|
|
126
|
+
// src/server/relations.ts
|
|
127
|
+
function isRef(v) {
|
|
128
|
+
return typeof v === "object" && v !== null && typeof v.collection === "string" && typeof v.id === "string";
|
|
129
|
+
}
|
|
130
|
+
async function resolveRelations(adapter, docs, options = {}) {
|
|
131
|
+
var _a, _b;
|
|
132
|
+
const list = Array.isArray(docs) ? docs : [docs];
|
|
133
|
+
const relations = (_a = options.relations) != null ? _a : {};
|
|
134
|
+
const fields = (_b = options.populate) != null ? _b : Object.keys(relations);
|
|
135
|
+
if (!fields.length || !list.length) return docs;
|
|
136
|
+
const cache = /* @__PURE__ */ new Map();
|
|
137
|
+
const load = (collection, id) => {
|
|
138
|
+
const key = `${collection}\0${id}`;
|
|
139
|
+
let pending = cache.get(key);
|
|
140
|
+
if (!pending) {
|
|
141
|
+
pending = adapter.fetchById(collection, id);
|
|
142
|
+
cache.set(key, pending);
|
|
143
|
+
}
|
|
144
|
+
return pending;
|
|
145
|
+
};
|
|
146
|
+
const toRef = (field, value) => {
|
|
147
|
+
if (isRef(value)) return value;
|
|
148
|
+
if (typeof value === "string" && relations[field]) {
|
|
149
|
+
return { collection: relations[field].collection, id: value };
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
};
|
|
153
|
+
const resolveValue = async (field, value) => {
|
|
154
|
+
const ref = toRef(field, value);
|
|
155
|
+
if (!ref) return value;
|
|
156
|
+
const resolved = await load(ref.collection, ref.id);
|
|
157
|
+
return resolved != null ? resolved : value;
|
|
158
|
+
};
|
|
159
|
+
await Promise.all(
|
|
160
|
+
list.map(async (doc) => {
|
|
161
|
+
for (const field of fields) {
|
|
162
|
+
const value = doc[field];
|
|
163
|
+
if (Array.isArray(value)) {
|
|
164
|
+
doc[field] = await Promise.all(
|
|
165
|
+
value.map((v) => resolveValue(field, v))
|
|
166
|
+
);
|
|
167
|
+
} else {
|
|
168
|
+
doc[field] = await resolveValue(field, value);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
);
|
|
173
|
+
return docs;
|
|
174
|
+
}
|
|
122
175
|
// Annotate the CommonJS export names for ESM import in node:
|
|
123
176
|
0 && (module.exports = {
|
|
124
177
|
UnauthorizedError,
|
|
125
178
|
createAdminGate,
|
|
126
|
-
createCmsHandlers
|
|
179
|
+
createCmsHandlers,
|
|
180
|
+
resolveRelations
|
|
127
181
|
});
|
|
128
182
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/index.ts","../../src/server/createAdminGate.ts","../../src/server/createCmsHandlers.ts"],"sourcesContent":["export {\n createCmsHandlers,\n type CmsHandlersDeps,\n} from \"./createCmsHandlers\";\nexport { createAdminGate, UnauthorizedError } from \"./createAdminGate\";\n\nexport type {\n DataAdapter,\n AuthAdapter,\n AuthIdentity,\n StorageAdapter,\n ClientStorageAdapter,\n ServerStorageAdapter,\n Query,\n Section,\n NestedSections,\n} from \"../types\";\n","import type { AuthAdapter, AuthIdentity } from \"../types\";\n\n/** Thrown when a request fails the admin gate; carried up to the route handler. */\nexport class UnauthorizedError extends Error {\n constructor(message = \"Unauthorized\") {\n super(message);\n this.name = \"UnauthorizedError\";\n }\n}\n\n/**\n * Wraps an {@link AuthAdapter} into a reusable server gate. Throws\n * {@link UnauthorizedError} unless the request resolves to an admin identity.\n */\nexport function createAdminGate(auth: AuthAdapter) {\n return async function requireAdmin(req: Request): Promise<AuthIdentity> {\n const identity = await auth.verifyRequest(req);\n if (!identity || !identity.isAdmin) {\n throw new UnauthorizedError(\n identity ? \"Forbidden - Not an authorized admin\" : \"Unauthorized\",\n );\n }\n return identity;\n };\n}\n","import type { DataAdapter, AuthAdapter, ServerStorageAdapter } from \"../types\";\nimport { createAdminGate, UnauthorizedError } from \"./createAdminGate\";\n\nexport interface CmsHandlersDeps {\n data: DataAdapter;\n auth: AuthAdapter;\n storage?: ServerStorageAdapter;\n}\n\n/** Next.js App Router passes dynamic params as a promise. */\ntype RouteContext = {\n params: Promise<{ collection: string; id: string }>;\n};\n\ntype RouteHandler = (req: Request, ctx: RouteContext) => Promise<Response>;\ntype SignHandler = (req: Request) => Promise<Response>;\n\nfunction json(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\n/**\n * Translate any thrown error into a response. Auth failures return 401 with\n * `{ logout: true }` so a client interceptor can force sign-out.\n */\nfunction errorResponse(error: unknown): Response {\n if (error instanceof UnauthorizedError && error.message === \"Unauthorized\") {\n return json({ error: \"Unauthorized\", logout: true }, 401);\n }\n const message = error instanceof Error ? error.message : \"Request failed\";\n return json({ error: message }, 403);\n}\n\n/**\n * Builds the generic admin CRUD handlers, replacing a hand-written\n * `/api/admin/[collection]/[id]/route.ts`. Mount like:\n *\n * ```ts\n * // app/api/admin/[collection]/[id]/route.ts\n * export const { GET, PATCH, PUT, DELETE } = createCmsHandlers({ data, auth });\n * ```\n *\n * If a `storage` adapter with a `sign` method is provided, also mount its sign\n * handler at e.g. `app/api/admin/sign/route.ts`:\n *\n * ```ts\n * export const POST = createCmsHandlers({ data, auth, storage }).sign;\n * ```\n */\nexport function createCmsHandlers(deps: CmsHandlersDeps): {\n GET: RouteHandler;\n PATCH: RouteHandler;\n PUT: RouteHandler;\n DELETE: RouteHandler;\n sign: SignHandler;\n} {\n const { data, auth, storage } = deps;\n const requireAdmin = createAdminGate(auth);\n\n const GET: RouteHandler = async (req, ctx) => {\n try {\n await requireAdmin(req);\n const { collection, id } = await ctx.params;\n const doc = await data.fetchById(collection, id);\n if (!doc) return json({ error: \"Document not found\" }, 404);\n return json(doc);\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n const PATCH: RouteHandler = async (req, ctx) => {\n try {\n await requireAdmin(req);\n const { collection, id } = await ctx.params;\n const body = await req.json();\n await data.update(collection, id, body);\n return json({ ok: true });\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n const PUT: RouteHandler = async (req, ctx) => {\n try {\n await requireAdmin(req);\n const { collection, id } = await ctx.params;\n const body = await req.json();\n await data.upsert(collection, id, body);\n return json({ ok: true });\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n const DELETE: RouteHandler = async (req, ctx) => {\n try {\n await requireAdmin(req);\n const { collection, id } = await ctx.params;\n await data.delete(collection, id);\n return json({ ok: true });\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n const sign: SignHandler = async (req) => {\n try {\n await requireAdmin(req);\n if (!storage?.sign) {\n return json({ error: \"No storage adapter with sign() configured\" }, 404);\n }\n const result = await storage.sign(req);\n return json(result);\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n return { GET, PATCH, PUT, DELETE, sign };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/server/index.ts","../../src/server/createAdminGate.ts","../../src/server/createCmsHandlers.ts","../../src/server/relations.ts"],"sourcesContent":["export {\n createCmsHandlers,\n type CmsHandlersDeps,\n} from \"./createCmsHandlers\";\nexport { createAdminGate, UnauthorizedError } from \"./createAdminGate\";\nexport {\n resolveRelations,\n type ResolveRelationsOptions,\n} from \"./relations\";\n\nexport type {\n DataAdapter,\n AuthAdapter,\n AuthIdentity,\n AuthorizeFn,\n StorageAdapter,\n ClientStorageAdapter,\n ServerStorageAdapter,\n Query,\n QueryFilter,\n QueryFilterGroup,\n QueryCondition,\n QueryFilterOp,\n Ref,\n RelationConfig,\n Editable,\n EntityAddress,\n Section,\n NestedSections,\n} from \"../types\";\n","import type { AuthAdapter, AuthIdentity, AuthorizeFn } from \"../types\";\n\n/** Thrown when a request fails the admin gate; carried up to the route handler. */\nexport class UnauthorizedError extends Error {\n constructor(message = \"Unauthorized\") {\n super(message);\n this.name = \"UnauthorizedError\";\n }\n}\n\n/** Default authorization: admin flag must be set. */\nconst defaultAuthorize: AuthorizeFn = (identity) => identity.isAdmin === true;\n\n/**\n * Wraps an {@link AuthAdapter} into a reusable server gate. Throws\n * {@link UnauthorizedError} unless the request resolves to an identity that\n * passes `authorize`. `authorize` defaults to `identity.isAdmin === true`;\n * override it to gate on roles, scopes, tenants, etc.\n */\nexport function createAdminGate(auth: AuthAdapter, authorize: AuthorizeFn = defaultAuthorize) {\n return async function requireAdmin(req: Request): Promise<AuthIdentity> {\n const identity = await auth.verifyRequest(req);\n if (!identity) {\n throw new UnauthorizedError(\"Unauthorized\");\n }\n if (!(await authorize(identity, req))) {\n throw new UnauthorizedError(\"Forbidden - Not an authorized admin\");\n }\n return identity;\n };\n}\n","import type {\n DataAdapter,\n AuthAdapter,\n AuthorizeFn,\n ServerStorageAdapter,\n} from \"../types\";\nimport { createAdminGate, UnauthorizedError } from \"./createAdminGate\";\n\nexport interface CmsHandlersDeps {\n data: DataAdapter;\n auth: AuthAdapter;\n storage?: ServerStorageAdapter;\n /** Custom authorization predicate. Defaults to `identity.isAdmin === true`. */\n authorize?: AuthorizeFn;\n}\n\n/** Next.js App Router passes dynamic params as a promise. */\ntype RouteContext = {\n params: Promise<{ collection: string; id: string }>;\n};\n\ntype RouteHandler = (req: Request, ctx: RouteContext) => Promise<Response>;\ntype SignHandler = (req: Request) => Promise<Response>;\n\nfunction json(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\n/**\n * Translate any thrown error into a response. Auth failures return 401 with\n * `{ logout: true }` so a client interceptor can force sign-out.\n */\nfunction errorResponse(error: unknown): Response {\n if (error instanceof UnauthorizedError && error.message === \"Unauthorized\") {\n return json({ error: \"Unauthorized\", logout: true }, 401);\n }\n const message = error instanceof Error ? error.message : \"Request failed\";\n return json({ error: message }, 403);\n}\n\n/**\n * Builds the generic admin CRUD handlers, replacing a hand-written\n * `/api/admin/[collection]/[id]/route.ts`. Mount like:\n *\n * ```ts\n * // app/api/admin/[collection]/[id]/route.ts\n * export const { GET, PATCH, PUT, DELETE } = createCmsHandlers({ data, auth });\n * ```\n *\n * If a `storage` adapter with a `sign` method is provided, also mount its sign\n * handler at e.g. `app/api/admin/sign/route.ts`:\n *\n * ```ts\n * export const POST = createCmsHandlers({ data, auth, storage }).sign;\n * ```\n */\nexport function createCmsHandlers(deps: CmsHandlersDeps): {\n GET: RouteHandler;\n PATCH: RouteHandler;\n PUT: RouteHandler;\n DELETE: RouteHandler;\n sign: SignHandler;\n} {\n const { data, auth, storage, authorize } = deps;\n const requireAdmin = createAdminGate(auth, authorize);\n\n const GET: RouteHandler = async (req, ctx) => {\n try {\n await requireAdmin(req);\n const { collection, id } = await ctx.params;\n const doc = await data.fetchById(collection, id);\n if (!doc) return json({ error: \"Document not found\" }, 404);\n return json(doc);\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n const PATCH: RouteHandler = async (req, ctx) => {\n try {\n await requireAdmin(req);\n const { collection, id } = await ctx.params;\n const body = await req.json();\n await data.update(collection, id, body);\n return json({ ok: true });\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n const PUT: RouteHandler = async (req, ctx) => {\n try {\n await requireAdmin(req);\n const { collection, id } = await ctx.params;\n const body = await req.json();\n await data.upsert(collection, id, body);\n return json({ ok: true });\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n const DELETE: RouteHandler = async (req, ctx) => {\n try {\n await requireAdmin(req);\n const { collection, id } = await ctx.params;\n await data.delete(collection, id);\n return json({ ok: true });\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n const sign: SignHandler = async (req) => {\n try {\n await requireAdmin(req);\n if (!storage?.sign) {\n return json({ error: \"No storage adapter with sign() configured\" }, 404);\n }\n const result = await storage.sign(req);\n return json(result);\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n return { GET, PATCH, PUT, DELETE, sign };\n}\n","import type { DataAdapter, Ref, RelationConfig } from \"../types\";\n\n/** A field value is a reference when it's a self-describing `{ collection, id }`. */\nfunction isRef(v: unknown): v is Ref {\n return (\n typeof v === \"object\" &&\n v !== null &&\n typeof (v as Ref).collection === \"string\" &&\n typeof (v as Ref).id === \"string\"\n );\n}\n\nexport interface ResolveRelationsOptions {\n /**\n * Which fields to resolve. Defaults to every field named in `relations`. Pass\n * a query's `populate` array here.\n */\n populate?: string[];\n /**\n * Maps bare-id reference fields → their target collection. Not needed for\n * self-describing `{ collection, id }` refs.\n */\n relations?: RelationConfig;\n}\n\n/**\n * Inline-resolves reference fields on one or more documents, backend-agnostic.\n * A reference may be a self-describing `{ collection, id }`, a bare id string\n * (resolved via `relations`), or an array of either. Resolution is deduplicated\n * and parallelized; unresolved refs are left untouched.\n *\n * Lives in the engine, never in an adapter, so it works the same over Postgres,\n * Firestore, or any custom {@link DataAdapter}.\n *\n * Mutates and returns the passed document(s).\n */\nexport async function resolveRelations<T extends Record<string, any>>(\n adapter: DataAdapter,\n docs: T,\n options?: ResolveRelationsOptions,\n): Promise<T>;\nexport async function resolveRelations<T extends Record<string, any>>(\n adapter: DataAdapter,\n docs: T[],\n options?: ResolveRelationsOptions,\n): Promise<T[]>;\nexport async function resolveRelations<T extends Record<string, any>>(\n adapter: DataAdapter,\n docs: T | T[],\n options: ResolveRelationsOptions = {},\n): Promise<T | T[]> {\n const list = Array.isArray(docs) ? docs : [docs];\n const relations = options.relations ?? {};\n const fields = options.populate ?? Object.keys(relations);\n if (!fields.length || !list.length) return docs;\n\n // Dedupe loads across all docs/fields: one fetch per (collection, id).\n const cache = new Map<string, Promise<unknown>>();\n const load = (collection: string, id: string): Promise<unknown> => {\n const key = `${collection}\u0000${id}`;\n let pending = cache.get(key);\n if (!pending) {\n pending = adapter.fetchById(collection, id);\n cache.set(key, pending);\n }\n return pending;\n };\n\n const toRef = (field: string, value: unknown): Ref | null => {\n if (isRef(value)) return value;\n if (typeof value === \"string\" && relations[field]) {\n return { collection: relations[field].collection, id: value };\n }\n return null;\n };\n\n const resolveValue = async (field: string, value: unknown): Promise<unknown> => {\n const ref = toRef(field, value);\n if (!ref) return value;\n const resolved = await load(ref.collection, ref.id);\n return resolved ?? value;\n };\n\n await Promise.all(\n list.map(async (doc) => {\n for (const field of fields) {\n const value = doc[field];\n if (Array.isArray(value)) {\n (doc as Record<string, unknown>)[field] = await Promise.all(\n value.map((v) => resolveValue(field, v)),\n );\n } else {\n (doc as Record<string, unknown>)[field] = await resolveValue(field, value);\n }\n }\n }),\n );\n\n return docs;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACGO,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAC3C,YAAY,UAAU,gBAAgB;AACpC,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAGA,IAAM,mBAAgC,CAAC,aAAa,SAAS,YAAY;AAQlE,SAAS,gBAAgB,MAAmB,YAAyB,kBAAkB;AAC5F,SAAO,eAAe,aAAa,KAAqC;AACtE,UAAM,WAAW,MAAM,KAAK,cAAc,GAAG;AAC7C,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,kBAAkB,cAAc;AAAA,IAC5C;AACA,QAAI,CAAE,MAAM,UAAU,UAAU,GAAG,GAAI;AACrC,YAAM,IAAI,kBAAkB,qCAAqC;AAAA,IACnE;AACA,WAAO;AAAA,EACT;AACF;;;ACNA,SAAS,KAAK,MAAe,SAAS,KAAe;AACnD,SAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;AAAA,IACxC;AAAA,IACA,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,EAChD,CAAC;AACH;AAMA,SAAS,cAAc,OAA0B;AAC/C,MAAI,iBAAiB,qBAAqB,MAAM,YAAY,gBAAgB;AAC1E,WAAO,KAAK,EAAE,OAAO,gBAAgB,QAAQ,KAAK,GAAG,GAAG;AAAA,EAC1D;AACA,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,SAAO,KAAK,EAAE,OAAO,QAAQ,GAAG,GAAG;AACrC;AAkBO,SAAS,kBAAkB,MAMhC;AACA,QAAM,EAAE,MAAM,MAAM,SAAS,UAAU,IAAI;AAC3C,QAAM,eAAe,gBAAgB,MAAM,SAAS;AAEpD,QAAM,MAAoB,OAAO,KAAK,QAAQ;AAC5C,QAAI;AACF,YAAM,aAAa,GAAG;AACtB,YAAM,EAAE,YAAY,GAAG,IAAI,MAAM,IAAI;AACrC,YAAM,MAAM,MAAM,KAAK,UAAU,YAAY,EAAE;AAC/C,UAAI,CAAC,IAAK,QAAO,KAAK,EAAE,OAAO,qBAAqB,GAAG,GAAG;AAC1D,aAAO,KAAK,GAAG;AAAA,IACjB,SAAS,OAAO;AACd,aAAO,cAAc,KAAK;AAAA,IAC5B;AAAA,EACF;AAEA,QAAM,QAAsB,OAAO,KAAK,QAAQ;AAC9C,QAAI;AACF,YAAM,aAAa,GAAG;AACtB,YAAM,EAAE,YAAY,GAAG,IAAI,MAAM,IAAI;AACrC,YAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,YAAM,KAAK,OAAO,YAAY,IAAI,IAAI;AACtC,aAAO,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,IAC1B,SAAS,OAAO;AACd,aAAO,cAAc,KAAK;AAAA,IAC5B;AAAA,EACF;AAEA,QAAM,MAAoB,OAAO,KAAK,QAAQ;AAC5C,QAAI;AACF,YAAM,aAAa,GAAG;AACtB,YAAM,EAAE,YAAY,GAAG,IAAI,MAAM,IAAI;AACrC,YAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,YAAM,KAAK,OAAO,YAAY,IAAI,IAAI;AACtC,aAAO,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,IAC1B,SAAS,OAAO;AACd,aAAO,cAAc,KAAK;AAAA,IAC5B;AAAA,EACF;AAEA,QAAM,SAAuB,OAAO,KAAK,QAAQ;AAC/C,QAAI;AACF,YAAM,aAAa,GAAG;AACtB,YAAM,EAAE,YAAY,GAAG,IAAI,MAAM,IAAI;AACrC,YAAM,KAAK,OAAO,YAAY,EAAE;AAChC,aAAO,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,IAC1B,SAAS,OAAO;AACd,aAAO,cAAc,KAAK;AAAA,IAC5B;AAAA,EACF;AAEA,QAAM,OAAoB,OAAO,QAAQ;AACvC,QAAI;AACF,YAAM,aAAa,GAAG;AACtB,UAAI,EAAC,mCAAS,OAAM;AAClB,eAAO,KAAK,EAAE,OAAO,4CAA4C,GAAG,GAAG;AAAA,MACzE;AACA,YAAM,SAAS,MAAM,QAAQ,KAAK,GAAG;AACrC,aAAO,KAAK,MAAM;AAAA,IACpB,SAAS,OAAO;AACd,aAAO,cAAc,KAAK;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO,EAAE,KAAK,OAAO,KAAK,QAAQ,KAAK;AACzC;;;AC/HA,SAAS,MAAM,GAAsB;AACnC,SACE,OAAO,MAAM,YACb,MAAM,QACN,OAAQ,EAAU,eAAe,YACjC,OAAQ,EAAU,OAAO;AAE7B;AAoCA,eAAsB,iBACpB,SACA,MACA,UAAmC,CAAC,GAClB;AAlDpB;AAmDE,QAAM,OAAO,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI;AAC/C,QAAM,aAAY,aAAQ,cAAR,YAAqB,CAAC;AACxC,QAAM,UAAS,aAAQ,aAAR,YAAoB,OAAO,KAAK,SAAS;AACxD,MAAI,CAAC,OAAO,UAAU,CAAC,KAAK,OAAQ,QAAO;AAG3C,QAAM,QAAQ,oBAAI,IAA8B;AAChD,QAAM,OAAO,CAAC,YAAoB,OAAiC;AACjE,UAAM,MAAM,GAAG,UAAU,KAAI,EAAE;AAC/B,QAAI,UAAU,MAAM,IAAI,GAAG;AAC3B,QAAI,CAAC,SAAS;AACZ,gBAAU,QAAQ,UAAU,YAAY,EAAE;AAC1C,YAAM,IAAI,KAAK,OAAO;AAAA,IACxB;AACA,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,CAAC,OAAe,UAA+B;AAC3D,QAAI,MAAM,KAAK,EAAG,QAAO;AACzB,QAAI,OAAO,UAAU,YAAY,UAAU,KAAK,GAAG;AACjD,aAAO,EAAE,YAAY,UAAU,KAAK,EAAE,YAAY,IAAI,MAAM;AAAA,IAC9D;AACA,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,OAAO,OAAe,UAAqC;AAC9E,UAAM,MAAM,MAAM,OAAO,KAAK;AAC9B,QAAI,CAAC,IAAK,QAAO;AACjB,UAAM,WAAW,MAAM,KAAK,IAAI,YAAY,IAAI,EAAE;AAClD,WAAO,8BAAY;AAAA,EACrB;AAEA,QAAM,QAAQ;AAAA,IACZ,KAAK,IAAI,OAAO,QAAQ;AACtB,iBAAW,SAAS,QAAQ;AAC1B,cAAM,QAAQ,IAAI,KAAK;AACvB,YAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,UAAC,IAAgC,KAAK,IAAI,MAAM,QAAQ;AAAA,YACtD,MAAM,IAAI,CAAC,MAAM,aAAa,OAAO,CAAC,CAAC;AAAA,UACzC;AAAA,QACF,OAAO;AACL,UAAC,IAAgC,KAAK,IAAI,MAAM,aAAa,OAAO,KAAK;AAAA,QAC3E;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AACT;","names":[]}
|
package/dist/server/index.d.cts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { DataAdapter, AuthAdapter, ServerStorageAdapter, AuthIdentity } from '../index.cjs';
|
|
2
|
-
export { ClientStorageAdapter, NestedSections, Query, Section, StorageAdapter } from '../index.cjs';
|
|
1
|
+
import { DataAdapter, AuthAdapter, ServerStorageAdapter, AuthorizeFn, AuthIdentity, RelationConfig } from '../index.cjs';
|
|
2
|
+
export { ClientStorageAdapter, Editable, EntityAddress, NestedSections, Query, QueryCondition, QueryFilter, QueryFilterGroup, QueryFilterOp, Ref, Section, StorageAdapter } from '../index.cjs';
|
|
3
3
|
|
|
4
4
|
interface CmsHandlersDeps {
|
|
5
5
|
data: DataAdapter;
|
|
6
6
|
auth: AuthAdapter;
|
|
7
7
|
storage?: ServerStorageAdapter;
|
|
8
|
+
/** Custom authorization predicate. Defaults to `identity.isAdmin === true`. */
|
|
9
|
+
authorize?: AuthorizeFn;
|
|
8
10
|
}
|
|
9
11
|
/** Next.js App Router passes dynamic params as a promise. */
|
|
10
12
|
type RouteContext = {
|
|
@@ -45,8 +47,36 @@ declare class UnauthorizedError extends Error {
|
|
|
45
47
|
}
|
|
46
48
|
/**
|
|
47
49
|
* Wraps an {@link AuthAdapter} into a reusable server gate. Throws
|
|
48
|
-
* {@link UnauthorizedError} unless the request resolves to an
|
|
50
|
+
* {@link UnauthorizedError} unless the request resolves to an identity that
|
|
51
|
+
* passes `authorize`. `authorize` defaults to `identity.isAdmin === true`;
|
|
52
|
+
* override it to gate on roles, scopes, tenants, etc.
|
|
49
53
|
*/
|
|
50
|
-
declare function createAdminGate(auth: AuthAdapter): (req: Request) => Promise<AuthIdentity>;
|
|
54
|
+
declare function createAdminGate(auth: AuthAdapter, authorize?: AuthorizeFn): (req: Request) => Promise<AuthIdentity>;
|
|
51
55
|
|
|
52
|
-
|
|
56
|
+
interface ResolveRelationsOptions {
|
|
57
|
+
/**
|
|
58
|
+
* Which fields to resolve. Defaults to every field named in `relations`. Pass
|
|
59
|
+
* a query's `populate` array here.
|
|
60
|
+
*/
|
|
61
|
+
populate?: string[];
|
|
62
|
+
/**
|
|
63
|
+
* Maps bare-id reference fields → their target collection. Not needed for
|
|
64
|
+
* self-describing `{ collection, id }` refs.
|
|
65
|
+
*/
|
|
66
|
+
relations?: RelationConfig;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Inline-resolves reference fields on one or more documents, backend-agnostic.
|
|
70
|
+
* A reference may be a self-describing `{ collection, id }`, a bare id string
|
|
71
|
+
* (resolved via `relations`), or an array of either. Resolution is deduplicated
|
|
72
|
+
* and parallelized; unresolved refs are left untouched.
|
|
73
|
+
*
|
|
74
|
+
* Lives in the engine, never in an adapter, so it works the same over Postgres,
|
|
75
|
+
* Firestore, or any custom {@link DataAdapter}.
|
|
76
|
+
*
|
|
77
|
+
* Mutates and returns the passed document(s).
|
|
78
|
+
*/
|
|
79
|
+
declare function resolveRelations<T extends Record<string, any>>(adapter: DataAdapter, docs: T, options?: ResolveRelationsOptions): Promise<T>;
|
|
80
|
+
declare function resolveRelations<T extends Record<string, any>>(adapter: DataAdapter, docs: T[], options?: ResolveRelationsOptions): Promise<T[]>;
|
|
81
|
+
|
|
82
|
+
export { AuthAdapter, AuthIdentity, AuthorizeFn, type CmsHandlersDeps, DataAdapter, RelationConfig, type ResolveRelationsOptions, ServerStorageAdapter, UnauthorizedError, createAdminGate, createCmsHandlers, resolveRelations };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { DataAdapter, AuthAdapter, ServerStorageAdapter, AuthIdentity } from '../index.js';
|
|
2
|
-
export { ClientStorageAdapter, NestedSections, Query, Section, StorageAdapter } from '../index.js';
|
|
1
|
+
import { DataAdapter, AuthAdapter, ServerStorageAdapter, AuthorizeFn, AuthIdentity, RelationConfig } from '../index.js';
|
|
2
|
+
export { ClientStorageAdapter, Editable, EntityAddress, NestedSections, Query, QueryCondition, QueryFilter, QueryFilterGroup, QueryFilterOp, Ref, Section, StorageAdapter } from '../index.js';
|
|
3
3
|
|
|
4
4
|
interface CmsHandlersDeps {
|
|
5
5
|
data: DataAdapter;
|
|
6
6
|
auth: AuthAdapter;
|
|
7
7
|
storage?: ServerStorageAdapter;
|
|
8
|
+
/** Custom authorization predicate. Defaults to `identity.isAdmin === true`. */
|
|
9
|
+
authorize?: AuthorizeFn;
|
|
8
10
|
}
|
|
9
11
|
/** Next.js App Router passes dynamic params as a promise. */
|
|
10
12
|
type RouteContext = {
|
|
@@ -45,8 +47,36 @@ declare class UnauthorizedError extends Error {
|
|
|
45
47
|
}
|
|
46
48
|
/**
|
|
47
49
|
* Wraps an {@link AuthAdapter} into a reusable server gate. Throws
|
|
48
|
-
* {@link UnauthorizedError} unless the request resolves to an
|
|
50
|
+
* {@link UnauthorizedError} unless the request resolves to an identity that
|
|
51
|
+
* passes `authorize`. `authorize` defaults to `identity.isAdmin === true`;
|
|
52
|
+
* override it to gate on roles, scopes, tenants, etc.
|
|
49
53
|
*/
|
|
50
|
-
declare function createAdminGate(auth: AuthAdapter): (req: Request) => Promise<AuthIdentity>;
|
|
54
|
+
declare function createAdminGate(auth: AuthAdapter, authorize?: AuthorizeFn): (req: Request) => Promise<AuthIdentity>;
|
|
51
55
|
|
|
52
|
-
|
|
56
|
+
interface ResolveRelationsOptions {
|
|
57
|
+
/**
|
|
58
|
+
* Which fields to resolve. Defaults to every field named in `relations`. Pass
|
|
59
|
+
* a query's `populate` array here.
|
|
60
|
+
*/
|
|
61
|
+
populate?: string[];
|
|
62
|
+
/**
|
|
63
|
+
* Maps bare-id reference fields → their target collection. Not needed for
|
|
64
|
+
* self-describing `{ collection, id }` refs.
|
|
65
|
+
*/
|
|
66
|
+
relations?: RelationConfig;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Inline-resolves reference fields on one or more documents, backend-agnostic.
|
|
70
|
+
* A reference may be a self-describing `{ collection, id }`, a bare id string
|
|
71
|
+
* (resolved via `relations`), or an array of either. Resolution is deduplicated
|
|
72
|
+
* and parallelized; unresolved refs are left untouched.
|
|
73
|
+
*
|
|
74
|
+
* Lives in the engine, never in an adapter, so it works the same over Postgres,
|
|
75
|
+
* Firestore, or any custom {@link DataAdapter}.
|
|
76
|
+
*
|
|
77
|
+
* Mutates and returns the passed document(s).
|
|
78
|
+
*/
|
|
79
|
+
declare function resolveRelations<T extends Record<string, any>>(adapter: DataAdapter, docs: T, options?: ResolveRelationsOptions): Promise<T>;
|
|
80
|
+
declare function resolveRelations<T extends Record<string, any>>(adapter: DataAdapter, docs: T[], options?: ResolveRelationsOptions): Promise<T[]>;
|
|
81
|
+
|
|
82
|
+
export { AuthAdapter, AuthIdentity, AuthorizeFn, type CmsHandlersDeps, DataAdapter, RelationConfig, type ResolveRelationsOptions, ServerStorageAdapter, UnauthorizedError, createAdminGate, createCmsHandlers, resolveRelations };
|
package/dist/server/index.js
CHANGED
|
@@ -5,13 +5,15 @@ var UnauthorizedError = class extends Error {
|
|
|
5
5
|
this.name = "UnauthorizedError";
|
|
6
6
|
}
|
|
7
7
|
};
|
|
8
|
-
|
|
8
|
+
var defaultAuthorize = (identity) => identity.isAdmin === true;
|
|
9
|
+
function createAdminGate(auth, authorize = defaultAuthorize) {
|
|
9
10
|
return async function requireAdmin(req) {
|
|
10
11
|
const identity = await auth.verifyRequest(req);
|
|
11
|
-
if (!identity
|
|
12
|
-
throw new UnauthorizedError(
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
if (!identity) {
|
|
13
|
+
throw new UnauthorizedError("Unauthorized");
|
|
14
|
+
}
|
|
15
|
+
if (!await authorize(identity, req)) {
|
|
16
|
+
throw new UnauthorizedError("Forbidden - Not an authorized admin");
|
|
15
17
|
}
|
|
16
18
|
return identity;
|
|
17
19
|
};
|
|
@@ -32,8 +34,8 @@ function errorResponse(error) {
|
|
|
32
34
|
return json({ error: message }, 403);
|
|
33
35
|
}
|
|
34
36
|
function createCmsHandlers(deps) {
|
|
35
|
-
const { data, auth, storage } = deps;
|
|
36
|
-
const requireAdmin = createAdminGate(auth);
|
|
37
|
+
const { data, auth, storage, authorize } = deps;
|
|
38
|
+
const requireAdmin = createAdminGate(auth, authorize);
|
|
37
39
|
const GET = async (req, ctx) => {
|
|
38
40
|
try {
|
|
39
41
|
await requireAdmin(req);
|
|
@@ -91,9 +93,60 @@ function createCmsHandlers(deps) {
|
|
|
91
93
|
};
|
|
92
94
|
return { GET, PATCH, PUT, DELETE, sign };
|
|
93
95
|
}
|
|
96
|
+
|
|
97
|
+
// src/server/relations.ts
|
|
98
|
+
function isRef(v) {
|
|
99
|
+
return typeof v === "object" && v !== null && typeof v.collection === "string" && typeof v.id === "string";
|
|
100
|
+
}
|
|
101
|
+
async function resolveRelations(adapter, docs, options = {}) {
|
|
102
|
+
var _a, _b;
|
|
103
|
+
const list = Array.isArray(docs) ? docs : [docs];
|
|
104
|
+
const relations = (_a = options.relations) != null ? _a : {};
|
|
105
|
+
const fields = (_b = options.populate) != null ? _b : Object.keys(relations);
|
|
106
|
+
if (!fields.length || !list.length) return docs;
|
|
107
|
+
const cache = /* @__PURE__ */ new Map();
|
|
108
|
+
const load = (collection, id) => {
|
|
109
|
+
const key = `${collection}\0${id}`;
|
|
110
|
+
let pending = cache.get(key);
|
|
111
|
+
if (!pending) {
|
|
112
|
+
pending = adapter.fetchById(collection, id);
|
|
113
|
+
cache.set(key, pending);
|
|
114
|
+
}
|
|
115
|
+
return pending;
|
|
116
|
+
};
|
|
117
|
+
const toRef = (field, value) => {
|
|
118
|
+
if (isRef(value)) return value;
|
|
119
|
+
if (typeof value === "string" && relations[field]) {
|
|
120
|
+
return { collection: relations[field].collection, id: value };
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
};
|
|
124
|
+
const resolveValue = async (field, value) => {
|
|
125
|
+
const ref = toRef(field, value);
|
|
126
|
+
if (!ref) return value;
|
|
127
|
+
const resolved = await load(ref.collection, ref.id);
|
|
128
|
+
return resolved != null ? resolved : value;
|
|
129
|
+
};
|
|
130
|
+
await Promise.all(
|
|
131
|
+
list.map(async (doc) => {
|
|
132
|
+
for (const field of fields) {
|
|
133
|
+
const value = doc[field];
|
|
134
|
+
if (Array.isArray(value)) {
|
|
135
|
+
doc[field] = await Promise.all(
|
|
136
|
+
value.map((v) => resolveValue(field, v))
|
|
137
|
+
);
|
|
138
|
+
} else {
|
|
139
|
+
doc[field] = await resolveValue(field, value);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
return docs;
|
|
145
|
+
}
|
|
94
146
|
export {
|
|
95
147
|
UnauthorizedError,
|
|
96
148
|
createAdminGate,
|
|
97
|
-
createCmsHandlers
|
|
149
|
+
createCmsHandlers,
|
|
150
|
+
resolveRelations
|
|
98
151
|
};
|
|
99
152
|
//# sourceMappingURL=index.js.map
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/createAdminGate.ts","../../src/server/createCmsHandlers.ts"],"sourcesContent":["import type { AuthAdapter, AuthIdentity } from \"../types\";\n\n/** Thrown when a request fails the admin gate; carried up to the route handler. */\nexport class UnauthorizedError extends Error {\n constructor(message = \"Unauthorized\") {\n super(message);\n this.name = \"UnauthorizedError\";\n }\n}\n\n/**\n * Wraps an {@link AuthAdapter} into a reusable server gate. Throws\n * {@link UnauthorizedError} unless the request resolves to an admin identity.\n */\nexport function createAdminGate(auth: AuthAdapter) {\n return async function requireAdmin(req: Request): Promise<AuthIdentity> {\n const identity = await auth.verifyRequest(req);\n if (!identity || !identity.isAdmin) {\n throw new UnauthorizedError(\n identity ? \"Forbidden - Not an authorized admin\" : \"Unauthorized\",\n );\n }\n return identity;\n };\n}\n","import type { DataAdapter, AuthAdapter, ServerStorageAdapter } from \"../types\";\nimport { createAdminGate, UnauthorizedError } from \"./createAdminGate\";\n\nexport interface CmsHandlersDeps {\n data: DataAdapter;\n auth: AuthAdapter;\n storage?: ServerStorageAdapter;\n}\n\n/** Next.js App Router passes dynamic params as a promise. */\ntype RouteContext = {\n params: Promise<{ collection: string; id: string }>;\n};\n\ntype RouteHandler = (req: Request, ctx: RouteContext) => Promise<Response>;\ntype SignHandler = (req: Request) => Promise<Response>;\n\nfunction json(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\n/**\n * Translate any thrown error into a response. Auth failures return 401 with\n * `{ logout: true }` so a client interceptor can force sign-out.\n */\nfunction errorResponse(error: unknown): Response {\n if (error instanceof UnauthorizedError && error.message === \"Unauthorized\") {\n return json({ error: \"Unauthorized\", logout: true }, 401);\n }\n const message = error instanceof Error ? error.message : \"Request failed\";\n return json({ error: message }, 403);\n}\n\n/**\n * Builds the generic admin CRUD handlers, replacing a hand-written\n * `/api/admin/[collection]/[id]/route.ts`. Mount like:\n *\n * ```ts\n * // app/api/admin/[collection]/[id]/route.ts\n * export const { GET, PATCH, PUT, DELETE } = createCmsHandlers({ data, auth });\n * ```\n *\n * If a `storage` adapter with a `sign` method is provided, also mount its sign\n * handler at e.g. `app/api/admin/sign/route.ts`:\n *\n * ```ts\n * export const POST = createCmsHandlers({ data, auth, storage }).sign;\n * ```\n */\nexport function createCmsHandlers(deps: CmsHandlersDeps): {\n GET: RouteHandler;\n PATCH: RouteHandler;\n PUT: RouteHandler;\n DELETE: RouteHandler;\n sign: SignHandler;\n} {\n const { data, auth, storage } = deps;\n const requireAdmin = createAdminGate(auth);\n\n const GET: RouteHandler = async (req, ctx) => {\n try {\n await requireAdmin(req);\n const { collection, id } = await ctx.params;\n const doc = await data.fetchById(collection, id);\n if (!doc) return json({ error: \"Document not found\" }, 404);\n return json(doc);\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n const PATCH: RouteHandler = async (req, ctx) => {\n try {\n await requireAdmin(req);\n const { collection, id } = await ctx.params;\n const body = await req.json();\n await data.update(collection, id, body);\n return json({ ok: true });\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n const PUT: RouteHandler = async (req, ctx) => {\n try {\n await requireAdmin(req);\n const { collection, id } = await ctx.params;\n const body = await req.json();\n await data.upsert(collection, id, body);\n return json({ ok: true });\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n const DELETE: RouteHandler = async (req, ctx) => {\n try {\n await requireAdmin(req);\n const { collection, id } = await ctx.params;\n await data.delete(collection, id);\n return json({ ok: true });\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n const sign: SignHandler = async (req) => {\n try {\n await requireAdmin(req);\n if (!storage?.sign) {\n return json({ error: \"No storage adapter with sign() configured\" }, 404);\n }\n const result = await storage.sign(req);\n return json(result);\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n return { GET, PATCH, PUT, DELETE, sign };\n}\n"],"mappings":";AAGO,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAC3C,YAAY,UAAU,gBAAgB;AACpC,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAMO,SAAS,gBAAgB,MAAmB;AACjD,SAAO,eAAe,aAAa,KAAqC;AACtE,UAAM,WAAW,MAAM,KAAK,cAAc,GAAG;AAC7C,QAAI,CAAC,YAAY,CAAC,SAAS,SAAS;AAClC,YAAM,IAAI;AAAA,QACR,WAAW,wCAAwC;AAAA,MACrD;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;;;ACPA,SAAS,KAAK,MAAe,SAAS,KAAe;AACnD,SAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;AAAA,IACxC;AAAA,IACA,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,EAChD,CAAC;AACH;AAMA,SAAS,cAAc,OAA0B;AAC/C,MAAI,iBAAiB,qBAAqB,MAAM,YAAY,gBAAgB;AAC1E,WAAO,KAAK,EAAE,OAAO,gBAAgB,QAAQ,KAAK,GAAG,GAAG;AAAA,EAC1D;AACA,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,SAAO,KAAK,EAAE,OAAO,QAAQ,GAAG,GAAG;AACrC;AAkBO,SAAS,kBAAkB,MAMhC;AACA,QAAM,EAAE,MAAM,MAAM,QAAQ,IAAI;AAChC,QAAM,eAAe,gBAAgB,IAAI;AAEzC,QAAM,MAAoB,OAAO,KAAK,QAAQ;AAC5C,QAAI;AACF,YAAM,aAAa,GAAG;AACtB,YAAM,EAAE,YAAY,GAAG,IAAI,MAAM,IAAI;AACrC,YAAM,MAAM,MAAM,KAAK,UAAU,YAAY,EAAE;AAC/C,UAAI,CAAC,IAAK,QAAO,KAAK,EAAE,OAAO,qBAAqB,GAAG,GAAG;AAC1D,aAAO,KAAK,GAAG;AAAA,IACjB,SAAS,OAAO;AACd,aAAO,cAAc,KAAK;AAAA,IAC5B;AAAA,EACF;AAEA,QAAM,QAAsB,OAAO,KAAK,QAAQ;AAC9C,QAAI;AACF,YAAM,aAAa,GAAG;AACtB,YAAM,EAAE,YAAY,GAAG,IAAI,MAAM,IAAI;AACrC,YAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,YAAM,KAAK,OAAO,YAAY,IAAI,IAAI;AACtC,aAAO,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,IAC1B,SAAS,OAAO;AACd,aAAO,cAAc,KAAK;AAAA,IAC5B;AAAA,EACF;AAEA,QAAM,MAAoB,OAAO,KAAK,QAAQ;AAC5C,QAAI;AACF,YAAM,aAAa,GAAG;AACtB,YAAM,EAAE,YAAY,GAAG,IAAI,MAAM,IAAI;AACrC,YAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,YAAM,KAAK,OAAO,YAAY,IAAI,IAAI;AACtC,aAAO,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,IAC1B,SAAS,OAAO;AACd,aAAO,cAAc,KAAK;AAAA,IAC5B;AAAA,EACF;AAEA,QAAM,SAAuB,OAAO,KAAK,QAAQ;AAC/C,QAAI;AACF,YAAM,aAAa,GAAG;AACtB,YAAM,EAAE,YAAY,GAAG,IAAI,MAAM,IAAI;AACrC,YAAM,KAAK,OAAO,YAAY,EAAE;AAChC,aAAO,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,IAC1B,SAAS,OAAO;AACd,aAAO,cAAc,KAAK;AAAA,IAC5B;AAAA,EACF;AAEA,QAAM,OAAoB,OAAO,QAAQ;AACvC,QAAI;AACF,YAAM,aAAa,GAAG;AACtB,UAAI,EAAC,mCAAS,OAAM;AAClB,eAAO,KAAK,EAAE,OAAO,4CAA4C,GAAG,GAAG;AAAA,MACzE;AACA,YAAM,SAAS,MAAM,QAAQ,KAAK,GAAG;AACrC,aAAO,KAAK,MAAM;AAAA,IACpB,SAAS,OAAO;AACd,aAAO,cAAc,KAAK;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO,EAAE,KAAK,OAAO,KAAK,QAAQ,KAAK;AACzC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/server/createAdminGate.ts","../../src/server/createCmsHandlers.ts","../../src/server/relations.ts"],"sourcesContent":["import type { AuthAdapter, AuthIdentity, AuthorizeFn } from \"../types\";\n\n/** Thrown when a request fails the admin gate; carried up to the route handler. */\nexport class UnauthorizedError extends Error {\n constructor(message = \"Unauthorized\") {\n super(message);\n this.name = \"UnauthorizedError\";\n }\n}\n\n/** Default authorization: admin flag must be set. */\nconst defaultAuthorize: AuthorizeFn = (identity) => identity.isAdmin === true;\n\n/**\n * Wraps an {@link AuthAdapter} into a reusable server gate. Throws\n * {@link UnauthorizedError} unless the request resolves to an identity that\n * passes `authorize`. `authorize` defaults to `identity.isAdmin === true`;\n * override it to gate on roles, scopes, tenants, etc.\n */\nexport function createAdminGate(auth: AuthAdapter, authorize: AuthorizeFn = defaultAuthorize) {\n return async function requireAdmin(req: Request): Promise<AuthIdentity> {\n const identity = await auth.verifyRequest(req);\n if (!identity) {\n throw new UnauthorizedError(\"Unauthorized\");\n }\n if (!(await authorize(identity, req))) {\n throw new UnauthorizedError(\"Forbidden - Not an authorized admin\");\n }\n return identity;\n };\n}\n","import type {\n DataAdapter,\n AuthAdapter,\n AuthorizeFn,\n ServerStorageAdapter,\n} from \"../types\";\nimport { createAdminGate, UnauthorizedError } from \"./createAdminGate\";\n\nexport interface CmsHandlersDeps {\n data: DataAdapter;\n auth: AuthAdapter;\n storage?: ServerStorageAdapter;\n /** Custom authorization predicate. Defaults to `identity.isAdmin === true`. */\n authorize?: AuthorizeFn;\n}\n\n/** Next.js App Router passes dynamic params as a promise. */\ntype RouteContext = {\n params: Promise<{ collection: string; id: string }>;\n};\n\ntype RouteHandler = (req: Request, ctx: RouteContext) => Promise<Response>;\ntype SignHandler = (req: Request) => Promise<Response>;\n\nfunction json(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\n/**\n * Translate any thrown error into a response. Auth failures return 401 with\n * `{ logout: true }` so a client interceptor can force sign-out.\n */\nfunction errorResponse(error: unknown): Response {\n if (error instanceof UnauthorizedError && error.message === \"Unauthorized\") {\n return json({ error: \"Unauthorized\", logout: true }, 401);\n }\n const message = error instanceof Error ? error.message : \"Request failed\";\n return json({ error: message }, 403);\n}\n\n/**\n * Builds the generic admin CRUD handlers, replacing a hand-written\n * `/api/admin/[collection]/[id]/route.ts`. Mount like:\n *\n * ```ts\n * // app/api/admin/[collection]/[id]/route.ts\n * export const { GET, PATCH, PUT, DELETE } = createCmsHandlers({ data, auth });\n * ```\n *\n * If a `storage` adapter with a `sign` method is provided, also mount its sign\n * handler at e.g. `app/api/admin/sign/route.ts`:\n *\n * ```ts\n * export const POST = createCmsHandlers({ data, auth, storage }).sign;\n * ```\n */\nexport function createCmsHandlers(deps: CmsHandlersDeps): {\n GET: RouteHandler;\n PATCH: RouteHandler;\n PUT: RouteHandler;\n DELETE: RouteHandler;\n sign: SignHandler;\n} {\n const { data, auth, storage, authorize } = deps;\n const requireAdmin = createAdminGate(auth, authorize);\n\n const GET: RouteHandler = async (req, ctx) => {\n try {\n await requireAdmin(req);\n const { collection, id } = await ctx.params;\n const doc = await data.fetchById(collection, id);\n if (!doc) return json({ error: \"Document not found\" }, 404);\n return json(doc);\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n const PATCH: RouteHandler = async (req, ctx) => {\n try {\n await requireAdmin(req);\n const { collection, id } = await ctx.params;\n const body = await req.json();\n await data.update(collection, id, body);\n return json({ ok: true });\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n const PUT: RouteHandler = async (req, ctx) => {\n try {\n await requireAdmin(req);\n const { collection, id } = await ctx.params;\n const body = await req.json();\n await data.upsert(collection, id, body);\n return json({ ok: true });\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n const DELETE: RouteHandler = async (req, ctx) => {\n try {\n await requireAdmin(req);\n const { collection, id } = await ctx.params;\n await data.delete(collection, id);\n return json({ ok: true });\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n const sign: SignHandler = async (req) => {\n try {\n await requireAdmin(req);\n if (!storage?.sign) {\n return json({ error: \"No storage adapter with sign() configured\" }, 404);\n }\n const result = await storage.sign(req);\n return json(result);\n } catch (error) {\n return errorResponse(error);\n }\n };\n\n return { GET, PATCH, PUT, DELETE, sign };\n}\n","import type { DataAdapter, Ref, RelationConfig } from \"../types\";\n\n/** A field value is a reference when it's a self-describing `{ collection, id }`. */\nfunction isRef(v: unknown): v is Ref {\n return (\n typeof v === \"object\" &&\n v !== null &&\n typeof (v as Ref).collection === \"string\" &&\n typeof (v as Ref).id === \"string\"\n );\n}\n\nexport interface ResolveRelationsOptions {\n /**\n * Which fields to resolve. Defaults to every field named in `relations`. Pass\n * a query's `populate` array here.\n */\n populate?: string[];\n /**\n * Maps bare-id reference fields → their target collection. Not needed for\n * self-describing `{ collection, id }` refs.\n */\n relations?: RelationConfig;\n}\n\n/**\n * Inline-resolves reference fields on one or more documents, backend-agnostic.\n * A reference may be a self-describing `{ collection, id }`, a bare id string\n * (resolved via `relations`), or an array of either. Resolution is deduplicated\n * and parallelized; unresolved refs are left untouched.\n *\n * Lives in the engine, never in an adapter, so it works the same over Postgres,\n * Firestore, or any custom {@link DataAdapter}.\n *\n * Mutates and returns the passed document(s).\n */\nexport async function resolveRelations<T extends Record<string, any>>(\n adapter: DataAdapter,\n docs: T,\n options?: ResolveRelationsOptions,\n): Promise<T>;\nexport async function resolveRelations<T extends Record<string, any>>(\n adapter: DataAdapter,\n docs: T[],\n options?: ResolveRelationsOptions,\n): Promise<T[]>;\nexport async function resolveRelations<T extends Record<string, any>>(\n adapter: DataAdapter,\n docs: T | T[],\n options: ResolveRelationsOptions = {},\n): Promise<T | T[]> {\n const list = Array.isArray(docs) ? docs : [docs];\n const relations = options.relations ?? {};\n const fields = options.populate ?? Object.keys(relations);\n if (!fields.length || !list.length) return docs;\n\n // Dedupe loads across all docs/fields: one fetch per (collection, id).\n const cache = new Map<string, Promise<unknown>>();\n const load = (collection: string, id: string): Promise<unknown> => {\n const key = `${collection}\u0000${id}`;\n let pending = cache.get(key);\n if (!pending) {\n pending = adapter.fetchById(collection, id);\n cache.set(key, pending);\n }\n return pending;\n };\n\n const toRef = (field: string, value: unknown): Ref | null => {\n if (isRef(value)) return value;\n if (typeof value === \"string\" && relations[field]) {\n return { collection: relations[field].collection, id: value };\n }\n return null;\n };\n\n const resolveValue = async (field: string, value: unknown): Promise<unknown> => {\n const ref = toRef(field, value);\n if (!ref) return value;\n const resolved = await load(ref.collection, ref.id);\n return resolved ?? value;\n };\n\n await Promise.all(\n list.map(async (doc) => {\n for (const field of fields) {\n const value = doc[field];\n if (Array.isArray(value)) {\n (doc as Record<string, unknown>)[field] = await Promise.all(\n value.map((v) => resolveValue(field, v)),\n );\n } else {\n (doc as Record<string, unknown>)[field] = await resolveValue(field, value);\n }\n }\n }),\n );\n\n return docs;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dalgoridim/headless-cms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Database-agnostic, inline-edit headless CMS engine for React / Next.js apps",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "dalgoridim",
|
|
@@ -95,7 +95,9 @@
|
|
|
95
95
|
}
|
|
96
96
|
},
|
|
97
97
|
"files": [
|
|
98
|
-
"dist"
|
|
98
|
+
"dist",
|
|
99
|
+
"ARCHITECTURE.md",
|
|
100
|
+
"REDESIGN.md"
|
|
99
101
|
],
|
|
100
102
|
"scripts": {
|
|
101
103
|
"build": "tsup",
|
|
@@ -137,14 +139,7 @@
|
|
|
137
139
|
"optional": true
|
|
138
140
|
}
|
|
139
141
|
},
|
|
140
|
-
"dependencies": {
|
|
141
|
-
"clsx": "^2.1.1",
|
|
142
|
-
"lucide-react": "^0.556.0",
|
|
143
|
-
"react-markdown": "^10.1.0",
|
|
144
|
-
"remark-gfm": "^4.0.1",
|
|
145
|
-
"sonner": "^2.0.7",
|
|
146
|
-
"tailwind-merge": "^3.4.0"
|
|
147
|
-
},
|
|
142
|
+
"dependencies": {},
|
|
148
143
|
"devDependencies": {
|
|
149
144
|
"@aws-sdk/client-s3": "^3.700.0",
|
|
150
145
|
"@aws-sdk/s3-request-presigner": "^3.700.0",
|