@dalgoridim/headless-cms 0.1.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/LICENSE +10 -0
- package/README.md +178 -0
- package/dist/adapters/firestore/index.cjs +152 -0
- package/dist/adapters/firestore/index.cjs.map +1 -0
- package/dist/adapters/firestore/index.d.cts +39 -0
- package/dist/adapters/firestore/index.d.ts +39 -0
- package/dist/adapters/firestore/index.js +120 -0
- package/dist/adapters/firestore/index.js.map +1 -0
- package/dist/adapters/postgres/index.cjs +299 -0
- package/dist/adapters/postgres/index.cjs.map +1 -0
- package/dist/adapters/postgres/index.d.cts +59 -0
- package/dist/adapters/postgres/index.d.ts +59 -0
- package/dist/adapters/postgres/index.js +277 -0
- package/dist/adapters/postgres/index.js.map +1 -0
- package/dist/auth/firebase/client/index.cjs +153 -0
- package/dist/auth/firebase/client/index.cjs.map +1 -0
- package/dist/auth/firebase/client/index.d.cts +29 -0
- package/dist/auth/firebase/client/index.d.ts +29 -0
- package/dist/auth/firebase/client/index.js +138 -0
- package/dist/auth/firebase/client/index.js.map +1 -0
- package/dist/auth/firebase/index.cjs +81 -0
- package/dist/auth/firebase/index.cjs.map +1 -0
- package/dist/auth/firebase/index.d.cts +23 -0
- package/dist/auth/firebase/index.d.ts +23 -0
- package/dist/auth/firebase/index.js +46 -0
- package/dist/auth/firebase/index.js.map +1 -0
- package/dist/auth/nextauth/index.cjs +51 -0
- package/dist/auth/nextauth/index.cjs.map +1 -0
- package/dist/auth/nextauth/index.d.cts +30 -0
- package/dist/auth/nextauth/index.d.ts +30 -0
- package/dist/auth/nextauth/index.js +25 -0
- package/dist/auth/nextauth/index.js.map +1 -0
- package/dist/client/index.cjs +1018 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +96 -0
- package/dist/client/index.d.ts +96 -0
- package/dist/client/index.js +994 -0
- package/dist/client/index.js.map +1 -0
- package/dist/index.cjs +19 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +122 -0
- package/dist/index.d.ts +122 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.cjs +128 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +52 -0
- package/dist/server/index.d.ts +52 -0
- package/dist/server/index.js +99 -0
- package/dist/server/index.js.map +1 -0
- package/dist/storage/cloudinary/index.cjs +55 -0
- package/dist/storage/cloudinary/index.cjs.map +1 -0
- package/dist/storage/cloudinary/index.d.cts +17 -0
- package/dist/storage/cloudinary/index.d.ts +17 -0
- package/dist/storage/cloudinary/index.js +30 -0
- package/dist/storage/cloudinary/index.js.map +1 -0
- package/dist/storage/cloudinary/server.cjs +56 -0
- package/dist/storage/cloudinary/server.cjs.map +1 -0
- package/dist/storage/cloudinary/server.d.cts +16 -0
- package/dist/storage/cloudinary/server.d.ts +16 -0
- package/dist/storage/cloudinary/server.js +31 -0
- package/dist/storage/cloudinary/server.js.map +1 -0
- package/dist/storage/local/index.cjs +44 -0
- package/dist/storage/local/index.cjs.map +1 -0
- package/dist/storage/local/index.d.cts +15 -0
- package/dist/storage/local/index.d.ts +15 -0
- package/dist/storage/local/index.js +19 -0
- package/dist/storage/local/index.js.map +1 -0
- package/dist/storage/local/server.cjs +61 -0
- package/dist/storage/local/server.cjs.map +1 -0
- package/dist/storage/local/server.d.cts +16 -0
- package/dist/storage/local/server.d.ts +16 -0
- package/dist/storage/local/server.js +26 -0
- package/dist/storage/local/server.js.map +1 -0
- package/dist/storage/s3/index.cjs +52 -0
- package/dist/storage/s3/index.cjs.map +1 -0
- package/dist/storage/s3/index.d.cts +14 -0
- package/dist/storage/s3/index.d.ts +14 -0
- package/dist/storage/s3/index.js +27 -0
- package/dist/storage/s3/index.js.map +1 -0
- package/dist/storage/s3/server.cjs +61 -0
- package/dist/storage/s3/server.cjs.map +1 -0
- package/dist/storage/s3/server.d.cts +19 -0
- package/dist/storage/s3/server.d.ts +19 -0
- package/dist/storage/s3/server.js +36 -0
- package/dist/storage/s3/server.js.map +1 -0
- package/package.json +165 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Copyright (c) 2026 dalgoridim. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software and its source code are proprietary and confidential. No license,
|
|
4
|
+
express or implied, is granted to use, copy, modify, merge, publish, distribute,
|
|
5
|
+
sublicense, or sell copies of this software without the prior written permission
|
|
6
|
+
of the copyright holder.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
9
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
10
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# @dalgoridim/headless-cms
|
|
2
|
+
|
|
3
|
+
Database-agnostic, inline-edit headless CMS engine for React / Next.js apps.
|
|
4
|
+
|
|
5
|
+
Content lives in your database and is editable **inline on the live site** by an
|
|
6
|
+
authenticated admin. The persistence, auth, and storage layers are fully
|
|
7
|
+
pluggable — swap Firestore for Postgres, Cloudinary for S3, Firebase auth for
|
|
8
|
+
NextAuth, without touching the editing UI.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @dalgoridim/headless-cms
|
|
14
|
+
# plus the adapters you actually use, e.g.:
|
|
15
|
+
npm install firebase firebase-admin cloudinary # Firestore + Cloudinary + Firebase
|
|
16
|
+
npm install pg # Postgres
|
|
17
|
+
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner # S3
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`react` / `react-dom` are peer deps; every backend SDK is an **optional** peer —
|
|
21
|
+
you only install the ones your chosen adapters need.
|
|
22
|
+
|
|
23
|
+
## Subpath exports
|
|
24
|
+
|
|
25
|
+
Server-only code never leaks into the client bundle. Import from the right entry:
|
|
26
|
+
|
|
27
|
+
| Entry | Contents |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `@dalgoridim/headless-cms` | Shared types only (safe anywhere) |
|
|
30
|
+
| `.../client` | `PageProvider`, `usePageContext`, `ContentEditSpan`, `EditableImage`, `MarkdownEditor`, `CmsAuthProvider`, `useCmsAuth` |
|
|
31
|
+
| `.../server` | `createCmsHandlers`, `createAdminGate` |
|
|
32
|
+
| `.../adapters/firestore` | `FirestoreDataAdapter` |
|
|
33
|
+
| `.../adapters/postgres` | `PostgresDataAdapter` (hybrid JSONB + typed tables) |
|
|
34
|
+
| `.../storage/cloudinary` \| `.../storage/s3` \| `.../storage/local` | **Client** upload adapters (`cloudinaryStorage`, `s3Storage`, `localStorage`) — pure `fetch`, safe in client components |
|
|
35
|
+
| `.../storage/cloudinary/server` \| `.../storage/s3/server` \| `.../storage/local/server` | **Server** signers (`cloudinarySign`, `s3Sign`, `localSign`) — pull in SDKs, server-only |
|
|
36
|
+
| `.../auth/firebase` | `firebaseAuth` (server gate) |
|
|
37
|
+
| `.../auth/firebase/client` | `FirebaseAuthProvider`, `useFirebaseAuth` |
|
|
38
|
+
| `.../auth/nextauth` | `nextAuthAuth`, `customAuth` |
|
|
39
|
+
|
|
40
|
+
## The three core interfaces
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import type { DataAdapter, AuthAdapter, StorageAdapter } from "@dalgoridim/headless-cms";
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- **`DataAdapter`** — backend CRUD over a neutral `Query`.
|
|
47
|
+
- **`AuthAdapter`** — `verifyRequest(req)` gates every admin route, server-side.
|
|
48
|
+
- **Storage** is split into two halves so server SDKs never reach the client
|
|
49
|
+
bundle: `ClientStorageAdapter` (`upload`, from `.../storage/<x>`) and
|
|
50
|
+
`ServerStorageAdapter` (`sign`, from `.../storage/<x>/server`).
|
|
51
|
+
|
|
52
|
+
## Wiring (Next.js App Router)
|
|
53
|
+
|
|
54
|
+
**1. The admin CRUD route** — replaces a hand-written handler:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// app/api/admin/[collection]/[id]/route.ts
|
|
58
|
+
import { createCmsHandlers } from "@dalgoridim/headless-cms/server";
|
|
59
|
+
import { FirestoreDataAdapter } from "@dalgoridim/headless-cms/adapters/firestore";
|
|
60
|
+
import { firebaseAuth } from "@dalgoridim/headless-cms/auth/firebase";
|
|
61
|
+
|
|
62
|
+
export const { GET, PATCH, PUT, DELETE } = createCmsHandlers({
|
|
63
|
+
data: new FirestoreDataAdapter({
|
|
64
|
+
credentials: {
|
|
65
|
+
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
|
|
66
|
+
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
|
|
67
|
+
privateKey: process.env.FIREBASE_PRIVATE_KEY,
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
auth: firebaseAuth({ adminEmails: process.env.ADMIN_EMAILS!.split(",") }),
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**2. The storage sign route** — uses the **server** signer:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
// app/api/admin/sign/route.ts
|
|
78
|
+
import { createCmsHandlers } from "@dalgoridim/headless-cms/server";
|
|
79
|
+
import { cloudinarySign } from "@dalgoridim/headless-cms/storage/cloudinary/server";
|
|
80
|
+
// ...same data + auth as above...
|
|
81
|
+
export const POST = createCmsHandlers({
|
|
82
|
+
data,
|
|
83
|
+
auth,
|
|
84
|
+
storage: cloudinarySign({
|
|
85
|
+
cloudName: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
|
|
86
|
+
apiKey: process.env.CLOUDINARY_API_KEY,
|
|
87
|
+
apiSecret: process.env.CLOUDINARY_API_SECRET,
|
|
88
|
+
folder: "uploads", // must match the client's folder
|
|
89
|
+
}),
|
|
90
|
+
}).sign;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**3. Providers + editable content** (client):
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
"use client";
|
|
97
|
+
import { PageProvider, ContentEditSpan, EditableImage } from "@dalgoridim/headless-cms/client";
|
|
98
|
+
import { FirebaseAuthProvider } from "@dalgoridim/headless-cms/auth/firebase/client";
|
|
99
|
+
import { cloudinaryStorage } from "@dalgoridim/headless-cms/storage/cloudinary";
|
|
100
|
+
import { auth, googleProvider } from "@/lib/firebase/config";
|
|
101
|
+
|
|
102
|
+
// Client upload adapter — posts to the sign route, then to Cloudinary.
|
|
103
|
+
const storage = cloudinaryStorage({ folder: "uploads", signEndpoint: "/api/admin/sign" });
|
|
104
|
+
|
|
105
|
+
export function Providers({ initialSections, children }) {
|
|
106
|
+
return (
|
|
107
|
+
<FirebaseAuthProvider auth={auth} googleProvider={googleProvider}>
|
|
108
|
+
<PageProvider initialSections={initialSections} storage={storage}>
|
|
109
|
+
{children}
|
|
110
|
+
</PageProvider>
|
|
111
|
+
</FirebaseAuthProvider>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Anywhere inside:
|
|
116
|
+
<ContentEditSpan collection="portfolio" sectionKey="hero" fieldKey="title" as="h1">
|
|
117
|
+
Default title
|
|
118
|
+
</ContentEditSpan>
|
|
119
|
+
<EditableImage collection="portfolio" sectionKey="hero" fieldKey="image"
|
|
120
|
+
docId="hero" src={section.image} />
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`PageProvider` saves `PATCH ${apiBasePath}/{collection}/{id}` (default
|
|
124
|
+
`apiBasePath` = `/api/admin`). Edit mode is driven by `useCmsAuth().isEditing`;
|
|
125
|
+
the built-in auth providers feed that context.
|
|
126
|
+
|
|
127
|
+
## Postgres (hybrid)
|
|
128
|
+
|
|
129
|
+
Unregistered collections live in a shared JSONB `documents` table; registered
|
|
130
|
+
collections map flat fields onto typed columns, with a JSONB `extra` column so
|
|
131
|
+
unmapped fields are never dropped.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { PostgresDataAdapter } from "@dalgoridim/headless-cms/adapters/postgres";
|
|
135
|
+
|
|
136
|
+
const data = new PostgresDataAdapter({
|
|
137
|
+
connectionString: process.env.DATABASE_URL!,
|
|
138
|
+
collections: {
|
|
139
|
+
projects: { table: "projects", columns: { title: "text", date: "date" } },
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
await data.migrate(); // creates documents + registered tables if missing
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Content markup
|
|
146
|
+
|
|
147
|
+
`ContentEditSpan` supports inline marks: `**bold**`, `*italic*`, `~~strike~~`,
|
|
148
|
+
`^^primary color^^`, `__underline__`, `~~br~~`, and `[label](https://url)`.
|
|
149
|
+
`MarkdownEditor` provides full GitHub-flavored markdown editing.
|
|
150
|
+
|
|
151
|
+
## Styling
|
|
152
|
+
|
|
153
|
+
The client components ship **Tailwind utility classes** (and use a `--color-primary`
|
|
154
|
+
custom property / `text-primary` utility for accents). The consuming app is
|
|
155
|
+
responsible for Tailwind. With Tailwind v4, point it at the package so those
|
|
156
|
+
classes are generated, and define a primary color:
|
|
157
|
+
|
|
158
|
+
```css
|
|
159
|
+
/* globals.css */
|
|
160
|
+
@import "tailwindcss";
|
|
161
|
+
@source "../node_modules/@dalgoridim/headless-cms/dist";
|
|
162
|
+
|
|
163
|
+
@theme inline {
|
|
164
|
+
--color-primary: var(--primary);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Build
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npm run build # tsup → dist (esm + cjs + d.ts), per subpath
|
|
172
|
+
npm run typecheck # tsc --noEmit
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Stability
|
|
176
|
+
|
|
177
|
+
Pre-`1.0`: the API may change between minor versions as the engine evolves.
|
|
178
|
+
Pin an exact version if you depend on it in production.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __defProps = Object.defineProperties;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
9
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
10
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
11
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
12
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
13
|
+
var __spreadValues = (a, b) => {
|
|
14
|
+
for (var prop in b || (b = {}))
|
|
15
|
+
if (__hasOwnProp.call(b, prop))
|
|
16
|
+
__defNormalProp(a, prop, b[prop]);
|
|
17
|
+
if (__getOwnPropSymbols)
|
|
18
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
19
|
+
if (__propIsEnum.call(b, prop))
|
|
20
|
+
__defNormalProp(a, prop, b[prop]);
|
|
21
|
+
}
|
|
22
|
+
return a;
|
|
23
|
+
};
|
|
24
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
25
|
+
var __export = (target, all) => {
|
|
26
|
+
for (var name in all)
|
|
27
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
28
|
+
};
|
|
29
|
+
var __copyProps = (to, from, except, desc) => {
|
|
30
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
31
|
+
for (let key of __getOwnPropNames(from))
|
|
32
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
33
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
34
|
+
}
|
|
35
|
+
return to;
|
|
36
|
+
};
|
|
37
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
38
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
39
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
40
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
41
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
42
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
43
|
+
mod
|
|
44
|
+
));
|
|
45
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
46
|
+
|
|
47
|
+
// src/adapters/firestore/index.ts
|
|
48
|
+
var firestore_exports = {};
|
|
49
|
+
__export(firestore_exports, {
|
|
50
|
+
FirestoreDataAdapter: () => FirestoreDataAdapter
|
|
51
|
+
});
|
|
52
|
+
module.exports = __toCommonJS(firestore_exports);
|
|
53
|
+
var import_firebase_admin = __toESM(require("firebase-admin"), 1);
|
|
54
|
+
var OP_MAP = {
|
|
55
|
+
eq: "==",
|
|
56
|
+
lt: "<",
|
|
57
|
+
lte: "<=",
|
|
58
|
+
gt: ">",
|
|
59
|
+
gte: ">=",
|
|
60
|
+
in: "in"
|
|
61
|
+
};
|
|
62
|
+
function serialize(data) {
|
|
63
|
+
if (Array.isArray(data)) {
|
|
64
|
+
return data.map(serialize);
|
|
65
|
+
}
|
|
66
|
+
if (data && typeof data === "object") {
|
|
67
|
+
const obj = data;
|
|
68
|
+
if ("_seconds" in obj && "_nanoseconds" in obj) {
|
|
69
|
+
return new Date(
|
|
70
|
+
obj._seconds * 1e3 + obj._nanoseconds / 1e6
|
|
71
|
+
).toISOString();
|
|
72
|
+
}
|
|
73
|
+
if (typeof obj.toDate === "function") {
|
|
74
|
+
return obj.toDate().toISOString();
|
|
75
|
+
}
|
|
76
|
+
const out = {};
|
|
77
|
+
for (const key in obj) out[key] = serialize(obj[key]);
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
return data;
|
|
81
|
+
}
|
|
82
|
+
var FirestoreDataAdapter = class {
|
|
83
|
+
constructor(config = {}) {
|
|
84
|
+
var _a, _b;
|
|
85
|
+
this.defaultOrderByField = (_a = config.defaultOrderByField) != null ? _a : "createdAt";
|
|
86
|
+
if (config.db) {
|
|
87
|
+
this.db = config.db;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (!import_firebase_admin.default.apps.length) {
|
|
91
|
+
const c = (_b = config.credentials) != null ? _b : {};
|
|
92
|
+
import_firebase_admin.default.initializeApp({
|
|
93
|
+
credential: import_firebase_admin.default.credential.cert({
|
|
94
|
+
projectId: c.projectId,
|
|
95
|
+
clientEmail: c.clientEmail,
|
|
96
|
+
privateKey: c.privateKey
|
|
97
|
+
}),
|
|
98
|
+
databaseURL: c.databaseURL
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
this.db = import_firebase_admin.default.firestore();
|
|
102
|
+
}
|
|
103
|
+
buildQuery(collection, q) {
|
|
104
|
+
var _a, _b;
|
|
105
|
+
let ref = this.db.collection(collection);
|
|
106
|
+
if (!q) {
|
|
107
|
+
return ref.orderBy(this.defaultOrderByField, "desc");
|
|
108
|
+
}
|
|
109
|
+
for (const f of (_a = q.filters) != null ? _a : []) {
|
|
110
|
+
ref = ref.where(f.field, OP_MAP[f.op], f.value);
|
|
111
|
+
}
|
|
112
|
+
for (const o of (_b = q.orderBy) != null ? _b : []) {
|
|
113
|
+
ref = ref.orderBy(o.field, o.direction);
|
|
114
|
+
}
|
|
115
|
+
if (q.limit != null) ref = ref.limit(q.limit);
|
|
116
|
+
return ref;
|
|
117
|
+
}
|
|
118
|
+
async fetchCollection(collection, q) {
|
|
119
|
+
const snap = await this.buildQuery(collection, q).get();
|
|
120
|
+
return snap.docs.map(
|
|
121
|
+
(doc) => serialize(__spreadValues({ id: doc.id }, doc.data()))
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
async fetchById(collection, id) {
|
|
125
|
+
const doc = await this.db.collection(collection).doc(id).get();
|
|
126
|
+
if (!doc.exists) return null;
|
|
127
|
+
return serialize(__spreadValues({ id: doc.id }, doc.data()));
|
|
128
|
+
}
|
|
129
|
+
async create(collection, data) {
|
|
130
|
+
const ref = this.db.collection(collection).doc();
|
|
131
|
+
await ref.set(__spreadProps(__spreadValues({}, data), { createdAt: /* @__PURE__ */ new Date(), updatedAt: /* @__PURE__ */ new Date() }));
|
|
132
|
+
return __spreadValues({ id: ref.id }, data);
|
|
133
|
+
}
|
|
134
|
+
async createWithId(collection, id, data) {
|
|
135
|
+
await this.db.collection(collection).doc(id).set(__spreadProps(__spreadValues({}, data), { createdAt: /* @__PURE__ */ new Date(), updatedAt: /* @__PURE__ */ new Date() }));
|
|
136
|
+
return __spreadValues({ id }, data);
|
|
137
|
+
}
|
|
138
|
+
async update(collection, id, data) {
|
|
139
|
+
await this.db.collection(collection).doc(id).update(__spreadProps(__spreadValues({}, data), { updatedAt: /* @__PURE__ */ new Date() }));
|
|
140
|
+
}
|
|
141
|
+
async upsert(collection, id, data) {
|
|
142
|
+
await this.db.collection(collection).doc(id).set(__spreadProps(__spreadValues({}, data), { updatedAt: /* @__PURE__ */ new Date() }), { merge: true });
|
|
143
|
+
}
|
|
144
|
+
async delete(collection, id) {
|
|
145
|
+
await this.db.collection(collection).doc(id).delete();
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
149
|
+
0 && (module.exports = {
|
|
150
|
+
FirestoreDataAdapter
|
|
151
|
+
});
|
|
152
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/adapters/firestore/index.ts"],"sourcesContent":["import admin from \"firebase-admin\";\nimport type {\n Firestore,\n Query as FirestoreQuery,\n WhereFilterOp,\n} from \"firebase-admin/firestore\";\nimport type { DataAdapter, Query, QueryFilterOp } from \"../../types\";\n\nexport interface FirestoreAdapterConfig {\n /** Provide an existing admin Firestore instance… */\n db?: Firestore;\n /** …or service-account credentials to initialize firebase-admin lazily. */\n credentials?: {\n projectId?: string;\n clientEmail?: string;\n privateKey?: string;\n databaseURL?: string;\n };\n /** Field used for default ordering when no query is given. Default `createdAt`. */\n defaultOrderByField?: string;\n}\n\nconst OP_MAP: Record<QueryFilterOp, WhereFilterOp> = {\n eq: \"==\",\n lt: \"<\",\n lte: \"<=\",\n gt: \">\",\n gte: \">=\",\n in: \"in\",\n};\n\n/** Firestore Timestamps → ISO strings, recursively, so the API returns plain JSON. */\nfunction serialize<T>(data: T): T {\n if (Array.isArray(data)) {\n return data.map(serialize) as unknown as T;\n }\n if (data && typeof data === \"object\") {\n const obj = data as Record<string, unknown>;\n if (\"_seconds\" in obj && \"_nanoseconds\" in obj) {\n return new Date(\n (obj._seconds as number) * 1000 + (obj._nanoseconds as number) / 1e6,\n ).toISOString() as unknown as T;\n }\n if (typeof (obj as { toDate?: unknown }).toDate === \"function\") {\n return (obj as { toDate: () => Date }).toDate().toISOString() as unknown as T;\n }\n const out: Record<string, unknown> = {};\n for (const key in obj) out[key] = serialize(obj[key]);\n return out as T;\n }\n return data;\n}\n\nexport class FirestoreDataAdapter implements DataAdapter {\n private readonly db: Firestore;\n private readonly defaultOrderByField: string;\n\n constructor(config: FirestoreAdapterConfig = {}) {\n this.defaultOrderByField = config.defaultOrderByField ?? \"createdAt\";\n\n if (config.db) {\n this.db = config.db;\n return;\n }\n\n if (!admin.apps.length) {\n const c = config.credentials ?? {};\n admin.initializeApp({\n credential: admin.credential.cert({\n projectId: c.projectId,\n clientEmail: c.clientEmail,\n privateKey: c.privateKey,\n }),\n databaseURL: c.databaseURL,\n });\n }\n this.db = admin.firestore();\n }\n\n private buildQuery(collection: string, q?: Query): FirestoreQuery {\n let ref: FirestoreQuery = this.db.collection(collection);\n\n if (!q) {\n return ref.orderBy(this.defaultOrderByField, \"desc\");\n }\n\n for (const f of q.filters ?? []) {\n ref = ref.where(f.field, OP_MAP[f.op], f.value);\n }\n for (const o of q.orderBy ?? []) {\n ref = ref.orderBy(o.field, o.direction);\n }\n if (q.limit != null) ref = ref.limit(q.limit);\n return ref;\n }\n\n async fetchCollection<T = Record<string, unknown>>(\n collection: string,\n q?: Query,\n ): Promise<(T & { id: string })[]> {\n const snap = await this.buildQuery(collection, q).get();\n return snap.docs.map((doc) =>\n serialize({ id: doc.id, ...(doc.data() as T) }),\n );\n }\n\n async fetchById<T = Record<string, unknown>>(\n collection: string,\n id: string,\n ): Promise<(T & { id: string }) | null> {\n const doc = await this.db.collection(collection).doc(id).get();\n if (!doc.exists) return null;\n return serialize({ id: doc.id, ...(doc.data() as T) });\n }\n\n async create<T = Record<string, unknown>>(\n collection: string,\n data: T,\n ): Promise<T & { id: string }> {\n const ref = this.db.collection(collection).doc();\n await ref.set({ ...data, createdAt: new Date(), updatedAt: new Date() });\n return { id: ref.id, ...(data as T) };\n }\n\n async createWithId<T = Record<string, unknown>>(\n collection: string,\n id: string,\n data: T,\n ): Promise<T & { id: string }> {\n await this.db\n .collection(collection)\n .doc(id)\n .set({ ...data, createdAt: new Date(), updatedAt: new Date() });\n return { id, ...(data as T) };\n }\n\n async update<T = Record<string, unknown>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void> {\n await this.db\n .collection(collection)\n .doc(id)\n .update({ ...data, updatedAt: new Date() });\n }\n\n async upsert<T = Record<string, unknown>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void> {\n await this.db\n .collection(collection)\n .doc(id)\n .set({ ...data, updatedAt: new Date() }, { merge: true });\n }\n\n async delete(collection: string, id: string): Promise<void> {\n await this.db.collection(collection).doc(id).delete();\n }\n}\n"],"mappings":"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","names":["admin"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Firestore } from 'firebase-admin/firestore';
|
|
2
|
+
import { DataAdapter, Query } from '../../index.cjs';
|
|
3
|
+
|
|
4
|
+
interface FirestoreAdapterConfig {
|
|
5
|
+
/** Provide an existing admin Firestore instance… */
|
|
6
|
+
db?: Firestore;
|
|
7
|
+
/** …or service-account credentials to initialize firebase-admin lazily. */
|
|
8
|
+
credentials?: {
|
|
9
|
+
projectId?: string;
|
|
10
|
+
clientEmail?: string;
|
|
11
|
+
privateKey?: string;
|
|
12
|
+
databaseURL?: string;
|
|
13
|
+
};
|
|
14
|
+
/** Field used for default ordering when no query is given. Default `createdAt`. */
|
|
15
|
+
defaultOrderByField?: string;
|
|
16
|
+
}
|
|
17
|
+
declare class FirestoreDataAdapter implements DataAdapter {
|
|
18
|
+
private readonly db;
|
|
19
|
+
private readonly defaultOrderByField;
|
|
20
|
+
constructor(config?: FirestoreAdapterConfig);
|
|
21
|
+
private buildQuery;
|
|
22
|
+
fetchCollection<T = Record<string, unknown>>(collection: string, q?: Query): Promise<(T & {
|
|
23
|
+
id: string;
|
|
24
|
+
})[]>;
|
|
25
|
+
fetchById<T = Record<string, unknown>>(collection: string, id: string): Promise<(T & {
|
|
26
|
+
id: string;
|
|
27
|
+
}) | null>;
|
|
28
|
+
create<T = Record<string, unknown>>(collection: string, data: T): Promise<T & {
|
|
29
|
+
id: string;
|
|
30
|
+
}>;
|
|
31
|
+
createWithId<T = Record<string, unknown>>(collection: string, id: string, data: T): Promise<T & {
|
|
32
|
+
id: string;
|
|
33
|
+
}>;
|
|
34
|
+
update<T = Record<string, unknown>>(collection: string, id: string, data: Partial<T>): Promise<void>;
|
|
35
|
+
upsert<T = Record<string, unknown>>(collection: string, id: string, data: Partial<T>): Promise<void>;
|
|
36
|
+
delete(collection: string, id: string): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { type FirestoreAdapterConfig, FirestoreDataAdapter };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Firestore } from 'firebase-admin/firestore';
|
|
2
|
+
import { DataAdapter, Query } from '../../index.js';
|
|
3
|
+
|
|
4
|
+
interface FirestoreAdapterConfig {
|
|
5
|
+
/** Provide an existing admin Firestore instance… */
|
|
6
|
+
db?: Firestore;
|
|
7
|
+
/** …or service-account credentials to initialize firebase-admin lazily. */
|
|
8
|
+
credentials?: {
|
|
9
|
+
projectId?: string;
|
|
10
|
+
clientEmail?: string;
|
|
11
|
+
privateKey?: string;
|
|
12
|
+
databaseURL?: string;
|
|
13
|
+
};
|
|
14
|
+
/** Field used for default ordering when no query is given. Default `createdAt`. */
|
|
15
|
+
defaultOrderByField?: string;
|
|
16
|
+
}
|
|
17
|
+
declare class FirestoreDataAdapter implements DataAdapter {
|
|
18
|
+
private readonly db;
|
|
19
|
+
private readonly defaultOrderByField;
|
|
20
|
+
constructor(config?: FirestoreAdapterConfig);
|
|
21
|
+
private buildQuery;
|
|
22
|
+
fetchCollection<T = Record<string, unknown>>(collection: string, q?: Query): Promise<(T & {
|
|
23
|
+
id: string;
|
|
24
|
+
})[]>;
|
|
25
|
+
fetchById<T = Record<string, unknown>>(collection: string, id: string): Promise<(T & {
|
|
26
|
+
id: string;
|
|
27
|
+
}) | null>;
|
|
28
|
+
create<T = Record<string, unknown>>(collection: string, data: T): Promise<T & {
|
|
29
|
+
id: string;
|
|
30
|
+
}>;
|
|
31
|
+
createWithId<T = Record<string, unknown>>(collection: string, id: string, data: T): Promise<T & {
|
|
32
|
+
id: string;
|
|
33
|
+
}>;
|
|
34
|
+
update<T = Record<string, unknown>>(collection: string, id: string, data: Partial<T>): Promise<void>;
|
|
35
|
+
upsert<T = Record<string, unknown>>(collection: string, id: string, data: Partial<T>): Promise<void>;
|
|
36
|
+
delete(collection: string, id: string): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { type FirestoreAdapterConfig, FirestoreDataAdapter };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
|
|
21
|
+
// src/adapters/firestore/index.ts
|
|
22
|
+
import admin from "firebase-admin";
|
|
23
|
+
var OP_MAP = {
|
|
24
|
+
eq: "==",
|
|
25
|
+
lt: "<",
|
|
26
|
+
lte: "<=",
|
|
27
|
+
gt: ">",
|
|
28
|
+
gte: ">=",
|
|
29
|
+
in: "in"
|
|
30
|
+
};
|
|
31
|
+
function serialize(data) {
|
|
32
|
+
if (Array.isArray(data)) {
|
|
33
|
+
return data.map(serialize);
|
|
34
|
+
}
|
|
35
|
+
if (data && typeof data === "object") {
|
|
36
|
+
const obj = data;
|
|
37
|
+
if ("_seconds" in obj && "_nanoseconds" in obj) {
|
|
38
|
+
return new Date(
|
|
39
|
+
obj._seconds * 1e3 + obj._nanoseconds / 1e6
|
|
40
|
+
).toISOString();
|
|
41
|
+
}
|
|
42
|
+
if (typeof obj.toDate === "function") {
|
|
43
|
+
return obj.toDate().toISOString();
|
|
44
|
+
}
|
|
45
|
+
const out = {};
|
|
46
|
+
for (const key in obj) out[key] = serialize(obj[key]);
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
return data;
|
|
50
|
+
}
|
|
51
|
+
var FirestoreDataAdapter = class {
|
|
52
|
+
constructor(config = {}) {
|
|
53
|
+
var _a, _b;
|
|
54
|
+
this.defaultOrderByField = (_a = config.defaultOrderByField) != null ? _a : "createdAt";
|
|
55
|
+
if (config.db) {
|
|
56
|
+
this.db = config.db;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!admin.apps.length) {
|
|
60
|
+
const c = (_b = config.credentials) != null ? _b : {};
|
|
61
|
+
admin.initializeApp({
|
|
62
|
+
credential: admin.credential.cert({
|
|
63
|
+
projectId: c.projectId,
|
|
64
|
+
clientEmail: c.clientEmail,
|
|
65
|
+
privateKey: c.privateKey
|
|
66
|
+
}),
|
|
67
|
+
databaseURL: c.databaseURL
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
this.db = admin.firestore();
|
|
71
|
+
}
|
|
72
|
+
buildQuery(collection, q) {
|
|
73
|
+
var _a, _b;
|
|
74
|
+
let ref = this.db.collection(collection);
|
|
75
|
+
if (!q) {
|
|
76
|
+
return ref.orderBy(this.defaultOrderByField, "desc");
|
|
77
|
+
}
|
|
78
|
+
for (const f of (_a = q.filters) != null ? _a : []) {
|
|
79
|
+
ref = ref.where(f.field, OP_MAP[f.op], f.value);
|
|
80
|
+
}
|
|
81
|
+
for (const o of (_b = q.orderBy) != null ? _b : []) {
|
|
82
|
+
ref = ref.orderBy(o.field, o.direction);
|
|
83
|
+
}
|
|
84
|
+
if (q.limit != null) ref = ref.limit(q.limit);
|
|
85
|
+
return ref;
|
|
86
|
+
}
|
|
87
|
+
async fetchCollection(collection, q) {
|
|
88
|
+
const snap = await this.buildQuery(collection, q).get();
|
|
89
|
+
return snap.docs.map(
|
|
90
|
+
(doc) => serialize(__spreadValues({ id: doc.id }, doc.data()))
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
async fetchById(collection, id) {
|
|
94
|
+
const doc = await this.db.collection(collection).doc(id).get();
|
|
95
|
+
if (!doc.exists) return null;
|
|
96
|
+
return serialize(__spreadValues({ id: doc.id }, doc.data()));
|
|
97
|
+
}
|
|
98
|
+
async create(collection, data) {
|
|
99
|
+
const ref = this.db.collection(collection).doc();
|
|
100
|
+
await ref.set(__spreadProps(__spreadValues({}, data), { createdAt: /* @__PURE__ */ new Date(), updatedAt: /* @__PURE__ */ new Date() }));
|
|
101
|
+
return __spreadValues({ id: ref.id }, data);
|
|
102
|
+
}
|
|
103
|
+
async createWithId(collection, id, data) {
|
|
104
|
+
await this.db.collection(collection).doc(id).set(__spreadProps(__spreadValues({}, data), { createdAt: /* @__PURE__ */ new Date(), updatedAt: /* @__PURE__ */ new Date() }));
|
|
105
|
+
return __spreadValues({ id }, data);
|
|
106
|
+
}
|
|
107
|
+
async update(collection, id, data) {
|
|
108
|
+
await this.db.collection(collection).doc(id).update(__spreadProps(__spreadValues({}, data), { updatedAt: /* @__PURE__ */ new Date() }));
|
|
109
|
+
}
|
|
110
|
+
async upsert(collection, id, data) {
|
|
111
|
+
await this.db.collection(collection).doc(id).set(__spreadProps(__spreadValues({}, data), { updatedAt: /* @__PURE__ */ new Date() }), { merge: true });
|
|
112
|
+
}
|
|
113
|
+
async delete(collection, id) {
|
|
114
|
+
await this.db.collection(collection).doc(id).delete();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
export {
|
|
118
|
+
FirestoreDataAdapter
|
|
119
|
+
};
|
|
120
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/adapters/firestore/index.ts"],"sourcesContent":["import admin from \"firebase-admin\";\nimport type {\n Firestore,\n Query as FirestoreQuery,\n WhereFilterOp,\n} from \"firebase-admin/firestore\";\nimport type { DataAdapter, Query, QueryFilterOp } from \"../../types\";\n\nexport interface FirestoreAdapterConfig {\n /** Provide an existing admin Firestore instance… */\n db?: Firestore;\n /** …or service-account credentials to initialize firebase-admin lazily. */\n credentials?: {\n projectId?: string;\n clientEmail?: string;\n privateKey?: string;\n databaseURL?: string;\n };\n /** Field used for default ordering when no query is given. Default `createdAt`. */\n defaultOrderByField?: string;\n}\n\nconst OP_MAP: Record<QueryFilterOp, WhereFilterOp> = {\n eq: \"==\",\n lt: \"<\",\n lte: \"<=\",\n gt: \">\",\n gte: \">=\",\n in: \"in\",\n};\n\n/** Firestore Timestamps → ISO strings, recursively, so the API returns plain JSON. */\nfunction serialize<T>(data: T): T {\n if (Array.isArray(data)) {\n return data.map(serialize) as unknown as T;\n }\n if (data && typeof data === \"object\") {\n const obj = data as Record<string, unknown>;\n if (\"_seconds\" in obj && \"_nanoseconds\" in obj) {\n return new Date(\n (obj._seconds as number) * 1000 + (obj._nanoseconds as number) / 1e6,\n ).toISOString() as unknown as T;\n }\n if (typeof (obj as { toDate?: unknown }).toDate === \"function\") {\n return (obj as { toDate: () => Date }).toDate().toISOString() as unknown as T;\n }\n const out: Record<string, unknown> = {};\n for (const key in obj) out[key] = serialize(obj[key]);\n return out as T;\n }\n return data;\n}\n\nexport class FirestoreDataAdapter implements DataAdapter {\n private readonly db: Firestore;\n private readonly defaultOrderByField: string;\n\n constructor(config: FirestoreAdapterConfig = {}) {\n this.defaultOrderByField = config.defaultOrderByField ?? \"createdAt\";\n\n if (config.db) {\n this.db = config.db;\n return;\n }\n\n if (!admin.apps.length) {\n const c = config.credentials ?? {};\n admin.initializeApp({\n credential: admin.credential.cert({\n projectId: c.projectId,\n clientEmail: c.clientEmail,\n privateKey: c.privateKey,\n }),\n databaseURL: c.databaseURL,\n });\n }\n this.db = admin.firestore();\n }\n\n private buildQuery(collection: string, q?: Query): FirestoreQuery {\n let ref: FirestoreQuery = this.db.collection(collection);\n\n if (!q) {\n return ref.orderBy(this.defaultOrderByField, \"desc\");\n }\n\n for (const f of q.filters ?? []) {\n ref = ref.where(f.field, OP_MAP[f.op], f.value);\n }\n for (const o of q.orderBy ?? []) {\n ref = ref.orderBy(o.field, o.direction);\n }\n if (q.limit != null) ref = ref.limit(q.limit);\n return ref;\n }\n\n async fetchCollection<T = Record<string, unknown>>(\n collection: string,\n q?: Query,\n ): Promise<(T & { id: string })[]> {\n const snap = await this.buildQuery(collection, q).get();\n return snap.docs.map((doc) =>\n serialize({ id: doc.id, ...(doc.data() as T) }),\n );\n }\n\n async fetchById<T = Record<string, unknown>>(\n collection: string,\n id: string,\n ): Promise<(T & { id: string }) | null> {\n const doc = await this.db.collection(collection).doc(id).get();\n if (!doc.exists) return null;\n return serialize({ id: doc.id, ...(doc.data() as T) });\n }\n\n async create<T = Record<string, unknown>>(\n collection: string,\n data: T,\n ): Promise<T & { id: string }> {\n const ref = this.db.collection(collection).doc();\n await ref.set({ ...data, createdAt: new Date(), updatedAt: new Date() });\n return { id: ref.id, ...(data as T) };\n }\n\n async createWithId<T = Record<string, unknown>>(\n collection: string,\n id: string,\n data: T,\n ): Promise<T & { id: string }> {\n await this.db\n .collection(collection)\n .doc(id)\n .set({ ...data, createdAt: new Date(), updatedAt: new Date() });\n return { id, ...(data as T) };\n }\n\n async update<T = Record<string, unknown>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void> {\n await this.db\n .collection(collection)\n .doc(id)\n .update({ ...data, updatedAt: new Date() });\n }\n\n async upsert<T = Record<string, unknown>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void> {\n await this.db\n .collection(collection)\n .doc(id)\n .set({ ...data, updatedAt: new Date() }, { merge: true });\n }\n\n async delete(collection: string, id: string): Promise<void> {\n await this.db.collection(collection).doc(id).delete();\n }\n}\n"],"mappings":"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","names":[]}
|