Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Firebase integration for [Effect](https://effect.website). Provides schemas, mod
| [effect-firebase](./packages/effect-firebase) | Core schemas, models, and query builder |
| [@effect-firebase/admin](./packages/admin) | Firebase Admin SDK + Cloud Functions |
| [@effect-firebase/client](./packages/client) | Firebase Client SDK |
| [@effect-firebase/react-native](./packages/react-native) | React Native Firebase SDK |
| [@effect-firebase/mock](./packages/mock) | In-memory mock for testing |

## Guides
Expand Down
47 changes: 47 additions & 0 deletions packages/react-native/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# @effect-firebase/react-native

React Native Firebase integration for Effect Firebase. Provides a `FirestoreService` implementation backed by [`@react-native-firebase`](https://rnfirebase.io) for use in React Native applications.

## Installation

```bash
npm install @effect-firebase/react-native effect-firebase effect
npm install @react-native-firebase/app @react-native-firebase/firestore
```

Requires `@react-native-firebase/*` v24 or later (the modular API). The Firebase app is configured natively (via `google-services.json` / `GoogleService-Info.plist`) — no JavaScript `initializeApp` call is needed.

## Usage

```typescript
import { Effect } from 'effect';
import { Client } from '@effect-firebase/react-native';
import { PostRepository } from './repositories/post-repository';
import { Query } from 'effect-firebase';

const program = Effect.gen(function* () {
const repo = yield* PostRepository;
const posts = yield* repo.query(
Query.and(
Query.where('status', '==', 'published'),
Query.orderBy('createdAt', 'desc'),
Query.limit(10),
),
);
return posts;
}).pipe(Effect.provide(PostRepository), Effect.provide(Client.layer()));

Effect.runPromise(program).then(console.log);
```

## Layer options

```typescript
Client.layer(); // uses the default (natively configured) Firebase app
Client.layer({ app }); // uses the provided app from getApp()
Client.layer({ firestore }); // uses a Firestore instance directly
```

## License

MIT
10 changes: 10 additions & 0 deletions packages/react-native/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import baseConfig from '../../eslint.config.mjs';

export default [
...baseConfig,
{
files: ['**/*.ts', '**/*.js'],
// Override or add rules here
rules: {},
},
];
47 changes: 47 additions & 0 deletions packages/react-native/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "@effect-firebase/react-native",
"version": "1.0.0-beta.5",
"type": "module",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/fwal/effect-firebase",
"directory": "packages/react-native"
},
"private": false,
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"@effect-firebase/source": "./src/index.ts",
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"files": [
"dist",
"!**/*.tsbuildinfo"
],
"dependencies": {
"tslib": "^2.8.1"
},
"devDependencies": {
"@react-native-firebase/app": "catalog:",
"@react-native-firebase/firestore": "catalog:",
"effect": "catalog:",
"effect-firebase": "workspace:*"
},
"peerDependencies": {
"@react-native-firebase/app": ">=24.0.0",
"@react-native-firebase/firestore": ">=24.0.0",
"effect": "catalog:",
"effect-firebase": "workspace:*"
},
"publishConfig": {
"access": "public",
"provenance": true
}
}
3 changes: 3 additions & 0 deletions packages/react-native/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * as Firestore from './lib/firestore/firestore-service.js';
export * as Client from './lib/client.js';
export * as App from './lib/app.js';
15 changes: 15 additions & 0 deletions packages/react-native/src/lib/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { FirebaseApp } from '@react-native-firebase/app';
import { Layer, Context } from 'effect';

export interface AppService {
readonly getApp: () => FirebaseApp;
}

export class App extends Context.Service<App, AppService>()(
'@effect-firebase/react-native/App',
) {}

export const layer = (app: FirebaseApp): Layer.Layer<App> =>
Layer.succeed(App, {
getApp: () => app,
});
83 changes: 83 additions & 0 deletions packages/react-native/src/lib/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { getApp, type FirebaseApp } from '@react-native-firebase/app';
import type { Firestore } from '@react-native-firebase/firestore';
import { Layer } from 'effect';
import {
layer as firestoreLayerLive,
layerFromFirestore as firestoreLayerFromFirestore,
} from './firestore/firestore-service.js';
import { type FirestoreService } from 'effect-firebase';
import { layer as appLayer } from './app.js';

export interface LayerOptions {
/**
* Explicit Firebase app instance to use.
*/
readonly app?: FirebaseApp;
/**
* Explicit Firestore instance to use.
* When provided, the app option is ignored.
*/
readonly firestore?: Firestore;
}

type ReadyLayer = Layer.Layer<FirestoreService, never, never>;

const resolveApp = (app?: FirebaseApp): FirebaseApp => {
if (app) {
return app;
}

try {
return getApp();
} catch (error) {
throw new Error(
'Client.layer: no Firebase app available. Pass { app } or ensure the default app is configured natively before calling Client.layer().',
{ cause: error },
);
}
};

/**
* Creates the default client layer with Firestore service.
*
* Resolution order:
* 1. `options.firestore`
* 2. `options.app`
* 3. default app (`getApp()`)
*
* @throws If both `app` and `firestore` are provided.
* @throws If no app is available and no `firestore` is provided.
*
* @example
* ```ts
* import { Client } from '@effect-firebase/react-native';
*
* const layer = Client.layer();
* ```
*
* @example
* ```ts
* import { getApp } from '@react-native-firebase/app';
* import { Client } from '@effect-firebase/react-native';
*
* const layer = Client.layer({ app: getApp() });
* ```
*/
export function layer(options: LayerOptions & { app: FirebaseApp }): ReadyLayer;
export function layer(
options: LayerOptions & { firestore: Firestore },
): ReadyLayer;
export function layer(options?: LayerOptions): ReadyLayer;
export function layer(options: LayerOptions = {}): ReadyLayer {
if (options.app && options.firestore) {
throw new Error(
'Client.layer: pass either { app } or { firestore }, not both.',
);
}

if (options.firestore) {
return firestoreLayerFromFirestore(options.firestore);
}

return Layer.provide(firestoreLayerLive, appLayer(resolveApp(options.app)));
}
Loading
Loading