@rebasepro/types 0.25.0 → 0.26.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.
@@ -24,6 +24,26 @@ import type { ScopeSummary } from "./scopes.js";
24
24
  * @group Models
25
25
  */
26
26
  export type ApiKeyKind = "service" | "personal";
27
+ /**
28
+ * The API key a request authenticated with, as server-side code sees it:
29
+ * `context.credential` in a collection callback, and `credential` on the
30
+ * identity the request's driver is scoped to.
31
+ *
32
+ * It tells a write made with a key from the same person's own session. A
33
+ * personal key acts as its owner, so `context.user` is the owner either way;
34
+ * this is what says "the CI token did it" rather than "Ada did it". Never the
35
+ * secret: only what the key list shows.
36
+ *
37
+ * @group Models
38
+ */
39
+ export interface CallerCredential {
40
+ /** `"personal"` acts as its owner (`context.user`); `"service"` as the key itself. */
41
+ kind: ApiKeyKind;
42
+ /** The key's id, as `GET /api/auth/keys` lists it. */
43
+ id: string;
44
+ /** The name the key was given when it was created. */
45
+ name: string;
46
+ }
27
47
  /**
28
48
  * An API key with the secret masked — what list / get / update return.
29
49
  * @group Models
@@ -65,6 +65,89 @@ export interface MountableRouter {
65
65
  * @group Auth
66
66
  */
67
67
  export declare const IMPERSONATE_HEADER = "x-rebase-impersonate";
68
+ /**
69
+ * The request header that asks for a data-plane request to run as a role —
70
+ * the RLS simulator's "view as". Alone it runs as a signed-in holder of that
71
+ * role who owns no rows; with {@link SIMULATE_USER_ID_HEADER} it runs as that
72
+ * uid holding that role, whether or not such an account exists.
73
+ *
74
+ * `anon` (or `anonymous`) runs as a caller with no session at all.
75
+ *
76
+ * Like {@link IMPERSONATE_HEADER}, only a signed-in administrator's own session
77
+ * may send it; anyone else is refused with 403, never answered as themselves.
78
+ *
79
+ * @group Auth
80
+ */
81
+ export declare const SIMULATE_ROLE_HEADER = "x-rebase-simulate-role";
82
+ /**
83
+ * The uid a simulated request runs as. With {@link SIMULATE_ROLE_HEADER} it is
84
+ * a hypothetical — "u_1 holding customer" — and need not name an account;
85
+ * without it, the uid must be an active user and runs with their roles now.
86
+ *
87
+ * @group Auth
88
+ */
89
+ export declare const SIMULATE_USER_ID_HEADER = "x-rebase-simulate-user-id";
90
+ /**
91
+ * The id of a service API key a simulated request runs as: the key's identity,
92
+ * its RLS roles and its scopes, exactly as a request presenting the key would.
93
+ * Excludes the other two simulation headers.
94
+ *
95
+ * @group Auth
96
+ */
97
+ export declare const SIMULATE_API_KEY_ID_HEADER = "x-rebase-simulate-api-key-id";
98
+ /**
99
+ * The response header naming what a simulated request ran as:
100
+ * `role=<role>;sub=<uid>;masked_columns=<a,b>`, plus `api_key=<id>` and
101
+ * `collection=<slug>` when they apply. Every value is URI-encoded. Read it
102
+ * with {@link parseSimulatedContext}.
103
+ *
104
+ * @group Auth
105
+ */
106
+ export declare const SIMULATED_CONTEXT_HEADER = "x-rebase-simulated-context";
107
+ /**
108
+ * What an administrator asks a request to run as. Exactly one of these
109
+ * shapes is honoured: a role (optionally with a uid), a uid alone (an existing
110
+ * user, with their roles), or an API key id alone.
111
+ *
112
+ * @group Auth
113
+ */
114
+ export interface AccessSimulation {
115
+ role?: string;
116
+ userId?: string;
117
+ apiKeyId?: string;
118
+ }
119
+ /**
120
+ * The request headers for an {@link AccessSimulation}. Empty when it asks for
121
+ * nothing, so spreading it into a header set is always safe.
122
+ *
123
+ * @group Auth
124
+ */
125
+ export declare function simulationHeaders(simulation: AccessSimulation | null | undefined): Record<string, string>;
126
+ /**
127
+ * What {@link SIMULATED_CONTEXT_HEADER} says a request ran as.
128
+ *
129
+ * @group Auth
130
+ */
131
+ export interface SimulatedContext {
132
+ role: string;
133
+ sub: string;
134
+ maskedColumns: string[];
135
+ apiKeyId?: string;
136
+ collection?: string;
137
+ }
138
+ /**
139
+ * Write a {@link SimulatedContext} as the header value.
140
+ *
141
+ * @group Auth
142
+ */
143
+ export declare function formatSimulatedContext(context: SimulatedContext): string;
144
+ /**
145
+ * Read {@link SIMULATED_CONTEXT_HEADER}. `undefined` for an absent header or one
146
+ * missing `role` or `sub`.
147
+ *
148
+ * @group Auth
149
+ */
150
+ export declare function parseSimulatedContext(value: string | null | undefined): SimulatedContext | undefined;
68
151
  /**
69
152
  * The normalized user object returned by `AuthAdapter.verifyRequest()`.
70
153
  *
@@ -7,6 +7,7 @@ import type { HistoryConfig } from "../controllers/client.js";
7
7
  import type { ChannelBusSetting } from "./channel_bus.js";
8
8
  import type { SchemaEditingAdmin } from "./schema_editing.js";
9
9
  import type { AccessModel } from "./scopes.js";
10
+ import type { ApiKeyKind } from "./api_keys.js";
10
11
  /**
11
12
  * Abstract database connection interface.
12
13
  * Represents a connection to any database system.
@@ -353,6 +354,14 @@ export interface RealtimeProvider {
353
354
  * still look live, so it is reported rather than inferred.
354
355
  */
