Skip to content

Commit bd35fb8

Browse files
committed
feat!: Add array support and isolate fields
1 parent ce2f5f9 commit bd35fb8

13 files changed

Lines changed: 82 additions & 77 deletions

File tree

packages/admin/src/lib/firestore/converter.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ describe('Firestore Converter', () => {
143143
const fakeFirestore = {} as unknown as Firestore;
144144
const result = toFirestoreDocumentData(
145145
fakeFirestore,
146-
FirestoreSchema.ArrayUnion.values(['a', 'b'])
146+
FirestoreSchema.ArrayUnion.make({ values: ['a', 'b'] })
147147
);
148148
expect(result).toStrictEqual(FieldValue.arrayUnion('a', 'b'));
149149
});
@@ -152,7 +152,7 @@ describe('Firestore Converter', () => {
152152
const fakeFirestore = {} as unknown as Firestore;
153153
const result = toFirestoreDocumentData(
154154
fakeFirestore,
155-
FirestoreSchema.ArrayRemove.values(['a'])
155+
FirestoreSchema.ArrayRemove.make({ values: ['a'] })
156156
);
157157
expect(result).toStrictEqual(FieldValue.arrayRemove('a'));
158158
});

packages/admin/src/lib/firestore/converter.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
GeoPoint,
88
Timestamp,
99
} from 'firebase-admin/firestore';
10-
import { FirestoreSchema } from 'effect-firebase';
10+
import { FirestoreSchema, FirestoreField } from 'effect-firebase';
1111

