@mohasinac/appkit 4.30.8 → 4.30.9

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.
@@ -1,3 +1,4 @@
1
+ import type { JsonValue } from "../../../schemas/types";
1
2
  import type { ListingType } from "../../products/types";
2
3
  /**
3
4
  * Coupons Repository
@@ -175,6 +176,10 @@ export declare class CouponsRepository extends BaseRepository<CouponDocument> {
175
176
  message?: string;
176
177
  }>;
177
178
  static readonly SIEVE_FIELDS: {
179
+ searchTxt: {
180
+ canFilter: boolean;
181
+ canSort: boolean;
182
+ };
178
183
  id: {
179
184
  canFilter: boolean;
180
185
  canSort: boolean;
@@ -250,7 +255,11 @@ export declare class CouponsRepository extends BaseRepository<CouponDocument> {
250
255
  * });
251
256
  * ```
252
257
  */
253
- list(model: SieveModel): Promise<FirebaseSieveResult<CouponDocument>>;
258
+ /** Derived on every write path via `applyWriteHooks`. */
259
+ protected buildSearchTxtFor(data: Record<string, JsonValue>): string[] | null;
260
+ list(model: SieveModel, opts?: {
261
+ search?: string;
262
+ }): Promise<FirebaseSieveResult<CouponDocument>>;
254
263
  /**
255
264
  * Cloud Functions: find active coupons whose validity period has expired.
256
265
  */
@@ -1,3 +1,5 @@
1
+ import { planSearchTxt, refineSearchTxt, emptySearchResult, } from "../../../utils/search-txt-query";
2
+ import { buildCouponSearchTxt } from "../../../utils/search-txt-builders";
1
3
  import { normalizeError } from "../../../errors/normalize";
2
4
  import { serverLogger } from "../../../monitoring/server-logger";
3
5
  /**
@@ -481,11 +483,24 @@ export class CouponsRepository extends BaseRepository {
481
483
  * });
482
484
  * ```
483
485
  */
