@progressive-development/pd-provider-firebase-functions 1.0.1 → 1.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.
@@ -1,4 +1,4 @@
1
- import { TrashItem } from './trash';
1
+ import { TrashItem } from './trash.js';
2
2
  export interface AuthTrashOptions {
3
3
  /**
4
4
  * How to determine the Auth UID from the document.
@@ -23,7 +23,10 @@ export interface AuthTrashOptions {
23
23
  * @param options - Auth and database options
24
24
  * @returns The created trash item
25
25
  */
26
- export declare function moveToTrashWithAuth<T = unknown>(collection: string, documentId: string, deletedBy: string, objectType: string, options?: AuthTrashOptions): Promise<TrashItem<T>>;
26
+ export declare function moveToTrashWithAuth<T = unknown>(collection: string, documentId: string, deletedBy: string | {
27
+ uid: string;
28
+ name: string;
29
+ }, objectType: string, options?: AuthTrashOptions): Promise<TrashItem<T>>;
27
30
  /**
28
31
  * Restore document from trash AND re-enable the Auth user.
29
32
  *
@@ -1 +1 @@
1
- {"version":3,"file":"auth-trash.d.ts","sourceRoot":"","sources":["../src/auth-trash.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,SAAS,CAAC;AAQjB,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,aAAa,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;IAEtC,8BAA8B;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAoCD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,GAAG,OAAO,EACnD,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CA0CvB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,wBAAwB,CAC5C,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,IAAI,CAAC,CAuCf;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,IAAI,CAAC,CAmCf"}
1
+ {"version":3,"file":"auth-trash.d.ts","sourceRoot":"","sources":["../src/auth-trash.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,YAAY,CAAC;AAQpB,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,aAAa,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;IAEtC,8BAA8B;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAoCD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,GAAG,OAAO,EACnD,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACjD,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CA0CvB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,wBAAwB,CAC5C,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,IAAI,CAAC,CAuCf;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,IAAI,CAAC,CAmCf"}
package/dist/auth.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CallableRequest } from 'firebase-functions/v2/https';
2
- import { AuthContext } from './types';
2
+ import { AuthContext } from './types.js';
3
3
  /**
4
4
  * Require authenticated user
5
5
  */
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGtC;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,WAAW,CASjE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,eAAe,EACxB,YAAY,EAAE,MAAM,EAAE,GACrB,WAAW,CAUb;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,WAAW,EACjB,eAAe,EAAE,MAAM,GACtB,IAAI,CAMN"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,WAAW,CASjE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,eAAe,EACxB,YAAY,EAAE,MAAM,EAAE,GACrB,WAAW,CAUb;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,WAAW,EACjB,eAAe,EAAE,MAAM,GACtB,IAAI,CAMN"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * A single field change.
3
+ * Absent sides are stored as null (not undefined — ignoreUndefinedProperties
4
+ * would drop the key on a Firestore write).
5
+ */
6
+ export interface FieldChange {
7
+ old: unknown;
8
+ new: unknown;
9
+ }
10
+ /** Field name → change. Granularity is the top-level field. */
11
+ export type FieldChanges = Record<string, FieldChange>;
12
+ export interface ComputeChangesOptions {
13
+ /** Top-level fields to skip when comparing (e.g. "lastEdit"). */
14
+ ignoreFields?: string[];
15
+ }
16
+ /**
17
+ * Compute which top-level fields changed between two object states.
18
+ *
19
+ * Values are compared deeply (nested objects, arrays), but changes are
20
+ * reported per top-level field with the full old/new value. Firestore value
21
+ * types are compared via their own isEqual(). undefined, null and missing
22
+ * keys are treated as the same "not set" state.
23
+ *
24
+ * @param before - Previous state; undefined means "object did not exist yet"
25
+ * @param after - New state
26
+ * @param options - Optional ignore list for noisy fields (e.g. "lastEdit")
27
+ * @returns The changed fields — or undefined if nothing changed, so
28
+ * "did anything change?" is a truthiness check on the result
29
+ */
30
+ export declare function computeChanges(before: Record<string, unknown> | undefined, after: Record<string, unknown>, options?: ComputeChangesOptions): FieldChanges | undefined;
31
+ //# sourceMappingURL=changes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changes.d.ts","sourceRoot":"","sources":["../src/changes.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;CACd;AAED,+DAA+D;AAC/D,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAEvD,MAAM,WAAW,qBAAqB;IACpC,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAkFD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC3C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,CAAC,EAAE,qBAAqB,GAC9B,YAAY,GAAG,SAAS,CAiB1B"}
@@ -0,0 +1,69 @@
1
+ function hasIsEqual(value) {
2
+ return typeof value === "object" && value !== null && typeof value.isEqual === "function";
3
+ }
4
+ function isUnset(value) {
5
+ return value === void 0 || value === null;
6
+ }
7
+ function isEqualValue(a, b) {
8
+ if (isUnset(a) && isUnset(b)) {
9
+ return true;
10
+ }
11
+ if (isUnset(a) || isUnset(b)) {
12
+ return false;
13
+ }
14
+ if (a === b) {
15
+ return true;
16
+ }
17
+ if (typeof a === "number" && typeof b === "number" && Number.isNaN(a) && Number.isNaN(b)) {
18
+ return true;
19
+ }
20
+ if (hasIsEqual(a) && hasIsEqual(b)) {
21
+ try {
22
+ return a.isEqual(b);
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+ if (hasIsEqual(a) || hasIsEqual(b)) {
28
+ return false;
29
+ }
30
+ if (a instanceof Date && b instanceof Date) {
31
+ return a.getTime() === b.getTime();
32
+ }
33
+ if (Array.isArray(a) && Array.isArray(b)) {
34
+ return a.length === b.length && a.every((item, i) => isEqualValue(item, b[i]));
35
+ }
36
+ if (Array.isArray(a) || Array.isArray(b)) {
37
+ return false;
38
+ }
39
+ if (typeof a === "object" && typeof b === "object") {
40
+ const aObj = a;
41
+ const bObj = b;
42
+ const keys = /* @__PURE__ */ new Set([...Object.keys(aObj), ...Object.keys(bObj)]);
43
+ for (const key of keys) {
44
+ if (!isEqualValue(aObj[key], bObj[key])) {
45
+ return false;
46
+ }
47
+ }
48
+ return true;
49
+ }
50
+ return false;
51
+ }
52
+ function computeChanges(before, after, options) {
53
+ const ignore = new Set(options?.ignoreFields ?? []);
54
+ const keys = /* @__PURE__ */ new Set([...Object.keys(before ?? {}), ...Object.keys(after)]);
55
+ const changes = {};
56
+ for (const key of keys) {
57
+ if (ignore.has(key)) {
58
+ continue;
59
+ }
60
+ const oldValue = before?.[key];
61
+ const newValue = after[key];
62
+ if (!isEqualValue(oldValue, newValue)) {
63
+ changes[key] = { old: oldValue ?? null, new: newValue ?? null };
64
+ }
65
+ }
66
+ return Object.keys(changes).length > 0 ? changes : void 0;
67
+ }
68
+
69
+ export { computeChanges };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=changes.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changes.spec.d.ts","sourceRoot":"","sources":["../src/changes.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,83 @@
1
+ import { Timestamp } from 'firebase-admin/firestore';
2
+ import { FieldChanges } from './changes.js';
3
+ /**
4
+ * Core event categories — fixed in the helper. App-specific semantics go
5
+ * into the free-form `action` field, NOT in here.
6
+ */
7
+ export declare const HistoryKind: {
8
+ readonly CREATED: "created";
9
+ readonly UPDATED: "updated";
10
+ readonly DELETED: "deleted";
11
+ readonly RESTORED: "restored";
12
+ readonly PROCESS: "process";
13
+ };
14
+ export type HistoryKind = (typeof HistoryKind)[keyof typeof HistoryKind];
15
+ /**
16
+ * A history event for an object.
17
+ * History is an append-only event log per object, not a snapshot/version
18
+ * system. Entries survive deletion of the object.
19
+ */
20
+ export interface HistoryEntry {
21
+ /** Auto-generated history document ID */
22
+ id: string;
23
+ /** Collection of the object (e.g., "locations") */
24
+ objectCollection: string;
25
+ /** Document ID of the object */
26
+ objectId: string;
27
+ /** Object type for filtering, same convention as trash (e.g., "location") */
28
+ objectType: string;
29
+ /** Core event category */
30
+ kind: HistoryKind;
31
+ /** When the event was recorded (server-side) */
32
+ at: Timestamp;
33
+ /** App-specific action (vocabulary owned by the app, e.g., "verify") */
34
+ action?: string;
35
+ /** UID of the authenticated actor */
36
+ userId?: string;
37
+ /** Label for non-authenticated/system actors (e.g., "cron", "import-script") */
38
+ actorLabel?: string;
39
+ /** Channel/entry point (app vocabulary: "member-app", "admin", "cron", …) */
40
+ app?: string;
41
+ /** Free text for display */
42
+ message?: string;
43
+ /** Field diff, usually only for kind=updated */
44
+ changes?: FieldChanges;
45
+ }
46
+ /** Input for a new entry — everything except id and at. */
47
+ export type NewHistoryEntry = Omit<HistoryEntry, "id" | "at">;
48
+ /**
49
+ * Write a history entry.
50
+ *
51
+ * Throws on failure — use recordHistory() at app write sites where a
52
+ * history error must never fail the main operation.
53
+ *
54
+ * @param entry - The event data (at is set server-side)
55
+ * @param databaseId - Optional named database
56
+ * @returns The created history entry
57
+ * @throws ValidationError if the object reference is incomplete
58
+ */
59
+ export declare function addHistoryEntry(entry: NewHistoryEntry, databaseId?: string): Promise<HistoryEntry>;
60
+ /**
61
+ * Best-effort variant of addHistoryEntry for app write sites: catches ANY
62
+ * error, logs it as a warning and returns null. The app's main operation
63
+ * can never fail because of history recording.
64
+ *
65
+ * @param entry - The event data
66
+ * @param databaseId - Optional named database
67
+ * @returns The created history entry, or null if recording failed
68
+ */
69
+ export declare function recordHistory(entry: NewHistoryEntry, databaseId?: string): Promise<HistoryEntry | null>;
70
+ /**
71
+ * List the history of an object, newest first.
72
+ *
73
+ * Requires a composite index in the consuming project:
74
+ * objectCollection ASC, objectId ASC, at DESC on collection "_history".
75
+ *
76
+ * @param objectCollection - Collection of the object (e.g., "locations")
77
+ * @param objectId - Document ID of the object
78
+ * @param limit - Maximum entries to return (default: 100)
79
+ * @param databaseId - Optional named database
80
+ * @returns Array of history entries, newest first
81
+ */
82
+ export declare function listHistory(objectCollection: string, objectId: string, limit?: number, databaseId?: string): Promise<HistoryEntry[]>;
83
+ //# sourceMappingURL=history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAIrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAYjD;;;GAGG;AACH,eAAO,MAAM,WAAW;;;;;;CAMd,CAAC;AACX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAEzE;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,mDAAmD;IACnD,gBAAgB,EAAE,MAAM,CAAC;IACzB,gCAAgC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,CAAC;IACnB,0BAA0B;IAC1B,IAAI,EAAE,WAAW,CAAC;IAClB,gDAAgD;IAChD,EAAE,EAAE,SAAS,CAAC;IACd,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB;AAED,2DAA2D;AAC3D,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC,CAAC;AAS9D;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,eAAe,EACtB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC,CAmBvB;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,eAAe,EACtB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAY9B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,gBAAgB,EAAE,MAAM,EACxB,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAY,EACnB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,EAAE,CAAC,CAczB"}
@@ -0,0 +1,53 @@
1
+ import { Timestamp } from 'firebase-admin/firestore';
2
+ import { getDb } from './firestore.js';
3
+ import { ValidationError } from './errors.js';
4
+ import { logger } from './logger.js';
5
+
6
+ const HISTORY_COLLECTION = "_history";
7
+ const HistoryKind = {
8
+ CREATED: "created",
9
+ UPDATED: "updated",
10
+ DELETED: "deleted",
11
+ RESTORED: "restored",
12
+ PROCESS: "process"
13
+ };
14
+ async function addHistoryEntry(entry, databaseId) {
15
+ if (!entry.objectCollection || !entry.objectId || !entry.objectType) {
16
+ throw new ValidationError(
17
+ "History entry requires objectCollection, objectId and objectType"
18
+ );
19
+ }
20
+ const db = getDb(databaseId);
21
+ const entryData = {
22
+ ...entry,
23
+ at: Timestamp.now()
24
+ };
25
+ const docRef = await db.collection(HISTORY_COLLECTION).add(entryData);
26
+ return {
27
+ id: docRef.id,
28
+ ...entryData
29
+ };
30
+ }
31
+ async function recordHistory(entry, databaseId) {
32
+ try {
33
+ return await addHistoryEntry(entry, databaseId);
34
+ } catch (error) {
35
+ logger.warn("recordHistory failed", {
36
+ error: error instanceof Error ? error.message : String(error),
37
+ objectCollection: entry.objectCollection,
38
+ objectId: entry.objectId,
39
+ kind: entry.kind
40
+ });
41
+ return null;
42
+ }
43
+ }
44
+ async function listHistory(objectCollection, objectId, limit = 100, databaseId) {
45
+ const db = getDb(databaseId);
46
+ const snapshot = await db.collection(HISTORY_COLLECTION).where("objectCollection", "==", objectCollection).where("objectId", "==", objectId).orderBy("at", "desc").limit(limit).get();
47
+ return snapshot.docs.map((doc) => ({
48
+ id: doc.id,
49
+ ...doc.data()
50
+ }));
51
+ }
52
+
53
+ export { HistoryKind, addHistoryEntry, listHistory, recordHistory };
package/dist/index.d.ts CHANGED
@@ -1,17 +1,23 @@
1
- export type { ApiResponse, AuthContext } from './types';
2
- export { StatusCode } from './types';
3
- export { AppError, ValidationError, UnauthorizedError, ForbiddenError, NotFoundError, ConflictError, EmailAlreadyUsedError, SC_EMAIL_ALREADY_USED, } from './errors';
4
- export { requireAuth, requireClaims, requireOwnership } from './auth';
5
- export { success, created, handleError } from './response';
6
- export { getDb } from './firestore';
7
- export { getNextCounter } from './counter';
8
- export type { TrashItem } from './trash';
9
- export { moveToTrash, restoreFromTrash, permanentDelete, listTrash, getTrashItem, } from './trash';
10
- export type { AuthTrashOptions } from './auth-trash';
11
- export { moveToTrashWithAuth, restoreFromTrashWithAuth, permanentDeleteWithAuth, } from './auth-trash';
12
- export { logger } from './logger';
13
- export type { LogType, ScriptContextConfig, ScriptContext, } from './script-helpers';
14
- export { scriptLog, initFirebaseAdmin, createScriptContext, } from './script-helpers';
15
- export type { ImportTemplatesConfig } from './import-templates';
16
- export { runImportTemplates } from './import-templates';
1
+ export type { ApiResponse, AuthContext } from './types.js';
2
+ export { StatusCode } from './types.js';
3
+ export { AppError, ValidationError, UnauthorizedError, ForbiddenError, NotFoundError, ConflictError, EmailAlreadyUsedError, SC_EMAIL_ALREADY_USED, } from './errors.js';
4
+ export { requireAuth, requireClaims, requireOwnership } from './auth.js';
5
+ export { success, created, handleError } from './response.js';
6
+ export { getDb } from './firestore.js';
7
+ export { getNextCounter } from './counter.js';
8
+ export type { TrashItem } from './trash.js';
9
+ export { moveToTrash, restoreFromTrash, permanentDelete, listTrash, getTrashItem, } from './trash.js';
10
+ export type { AuthTrashOptions } from './auth-trash.js';
11
+ export { moveToTrashWithAuth, restoreFromTrashWithAuth, permanentDeleteWithAuth, } from './auth-trash.js';
12
+ export type { FieldChange, FieldChanges, ComputeChangesOptions, } from './changes.js';
13
+ export { computeChanges } from './changes.js';
14
+ export type { HistoryEntry, NewHistoryEntry } from './history.js';
15
+ export { HistoryKind, addHistoryEntry, recordHistory, listHistory, } from './history.js';
16
+ export type { MailObject, MailTemplate, MailMessage, MailAttachment, StoredMail, } from './mail.js';
17
+ export { addMailForSend, getMailById, getMailsByField } from './mail.js';
18
+ export { logger } from './logger.js';
19
+ export type { LogType, ScriptContextConfig, ScriptContext, } from './script-helpers.js';
20
+ export { scriptLog, initFirebaseAdmin, createScriptContext, } from './script-helpers.js';
21
+ export type { ImportTemplatesConfig } from './import-templates.js';
22
+ export { runImportTemplates } from './import-templates.js';
17
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAGrC,OAAO,EACL,QAAQ,EACR,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAGtE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG3D,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAGpC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAG3C,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,SAAS,EACT,YAAY,GACb,MAAM,SAAS,CAAC;AAGjB,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGlC,YAAY,EACV,OAAO,EACP,mBAAmB,EACnB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAGxC,OAAO,EACL,QAAQ,EACR,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,aAAa,EACb,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGzE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG9D,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAGvC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,SAAS,EACT,YAAY,GACb,MAAM,YAAY,CAAC;AAGpB,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,iBAAiB,CAAC;AAGzB,YAAY,EACV,WAAW,EACX,YAAY,EACZ,qBAAqB,GACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EACL,WAAW,EACX,eAAe,EACf,aAAa,EACb,WAAW,GACZ,MAAM,cAAc,CAAC;AAGtB,YAAY,EACV,UAAU,EACV,YAAY,EACZ,WAAW,EACX,cAAc,EACd,UAAU,GACX,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAGzE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,YAAY,EACV,OAAO,EACP,mBAAmB,EACnB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAG7B,YAAY,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -6,6 +6,9 @@ export { getDb } from './firestore.js';
6
6
  export { getNextCounter } from './counter.js';
7
7
  export { getTrashItem, listTrash, moveToTrash, permanentDelete, restoreFromTrash } from './trash.js';
8
8
  export { moveToTrashWithAuth, permanentDeleteWithAuth, restoreFromTrashWithAuth } from './auth-trash.js';
9
+ export { computeChanges } from './changes.js';
10
+ export { HistoryKind, addHistoryEntry, listHistory, recordHistory } from './history.js';
11
+ export { addMailForSend, getMailById, getMailsByField } from './mail.js';
9
12
  export { logger } from './logger.js';
10
13
  export { createScriptContext, initFirebaseAdmin, scriptLog } from './script-helpers.js';
11
14
  export { runImportTemplates } from './import-templates.js';
package/dist/mail.d.ts ADDED
@@ -0,0 +1,79 @@
1
+ import { Timestamp } from 'firebase-admin/firestore';
2
+ export interface MailAttachment {
3
+ filename: string;
4
+ path: string;
5
+ contentType?: string;
6
+ }
7
+ /** Template-based mail: the extension renders template `name` with `data`. */
8
+ export interface MailTemplate {
9
+ name: string;
10
+ data: Record<string, unknown>;
11
+ attachments?: MailAttachment[];
12
+ }
13
+ /** Direct mail content, used when no template is involved. */
14
+ export interface MailMessage {
15
+ subject: string;
16
+ html: string;
17
+ text?: string;
18
+ attachments?: MailAttachment[];
19
+ }
20
+ /**
21
+ * Core mail shape — the fields consumed by the Firebase mail extension.
22
+ * Apps extend this with their own correlation fields (e.g. eventId, counter)
23
+ * via the generic parameter of the mail functions.
24
+ */
25
+ export interface MailObject {
26
+ to: string;
27
+ cc?: string;
28
+ bcc?: string;
29
+ replyTo?: string;
30
+ template?: MailTemplate;
31
+ message?: MailMessage;
32
+ }
33
+ /** A stored mail document, including extension-written delivery state. */
34
+ export type StoredMail<T extends MailObject = MailObject> = T & {
35
+ /** Auto-generated mail document ID */
36
+ id: string;
37
+ /** When the mail document was created (server-side) */
38
+ creation: Timestamp;
39
+ /** Delivery state, written by the mail extension */
40
+ delivery?: unknown;
41
+ };
42
+ /**
43
+ * Write a mail document for the Firebase mail extension to send.
44
+ *
45
+ * Throws on failure — the calling app decides whether a mail error aborts
46
+ * its flow or is just logged.
47
+ *
48
+ * @param mail - The mail data; app-specific extra fields are stored as-is
49
+ * @param collection - Mail collection name (default: "mail")
50
+ * @param databaseId - Optional named database
51
+ * @returns The created mail document ID
52
+ * @throws ValidationError if `to` is missing or neither template nor message is set
53
+ */
54
+ export declare function addMailForSend<T extends MailObject>(mail: T, collection?: string, databaseId?: string): Promise<string>;
55
+ /**
56
+ * Get a single mail document by ID (e.g. to inspect delivery state).
57
+ *
58
+ * @param mailId - The mail document ID
59
+ * @param collection - Mail collection name (default: "mail")
60
+ * @param databaseId - Optional named database
61
+ * @returns The stored mail or null if not found
62
+ */
63
+ export declare function getMailById<T extends MailObject>(mailId: string, collection?: string, databaseId?: string): Promise<StoredMail<T> | null>;
64
+ /**
65
+ * List mail documents by an app-specific correlation field
66
+ * (e.g. field "eventId").
67
+ *
68
+ * Deliberately without orderBy — no composite index requirement per queried
69
+ * field; sorting is up to the caller.
70
+ *
71
+ * @param field - Field name to filter on
72
+ * @param value - Value the field must equal
73
+ * @param collection - Mail collection name (default: "mail")
74
+ * @param limit - Maximum documents to return (default: 100)
75
+ * @param databaseId - Optional named database
76
+ * @returns Array of stored mails
77
+ */
78
+ export declare function getMailsByField<T extends MailObject>(field: string, value: unknown, collection?: string, limit?: number, databaseId?: string): Promise<StoredMail<T>[]>;
79
+ //# sourceMappingURL=mail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mail.d.ts","sourceRoot":"","sources":["../src/mail.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAcrD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,8EAA8E;AAC9E,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;CAChC;AAED,8DAA8D;AAC9D,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;CAChC;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED,0EAA0E;AAC1E,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,IAAI,CAAC,GAAG;IAC9D,sCAAsC;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,uDAAuD;IACvD,QAAQ,EAAE,SAAS,CAAC;IACpB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAMF;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAC,CAAC,SAAS,UAAU,EACvD,IAAI,EAAE,CAAC,EACP,UAAU,GAAE,MAAgC,EAC5C,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,CAejB;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,CAAC,SAAS,UAAU,EACpD,MAAM,EAAE,MAAM,EACd,UAAU,GAAE,MAAgC,EAC5C,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAY/B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CAAC,CAAC,SAAS,UAAU,EACxD,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,OAAO,EACd,UAAU,GAAE,MAAgC,EAC5C,KAAK,GAAE,MAAY,EACnB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAe1B"}
package/dist/mail.js ADDED
@@ -0,0 +1,42 @@
1
+ import { Timestamp } from 'firebase-admin/firestore';
2
+ import { getDb } from './firestore.js';
3
+ import { ValidationError } from './errors.js';
4
+
5
+ const DEFAULT_MAIL_COLLECTION = "mail";
6
+ async function addMailForSend(mail, collection = DEFAULT_MAIL_COLLECTION, databaseId) {
7
+ if (!mail.to) {
8
+ throw new ValidationError("Mail requires a recipient (to)");
9
+ }
10
+ if (!mail.template && !mail.message) {
11
+ throw new ValidationError("Mail requires a template or a message");
12
+ }
13
+ const db = getDb(databaseId);
14
+ const docRef = await db.collection(collection).add({
15
+ ...mail,
16
+ creation: Timestamp.now()
17
+ });
18
+ return docRef.id;
19
+ }
20
+ async function getMailById(mailId, collection = DEFAULT_MAIL_COLLECTION, databaseId) {
21
+ const db = getDb(databaseId);
22
+ const doc = await db.collection(collection).doc(mailId).get();
23
+ if (!doc.exists) {
24
+ return null;
25
+ }
26
+ return {
27
+ id: doc.id,
28
+ ...doc.data()
29
+ };
30
+ }
31
+ async function getMailsByField(field, value, collection = DEFAULT_MAIL_COLLECTION, limit = 100, databaseId) {
32
+ const db = getDb(databaseId);
33
+ const snapshot = await db.collection(collection).where(field, "==", value).limit(limit).get();
34
+ return snapshot.docs.map(
35
+ (doc) => ({
36
+ id: doc.id,
37
+ ...doc.data()
38
+ })
39
+ );
40
+ }
41
+
42
+ export { addMailForSend, getMailById, getMailsByField };
@@ -1,4 +1,4 @@
1
- import { ApiResponse } from './types';
1
+ import { ApiResponse } from './types.js';
2
2
  export declare function success<T>(data: T): ApiResponse<T>;
3
3
  export declare function created<T>(data: T): ApiResponse<T>;
4
4
  export declare function handleError(error: unknown): ApiResponse<{
@@ -1 +1 @@
1
- {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAc,MAAM,SAAS,CAAC;AAIlD,wBAAgB,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAElD;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAElD;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,OAAO,GACb,WAAW,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAsB9C"}
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../src/response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAc,MAAM,YAAY,CAAC;AAIrD,wBAAgB,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAElD;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAElD;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,OAAO,GACb,WAAW,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAsB9C"}
package/dist/trash.d.ts CHANGED
@@ -16,6 +16,9 @@ export interface TrashItem<T = unknown> {
16
16
  deletedAt: Timestamp;
17
17
  /** UID of user who deleted */
18
18
  deletedBy: string;
19
+ /** Denormalized display name of the deleting user (survives account
20
+ * deletion; written when moveToTrash receives a stamp object) */
21
+ deletedByName?: string;
19
22
  /** The original document data */
20
23
  data: T;
21
24
  }
@@ -29,13 +32,17 @@ export interface TrashItem<T = unknown> {
29
32
  *
30
33
  * @param collection - The collection path (e.g., "djRequests")
31
34
  * @param documentId - The document ID to trash
32
- * @param deletedBy - UID of the user performing the deletion
35
+ * @param deletedBy - UID of the deleting user, or a { uid, name } stamp
36
+ * (the name is denormalized into the trash item)
33
37
  * @param objectType - Type identifier for filtering (e.g., "djRequest")
34
38
  * @param databaseId - Optional named database
35
39
  * @returns The created trash item
36
40
  * @throws NotFoundError if document doesn't exist
37
41
  */
38
- export declare function moveToTrash<T = unknown>(collection: string, documentId: string, deletedBy: string, objectType: string, databaseId?: string): Promise<TrashItem<T>>;
42
+ export declare function moveToTrash<T = unknown>(collection: string, documentId: string, deletedBy: string | {
43
+ uid: string;
44
+ name: string;
45
+ }, objectType: string, databaseId?: string): Promise<TrashItem<T>>;
39
46
  /**
40
47
  * Restore a document from trash to its original location.
41
48
  *
@@ -1 +1 @@
1
- {"version":3,"file":"trash.d.ts","sourceRoot":"","sources":["../src/trash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAcrD;;;GAGG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC,GAAG,OAAO;IACpC,uCAAuC;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,2BAA2B;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,+BAA+B;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,gCAAgC;IAChC,SAAS,EAAE,SAAS,CAAC;IACrB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,IAAI,EAAE,CAAC,CAAC;CACT;AASD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,WAAW,CAAC,CAAC,GAAG,OAAO,EAC3C,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAmCvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAAC,CAAC,GAAG,OAAO,EACzC,UAAU,CAAC,EAAE,MAAM,EACnB,KAAK,GAAE,MAAY,EACnB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAiBzB;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,CAAC,GAAG,OAAO,EAC5C,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAa9B"}
1
+ {"version":3,"file":"trash.d.ts","sourceRoot":"","sources":["../src/trash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAcrD;;;GAGG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC,GAAG,OAAO;IACpC,uCAAuC;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,2BAA2B;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,+BAA+B;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,gCAAgC;IAChC,SAAS,EAAE,SAAS,CAAC;IACrB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB;sEACkE;IAClE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iCAAiC;IACjC,IAAI,EAAE,CAAC,CAAC;CACT;AASD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,WAAW,CAAC,CAAC,GAAG,OAAO,EAC3C,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACjD,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAsCvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAAC,CAAC,GAAG,OAAO,EACzC,UAAU,CAAC,EAAE,MAAM,EACnB,KAAK,GAAE,MAAY,EACnB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAiBzB;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,CAAC,GAAG,OAAO,EAC5C,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAa9B"}
package/dist/trash.js CHANGED
@@ -18,7 +18,10 @@ async function moveToTrash(collection, documentId, deletedBy, objectType, databa
18
18
  originalCollection: collection,
19
19
  objectType,
20
20
  deletedAt: Timestamp.now(),
21
- deletedBy,
21
+ deletedBy: typeof deletedBy === "string" ? deletedBy : deletedBy.uid,
22
+ ...typeof deletedBy === "object" && {
23
+ deletedByName: deletedBy.name
24
+ },
22
25
  data: originalData
23
26
  };
24
27
  transaction.set(trashRef, trashData);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@progressive-development/pd-provider-firebase-functions",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "Firebase Functions v2 utilities for pd-spa-helper backend",
5
5
  "author": "PD Progressive Development",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -39,6 +39,7 @@
39
39
  "clean:all": "rm -rf dist node_modules pnpm-lock.yaml",
40
40
  "lint": "eslint --ext .ts src --ignore-path ../../.eslintignore && prettier \"**/*.ts\" --check --ignore-path ../../.eslintignore",
41
41
  "format": "eslint --ext .ts src --fix --ignore-path ../../.eslintignore && prettier \"**/*.ts\" --write --ignore-path ../../.eslintignore",
42
- "check": "pnpm run lint && pnpm run build"
42
+ "check": "pnpm run lint && pnpm run build",
43
+ "test": "vitest run"
43
44
  }
44
45
  }