355
356
  health?(): RealtimeListenerHealth[];
357
+ /**
358
+ * Run the provider's own background reads — the refetches a change
359
+ * schedules, whatever announced it (a write here, change capture, another
360
+ * instance) — through `run`. The server passes one that enters the backend
361
+ * owning this provider, so a hook such a read reaches sees that backend as
362
+ * `rebase`. Without it they run in the context of whatever scheduled them.
363
+ */
364
+ setScopeRunner?(run: <T>(fn: () => T) => T): void;
356
365
  }
357
366
  /**
358
367
  * One long-lived realtime connection, as `/health` reports it.
@@ -697,6 +706,13 @@ export interface BranchInfo {
697
706
  createdAt: Date;
698
707
  /** Size in bytes, if available from the server. */
699
708
  sizeBytes?: number;
709
+ /**
710
+ * The transaction that created the branch database, recorded when it was
711
+ * made: the fork point a branch diff tells the two sides' writes apart by.
712
+ * Absent for a branch made before it was recorded, or by a server that
713
+ * does not record it.
714
+ */
715
+ forkXid?: string;
700
716
  }
701
717
  /**
702
718
  * Administrative operations for database branching.
@@ -715,6 +731,250 @@ export interface BranchAdmin {
715
731
  listBranches(): Promise<BranchInfo[]>;
716
732
  /** Get info about a specific branch. */
717
733
  getBranchInfo(name: string): Promise<BranchInfo | undefined>;