484
- async list(model) {
485
- return this.sieveQuery(model, CouponsRepository.SIEVE_FIELDS, {
486
+ /** Derived on every write path via `applyWriteHooks`. */
487
+ buildSearchTxtFor(data) {
488
+ return buildCouponSearchTxt(data);
489
+ }
490
+ async list(model, opts) {
491
+ const plan = planSearchTxt(opts?.search);
492
+ if (plan.empty)
493
+ return emptySearchResult();
494
+ let baseQuery = this.getCollection();
495
+ if (plan.head) {
496
+ baseQuery = baseQuery.where("searchTxt", "array-contains", plan.head);
497
+ }
498
+ const result = await this.sieveQuery(model, CouponsRepository.SIEVE_FIELDS, {
499
+ baseQuery,
486
500
  defaultPageSize: 50,
487
501
  maxPageSize: 200,
488
502
  });
503
+ return refineSearchTxt(result, plan.rest);
489
504
  }
490
505
  /**
491
506
  * Cloud Functions: find active coupons whose validity period has expired.
@@ -513,6 +528,7 @@ export class CouponsRepository extends BaseRepository {
513
528
  // Sieve-powered list query
514
529
  // ---------------------------------------------------------------------------
515
530
  CouponsRepository.SIEVE_FIELDS = {
531
+ searchTxt: { canFilter: true, canSort: false },
516
532
  id: { canFilter: true, canSort: false },
517
533
  code: { canFilter: true, canSort: true },
518
534
  name: { canFilter: true, canSort: true },
@@ -44,6 +44,8 @@ export interface CouponStats {
44
44
  totalDiscount: number;
45
45
  }
46
46
  export interface CouponDocument extends BaseDocument {
47
+ /** Word-prefix search tokens. Derived on write; never fed by a PII field. */
48
+ searchTxt?: string[];
47
49
  code: string;
48
50
  name: string;
49
51
  description: string;
@@ -103,7 +105,7 @@ export declare const CLAIMED_COUPONS_INDEXED_FIELDS: readonly ["userId", "coupon
103
105
  export declare function createClaimedCouponId(userId: string, couponCode: string): string;
104
106
  export declare const COUPONS_COLLECTION: "coupons";
105
107
  export declare const COUPON_USAGE_SUBCOLLECTION: "couponUsage";
106
- export declare const COUPONS_INDEXED_FIELDS: readonly ["code", "validity.isActive", "validity.startDate", "validity.endDate", "type", "createdBy"];
108
+ export declare const COUPONS_INDEXED_FIELDS: readonly ["searchTxt", "code", "validity.isActive", "validity.startDate", "validity.endDate", "type", "createdBy"];
107
109
  export declare const COUPON_FIELDS: {
108
110
  readonly CODE: "code";
109
111
  readonly TYPE: "type";
@@ -18,6 +18,7 @@ export function createClaimedCouponId(userId, couponCode) {
18
18
  export const COUPONS_COLLECTION = "coupons";
19
19
  export const COUPON_USAGE_SUBCOLLECTION = "couponUsage";
20
20
  export const COUPONS_INDEXED_FIELDS = [
21
+ "searchTxt",
21
22
  "code",
22
23
  "validity.isActive",
23
24
  "validity.startDate",
@@ -4,6 +4,7 @@
4
4
  * Data access layer for the `offers` Firestore collection.
5
5
  * Handles buyer make-an-offer, seller counter/accept/decline lifecycle.
6
6
  */
7
+ import type { JsonValue } from "../../../schemas/types";
7
8
  import { BaseRepository, parseSieveDateValue } from "../../../providers/db-firebase";
8
9
  import type { SieveModel, FirebaseSieveResult } from "../../../providers/db-firebase";
9
10
  import type { OfferDocument, OfferCreateInput, OfferUpdateInput } from "../schemas";
@@ -36,6 +37,10 @@ declare class OfferRepository extends BaseRepository<OfferDocument> {
36
37
  * name does not have, so the audit cannot flag it for you.
37
38
  */
38
39
  static readonly SIEVE_FIELDS: {
40
+ searchTxt: {
41
+ canFilter: boolean;
42
+ canSort: boolean;
43
+ };
39
44
  id: {
40
45
  canFilter: boolean;
41
46
  canSort: boolean;
@@ -100,7 +105,11 @@ declare class OfferRepository extends BaseRepository<OfferDocument> {
100
105
  * mandatory ownership filters; this one is deliberately unscoped and is only
101
106
  * reachable behind `admin:offers:read`.
102
107
  */
103
- list(model: SieveModel): Promise<FirebaseSieveResult<OfferDocument>>;
108
+ /** Derived on every write path via `applyWriteHooks`. */
109
+ protected buildSearchTxtFor(data: Record<string, JsonValue>): string[] | null;
110
+ list(model: SieveModel, opts?: {
111
+ search?: string;
112
+ }): Promise<FirebaseSieveResult<OfferDocument>>;
104
113
  create(input: OfferCreateInput): Promise<OfferDocument>;
105
114
  findByBuyer(buyerUid: string, model?: SieveModel): Promise<FirebaseSieveResult<OfferDocument>>;
106
115
  findByStore(storeId: string, model?: SieveModel): Promise<FirebaseSieveResult<OfferDocument>>;
@@ -4,6 +4,8 @@
4
4
  * Data access layer for the `offers` Firestore collection.
5
5
  * Handles buyer make-an-offer, seller counter/accept/decline lifecycle.
6
6
  */
7
+ import { planSearchTxt, refineSearchTxt, emptySearchResult, } from "../../../utils/search-txt-query";
8
+ import { buildOfferSearchTxt } from "../../../utils/search-txt-builders";
7
9
  import { BaseRepository, prepareForFirestore, parseSieveDateValue, } from "../../../providers/db-firebase";
8
10
  import { OFFER_COLLECTION, OFFER_FIELDS, OFFER_TRACKED_FIELDS, createOfferId } from "../schemas";
9
11
  import { withHistory } from "../../../_internal/shared/history/index";
@@ -29,11 +31,24 @@ class OfferRepository extends BaseRepository {
29
31
  * mandatory ownership filters; this one is deliberately unscoped and is only
30
32
  * reachable behind `admin:offers:read`.
31
33
  */
32
- async list(model) {
33
- return this.sieveQuery(model, OfferRepository.SIEVE_FIELDS, {
34
+ /** Derived on every write path via `applyWriteHooks`. */
35
+ buildSearchTxtFor(data) {
36
+ return buildOfferSearchTxt(data);
37
+ }
38
+ async list(model, opts) {
39
+ const plan = planSearchTxt(opts?.search);
40
+ if (plan.empty)
41
+ return emptySearchResult();
42
+ let baseQuery = this.getCollection();
43
+ if (plan.head) {
44
+ baseQuery = baseQuery.where("searchTxt", "array-contains", plan.head);
45
+ }
46
+ const result = await this.sieveQuery(model, OfferRepository.SIEVE_FIELDS, {
47
+ baseQuery,
34
48
  defaultPageSize: 50,
35
49
  maxPageSize: 200,
36
50
  });
51
+ return refineSearchTxt(result, plan.rest);
37
52
  }
38
53
  // --- Create --------------------------------------------------------------
39
54
  async create(input) {
@@ -367,6 +382,7 @@ class OfferRepository extends BaseRepository {
367
382
  * name does not have, so the audit cannot flag it for you.
368
383
  */
369
384
  OfferRepository.SIEVE_FIELDS = {
385
+ searchTxt: { canFilter: true, canSort: false },
370
386
  id: { canFilter: true, canSort: false },
371
387
  status: { canFilter: true, canSort: true },
372
388
  productId: { canFilter: true, canSort: false },
@@ -16,6 +16,8 @@ export declare const OfferStatusValues: {
16
16
  readonly PAID: "paid";
17
17
  };
18
18
  export interface OfferDocument extends BaseDocument {
19
+ /** Word-prefix search tokens. Derived on write; never fed by a PII field. */
20
+ searchTxt?: string[];
19
21
  productId: string;
20
22
  productTitle: string;
21
23
  productSlug?: string;
@@ -87,7 +89,7 @@ export interface OfferDocument extends BaseDocument {
87
89
  */
88
90
  export declare const OFFER_TRACKED_FIELDS: readonly ["status", "offerAmount", "counterAmount", "lockedPrice", "checkoutDeadline", "paidOrderId", "supersededByOfferId", "previousOfferId"];
89
91
  export declare const OFFER_COLLECTION: "offers";
90
- export declare const OFFER_INDEXED_FIELDS: readonly ["buyerUid", "storeId", "productId", "status", "createdAt", "expiresAt", "checkoutDeadline"];
92
+ export declare const OFFER_INDEXED_FIELDS: readonly ["searchTxt", "buyerUid", "storeId", "productId", "status", "createdAt", "expiresAt", "checkoutDeadline"];
91
93
  export declare const OFFER_FIELDS: {
92
94
  readonly ID: "id";
93
95
  readonly PRODUCT_ID: "productId";
@@ -32,6 +32,7 @@ export const OFFER_TRACKED_FIELDS = [
32
32
  ];
33
33
  export const OFFER_COLLECTION = "offers";
34
34
  export const OFFER_INDEXED_FIELDS = [
35
+ "searchTxt",
35
36
  "buyerUid",
36
37
  "storeId",
37
38
  "productId",
@@ -3,8 +3,12 @@ import type { EventDocument } from "../../features/events/schemas/firestore";
3
3
  import type { BlogPostDocument } from "../../features/blog/schemas/firestore";
4
4
  import type { ReviewDocument } from "../../features/reviews/schemas/firestore";
5
5
  import type { OrderDocument } from "../../features/orders/schemas/firestore";
6
+ import type { CouponDocument } from "../../features/promotions/schemas/firestore";
7
+ import type { OfferDocument } from "../../features/seller/schemas/firestore";
6
8
  export declare function withStoreSearchTxt<T extends Partial<StoreDocument>>(p: T): T;
7
9
  export declare function withEventSearchTxt<T extends Partial<EventDocument>>(p: T): T;
8
10
  export declare function withBlogSearchTxt<T extends Partial<BlogPostDocument>>(p: T): T;
9
11
  export declare function withReviewSearchTxt<T extends Partial<ReviewDocument>>(p: T): T;
10
12
  export declare function withOrderSearchTxt<T extends Partial<OrderDocument>>(p: T): T;
13
+ export declare function withCouponSearchTxt<T extends Partial<CouponDocument>>(p: T): T;
14
+ export declare function withOfferSearchTxt<T extends Partial<OfferDocument>>(p: T): T;
@@ -1,4 +1,4 @@
1
- import { buildStoreSearchTxt, buildEventSearchTxt, buildBlogSearchTxt, buildReviewSearchTxt, buildOrderSearchTxt, } from "../../utils/search-txt-builders";
1
+ import { buildStoreSearchTxt, buildEventSearchTxt, buildBlogSearchTxt, buildReviewSearchTxt, buildOrderSearchTxt, buildCouponSearchTxt, buildOfferSearchTxt, } from "../../utils/search-txt-builders";
2
2
  export function withStoreSearchTxt(p) {
3
3
  return { ...p, searchTxt: buildStoreSearchTxt(p) };
4
4
  }
@@ -14,3 +14,9 @@ export function withReviewSearchTxt(p) {
14
14
  export function withOrderSearchTxt(p) {
15
15
  return { ...p, searchTxt: buildOrderSearchTxt(p) };
16
16
  }
17
+ export function withCouponSearchTxt(p) {
18
+ return { ...p, searchTxt: buildCouponSearchTxt(p) };
19
+ }
20
+ export function withOfferSearchTxt(p) {
21
+ return { ...p, searchTxt: buildOfferSearchTxt(p) };
22
+ }
@@ -12,6 +12,7 @@
12
12
  * @tag consumers:seed/index.ts,seed/runner.ts
13
13
  * @tag sideEffects:none
14
14
  */
15
+ import { withCouponSearchTxt } from "./_helpers/search-txt-wrappers";
15
16
  import { COUPON_FIELDS } from "../constants/field-names";
16
17
  const NOW = new Date();
17
18
  const daysAgo = (n) => new Date(NOW.getTime() - n * 86400000);
@@ -220,4 +221,6 @@ export const couponsSeedData = [
220
221
  updatedAt: daysAgo(2),
221
222
  stats: { totalUses: 41, totalRevenue: 205000, totalDiscount: 20500 },
222
223
  },
223
- ];
224
+ // Derived through the wrapper, never per record — an inline literal is how
225
+ // five product seed files shipped their last fixture with no tokens.
226
+ ].map(withCouponSearchTxt);
@@ -35,6 +35,7 @@
35
35
  * @tag consumers:seed/index.ts,seed/runner.ts
36
36
  * @tag sideEffects:none
37
37
  */
38
+ import { withOfferSearchTxt } from "./_helpers/search-txt-wrappers";
38
39
  import { OfferStatusValues } from "../features/seller/schemas/firestore";
39
40
  const NOW = new Date();
40
41
  const daysAgo = (n) => new Date(NOW.getTime() - n * 86400000);
@@ -392,4 +393,6 @@ export const offersSeedData = [
392
393
  }, { actorUid: BUYER_ADMIN, note: "Interested in this piece. Would you accept ₹950?" }),
393
394
  ],
394
395
  },
395
- ];
396
+ // Derived through the wrapper, never per record — an inline literal is how
397
+ // five product seed files shipped their last fixture with no tokens.
398
+ ].map(withOfferSearchTxt);
@@ -4,6 +4,8 @@ import type { EventDocument } from "../features/events/schemas/firestore";
4
4
  import type { BlogPostDocument } from "../features/blog/schemas/firestore";
5
5
  import type { ReviewDocument } from "../features/reviews/schemas/firestore";
6
6
  import type { OrderDocument } from "../features/orders/schemas/firestore";
7
+ import type { CouponDocument } from "../features/promotions/schemas/firestore";
8
+ import type { OfferDocument } from "../features/seller/schemas/firestore";
7
9
  /** products — title, description, brand, taxonomy, condition, card/grading, specs. */
8
10
  export declare function buildProductSearchTxt(p: Partial<ProductDocument>): string[];
9
11
  /** stores — name, description, category. No payout details, no contact. */
@@ -32,3 +34,14 @@ export declare function buildReviewSearchTxt(r: Partial<ReviewDocument>): string
32
34
  * Indexing any of them would undo the encryption they are stored under (D1).
33
35
  */
34
36
  export declare function buildOrderSearchTxt(o: Partial<OrderDocument>): string[];
37
+ /** coupons — code, name, description. Nothing here is PII. */
38
+ export declare function buildCouponSearchTxt(c: Partial<CouponDocument>): string[];
39
+ /**
40
+ * offers — the product and the store the negotiation is about.
41
+ *
42
+ * 🛑 `buyerName`, `buyerEmail` and `sellerName` are OFFER_PII_FIELDS and are
43
+ * absent. `sellerNote` is free text a seller wrote to one buyer; it is not in
44
+ * the PII registry, but it is private correspondence and has no business being
45
+ * findable by substring from an admin list.
46
+ */
47
+ export declare function buildOfferSearchTxt(o: Partial<OfferDocument>): string[];
@@ -101,3 +101,18 @@ export function buildOrderSearchTxt(o) {
101
101
  (o.items ?? []).map((i) => i.productTitle),
102
102
  ]);
103
103
  }
104
+ /** coupons — code, name, description. Nothing here is PII. */
105
+ export function buildCouponSearchTxt(c) {
106
+ return buildSearchTxt([c.code, c.name, c.description]);
107
+ }
108
+ /**
109
+ * offers — the product and the store the negotiation is about.
110
+ *
111
+ * 🛑 `buyerName`, `buyerEmail` and `sellerName` are OFFER_PII_FIELDS and are
112
+ * absent. `sellerNote` is free text a seller wrote to one buyer; it is not in
113
+ * the PII registry, but it is private correspondence and has no business being
114
+ * findable by substring from an admin list.
115
+ */
116
+ export function buildOfferSearchTxt(o) {
117
+ return buildSearchTxt([o.productTitle, o.productSlug, o.storeName]);
118
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohasinac/appkit",
3
- "version": "4.30.8",
3
+ "version": "4.30.9",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"