Skip to content

Commit b1bb2b6

Browse files
feat: update emulator data handling and remove obsolete exports directory
1 parent 656e1fd commit b1bb2b6

File tree

6 files changed

+53
-111
lines changed

6 files changed

+53
-111
lines changed

apps/demo/firebase/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,6 @@ dist/
7474

7575
# Node modules (if not already ignored)
7676
node_modules/
77+
78+
# Emulator data (comment out if you want to commit emulator data)
79+
emulator-data/

apps/demo/firebase/exports/.gitkeep

Lines changed: 0 additions & 1 deletion
This file was deleted.

apps/demo/firebase/project.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,14 @@
4545
"executor": "nx:run-commands",
4646
"options": {
4747
"cwd": "apps/demo/firebase",
48-
"command": "mkdir -p exports && if [ -d \"exports\" ] && [ \"$(ls -A exports)\" ]; then firebase emulators:start --only=functions,firestore,hosting,auth --import=./exports; else firebase emulators:start --only=functions,firestore,hosting,auth; fi"
48+
"command": "firebase emulators:start --only=functions,firestore,hosting,auth --import=./emulator-data --export-on-exit=./emulator-data"
4949
}
5050
},
5151
"emulators:debug": {
5252
"executor": "nx:run-commands",
5353
"options": {
5454
"cwd": "apps/demo/firebase",
55-
"command": "mkdir -p exports && if [ -d \"exports\" ] && [ \"$(ls -A exports)\" ]; then firebase emulators:start --inspect-functions --only=functions,firestore,hosting,auth --import=./exports; else firebase emulators:start --inspect-functions --only=functions,firestore,hosting,auth; fi"
55+
"command": "firebase emulators:start --inspect-functions --only=functions,firestore,hosting,auth --import=./emulator-data --export-on-exit=./emulator-data"
5656
}
5757
},
5858
"emulators:stop": {
@@ -89,7 +89,7 @@
8989
"options": {
9090
"commands": [
9191
"nx build demo-functions --watch",
92-
"cd apps/demo/firebase && mkdir -p exports && firebase emulators:start --only=functions,firestore,hosting,auth"
92+
"cd apps/demo/firebase && firebase emulators:start --only=functions,firestore,hosting,auth --import=./emulator-data --export-on-exit=./emulator-data"
9393
],
9494
"parallel": true
9595
}
@@ -98,14 +98,14 @@
9898
"executor": "nx:run-commands",
9999
"options": {
100100
"cwd": "apps/demo/firebase",
101-
"command": "firebase emulators:export ./exports --force"
101+
"command": "firebase emulators:export ./emulator-data --force"
102102
}
103103
},
104104
"data:import": {
105105
"executor": "nx:run-commands",
106106
"options": {
107107
"cwd": "apps/demo/firebase",
108-
"command": "firebase emulators:start --import=./exports --export-on-exit=./exports"
108+
"command": "firebase emulators:start --import=./emulator-data --export-on-exit=./emulator-data"
109109
}
110110
},
111111
"data:seed": {

libs/demo/functions/user/src/lib/user.trigger.spec.ts

Lines changed: 0 additions & 82 deletions
This file was deleted.
Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,36 @@
1-
import { User } from '@firemono/demo/data';
2-
import { getFirestore, Timestamp } from 'firebase-admin/firestore';
1+
import { FieldValue, getFirestore } from 'firebase-admin/firestore';
32
import { firestore } from 'firebase-functions';
43

5-
6-
export const setLastUpdate = firestore.onDocumentUpdated("user/{userId}", async (event) => {
4+
// Fires only when an existing user document changes
5+
export const setLastUpdateMeta = firestore.onDocumentUpdated('user/{userId}', async (event) => {
76
const db = getFirestore();
8-
const userAfter: User = event.data?.after.data() as User;
7+
const userId = event.params.userId;
8+
const after = event.data?.after;
9+
if (!after) return;
910

10-
if (!userAfter) {
11-
return;
12-
}
11+
// Optional: pick fields to denormalize into metadata
12+
const displayName = after.get('displayName') ?? null;
13+
const roles = after.get('roles') ?? null;
1314

14-
return db.collection("user").doc(event.params.userId).update({ lastUpdate: Timestamp.now() });
15-
})
15+
const metaRef = db.collection('user_metadata').doc(userId);
16+
const eventId = event.id; // unique-ish per delivery; functions are "at-least-once"
1617

18+
// Idempotency guard: only write if we haven't processed this eventId yet
19+
await db.runTransaction(async (tx) => {
20+
const snap = await tx.get(metaRef);
21+
const lastEventId = snap.exists ? snap.get('lastEventId') : null;
22+
if (lastEventId === eventId) return; // already processed this event
1723

24+
tx.set(
25+
metaRef,
26+
{
27+
lastUpdate: FieldValue.serverTimestamp(), // server time ✅
28+
lastEventId: eventId, // idempotency marker
29+
displayName,
30+
roles,
31+
updatedBy: 'setLastUpdateMeta',
32+
},
33+
{ merge: true }
34+
);
35+
});
36+
});

tools/nx/src/generators/init-app/init.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,10 @@ nx deploy-functions ${projectName}` : ''}
161161
162162
### Data Management
163163
\`\`\`bash
164-
# Export emulator data
164+
# Export emulator data (also auto-exported on emulator exit)
165165
nx data:export ${projectName}
166166
167-
# Import emulator data
167+
# Import emulator data (also auto-imported on emulator start)
168168
nx data:import ${projectName}
169169
170170
# Seed development data (customize the seed target)
@@ -225,7 +225,7 @@ ${features.includes('firestore') ? `├── firestore.rules # Firestore
225225
├── firestore.indexes.json # Firestore indexes` : ''}
226226
${features.includes('hosting') ? `├── public/ # Hosting files` : ''}
227227
${features.includes('storage') ? `├── storage.rules # Storage security rules` : ''}
228-
└── exports/ # Emulator data exports
228+
└── emulator-data/ # Emulator data exports (auto-imported/exported)
229229
\`\`\`
230230
231231
## Learn More
@@ -238,7 +238,7 @@ ${features.includes('storage') ? `├── storage.rules # Storage secu
238238
tree.write(joinPathFragments(projectRoot, 'README.md'), readmeContent);
239239
}
240240