734
+ /**
735
+ * What the branch changed, against the database it was made from: its
736
+ * tables, columns, constraints, indexes, triggers, row-level-security
737
+ * policies, enum types, views and functions, and per table the rows it
738
+ * inserted, updated and deleted. Reads only.
739
+ */
740
+ diffBranch?(name: string, options?: BranchDiffOptions): Promise<BranchDiff>;
741
+ /**
742
+ * Apply the rows the branch changed to the database it was made from. See
743
+ * {@link BranchMergeOptions} for what wins, and when nothing is written.
744
+ */
745
+ mergeBranch?(name: string, options: BranchMergeOptions): Promise<BranchMergeResult>;
746
+ }
747
+ /**
748
+ * What {@link BranchAdmin.diffBranch} compares.
749
+ * @group Admin
750
+ */
751
+ export interface BranchDiffOptions {
752
+ /** Rows shown per table, across every kind of change. Default 100, at most 1,000. */
753
+ sampleLimit?: number;
754
+ /** Schemas to compare. Default: every schema but the system's, without Rebase's own tables. */
755
+ schemas?: string[];
756
+ /**
757
+ * Rows a table may hold, on either side, for its rows to be matched one by
758
+ * one. A larger table that differs is reported with its row counts only,
759
+ * and cannot be merged. Default 100,000.
760
+ */
761
+ maxRowsPerTable?: number;
762
+ }
763
+ /**
764
+ * One change to the schema: a table, a column, a policy, a table's row-level
765
+ * security, a constraint (unique, foreign key, check, exclusion), an index, a
766
+ * trigger — or, outside any table, an enum type, a view or a function.
767
+ * @group Admin
768
+ */
769
+ export interface BranchSchemaChange {
770
+ object: "table" | "column" | "policy" | "rls" | "constraint" | "index" | "trigger" | "enum" | "view" | "function";
771
+ change: "added" | "dropped" | "altered";
772
+ schema: string;
773
+ /**
774
+ * The table the change is on — or, for an enum, a view or a function, the
775
+ * object's own name (a function's with its argument types).
776
+ */
777
+ table: string;
778
+ /** The column's, policy's, constraint's, index's or trigger's name. */
779
+ name?: string;
780
+ /** The definition on the target, as SQL. Absent for an addition. */
781
+ before?: string;
782
+ /** The definition on the branch, as SQL. Absent for a drop. */
783
+ after?: string;
784
+ }
785
+ /**
786
+ * Why the changed rows of a table cannot be merged.
787
+ * @group Admin
788
+ */
789
+ export interface BranchMergeBlock {
790
+ /**
791
+ * `TABLE_REWRITTEN`: one side wrote every row anew since the branch was
792
+ * made (a column type change, a volatile default, `VACUUM FULL`), so which
793
+ * side changed which row can no longer be told.
794
+ */
795
+ code: "TABLE_ADDED" | "TABLE_DROPPED" | "COLUMNS_DIFFER" | "PRIMARY_KEY_DIFFERS" | "NO_PRIMARY_KEY" | "TOO_LARGE" | "TABLE_REWRITTEN";
796
+ message: string;
797
+ }
798
+ /**
799
+ * One row the branch changed, or one both sides changed.
800
+ *
801
+ * Values are the text the database writes for them, `null` for SQL NULL, so a
802
+ * `bigint` or a `numeric` reads exactly as it is stored — except a column named
803
+ * like a credential (`password_hash`, `*_token`, `*secret*`, `*_hash`), whose
804
+ * value reads `"[redacted]"`. It is still listed in {@link changedColumns}.
805
+ *
806
+ * A `"conflict"` with no {@link after} is a row the branch deleted that rows
807
+ * the target wrote since the branch was made still reference — see
808
+ * {@link BranchTableDataDiff.referencedBy}.
809
+ * @group Admin
810
+ */
811
+ export interface BranchRowChange {
812
+ change: "inserted" | "updated" | "deleted" | "conflict";
813
+ /** The primary key, as a JSON array of its columns' values. */
814
+ key: string;
815
+ /** The row on the target. Absent for an insert. */
816
+ before?: Record<string, string | null>;
817
+ /** The row on the branch. Absent for a delete. */
818
+ after?: Record<string, string | null>;
819
+ /** The columns whose values differ, for an update or a conflict. */
820
+ changedColumns?: string[];
821
+ }
822
+ /**
823
+ * The rows of one table that differ between the branch and its target.
824
+ * @group Admin
825
+ */
826
+ export interface BranchTableDataDiff {
827
+ schema: string;
828
+ table: string;
829
+ /** The collection whose table this is, when the server knows one. */
830
+ collection?: string;
831
+ primaryKey: string[];
832
+ /** Rows the branch inserted. */
833
+ inserted: number;
834
+ /** Rows the branch updated. */
835
+ updated: number;
836
+ /** Rows the branch deleted. */
837
+ deleted: number;
838
+ /**
839
+ * Rows both sides changed since the branch was made, differently — and
840
+ * rows the branch deleted that rows the target wrote since still
841
+ * reference through a foreign key ({@link referencedBy}). Deleting one of
842
+ * those on the target would cascade into, or rewrite, the target's own
843
+ * rows, so it is a conflict {@link BranchMergeOptions.onConflict} decides.
844
+ */
845
+ conflicts: number;
846
+ /**
847
+ * Rows the target changed since the branch was made, and the branch did
848
+ * not. A merge leaves them as they are.
849
+ */
850
+ targetChanges: number;
851
+ /** Up to {@link BranchDiffOptions.sampleLimit} of the rows counted above, but not of {@link targetChanges}. */
852
+ samples: BranchRowChange[];
853
+ /** Present when this table's rows cannot be merged. A merge refuses it unless told to skip it. */
854
+ blocked?: BranchMergeBlock;
855
+ /**
856
+ * Rows on each side, for a table whose rows were not matched one by one.
857
+ * Its inserted, updated, deleted and conflict counts are then 0 because
858
+ * they are unknown, not because nothing changed.
859
+ */
860
+ rowCounts?: {
861
+ target: number;
862
+ branch: number;
863
+ };
864
+ /**
865
+ * The foreign keys through which rows the target wrote since the branch
866
+ * was made reference rows of this table the branch deleted. Each such
867
+ * delete is counted in {@link conflicts}.
868
+ */
869
+ referencedBy?: BranchReferencingRows[];
870
+ }
871
+ /**
872
+ * Rows of one table, written on the target since the branch was made, that
873
+ * reference rows the branch deleted.
874
+ * @group Admin
875
+ */
876
+ export interface BranchReferencingRows {
877
+ schema: string;
878
+ table: string;
879
+ /** The foreign key's name. */
880
+ constraint: string;
881
+ /** What deleting the referenced row does to them on the target. */
882
+ onDelete: "cascade" | "set null" | "set default" | "restrict" | "no action";
883
+ /** How many of them there are. */
884
+ rows: number;
885
+ }
886
+ /**
887
+ * What {@link BranchAdmin.diffBranch} returns.
888
+ * @group Admin
889
+ */
890
+ export interface BranchDiff {
891
+ branch: string;
892
+ /** The branch's database. */
893
+ branchDatabase: string;
894
+ /** The database the branch was made from, which a merge writes to. */
895
+ targetDatabase: string;
896
+ /** The schemas compared. */
897
+ schemas: string[];
898
+ /**
899
+ * Whether rows could be told apart by which side changed them since the
900
+ * branch was made. When `false`, every row that differs is counted as the
901
+ * branch's change, and a merge would undo what the target changed.
902
+ */
903
+ forkPointKnown: boolean;
904
+ /** Pass to {@link BranchAdmin.mergeBranch}: what this diff saw, on both sides. */
905
+ fingerprint: string;
906
+ schema: BranchSchemaChange[];
907
+ /** Only the tables with rows that differ. */
908
+ data: BranchTableDataDiff[];
909
+ sampleLimit: number;
910
+ computedAt: string;
911
+ }
912
+ /**
913
+ * How {@link BranchAdmin.mergeBranch} applies a branch.
914
+ *
915
+ * A merge writes the rows the branch inserted, updated and deleted to the
916
+ * target, in one statement: all of them, or none. Rows the target changed
917
+ * since the branch was made are kept. Schema and policy changes are never
918
+ * merged — they belong to the collections and their migrations.
919
+ *
920
+ * Nothing is written when either side changed since the diff that produced
921
+ * {@link expectedFingerprint}, when a table with changed rows cannot be merged
922
+ * (unless {@link skipBlocked}), or when a row was changed on both sides and
923
+ * {@link onConflict} is `"abort"`.
924
+ * @group Admin
925
+ */
926
+ export interface BranchMergeOptions {
927
+ /** {@link BranchDiff.fingerprint} of the diff that was reviewed. */
928
+ expectedFingerprint: string;
929
+ /**
930
+ * A row both sides changed: `"abort"` (the default) writes nothing,
931
+ * `"branch"` writes the branch's row over the target's, `"target"` keeps
932
+ * the target's.
933
+ *
934
+ * A row the branch deleted that the target's newer rows reference
935
+ * ({@link BranchTableDataDiff.referencedBy}) is a conflict too: `"target"`
936
+ * keeps it, and with it the rows that reference it; `"branch"` deletes it,
937
+ * and the foreign key's `ON DELETE` reaches those rows — the result's
938
+ * `warnings` say which.
939
+ */
940
+ onConflict?: "abort" | "branch" | "target";
941
+ /** Merge the other tables when some cannot be merged, instead of writing nothing. */
942
+ skipBlocked?: boolean;
943
+ /** The schemas the reviewed diff compared, when it was not the default. */
944
+ schemas?: string[];
945
+ /** The row limit the reviewed diff used, when it was not the default. */
946
+ maxRowsPerTable?: number;
947
+ }
948
+ /**
949
+ * What {@link BranchAdmin.mergeBranch} wrote.
950
+ * @group Admin
951
+ */
952
+ export interface BranchMergeResult {
953
+ branch: string;
954
+ targetDatabase: string;
955
+ tables: {
956
+ schema: string;
957
+ table: string;
958
+ inserted: number;
959
+ updated: number;
960
+ deleted: number;
961
+ }[];
962
+ /** Tables left unmerged because they could not be, with `skipBlocked`. */
963
+ skipped: {
964
+ schema: string;
965
+ table: string;
966
+ reason: BranchMergeBlock;
967
+ }[];
968
+ /** Rows both sides changed that kept the target's version, with `onConflict: "target"`. */
969
+ conflictsKept: number;
970
+ /** Schema and policy changes on the branch, none of which a merge applies. */
971
+ schemaChangesNotMerged: number;
972
+ /**
973
+ * What the merge did beyond the rows it counts, or what went wrong after
974
+ * they were written: a delete whose foreign keys reached the target's own
975
+ * rows (with `onConflict: "branch"`), a sequence left behind them.
976
+ */
977
+ warnings: string[];
718
978
  }