1212
export const toFirestoreDocumentData = (
1313
db: Firestore,
@@ -35,13 +35,13 @@ export const toFirestoreDocumentData = (
3535
if (data instanceof FirestoreSchema.ServerTimestamp) {
3636
return FieldValue.serverTimestamp();
3737
}
38-
if (data instanceof FirestoreSchema.Delete) {
38+
if (data instanceof FirestoreField.Delete) {
3939
return FieldValue.delete();
4040
}
41-
if (data instanceof FirestoreSchema.ArrayUnion) {
41+
if (data instanceof FirestoreField.ArrayUnion) {
4242
return FieldValue.arrayUnion(...data.values);
4343
}
44-
if (data instanceof FirestoreSchema.ArrayRemove) {
44+
if (data instanceof FirestoreField.ArrayRemove) {
4545
return FieldValue.arrayRemove(...data.values);
4646
}
4747
if (Array.isArray(data)) {

packages/client/src/lib/firestore/converter.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
serverTimestamp,
1313
Timestamp,
1414
} from 'firebase/firestore';
15-
import { FirestoreSchema } from 'effect-firebase';
15+
import { FirestoreSchema, FirestoreField } from 'effect-firebase';
1616

1717
export const toFirestoreDocumentData = (
1818
db: Firestore,
@@ -40,13 +40,13 @@ export const toFirestoreDocumentData = (
4040
if (data instanceof FirestoreSchema.ServerTimestamp) {
4141
return serverTimestamp();
4242
}
43-
if (data instanceof FirestoreSchema.Delete) {
43+
if (data instanceof FirestoreField.Delete) {
4444
return deleteField();
4545
}
46-
if (data instanceof FirestoreSchema.ArrayUnion) {
46+
if (data instanceof FirestoreField.ArrayUnion) {
4747
return arrayUnion(...data.values);
4848
}
49-
if (data instanceof FirestoreSchema.ArrayRemove) {
49+
if (data instanceof FirestoreField.ArrayRemove) {
5050
return arrayRemove(...data.values);
5151
}
5252
if (Array.isArray(data)) {

packages/effect-firebase/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * as FirestoreSchema from './lib/firestore/schema/schema.js';
2+
export * as FirestoreField from './lib/firestore/fields/fields.js';
23
export * from './lib/firestore/firestore-service.js';
34
export * from './lib/firestore/errors.js';
45
export * from './lib/firestore/snapshot.js';
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Schema } from 'effect';
2+
3+
/**
4+
* Represents an arrayUnion operation. This will add elements to an array field.
5+
* Only valid in the `update` variant — use `WithArraySentinels` to add support to a field.
6+
*/
7+
export class ArrayUnion {
8+
constructor(public readonly values: readonly unknown[]) {}
9+
}
10+
export const ArrayUnionInstance = Schema.instanceOf(ArrayUnion);
11+
12+
/**
13+
* Represents an arrayRemove operation. This will remove elements from an array field.
14+
* Only valid in the `update` variant — use `WithArraySentinels` to add support to a field.
15+
*/
16+
export class ArrayRemove {
17+
constructor(public readonly values: readonly unknown[]) {}
18+
}
19+
20+
export const ArrayRemoveInstance = Schema.instanceOf(ArrayRemove);
21+
22+
/** Add elements to an array field. */
23+
export const arrayUnion =
24+
/** Add elements to an array field. */
25+
(values: readonly unknown[]) => new ArrayUnion(values);
26+
27+
/** Remove elements from an array field. */
28+
export const arrayRemove = (values: readonly unknown[]) =>
29+
new ArrayRemove(values);
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Schema } from 'effect';
2+
3+
/**
4+
* Represents a delete operation. This will remove the field from the document.
5+
*/
6+
export class Delete {}
7+
export const DeleteInstance = Schema.instanceOf(Delete);
8+
9+
const _delete = () => new Delete();
10+
export {
11+
/** Delete the field, removing it from the document. */
12+
_delete as delete,
13+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './delete.js';
2+
export * from './array.js';

packages/effect-firebase/src/lib/firestore/model/array.spec.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import { Schema } from 'effect';
22
import { describe, expect, it } from 'vitest';
33
import { Class } from './core.js';
44
import { WithArrayFields, Array } from './array.js';
5-
import { ArrayUnion, ArrayRemove } from '../schema/fields.js';
5+
import {
6+
ArrayUnion,
7+
ArrayRemove,
8+
arrayUnion,
9+
arrayRemove,
10+
} from '../fields/array.js';
611

712
describe('WithArrayFields', () => {
813
class TestModel extends Class<TestModel>('TestModel')({
@@ -39,7 +44,7 @@ describe('WithArrayFields', () => {
3944
it('should accept an ArrayUnion sentinel', () => {
4045
const result = Schema.decodeUnknownSync(TestModel.update)({
4146
name: 'Post',
42-
tags: ArrayUnion.values(['c', 'd']),
47+
tags: arrayUnion(['c', 'd']),
4348
});
4449
expect(result.tags).toBeInstanceOf(ArrayUnion);
4550
expect(result.tags.values).toEqual(['c', 'd']);
@@ -48,7 +53,7 @@ describe('WithArrayFields', () => {
4853
it('should accept an ArrayRemove sentinel', () => {
4954
const result = Schema.decodeUnknownSync(TestModel.update)({
5055
name: 'Post',
51-
tags: ArrayRemove.values(['a']),
56+
tags: arrayRemove(['a']),
5257
});
5358
expect(result.tags).toBeInstanceOf(ArrayRemove);
5459
expect(result.tags.values).toEqual(['a']);
@@ -57,7 +62,7 @@ describe('WithArrayFields', () => {
5762
it('should encode ArrayUnion sentinel as-is (for converter to handle)', () => {
5863
const result = Schema.encodeSync(TestModel.update)({
5964
name: 'Post',
60-
tags: ArrayUnion.values(['c']),
65+
tags: arrayUnion(['c']),
6166
});
6267
expect(result.tags).toBeInstanceOf(ArrayUnion);
6368
expect(result.tags.values).toEqual(['c']);
@@ -66,7 +71,7 @@ describe('WithArrayFields', () => {
6671
it('should encode ArrayRemove sentinel as-is (for converter to handle)', () => {
6772
const result = Schema.encodeSync(TestModel.update)({
6873
name: 'Post',
69-
tags: ArrayRemove.values(['a']),
74+
tags: arrayRemove(['a']),
7075
});
7176
expect(result.tags).toBeInstanceOf(ArrayRemove);
7277
expect(result.tags.values).toEqual(['a']);
@@ -86,7 +91,7 @@ describe('WithArrayFields', () => {
8691
expect(() =>
8792
Schema.decodeUnknownSync(TestModel.json)({
8893
name: 'Post',
89-
tags: ArrayUnion.make({ values: ['c'] }),
94+
tags: arrayUnion(['c']),
9095
})
9196
).toThrow();
9297
});
@@ -110,15 +115,15 @@ describe('Array', () => {
110115
it('update variant accepts ArrayUnion', () => {
111116
const result = Schema.decodeUnknownSync(TestModel.update)({
112117
name: 'Post',
113-
tags: ArrayUnion.values(['y']),
118+
tags: arrayUnion(['y']),
114119
});
115120
expect(result.tags).toBeInstanceOf(ArrayUnion);
116121
});
117122

118123
it('update variant accepts ArrayRemove', () => {
119124
const result = Schema.decodeUnknownSync(TestModel.update)({
120125
name: 'Post',
121-
tags: ArrayRemove.values(['x']),
126+
tags: arrayRemove(['x']),
122127
});
123128
expect(result.tags).toBeInstanceOf(ArrayRemove);
124129
});

packages/effect-firebase/src/lib/firestore/model/array.ts

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
import { Schema } from 'effect';
22
import { VariantSchema } from '@effect/experimental';
33
import { fieldEvolve } from './core.js';
4-
import {
5-
ArrayUnion,
6-
ArrayUnionInstance,
7-
ArrayRemove,
8-
ArrayRemoveInstance,
9-
} from '../schema/fields.js';
4+
import { ArrayUnionInstance, ArrayRemoveInstance } from '../fields/array.js';
105

116
/**
127
* Adds `ArrayUnion` and `ArrayRemove` sentinel support to an array field's `update` variant.
@@ -25,19 +20,15 @@ import {
2520
*
2621
* // update variant accepts:
2722
* postRepo.update('id', { tags: ['a', 'b'] }); // replace
28-
* postRepo.update('id', { tags: ArrayUnion.withValues(['c']) }); // arrayUnion
29-
* postRepo.update('id', { tags: ArrayRemove.withValues(['a']) }); // arrayRemove
23+
* postRepo.update('id', { tags: arrayUnion(['c']) }); // arrayUnion
24+
* postRepo.update('id', { tags: arrayRemove(['a']) }); // arrayRemove
3025
* ```
3126
*/
3227
export type WithArrayFields<S extends Schema.Schema.Any> = VariantSchema.Field<{
3328
readonly get: S;
3429
readonly add: S;
3530
readonly update: Schema.Union<
36-
[
37-
S,
38-
Schema.instanceOf<typeof ArrayUnion>,
39-
Schema.instanceOf<typeof ArrayRemove>
40-
]
31+
[S, typeof ArrayUnionInstance, typeof ArrayRemoveInstance]
4132
>;
4233
readonly json: S;
4334
readonly jsonAdd: S;
@@ -57,11 +48,7 @@ export const WithArrayFields: <
5748
readonly [K in keyof S]: S[K] extends Schema.Schema.Any
5849
? K extends 'update'
5950
? Schema.Union<
60-
[
61-
S[K],
62-
Schema.instanceOf<typeof ArrayUnion>,
63-
Schema.instanceOf<typeof ArrayRemove>
64-
]
51+
[S[K], typeof ArrayUnionInstance, typeof ArrayRemoveInstance]
6552
>
6653
: S[K]
6754
: never;

packages/effect-firebase/src/lib/firestore/model/optional.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest';
33
import { Class } from './core.js';
44

55
import { Optional, OptionalNull, OptionalDeletable } from './optional.js';
6-
import { Delete } from '../schema/fields.js';
6+
import { Delete, delete as deleteField } from '../fields/delete.js';
77

88
describe('Optional', () => {
99
class TestModel extends Class<TestModel>('TestModel')({
@@ -170,7 +170,7 @@ describe('OptionalDeletable', () => {
170170
describe('update variant', () => {
171171
it('should decode Delete value to Option.some', () => {
172172
const decode = Schema.decodeUnknownSync(TestModel.update);
173-
const result = decode({ name: 'John', bio: Delete.make() });
173+
const result = decode({ name: 'John', bio: deleteField() });
174174

175175
expect(Option.isSome(result.bio)).toBe(true);
176176
expect(Option.getOrNull(result.bio)).toBeInstanceOf(Delete);
@@ -209,7 +209,7 @@ describe('OptionalDeletable', () => {
209209
const encode = Schema.encodeSync(TestModel.update);
210210
const result = encode({
211211
name: 'John',
212-
bio: Option.some(new Delete({})),
212+
bio: Option.some(deleteField()),
213213
});
214214

215215
expect(result.bio).toBeDefined();

0 commit comments

Comments
 (0)