241-
function createFunctionsNxApp(tree: Tree, baseProjectName: string, projectDir: string, initDir: string, dynamicConfig: any) {
241+
function createFunctionsNxApp(tree: Tree, baseProjectName: string, projectDir: string, initDir: string) {
242242
const functionsAppName = `${baseProjectName}-functions`;
243243
const functionsAppRoot = joinPathFragments(projectDir, 'functions');
244244
const functionsSourceDir = join(initDir, 'functions');
@@ -482,7 +482,7 @@ export default async function (tree: Tree, schema: Schema) {
482482
// Create Functions Nx app if functions are detected
483483
let functionsAppName: string | null = null;
484484
if (features.includes('functions')) {
485-
functionsAppName = createFunctionsNxApp(tree, baseProjectName, projectDir, initDirResolved, null);
485+
functionsAppName = createFunctionsNxApp(tree, baseProjectName, projectDir, initDirResolved);
486486
}
487487

488488
// Get dynamic configuration based on detected features
@@ -547,7 +547,7 @@ export default async function (tree: Tree, schema: Schema) {
547547
options: {
548548
cwd: projectRoot,
549549
command: dynamicConfig.hasEmulators && dynamicConfig.emulatorServices
550-
? `mkdir -p exports && if [ -d "exports" ] && [ "$(ls -A exports)" ]; then firebase emulators:start --only=${dynamicConfig.emulatorServices} --import=./exports; else firebase emulators:start --only=${dynamicConfig.emulatorServices}; fi`
550+
? `firebase emulators:start --only=${dynamicConfig.emulatorServices} --import=./emulator-data --export-on-exit=./emulator-data`
551551
: 'echo "No emulators configured"'
552552
}
553553
},
@@ -556,7 +556,7 @@ export default async function (tree: Tree, schema: Schema) {
556556
options: {
557557
cwd: projectRoot,
558558
command: dynamicConfig.hasEmulators && dynamicConfig.emulatorServices
559-
? `mkdir -p exports && if [ -d "exports" ] && [ "$(ls -A exports)" ]; then firebase emulators:start --inspect-functions --only=${dynamicConfig.emulatorServices} --import=./exports; else firebase emulators:start --inspect-functions --only=${dynamicConfig.emulatorServices}; fi`
559+
? `firebase emulators:start --inspect-functions --only=${dynamicConfig.emulatorServices} --import=./emulator-data --export-on-exit=./emulator-data`
560560
: 'echo "No emulators configured"'
561561
}
562562
},
@@ -596,28 +596,28 @@ export default async function (tree: Tree, schema: Schema) {
596596
options: functionsAppName && dynamicConfig.hasEmulators ? {
597597
commands: [
598598
`nx build ${functionsAppName} --watch`,
599-
`cd ${projectRoot} && mkdir -p exports && firebase emulators:start --only=${dynamicConfig.emulatorServices}`
599+
`cd ${projectRoot} && firebase emulators:start --only=${dynamicConfig.emulatorServices} --import=./emulator-data --export-on-exit=./emulator-data`
600600
],
601601
parallel: true
602602
} : {
603603
cwd: projectRoot,
604604
command: dynamicConfig.hasEmulators
605-
? `mkdir -p exports && firebase emulators:start --only=${dynamicConfig.emulatorServices}`
605+
? `firebase emulators:start --only=${dynamicConfig.emulatorServices} --import=./emulator-data --export-on-exit=./emulator-data`
606606
: 'echo "No development environment configured"'
607607
}
608608
},
609609
'data:export': {
610610
executor: 'nx:run-commands',
611611
options: {
612612
cwd: projectRoot,
613-
command: 'firebase emulators:export ./exports --force'
613+
command: 'firebase emulators:export ./emulator-data --force'
614614
}
615615
},
616616
'data:import': {
617617
executor: 'nx:run-commands',
618618
options: {
619619
cwd: projectRoot,
620-
command: 'firebase emulators:start --import=./exports --export-on-exit=./exports'
620+
command: 'firebase emulators:start --import=./emulator-data --export-on-exit=./emulator-data'
621621
}
622622
},
623623
'data:seed': {
@@ -663,8 +663,8 @@ export default async function (tree: Tree, schema: Schema) {
663663
// Fix ESLint configuration conflicts
664664
fixEslintConfiguration(tree, projectRoot);
665665

666-
// Create exports directory for emulator data
667-
tree.write(joinPathFragments(projectRoot, 'exports', '.gitkeep'), '# This directory stores emulator data exports\n');
666+
// Create emulator-data directory for emulator data
667+
tree.write(joinPathFragments(projectRoot, 'emulator-data', '.gitkeep'), '# This directory stores emulator data exports\n# The emulator-data directory is automatically used by Firebase emulators for data import/export\n# Data is automatically saved on emulator exit and loaded on startup\n');
668668

669669
// Generate project-specific README
670670
generateProjectReadme(tree, projectRoot, baseProjectName, features, firebaseProjectName);
@@ -679,6 +679,9 @@ dist/
679679
680680
# Node modules (if not already ignored)
681681
node_modules/
682+
683+
# Emulator data (comment out if you want to commit emulator data)
684+
emulator-data/
682685
`;
683686

684687
if (!existingGitignore.includes('.nx/')) {

0 commit comments

Comments
 (0)