719
979
  /**
720
980
  * Union type for all admin capabilities.
@@ -890,10 +1150,16 @@ export interface RealtimeSocketOptions {
890
1150
  uid: string;
891
1151
  roles: string[];
892
1152
  scopes: string[];
893
- /** The key itself: its id and `rate_limit` decide which rate-limit bucket its frames count in. */
1153
+ /**
1154
+ * The key itself: its id and `rate_limit` decide which rate-limit
1155
+ * bucket its frames count in. With `name` and `kind`, collection
1156
+ * callbacks on its write frames read it as `context.credential`.
1157
+ */
894
1158
  apiKey?: {
895
1159
  id: string;
896
1160
  rate_limit: number | null;
1161
+ name?: string;
1162
+ kind?: ApiKeyKind;
897
1163
  };
898
1164
  } | {
899
1165
  message: string;
@@ -960,6 +1226,14 @@ export interface RealtimeSocketOptions {
960
1226
  * answered `404` and closed, so the client is not left hanging.
961
1227
  */
962
1228
  path?: string;
1229
+ /**
1230
+ * Run `fn` on behalf of the backend that owns this socket: each connection
1231
+ * and each frame is handled inside it, so a hook a frame reaches sees that
1232
+ * backend as `rebase`, as it does over REST. The server passes its own.
1233
+ * Absent, frames run in the context the HTTP connection was accepted in —
1234
+ * the process default's.
1235
+ */
1236
+ runInScope?: <T>(fn: () => T) => T;
963
1237
  }
964
1238
  /**
965
1239
  * A `BackendBootstrapper` encapsulates all driver-specific initialization logic.
@@ -392,9 +392,9 @@ export interface BaseProperty<CustomProps = unknown> {
392
392
  * Give the expression, in the template form `isId` takes
393
393
  * (``databaseDefault: "sql`gen_random_uuid()`"``), and the schema planner
394
394
  * writes it as the column's `DEFAULT`, so `rebase db push` keeps it. `true`
395
- * declares a DEFAULT someone else wrote and the planner does not write
396
- * one, so a push plans dropping it. "Import from table" and
397
- * `rebase schema introspect` set the expression they read.
395
+ * declares a DEFAULT someone else wrote: the planner writes none, and
396
+ * `rebase db push` leaves the one the database holds as it is. "Import
397
+ * from table" and `rebase schema introspect` set the expression they read.
398
398
  */
399
399
  databaseDefault?: boolean | SqlExpression;
400
400
  /**
@@ -314,9 +314,11 @@ export interface ViaRelation extends RelationBase {
314
314
  * The joins, in order, from this collection's table to the target's.
315
315
  *
316
316
  * Each step names a table and the columns to join it on; the last step's
317
- * table is the target. Read-only, because Rebase will not work out how to
318
- * write through an arbitrary chain, and guessing is what this kind exists to
319
- * stop.
317
+ * table is the target. A single-hop chain is writable through a create or
318
+ * update — to-one in one step (the target holds the key), to-many through
319
+ * one junction table — and a longer one is read-only, because Rebase will
320
+ * not work out how to write through an arbitrary chain, and guessing is
321
+ * what this kind exists to stop.
320
322
  */
321
323
  joinPath: JoinStep[];
322
324
  }
