@lpdjs/firestore-repo-service 2.4.3 → 2.6.2-beta.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/README.md +92 -50
- package/dist/{create-servers-B9dTUhvR.d.cts → create-servers-B4GrBqdA.d.cts} +6 -6
- package/dist/{create-servers-BFhdPPeo.d.ts → create-servers-CVudVM8e.d.ts} +6 -6
- package/dist/{firebase-auth-D1APf9PA.d.cts → firebase-auth-Dpvrd8MP.d.cts} +13 -0
- package/dist/{firebase-auth-D1APf9PA.d.ts → firebase-auth-Dpvrd8MP.d.ts} +13 -0
- package/dist/history/index.cjs +1 -1
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +10 -4
- package/dist/history/index.d.ts +10 -4
- package/dist/history/index.js +1 -1
- package/dist/history/index.js.map +1 -1
- package/dist/{index-BxurOEz1.d.ts → index-DzO9MfNI.d.cts} +9 -2
- package/dist/{index-BmagC7uw.d.cts → index-oFhGCBrY.d.ts} +9 -2
- package/dist/index.cjs +84 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -9
- package/dist/index.d.ts +9 -9
- package/dist/index.js +84 -84
- package/dist/index.js.map +1 -1
- package/dist/{openapi-ML_1hTx2.d.cts → openapi-B2w5tVRR.d.cts} +1 -1
- package/dist/{openapi-DIoQV_yQ.d.ts → openapi-DB8bXZB-.d.ts} +1 -1
- package/dist/{queue-xMOZxY0M.d.cts → queue-B8YUTnBT.d.cts} +20 -5
- package/dist/{queue-CVchaGAh.d.ts → queue-DYmbVDu5.d.ts} +20 -5
- package/dist/{read-BSyLao3I.d.cts → read-CTWZjxyh.d.cts} +1 -1
- package/dist/{read-BSyLao3I.d.ts → read-CTWZjxyh.d.ts} +1 -1
- package/dist/servers/admin/index.cjs +48 -48
- package/dist/servers/admin/index.cjs.map +1 -1
- package/dist/servers/admin/index.d.cts +4 -4
- package/dist/servers/admin/index.d.ts +4 -4
- package/dist/servers/admin/index.js +48 -48
- package/dist/servers/admin/index.js.map +1 -1
- package/dist/servers/auth/index.cjs +25 -12
- package/dist/servers/auth/index.cjs.map +1 -1
- package/dist/servers/auth/index.d.cts +1 -1
- package/dist/servers/auth/index.d.ts +1 -1
- package/dist/servers/auth/index.js +25 -12
- package/dist/servers/auth/index.js.map +1 -1
- package/dist/servers/crud/index.cjs +2 -2
- package/dist/servers/crud/index.cjs.map +1 -1
- package/dist/servers/crud/index.d.cts +6 -6
- package/dist/servers/crud/index.d.ts +6 -6
- package/dist/servers/crud/index.js +2 -2
- package/dist/servers/crud/index.js.map +1 -1
- package/dist/servers/hono/cli.cjs +142 -53
- package/dist/servers/hono/cli.cjs.map +1 -1
- package/dist/servers/hono/cli.js +142 -53
- package/dist/servers/hono/cli.js.map +1 -1
- package/dist/servers/hono/index.cjs +5 -5
- package/dist/servers/hono/index.cjs.map +1 -1
- package/dist/servers/hono/index.d.cts +241 -24
- package/dist/servers/hono/index.d.ts +241 -24
- package/dist/servers/hono/index.js +5 -5
- package/dist/servers/hono/index.js.map +1 -1
- package/dist/servers/index.cjs +98 -98
- package/dist/servers/index.cjs.map +1 -1
- package/dist/servers/index.d.cts +9 -9
- package/dist/servers/index.d.ts +9 -9
- package/dist/servers/index.js +98 -98
- package/dist/servers/index.js.map +1 -1
- package/dist/sync/bigquery.cjs +3 -3
- package/dist/sync/bigquery.cjs.map +1 -1
- package/dist/sync/bigquery.d.cts +18 -2
- package/dist/sync/bigquery.d.ts +18 -2
- package/dist/sync/bigquery.js +3 -3
- package/dist/sync/bigquery.js.map +1 -1
- package/dist/sync/index.cjs +37 -37
- package/dist/sync/index.cjs.map +1 -1
- package/dist/sync/index.d.cts +5 -5
- package/dist/sync/index.d.ts +5 -5
- package/dist/sync/index.js +37 -37
- package/dist/sync/index.js.map +1 -1
- package/dist/{types-5vgXdUM2.d.ts → types-BHZ-Gk-s.d.ts} +71 -4
- package/dist/{types-ChzVPw4k.d.ts → types-FLGn8CAI.d.ts} +15 -1
- package/dist/{types-BtdC0Qhu.d.cts → types-GvexCqrq.d.cts} +71 -4
- package/dist/{types-BgIGWlR1.d.cts → types-wcX7xfdo.d.cts} +15 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -102,20 +102,23 @@ if (post) {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
// Update
|
|
105
|
-
const updated = await repos.users.update("user123", {
|
|
105
|
+
const updated = await repos.users.update("user123", {
|
|
106
|
+
name: "New name",
|
|
107
|
+
age: 31,
|
|
108
|
+
});
|
|
106
109
|
```
|
|
107
110
|
|
|
108
111
|
## API reference
|
|
109
112
|
|
|
110
113
|
### `createRepositoryConfig()`
|
|
111
114
|
|
|
112
|
-
| Option | Description
|
|
113
|
-
| ------------- |
|
|
114
|
-
| `path` | Collection path in Firestore
|
|
115
|
-
| `isGroup` | `true` for collection group, `false` for simple
|
|
116
|
-
| `foreignKeys` | Keys for `get.by*` methods (single document lookup)
|
|
117
|
-
| `queryKeys` | Keys for `query.by*` methods (multi-document query)
|
|
118
|
-
| `refCb` | Function that returns the document reference
|
|
115
|
+
| Option | Description |
|
|
116
|
+
| ------------- | --------------------------------------------------- |
|
|
117
|
+
| `path` | Collection path in Firestore |
|
|
118
|
+
| `isGroup` | `true` for collection group, `false` for simple |
|
|
119
|
+
| `foreignKeys` | Keys for `get.by*` methods (single document lookup) |
|
|
120
|
+
| `queryKeys` | Keys for `query.by*` methods (multi-document query) |
|
|
121
|
+
| `refCb` | Function that returns the document reference |
|
|
119
122
|
|
|
120
123
|
**Sub-collection example:**
|
|
121
124
|
|
|
@@ -134,8 +137,8 @@ comments: createRepositoryConfig<CommentModel>()({
|
|
|
134
137
|
|
|
135
138
|
```typescript
|
|
136
139
|
interface QueryOptions<T> {
|
|
137
|
-
where?: [keyof T, WhereFilterOp, any][];
|
|
138
|
-
orWhere?: [keyof T, WhereFilterOp, any][][];
|
|
140
|
+
where?: [keyof T, WhereFilterOp, any][]; // AND conditions
|
|
141
|
+
orWhere?: [keyof T, WhereFilterOp, any][][]; // OR conditions
|
|
139
142
|
orderBy?: { field: keyof T; direction?: "asc" | "desc" }[];
|
|
140
143
|
limit?: number;
|
|
141
144
|
offset?: number;
|
|
@@ -226,7 +229,9 @@ const page = await repos.posts.query.paginate({
|
|
|
226
229
|
```typescript
|
|
227
230
|
import { count, sum, average } from "@lpdjs/firestore-repo-service";
|
|
228
231
|
|
|
229
|
-
const activeCount = await repos.users.aggregate.count({
|
|
232
|
+
const activeCount = await repos.users.aggregate.count({
|
|
233
|
+
where: [["isActive", "==", true]],
|
|
234
|
+
});
|
|
230
235
|
const totalViews = await repos.posts.aggregate.sum("views");
|
|
231
236
|
const avgAge = await repos.users.aggregate.average("age");
|
|
232
237
|
```
|
|
@@ -237,7 +242,9 @@ const avgAge = await repos.users.aggregate.average("age");
|
|
|
237
242
|
const result = await repos.users.transaction.run(async (txn) => {
|
|
238
243
|
const user = await txn.get(repos.users.documentRef("user123"));
|
|
239
244
|
if (user.exists()) {
|
|
240
|
-
txn.update(repos.users.documentRef("user123"), {
|
|
245
|
+
txn.update(repos.users.documentRef("user123"), {
|
|
246
|
+
age: user.data().age + 1,
|
|
247
|
+
});
|
|
241
248
|
}
|
|
242
249
|
return { success: true };
|
|
243
250
|
});
|
|
@@ -249,8 +256,14 @@ const result = await repos.users.transaction.run(async (txn) => {
|
|
|
249
256
|
// (status = 'active' AND age >= 18) OR (status = 'pending' AND verified = true)
|
|
250
257
|
const users = await repos.users.query.by({
|
|
251
258
|
orWhere: [
|
|
252
|
-
[
|
|
253
|
-
|
|
259
|
+
[
|
|
260
|
+
["status", "==", "active"],
|
|
261
|
+
["age", ">=", 18],
|
|
262
|
+
],
|
|
263
|
+
[
|
|
264
|
+
["status", "==", "pending"],
|
|
265
|
+
["verified", "==", true],
|
|
266
|
+
],
|
|
254
267
|
],
|
|
255
268
|
});
|
|
256
269
|
```
|
|
@@ -304,9 +317,9 @@ npm i -D @asteasolutions/zod-to-openapi
|
|
|
304
317
|
### Bootstrap
|
|
305
318
|
|
|
306
319
|
```bash
|
|
307
|
-
npx frs
|
|
308
|
-
npx frs
|
|
309
|
-
npx frs
|
|
320
|
+
npx frs init # interactive — creates apis.ts + manifest stub
|
|
321
|
+
npx frs new createPost --domain posts --method post --api v1
|
|
322
|
+
npx frs gen --root src/domains # refresh manifest (run before each build)
|
|
310
323
|
```
|
|
311
324
|
|
|
312
325
|
### Configure your APIs (`apis.ts`)
|
|
@@ -337,7 +350,7 @@ import { z } from "zod";
|
|
|
337
350
|
import { defineRoute } from "../../../../apis.js";
|
|
338
351
|
|
|
339
352
|
export default defineRoute({
|
|
340
|
-
api: "v1",
|
|
353
|
+
api: "v1", // typed: only registered tags accepted
|
|
341
354
|
method: "post",
|
|
342
355
|
input: z.object({ title: z.string() }),
|
|
343
356
|
output: z.object({ id: z.string() }),
|
|
@@ -365,16 +378,16 @@ export const { v1 } = apis.toFunctions(routes, onRequest, {
|
|
|
365
378
|
|
|
366
379
|
### Key features
|
|
367
380
|
|
|
368
|
-
| Feature
|
|
369
|
-
|
|
|
370
|
-
| **File-based routing** | `routes.ts` next to each useCase, scanned at build time
|
|
371
|
-
| **Multi-API registry** | One Cloud Function per tag, typed `api` field
|
|
372
|
-
| **Zod validation**
|
|
373
|
-
| **OpenAPI 3.1**
|
|
374
|
-
| **Interceptor**
|
|
375
|
-
| **Middlewares**
|
|
376
|
-
| **Typed context**
|
|
377
|
-
| **CLI**
|
|
381
|
+
| Feature | Details |
|
|
382
|
+
| ---------------------- | ------------------------------------------------------------------- |
|
|
383
|
+
| **File-based routing** | `routes.ts` next to each useCase, scanned at build time |
|
|
384
|
+
| **Multi-API registry** | One Cloud Function per tag, typed `api` field |
|
|
385
|
+
| **Zod validation** | Body / query / path params + optional response validation |
|
|
386
|
+
| **OpenAPI 3.1** | Auto-generated from Zod schemas; Scalar UI at `/docs` |
|
|
387
|
+
| **Interceptor** | Around-style hook for envelopes, error mapping, tracing |
|
|
388
|
+
| **Middlewares** | Per-API and per-route Hono middlewares |
|
|
389
|
+
| **Typed context** | Augment `ContextVariableMap` once, `c.get("user")` typed everywhere |
|
|
390
|
+
| **CLI** | `init` / `new` (interactive) / `gen` |
|
|
378
391
|
|
|
379
392
|
Full documentation: [frs.lpdjs.fr/guide/hono](https://frs.lpdjs.fr/guide/hono)
|
|
380
393
|
|
|
@@ -446,9 +459,18 @@ export const { functions } = servers.sync({
|
|
|
446
459
|
|
|
447
460
|
// Spread Cloud Functions into your exports
|
|
448
461
|
export const {
|
|
449
|
-
users_onCreate,
|
|
450
|
-
|
|
451
|
-
|
|
462
|
+
users_onCreate,
|
|
463
|
+
users_onUpdate,
|
|
464
|
+
users_onDelete,
|
|
465
|
+
sync_users,
|
|
466
|
+
posts_onCreate,
|
|
467
|
+
posts_onUpdate,
|
|
468
|
+
posts_onDelete,
|
|
469
|
+
sync_posts,
|
|
470
|
+
comments_onCreate,
|
|
471
|
+
comments_onUpdate,
|
|
472
|
+
comments_onDelete,
|
|
473
|
+
sync_comments,
|
|
452
474
|
adminsync,
|
|
453
475
|
} = functions;
|
|
454
476
|
```
|
|
@@ -488,7 +510,6 @@ Firestore emulator runs on `localhost:8080`, UI on `http://localhost:4000`.
|
|
|
488
510
|
|
|
489
511
|
MIT
|
|
490
512
|
|
|
491
|
-
|
|
492
513
|
## Installation
|
|
493
514
|
|
|
494
515
|
```bash
|
|
@@ -579,20 +600,23 @@ if (post) {
|
|
|
579
600
|
}
|
|
580
601
|
|
|
581
602
|
// Update
|
|
582
|
-
const updated = await repos.users.update("user123", {
|
|
603
|
+
const updated = await repos.users.update("user123", {
|
|
604
|
+
name: "New name",
|
|
605
|
+
age: 31,
|
|
606
|
+
});
|
|
583
607
|
```
|
|
584
608
|
|
|
585
609
|
## API reference
|
|
586
610
|
|
|
587
611
|
### `createRepositoryConfig()`
|
|
588
612
|
|
|
589
|
-
| Option | Description
|
|
590
|
-
| ------------- |
|
|
591
|
-
| `path` | Collection path in Firestore
|
|
592
|
-
| `isGroup` | `true` for collection group, `false` for simple
|
|
593
|
-
| `foreignKeys` | Keys for `get.by*` methods (single document lookup)
|
|
594
|
-
| `queryKeys` | Keys for `query.by*` methods (multi-document query)
|
|
595
|
-
| `refCb` | Function that returns the document reference
|
|
613
|
+
| Option | Description |
|
|
614
|
+
| ------------- | --------------------------------------------------- |
|
|
615
|
+
| `path` | Collection path in Firestore |
|
|
616
|
+
| `isGroup` | `true` for collection group, `false` for simple |
|
|
617
|
+
| `foreignKeys` | Keys for `get.by*` methods (single document lookup) |
|
|
618
|
+
| `queryKeys` | Keys for `query.by*` methods (multi-document query) |
|
|
619
|
+
| `refCb` | Function that returns the document reference |
|
|
596
620
|
|
|
597
621
|
**Sub-collection example:**
|
|
598
622
|
|
|
@@ -611,8 +635,8 @@ comments: createRepositoryConfig<CommentModel>()({
|
|
|
611
635
|
|
|
612
636
|
```typescript
|
|
613
637
|
interface QueryOptions<T> {
|
|
614
|
-
where?: [keyof T, WhereFilterOp, any][];
|
|
615
|
-
orWhere?: [keyof T, WhereFilterOp, any][][];
|
|
638
|
+
where?: [keyof T, WhereFilterOp, any][]; // AND conditions
|
|
639
|
+
orWhere?: [keyof T, WhereFilterOp, any][][]; // OR conditions
|
|
616
640
|
orderBy?: { field: keyof T; direction?: "asc" | "desc" }[];
|
|
617
641
|
limit?: number;
|
|
618
642
|
offset?: number;
|
|
@@ -703,7 +727,9 @@ const page = await repos.posts.query.paginate({
|
|
|
703
727
|
```typescript
|
|
704
728
|
import { count, sum, average } from "@lpdjs/firestore-repo-service";
|
|
705
729
|
|
|
706
|
-
const activeCount = await repos.users.aggregate.count({
|
|
730
|
+
const activeCount = await repos.users.aggregate.count({
|
|
731
|
+
where: [["isActive", "==", true]],
|
|
732
|
+
});
|
|
707
733
|
const totalViews = await repos.posts.aggregate.sum("views");
|
|
708
734
|
const avgAge = await repos.users.aggregate.average("age");
|
|
709
735
|
```
|
|
@@ -714,7 +740,9 @@ const avgAge = await repos.users.aggregate.average("age");
|
|
|
714
740
|
const result = await repos.users.transaction.run(async (txn) => {
|
|
715
741
|
const user = await txn.get(repos.users.documentRef("user123"));
|
|
716
742
|
if (user.exists()) {
|
|
717
|
-
txn.update(repos.users.documentRef("user123"), {
|
|
743
|
+
txn.update(repos.users.documentRef("user123"), {
|
|
744
|
+
age: user.data().age + 1,
|
|
745
|
+
});
|
|
718
746
|
}
|
|
719
747
|
return { success: true };
|
|
720
748
|
});
|
|
@@ -726,8 +754,14 @@ const result = await repos.users.transaction.run(async (txn) => {
|
|
|
726
754
|
// (status = 'active' AND age >= 18) OR (status = 'pending' AND verified = true)
|
|
727
755
|
const users = await repos.users.query.by({
|
|
728
756
|
orWhere: [
|
|
729
|
-
[
|
|
730
|
-
|
|
757
|
+
[
|
|
758
|
+
["status", "==", "active"],
|
|
759
|
+
["age", ">=", 18],
|
|
760
|
+
],
|
|
761
|
+
[
|
|
762
|
+
["status", "==", "pending"],
|
|
763
|
+
["verified", "==", true],
|
|
764
|
+
],
|
|
731
765
|
],
|
|
732
766
|
});
|
|
733
767
|
```
|
|
@@ -798,9 +832,18 @@ export const { functions } = servers.sync({
|
|
|
798
832
|
|
|
799
833
|
// Spread Cloud Functions into your exports
|
|
800
834
|
export const {
|
|
801
|
-
users_onCreate,
|
|
802
|
-
|
|
803
|
-
|
|
835
|
+
users_onCreate,
|
|
836
|
+
users_onUpdate,
|
|
837
|
+
users_onDelete,
|
|
838
|
+
sync_users,
|
|
839
|
+
posts_onCreate,
|
|
840
|
+
posts_onUpdate,
|
|
841
|
+
posts_onDelete,
|
|
842
|
+
sync_posts,
|
|
843
|
+
comments_onCreate,
|
|
844
|
+
comments_onUpdate,
|
|
845
|
+
comments_onDelete,
|
|
846
|
+
sync_comments,
|
|
804
847
|
adminsync,
|
|
805
848
|
} = functions;
|
|
806
849
|
```
|
|
@@ -837,4 +880,3 @@ Firestore emulator runs on `localhost:8080`, UI on `http://localhost:4000`.
|
|
|
837
880
|
## License
|
|
838
881
|
|
|
839
882
|
MIT
|
|
840
|
-
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { S as SyncQueue } from './queue-
|
|
2
|
-
import { F as FirestoreSyncConfig, S as SyncEvent } from './types-
|
|
3
|
-
import { O as OpenAPIDocument } from './openapi-
|
|
1
|
+
import { S as SyncQueue } from './queue-B8YUTnBT.cjs';
|
|
2
|
+
import { F as FirestoreSyncConfig, S as SyncEvent } from './types-wcX7xfdo.cjs';
|
|
3
|
+
import { O as OpenAPIDocument } from './openapi-B2w5tVRR.cjs';
|
|
4
4
|
import * as firebase_functions_v2_https from 'firebase-functions/v2/https';
|
|
5
5
|
import { onRequest, HttpsOptions } from 'firebase-functions/v2/https';
|
|
6
|
-
import { H as HistoryTriggersConfig } from './read-
|
|
7
|
-
import { C as ConfiguredRepository, a as CrudServerOptions, b as CrudRepoConfig } from './types-
|
|
8
|
-
import { A as AdminServerOptions, a as AdminRepoConfig } from './index-
|
|
6
|
+
import { H as HistoryTriggersConfig } from './read-CTWZjxyh.cjs';
|
|
7
|
+
import { C as ConfiguredRepository, a as CrudServerOptions, b as CrudRepoConfig } from './types-GvexCqrq.cjs';
|
|
8
|
+
import { A as AdminServerOptions, a as AdminRepoConfig } from './index-DzO9MfNI.cjs';
|
|
9
9
|
|
|
10
10
|
/** Per-repo admin config with `repo` omitted (auto-bound from the registry key). */
|
|
11
11
|
type BoundAdminRepoConfig<TRepo extends ConfiguredRepository<any>> = Omit<AdminRepoConfig<TRepo>, "repo">;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { S as SyncQueue } from './queue-
|
|
2
|
-
import { F as FirestoreSyncConfig, S as SyncEvent } from './types-
|
|
3
|
-
import { O as OpenAPIDocument } from './openapi-
|
|
1
|
+
import { S as SyncQueue } from './queue-DYmbVDu5.js';
|
|
2
|
+
import { F as FirestoreSyncConfig, S as SyncEvent } from './types-FLGn8CAI.js';
|
|
3
|
+
import { O as OpenAPIDocument } from './openapi-DB8bXZB-.js';
|
|
4
4
|
import * as firebase_functions_v2_https from 'firebase-functions/v2/https';
|
|
5
5
|
import { onRequest, HttpsOptions } from 'firebase-functions/v2/https';
|
|
6
|
-
import { H as HistoryTriggersConfig } from './read-
|
|
7
|
-
import { C as ConfiguredRepository, a as CrudServerOptions, b as CrudRepoConfig } from './types-
|
|
8
|
-
import { A as AdminServerOptions, a as AdminRepoConfig } from './index-
|
|
6
|
+
import { H as HistoryTriggersConfig } from './read-CTWZjxyh.js';
|
|
7
|
+
import { C as ConfiguredRepository, a as CrudServerOptions, b as CrudRepoConfig } from './types-BHZ-Gk-s.js';
|
|
8
|
+
import { A as AdminServerOptions, a as AdminRepoConfig } from './index-oFhGCBrY.js';
|
|
9
9
|
|
|
10
10
|
/** Per-repo admin config with `repo` omitted (auto-bound from the registry key). */
|
|
11
11
|
type BoundAdminRepoConfig<TRepo extends ConfiguredRepository<any>> = Omit<AdminRepoConfig<TRepo>, "repo">;
|
|
@@ -272,6 +272,19 @@ interface FirebaseAuthConfig<TContext = unknown> {
|
|
|
272
272
|
secureCookie?: boolean;
|
|
273
273
|
/** Cookie `SameSite`. Default: `"Lax"`. */
|
|
274
274
|
sameSite?: "Strict" | "Lax" | "None";
|
|
275
|
+
/**
|
|
276
|
+
* Enable the built-in CSRF defense: state-changing requests
|
|
277
|
+
* (`POST`/`PUT`/`PATCH`/`DELETE`) authenticated by **session cookie** must
|
|
278
|
+
* carry an `Origin`/`Referer` header matching the request host. Bearer
|
|
279
|
+
* requests are exempt (a browser never auto-attaches an `Authorization`
|
|
280
|
+
* header, so they are not CSRF-able).
|
|
281
|
+
*
|
|
282
|
+
* Defaults to `true` for `cookie`/`both` modes and `false` for `bearer`.
|
|
283
|
+
* This is the primary protection when `sameSite: "None"` is used and a
|
|
284
|
+
* defense-in-depth layer otherwise. Disable only if you implement your own
|
|
285
|
+
* CSRF protection upstream.
|
|
286
|
+
*/
|
|
287
|
+
csrfProtection?: boolean;
|
|
275
288
|
/**
|
|
276
289
|
* Behaviour when authentication fails or `allow()` returns `null`.
|
|
277
290
|
* - `"redirect"` (default in cookie mode) → 302 to the login page,
|
|
@@ -272,6 +272,19 @@ interface FirebaseAuthConfig<TContext = unknown> {
|
|
|
272
272
|
secureCookie?: boolean;
|
|
273
273
|
/** Cookie `SameSite`. Default: `"Lax"`. */
|
|
274
274
|
sameSite?: "Strict" | "Lax" | "None";
|
|
275
|
+
/**
|
|
276
|
+
* Enable the built-in CSRF defense: state-changing requests
|
|
277
|
+
* (`POST`/`PUT`/`PATCH`/`DELETE`) authenticated by **session cookie** must
|
|
278
|
+
* carry an `Origin`/`Referer` header matching the request host. Bearer
|
|
279
|
+
* requests are exempt (a browser never auto-attaches an `Authorization`
|
|
280
|
+
* header, so they are not CSRF-able).
|
|
281
|
+
*
|
|
282
|
+
* Defaults to `true` for `cookie`/`both` modes and `false` for `bearer`.
|
|
283
|
+
* This is the primary protection when `sameSite: "None"` is used and a
|
|
284
|
+
* defense-in-depth layer otherwise. Disable only if you implement your own
|
|
285
|
+
* CSRF protection upstream.
|
|
286
|
+
*/
|
|
287
|
+
csrfProtection?: boolean;
|
|
275
288
|
/**
|
|
276
289
|
* Behaviour when authentication fails or `allow()` returns `null`.
|
|
277
290
|
* - `"redirect"` (default in cookie mode) → 302 to the login page,
|
package/dist/history/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
'use strict';var firestore=require('firebase-admin/firestore'),crypto=require('crypto');function
|
|
1
|
+
'use strict';var firestore=require('firebase-admin/firestore'),crypto=require('crypto');function w(t){if(t===null)return "null";if(t===void 0)return "undefined";if(t instanceof firestore.Timestamp)return "timestamp";if(t instanceof Date)return "date";if(Array.isArray(t))return "array";let e=typeof t;return e==="string"||e==="number"||e==="boolean"?e:"object"}function V(t,e,o=new WeakMap){if(t===e)return true;if(t===null||e===null||t===void 0||e===void 0)return t===e;if(t instanceof firestore.Timestamp&&e instanceof firestore.Timestamp)return t.isEqual(e);if(t instanceof Date&&e instanceof Date)return t.getTime()===e.getTime();if(Array.isArray(t)||Array.isArray(e)){if(!Array.isArray(t)||!Array.isArray(e)||t.length!==e.length)return false;if(o.get(t)===e)return true;o.set(t,e);for(let r=0;r<t.length;r++)if(!V(t[r],e[r],o))return false;return true}if(typeof t=="object"&&typeof e=="object"){if(t instanceof Date||e instanceof Date||t instanceof firestore.Timestamp||e instanceof firestore.Timestamp)return false;if(o.get(t)===e)return true;o.set(t,e);let r=Object.keys(t),i=Object.keys(e);if(r.length!==i.length)return false;for(let a of r)if(!Object.prototype.hasOwnProperty.call(e,a)||!V(t[a],e[a],o))return false;return true}return false}function D(t,e,o={}){let r=new Set([...o.exclude??[],...o.metaFields??[],...o.systemKeys??[]]),i=o.include?new Set(o.include):null,a=t??{},c=e??{},y=new Set([...Object.keys(a),...Object.keys(c)]),T={};for(let p of y){if(r.has(p)||i&&!i.has(p))continue;let s=a[p],n=c[p];V(s,n)||(T[p]={oldValue:s===void 0?null:s,newValue:n===void 0?null:n,type:{old:w(s),new:w(n)}});}return T}var U=7e5;function b(t){let e=t.ttlOverride??t.config.ttl,r={schemaVersion:2,historyDocId:crypto.randomUUID(),historyToObjectId:t.entityId,historySetAt:firestore.Timestamp.now(),operation:t.operation,meta:t.meta,changes:t.changes};e&&(r.expiresAt=firestore.Timestamp.fromMillis(Date.now()+e.days*24*60*60*1e3));let i=a=>{try{return Buffer.byteLength(JSON.stringify(a,(c,y)=>y instanceof firestore.Timestamp?y.toMillis():y),"utf8")}catch{return 0}};if(i(r.changes)>U){let a={};for(let[c,y]of Object.entries(r.changes)){let T=i(y.oldValue),p=i(y.newValue);a[c]={oldValue:T>5e4?"[truncated]":y.oldValue,newValue:p>5e4?"[truncated]":y.newValue,type:y.type};}r.changes=a,r._truncated=true;}return r}async function M(t,e,o,r){let i=e;return o.onBeforeWrite&&(i=await o.onBeforeWrite(e,r)),i?(await t.doc(i.historyDocId).set(i),{written:true,entry:i}):{written:false,reason:"dropped-by-onBeforeWrite"}}function k(t,e){let o=t??{},r={},i=e.meta;if(!i)return r;let a=s=>{if(!s)return;let n=o[s];return n===void 0||n===null?null:String(n)},c=a(i.userId);c!==void 0&&(r.userId=c);let y=a(i.userEmail);y!==void 0&&(r.userEmail=y);let T=a(i.reason);T!==void 0&&(r.reason=T);let p=a(i.comment);if(p!==void 0&&(r.comment=p),i.extras&&i.extras.length>0){let s={},n=false;for(let f of i.extras)f in o&&(s[f]=o[f],n=true);n&&(r.extras=s);}return r}function E(t){let e=t.meta;if(!e)return [];let o=[];return e.userId&&o.push(e.userId),e.userEmail&&o.push(e.userEmail),e.reason&&o.push(e.reason),e.comment&&o.push(e.comment),e.extras&&o.push(...e.extras),o}var B="history";function K(t,e){let o=e.ref?.path??void 0;return o?`${o}/{docId}`:(console.warn(`[HistoryTriggers] Cannot determine collection path for "${t}". Skipping.`),null)}function N(t,e){let{onDocumentWritten:o}=e.deps,r={};for(let[i,a]of Object.entries(t)){let c=a._historyConfig??(typeof a.history=="object"&&a.history!==null&&"enabled"in a.history?a.history:void 0);if(!c?.enabled)continue;let y=c.subcollection??B,T=c.ttl??e.defaults?.ttl,p=e.repos?.[i],s;if(a._isGroup){if(!p?.triggerPath){console.warn(`[HistoryTriggers] Skipping collection-group repo "${i}". Provide a triggerPath in the history triggers repos override.`);continue}s=p.triggerPath;}else s=p?.triggerPath??K(i,a);if(!s)continue;let n=a._systemKeys??[],f=n[0]??"docId",l=E(c);r[`${i}_onHistory`]=o(s,async u=>{try{let m=u.data?.before?.data(),d=u.data?.after?.data(),H;if(!m&&d)H="create";else if(m&&!d)H="delete";else if(m&&d)H="update";else return;let h=String(d?.[f]??m?.[f]??u.params?.docId??u.data?.after?.id??u.data?.before?.id??"");if(!h)return;let g=D(m??{},d??{},{include:c.include,exclude:c.exclude,metaFields:l,systemKeys:n});if(H==="update"&&Object.keys(g).length===0)return;let R=k(d??m??null,c),L=b({entityId:h,operation:H,changes:g,meta:R,config:c,ttlOverride:T}),I=u.data?.after?.ref??u.data?.before?.ref;if(!I)return;let P=I.collection(y);await M(P,L,c,{repoName:i,docId:h,before:m??null,after:d??null});}catch(m){console.error(`[HistoryTriggers] Failed to record history for "${i}":`,m);}});}return r}var j=5;function W(t){return t.schemaVersion===2}function F(t){if(typeof t!="string")return "object";switch(t){case "string":case "number":case "boolean":case "object":case "array":case "timestamp":case "date":case "null":case "undefined":return t;default:return "object"}}function v(t){return {historyDocId:t.historyDocId,historyToObjectId:t.historyToObjectId,historySetAt:t.historySetAt,schemaVersion:2,operation:t.operation,meta:t.meta??{},changes:t.changes??{}}}function z(t){let e={};t.historyUserId!==void 0&&(e.userId=t.historyUserId??null),t.historyUserEmail!==void 0&&(e.userEmail=t.historyUserEmail??null);let o=t.extraHistoryDetails??null;o&&(o.reason!==void 0&&(e.reason=o.reason??null),o.comment!==void 0&&(e.comment=o.comment??null));let r={},i=false;if(t.historyDetails&&typeof t.historyDetails=="object")for(let[a,c]of Object.entries(t.historyDetails))r[a]=c,i=true;if(t.extraContentKeys&&typeof t.extraContentKeys=="object")for(let[a,c]of Object.entries(t.extraContentKeys))r[`content.${a}`]=c,i=true;return i&&(e.extras=r),e}function $(t){let e=t.changes?.oldValue??null,o=t.changes?.newValue??null,r=t.types?.oldValue,i=t.types?.newValue;return {oldValue:e,newValue:o,type:{old:r?F(r):w(e),new:i?F(i):w(o)}}}function q(t){let e=$(t),o=z(t);return {historyDocId:t.historyDocId,historyToObjectId:t.historyToObjectId,historySetAt:t.historySetAt,schemaVersion:1,operation:"update",meta:o,changes:{[t.field]:e}}}function G(t,e,o){return Math.abs(t.toMillis()-e.toMillis())<=o}function J(t,e){return (t.userId??null)===(e.userId??null)}function C(t,e={}){let o=e.groupToleranceMs??j,r=[];for(let i of t){if(W(i)){r.push(v(i));continue}let c=q(i),y=r[r.length-1];y&&y.schemaVersion===1&&G(y.historySetAt,c.historySetAt,o)&&J(y.meta,c.meta)?Object.assign(y.changes,c.changes):r.push(c);}return r}var X="history",Y=50;function S(t,e,o){return t(...o).collection(e)}function Q(t){return String(t[t.length-1]??"")}function Z(t,e,o,r){if(!r?.enabled)return null;let i=r.subcollection??X;async function a(...s){let n={},f=s,l=s[s.length-1];l!==null&&typeof l=="object"&&!(l instanceof firestore.Timestamp)&&("limit"in l||"cursor"in l||"direction"in l)&&(n=l,f=s.slice(0,-1));let u=S(t,i,f),m=n.direction??"desc",d=u.orderBy("historySetAt",m);return n.cursor&&(d=d.startAfter(n.cursor)),n.limit&&n.limit>0&&(d=d.limit(n.limit)),(await d.get()).docs.map(h=>({id:h.id,data:h.data()}))}async function c(...s){let n={},f=s,l=s[s.length-1];l!==null&&typeof l=="object"&&!(l instanceof firestore.Timestamp)&&("limit"in l||"cursor"in l||"direction"in l||"fields"in l||"operations"in l)&&(n=l,f=s.slice(0,-1));let u=n.limit??Y,m=Math.max(u,Math.min(u*8,500)),d=await a(...f,{limit:m,cursor:n.cursor,direction:n.direction??"desc"}),H=C(d.map(h=>h.data));if(n.fields&&n.fields.length>0){let h=new Set(n.fields);H=H.filter(g=>Object.keys(g.changes).some(R=>h.has(R)));}if(n.operations&&n.operations.length>0){let h=new Set(n.operations);H=H.filter(g=>h.has(g.operation));}return H.slice(0,u)}async function y(...s){let n=s[s.length-1],f={},l,u;return n!==null&&typeof n=="object"&&!(n instanceof firestore.Timestamp)?(f=n,l=s[s.length-2],u=s.slice(0,-2)):(l=n,u=s.slice(0,-1)),c(...u,{...f,fields:[l]})}async function T(...s){let n=s[s.length-1],f={},l,u;return n!==null&&typeof n=="object"&&!(n instanceof firestore.Timestamp)?(f=n,l=s[s.length-2],u=s.slice(0,-2)):(l=n,u=s.slice(0,-1)),c(...u,{...f,operations:[l]})}async function p(...s){let n=s[s.length-1],f=s.slice(0,-1),l=Q(f),u=D(n.before??{},n.after??{},{include:r.include,exclude:r.exclude,metaFields:E(r),systemKeys:e});if(n.operation==="update"&&Object.keys(u).length===0)return null;let d={...k(n.after??n.before??null,r),...n.meta??{}},H=b({entityId:l,operation:n.operation,changes:u,meta:d,config:r}),h=S(t,i,f),g=await M(h,H,r,{repoName:o,docId:l,before:n.before??null,after:n.after??null});return !g.written||!g.entry?null:{historyDocId:g.entry.historyDocId,historyToObjectId:g.entry.historyToObjectId,historySetAt:g.entry.historySetAt,schemaVersion:2,operation:g.entry.operation,meta:g.entry.meta,changes:g.entry.changes}}return {list:c,raw:a,byField:y,byOperation:T,recordManual:p}}exports.DEFAULT_GROUP_TOLERANCE_MS=j;exports.buildHistoryEntry=b;exports.computeDiff=D;exports.createHistoryMethods=Z;exports.createHistoryTriggers=N;exports.extractMeta=k;exports.metaFieldsOf=E;exports.normalizeHistoryDocs=C;exports.valueType=w;exports.valuesEqual=V;exports.writeHistoryEntry=M;//# sourceMappingURL=index.cjs.map
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|