@@ -379,8 +381,12 @@ export interface ResolvedRelationBase {
379
381
  */
380
382
  cardinality: "one" | "many";
381
383
  /**
382
- * Whether Rebase knows how to write through this link. False only for
383
- * {@link ResolvedVia}, whose join chain it will not invent a write for.
384
+ * Whether Rebase writes this link through a create or update. True for
385
+ * every kind but a multi-hop {@link ResolvedVia}: a `via` is writable when
386
+ * it has a single link to set — a to-one chain of one step (the target
387
+ * holds the key) or a to-many chain through one junction table — and
388
+ * read-only otherwise, since Rebase will not invent a write for a longer
389
+ * chain.
384
390
  */
385
391
  writable: boolean;
386
392
  /**
@@ -463,7 +469,12 @@ export interface ResolvedManyToMany extends ResolvedRelationBase {
463
469
  /** @group Models */
464
470
  export interface ResolvedVia extends ResolvedRelationBase {
465
471
  kind: "via";
466
- writable: false;
472
+ /**
473
+ * True for a single-hop chain — to-one through one step, or to-many through
474
+ * one junction table — and false for anything longer. See
475
+ * {@link ResolvedRelationBase.writable}.
476
+ */
477
+ writable: boolean;
467
478
  /** The chain as authored — see {@link ViaRelation.joinPath}. Nothing to default. */
468
479
  joinPath: JoinStep[];
469
480
  }
@@ -164,6 +164,20 @@ export declare function isDataPlaneScope(scope: string): scope is DataPlaneScope
164
164
  export declare function isAdminScope(scope: string): scope is AdminScope;
165
165
  /** Does this list of roles include the built-in `admin` role? @group Auth */
166
166
  export declare function hasAdminRole(roles: readonly string[] | null | undefined): boolean;
167
+ /**
168
+ * Why `name` cannot be written as a role, or `undefined` when it can.
169
+ *
170
+ * No naming pattern is imposed — an app may call its roles anything. What is
171
+ * refused is a name that would be read back as *other* roles: a role travels as
172
+ * one element of a Postgres `text[]` and as one entry of the comma-joined
173
+ * `app.user_roles` setting that RLS policies split with
174
+ * `string_to_array(…, ',')`. Written as `"support,admin"`, a role comes back as
175
+ * `admin`; braces, quotes and backslashes are array-literal syntax, so they are
176
+ * refused too, as are empty names and control characters.
177
+ *
178
+ * @group Auth
179
+ */
180
+ export declare function roleNameProblem(name: unknown): string | undefined;
167
181
  /**
168
182
  * Does a set of held scopes grant `scope`, optionally on one `target`?
169
183
  *
@@ -1,3 +1,4 @@
1
+ import type { CallerCredential } from "../types/api_keys.js";
1
2
  /**
2
3
  * The canonical representation of an authenticated user in the Rebase ecosystem.
3
4
  *
@@ -86,5 +87,12 @@ export type User = {
86
87
  * anonymous request.
87
88
  */
88
89
  readonly claims?: Record<string, unknown>;
90
+ /**
91
+ * The API key this request authenticated with, when it was one. Set
92
+ * server-side on the identity a request's driver is scoped to, and read by
93
+ * collection callbacks as `context.credential`. Absent for a session, a
94
+ * service-key request and in the browser.
95
+ */
96
+ readonly credential?: CallerCredential;
89
97
  getIdToken?: (forceRefresh?: boolean) => Promise<string>;
90
98
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebasepro/types",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "Rebase type definitions — shared interfaces and controller types",
5
5
  "keywords": [
6
6
  "rebase",
@@ -79,9 +79,6 @@
79
79
  "node"
80
80
  ],
81
81
  "testEnvironment": "node",
82
- "testPathIgnorePatterns": [
83
- "/bivariance/"
84
- ],
85
82
  "moduleNameMapper": {
86
83
  "\\.(css|less)$": "<rootDir>/test/__mocks__/styleMock.js"
87
84
  }