@m1212e/rumble 0.26.4 → 0.27.3

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.
package/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # rumble
2
+
2
3
  rumble is a combined ability and graphql builder built around [drizzle](https://orm.drizzle.team/docs/overview) and [pothos](https://pothos-graphql.dev/docs/plugins/drizzle), inspired by [CASL](https://casl.js.org/v6/en/). It takes much of the required configuration off your shoulders and makes creating a GraphQL (or event REST via [SOFA](https://the-guild.dev/graphql/sofa-api)) api very easy! Additionally it offers strong support for real time data via GraphQL subscriptions!
3
4
 
4
5
  > Please note that drizzle hasn't reached a full stable release yet and, as shown in the warning [here](https://pothos-graphql.dev/docs/plugins/drizzle), this is not stable yet.
@@ -10,6 +11,7 @@ rumble is a combined ability and graphql builder built around [drizzle](https://
10
11
  > Comes with a fully TS based client to consume any GraphQL API. See the client docs at the end to learn about usage with rumble and independent usage with any GraphQL API.
11
12
 
12
13
  ## Getting started
14
+
13
15
  The following example is an excerpt from the example setup you can find [here](./example). If you are interested in a real world app thats using rumble (and is still work in progress) please see [CHASE](https://github.com/DeutscheModelUnitedNations/munify-chase).
14
16
 
15
17
  First, install rumble into your project:
@@ -24,8 +26,8 @@ import * as relations from "./db/relations";
24
26
  import { rumble } from "@m1212e/rumble";
25
27
 
26
28
  export const db = drizzle(
27
- "postgres://postgres:postgres@localhost:5432/postgres",
28
- { schema, relations }
29
+ "postgres://postgres:postgres@localhost:5432/postgres",
30
+ { schema, relations },
29
31
  );
30
32
 
31
33
  const { abilityBuilder } = rumble({ db });
@@ -35,9 +37,11 @@ const { abilityBuilder } = rumble({ db });
35
37
  The rumble creator returns some functions which you can use to implement your api. The concepts rumble uses are described in the following sections:
36
38
 
37
39
  ## Abilities
40
+
38
41
  Abilities are the way you define who can do things in your app. You can imagine an ability as `a thing that is allowed`. Abilities can be very wide and applied in general or precisely and narrowly scoped to very specific conditions. You can create abilities with the `abilityBuilder` function returned from the rumble initiator. There are three kinds of abilities:
39
42
 
40
43
  ### Wildcard
44
+
41
45
  Wildcard abilities allow everyone to do a thing. The `allow` call takes a single `action` or an array of `action` strings. You can customize the available actions when calling the rumble initializer.
42
46
  ```ts
43
47
  // everyone can read posts
@@ -48,46 +52,51 @@ abilityBuilder.posts.allow(["read", "write"]);
48
52
  ```
49
53
 
50
54
  ### Condition Object
51
- Condition object abilities allow a thing under a certain, fixed condition which does not change. __Note, that the object has the same type as a drizzle query call.__
55
+
56
+ Condition object abilities allow a thing under a certain, fixed condition which does not change. **Note, that the object has the same type as a drizzle query call.**
52
57
  ```ts
53
58
  // everyone can read published posts
54
59
  abilityBuilder.posts.allow("read").when({
55
- where: { published: true },
60
+ where: { published: true },
56
61
  });
57
62
  ```
58
63
 
59
64
  ### Condition Function
65
+
60
66
  Condition functions are functions that return condition objects. They are called each time an evaluation takes place and can dynamically decide if something should be allowed or not. They receive the request context as a parameter to decide e.g. based on cookies or headers if something is allowed or not.
61
67
  ```ts
62
68
  // only the author can update posts
63
69
  abilityBuilder.posts
64
- .allow(["update", "delete"])
65
- .when(({ userId }) => ({ where: { authorId: userId } }));
70
+ .allow(["update", "delete"])
71
+ .when(({ userId }) => ({ where: { authorId: userId } }));
66
72
 
67
73
  abilityBuilder.posts.allow("read").when(async ({ userId }) => ({
68
- where: { teamId: { in: await permissions.teamsOf(userId) } },
74
+ where: { teamId: { in: await permissions.teamsOf(userId) } },
69
75
  }));
70
-
71
76
  ```
72
77
 
73
78
  ### Application level filters
79
+
74
80
  In some cases you can't implement all your checks via a database query filter. Say, for example, you want to query an external api which handles your authorization, before you return the data to the user. This can be done with application layer filters. They can be set very similar to abilities:
75
81
  ```ts
76
82
  abilityBuilder.users.filter("read").by(({ context, entities }) => {
77
- // const allowed = await queryExternalAuthorizationService(context.user, entities);
83
+ // const allowed = await queryExternalAuthorizationService(context.user, entities);
78
84
 
79
- // we could filter the list to only return the entities the user is allowed to see
80
- // even mapping to prevent leakage of certain fields is possible
81
- return entities;
85
+ // we could filter the list to only return the entities the user is allowed to see
86
+ // even mapping to prevent leakage of certain fields is possible
87
+ return entities;
82
88
  });
83
89
  ```
84
90
  The default implementation helpers automatically respect and call the filters, if you set any. Filters work in addition to abilities. They run on the completed query, which in most cases has had an ability applied, hence abilities have higher priority than filters. If you need to apply a filter to a manually implemented object, please use the `applyFilters` config field as shown in the example project.
85
91
 
86
92
 
87
93
  ### Column masks and filter arguments
94
+
88
95
  An ability may grant columns only on the rows its `where` matches. The other rows are returned without them:
89
96
  ```ts
90
- abilityBuilder.users.allow("read").when(({ userId }) => ({ where: { id: userId } })); // everything on the own row
97
+ abilityBuilder.users
98
+ .allow("read")
99
+ .when(({ userId }) => ({ where: { id: userId } })); // everything on the own row
91
100
  abilityBuilder.users.allow("read").when({ columns: { id: true, name: true } }); // the rest on any other
92
101
  ```
93
102
  Masking happens after the query ran, but a `where` or `orderBy` argument is applied by the database to the real values: `users(where: { email: { eq: "a@b.c" } })` would reveal whose email that is. The default helpers (`query`, `countQuery` and the generated relations) therefore restrict these arguments to what the caller may read:
@@ -99,26 +108,28 @@ Masking happens after the query ran, but a `where` or `orderBy` argument is appl
99
108
  In your own resolvers, pass client arguments through `guardFilterInput(ctx.abilities, table, action, { where, orderBy })` before merging them. Filters you build yourself are not restricted.
100
109
 
101
110
  ### Applying abilities
111
+
102
112
  As you might have noticed, abilities resolve around drizzle query filters. This means, that we can use them to query the database with filters applied that directly restrict what the user is allowed to see, update and retrieve.
103
113
  ```ts
104
114
  schemaBuilder.queryFields((t) => {
105
- return {
106
- posts: t.drizzleField({
107
- type: [PostRef],
108
- resolve: async (query, root, args, ctx, info) => {
109
- return db.query.posts.findMany(
110
- // here we apply our filter (filter() is async, so remember to await it)
111
- query((await ctx.abilities.posts.filter("read")).query.many),
112
- );
113
- },
114
- }),
115
- };
115
+ return {
116
+ posts: t.drizzleField({
117
+ type: [PostRef],
118
+ resolve: async (query, root, args, ctx, info) => {
119
+ return db.query.posts.findMany(
120
+ // here we apply our filter (filter() is async, so remember to await it)
121
+ query((await ctx.abilities.posts.filter("read")).query.many),
122
+ );
123
+ },
124
+ }),
125
+ };
116
126
  });
117
127
  ```
118
128
 
119
129
  > The `filter()` call returns an object with `query` and `sql` fields. Depending on if you are using the drizzle query or and SQL based API (like update or delete), you need to apply different filters. Same goes for the `query.many` and `query.single` fields.
120
130
 
121
131
  #### Applying filters
132
+
122
133
  Applying filters on objects is done automatically if you use the helpers. If you manually implement an object ref, you can use the `applyFilters` config field to ensure the filters run as expected:
123
134
  ```ts
124
135
  const PostRef = schemaBuilder.drizzleObject("posts", {
@@ -133,75 +144,80 @@ const PostRef = schemaBuilder.drizzleObject("posts", {
133
144
  ```
134
145
 
135
146
  ## Context & Configuration
147
+
136
148
  The `rumble` initiator offers various configuration options which you can pass. Most importantly, the `context` provider function which creates the request context that is passed to your abilities and resolvers.
137
149
  ```ts
138
150
  rumble({
139
- db,
140
- context(request) {
141
- return {
142
- // here you could instead read some cookies or HTTP headers to retrieve an actual userId
143
- userId: 2,
144
- };
145
- },
151
+ db,
152
+ context(request) {
153
+ return {
154
+ // here you could instead read some cookies or HTTP headers to retrieve an actual userId
155
+ userId: 2,
156
+ };
157
+ },
146
158
  });
147
159
  ```
148
160
  > `rumble` offers more config options, use intellisense or take a look at [the rumble input type](lib/types/rumbleInput.ts) if you want to know more.
149
161
 
150
162
  ## Helpers
163
+
151
164
  Rumble offers various helpers to make it easy and fast to implement your api. Ofcourse you can write your api by hand using the provided `schemaBuilder` from the rumble initiator, but since this might get repetitive, the provided helpers automate a lot of this work for you while also automatically applying the concepts of rumble directly into your api.
152
165
 
153
166
  ### whereArg
167
+
154
168
  `whereArg` is a helper to implement query arguments for filtering the results of a query for certain results. In many cases you would implement arguments for a query with something as `matchUsername: t.arg.string()` which is supposed to restrict the query to users which have that username. The whereArg helper implements such a filter tailored to the specific entity which you then can directly pass on to the database query.
155
169
  ```ts
156
170
  const WhereArgs = whereArg({
157
- table: "posts",
171
+ table: "posts",
158
172
  });
159
173
 
160
174
  schemaBuilder.queryFields((t) => {
161
- return {
162
- postsFiltered: t.drizzleField({
163
- type: [PostRef],
164
- args: {
165
- // here we set our generated type as type for the where argument
166
- where: t.arg({ type: WhereArgs }),
167
- },
168
- resolve: async (query, root, args, ctx, info) => {
169
- return db.query.posts.findMany(
170
- query(
171
- // here we apply the ability filter
172
- (await ctx.abilities.users.filter("read"))
173
- // we can merge one time filters into the permission filter for this specific request
174
- .merge({ where: args.where }).query.many,
175
- ),
176
- );
177
- },
178
- }),
179
- };
175
+ return {
176
+ postsFiltered: t.drizzleField({
177
+ type: [PostRef],
178
+ args: {
179
+ // here we set our generated type as type for the where argument
180
+ where: t.arg({ type: WhereArgs }),
181
+ },
182
+ resolve: async (query, root, args, ctx, info) => {
183
+ return db.query.posts.findMany(
184
+ query(
185
+ // here we apply the ability filter
186
+ (await ctx.abilities.users.filter("read"))
187
+ // we can merge one time filters into the permission filter for this specific request
188
+ .merge({ where: args.where }).query.many,
189
+ ),
190
+ );
191
+ },
192
+ }),
193
+ };
180
194
  });
181
195
  ```
182
196
 
183
197
  ### object
198
+
184
199
  `object` is a helper to implement an object with relations. Don't worry about abilities, they are automatically applied. The helper returns the object reference which you can use in the rest of your api, for an example on how to use a type, see the above code snippet (`type: [PostRef],`).
185
200
  ```ts
186
201
  const UserRef = object({
187
- table: "users",
202
+ table: "users",
188
203
  });
189
204
  ```
190
205
 
191
206
  ### query
207
+
192
208
  The `query` helper is even simpler. It implements a `findFirst` and `findMany` query for the specified entity named as singular and plural of the entities name.
193
209
  ```ts
194
210
  query({
195
- table: "users",
211
+ table: "users",
196
212
  });
197
-
198
213
  ```
199
214
 
200
215
  ### pubsub
216
+
201
217
  In case you want to use subscriptions, `rumble` has got you covered! The rumble helpers all use the `smart subscriptions plugin` from `pothos`. The `pubsub` helper lets you easily hook into the subscription notification logic.
202
218
  ```ts
203
219
  const { updated, created, removed } = pubsub({
204
- table: "users",
220
+ table: "users",
205
221
  });
206
222
  ```
207
223
  Now just call the functions whenever your application does the respective action and your subscriptions will get notified:
@@ -223,15 +239,17 @@ All `query` and `object` helper implementations will automatically update and wo
223
239
  > The rumble initiator lets you configure the subscription notifiers in case you want to use an external service like redis for your pubsub notifications instead of the internal default one
224
240
 
225
241
  ### enum_
242
+
226
243
  The `enum_` helper is a little different to the others, as it will get called internally automatically if another helpers like `object` or `arg` detects an enum field. In most cases you should be good without calling it manually but in case you would like to have a reference to an enum object, you can get it from this helper.
227
244
  ```ts
228
245
  const enumRef = enum_({
229
- tsName: "moodEnum",
246
+ tsName: "moodEnum",
230
247
  });
231
248
  ```
232
249
  > The enum parameter allows other fields to be used to reference an enum. This is largely due to how this is used internally. Because of the way how drizzle handles enums, we are not able to provide type safety with enums. In case you actually need to use it, the above way is the recommended approach.
233
250
 
234
251
  ### Enable search
252
+
235
253
  > Search is currently only supported in postgres!
236
254
 
237
255
  rumble and its helpers offer out of the box search capabilities. You can activate this functionality by passing the `search` parameter to the `createRumble` function.
@@ -258,9 +276,12 @@ This will add a search field to all list queries and many relations created by t
258
276
  Additionally, a search distance will be returned for each result if the search argument is used (null otherwise). The results are returned sorted by their distance with the best matching fields in the first place. The search will respect all text fields (IDs included) on the table, you always search for all text columns at once. Matches in multiple columns stack and therefore result in a better matching score. So if you search for "alice" and there is a user with the name "Alice" and an email "alice@example.com", the two matching columns will result in a better score than e.g. "al007@example.com"/"Alice".
259
277
  > If you have abilities in place which prevent a caller from accessing certain columns, the search will not respect those columns to prevent leaking information.
260
278
  #### Pro tip: Indexing
279
+
261
280
  In case your table grows large, it can be a good idea to create an index to increase performance. Under the hood, searching uses the [pg_trgm](https://www.postgresql.org/docs/current/pgtrgm.html#PGTRGM-INDEX) extension. To create indexes for our searchable columns, we can adjust our drizzle schema accordingly:
262
281
  ```ts
263
- export const user = pgTable('user', {
282
+ export const user = pgTable(
283
+ "user",
284
+ {
264
285
  id: text()
265
286
  .$defaultFn(() => nanoid())
266
287
  .primaryKey(),
@@ -268,14 +289,14 @@ export const user = pgTable('user', {
268
289
  name: text().notNull(),
269
290
  },
270
291
  (table) => [
271
- index('user_id_trgm')
272
- .using('gin', sql`${table.id} gin_trgm_ops`)
292
+ index("user_id_trgm")
293
+ .using("gin", sql`${table.id} gin_trgm_ops`)
273
294
  .concurrently(),
274
- index('user_email_trgm')
275
- .using('gin', sql`${table.email} gin_trgm_ops`)
295
+ index("user_email_trgm")
296
+ .using("gin", sql`${table.email} gin_trgm_ops`)
276
297
  .concurrently(),
277
- index('user_name_trgm')
278
- .using('gin', sql`${table.name} gin_trgm_ops`)
298
+ index("user_name_trgm")
299
+ .using("gin", sql`${table.name} gin_trgm_ops`)
279
300
  .concurrently(),
280
301
  ],
281
302
  );
@@ -295,23 +316,25 @@ Another important aspect to consider when using indexes is that the PostgreSQL i
295
316
  Please also note that `GIST` indexes are also supported by `pg_trgm` but will oftentimes not be useable for the sorting since a lot of columns are accumulated in the query which causes the query planner to not use them. Feel free to experiment with different index types and configurations to find the best fit for your specific use case.
296
317
 
297
318
  ### and more...
319
+
298
320
  See [the example file](./example/src/main.ts) or clone it and let intellisense guide you. Rumble offers various other helpers which might become handy!
299
321
 
300
322
  ## Running the server
323
+
301
324
  In case you directly want to run a server from your rumble instance, you can do so by using the `createYoga` function. It returns a graphql `yoga` instance which can be used to provide a graphql api in [a multitude of ways](https://the-guild.dev/graphql/yoga-server/docs/integrations/z-other-environments).
302
325
  ```ts
303
326
  import { createServer } from "node:http";
304
327
  const server = createServer(createYoga());
305
328
  server.listen(3000, () => {
306
- console.info("Visit http://localhost:3000/graphql");
329
+ console.info("Visit http://localhost:3000/graphql");
307
330
  });
308
-
309
331
  ```
310
332
  > `createYoga` sends no CORS headers unless you pass `cors` (Yoga's own default would reflect any origin with credentials, which lets other websites use a visitor's cookie session). An API that browsers on other origins call needs `cors: { origin: ["https://app.example.org"], credentials: true }`.
311
333
  >
312
334
  > If you hit `Query depth limit of N exceeded` (or alias/directive/token limit) errors in production, pass `armorConfig` through to relax the [graphql-armor](https://github.com/Escape-Technologies/graphql-armor) defaults — e.g. `createYoga({ armorConfig: { maxDepth: { n: 20 } } })`. `armorConfig` is forwarded verbatim to `EnvelopArmorPlugin(...)` and is only applied when `enableApiDocs` is `false` (i.e. in production).
313
335
 
314
336
  ## Usage & client generation
337
+
315
338
  You can use the GraphQL api with any client you like. However, rumble provides a generation api which can output a TypeScript client from a rumble instance. To perform a generation, use the exported function from your rumble instance like this:
316
339
  ```ts
317
340
 
@@ -344,8 +367,8 @@ An example usage might look like this:
344
367
  import { client } from "./generated-client/client";
345
368
 
346
369
  const users = client.liveQuery.users({
347
- id: true,
348
- name: true,
370
+ id: true,
371
+ name: true,
349
372
  });
350
373
 
351
374
  users.subscribe((s) => s?.at(0));
@@ -357,19 +380,20 @@ If you would like to ensure that there is data present before subscribing to upd
357
380
  ```ts
358
381
  // this waits for the first value to arrive
359
382
  const users = await client.liveQuery.users({
360
- id: true,
361
- name: true,
383
+ id: true,
384
+ name: true,
362
385
  });
363
386
 
364
387
  // this will guaranteed to have a value set
365
388
  users.subscribe((s) => s?.at(0));
366
389
 
367
390
  // you can directly access the values of an awaited result
368
- console.log(users.firstName)
391
+ console.log(users.firstName);
369
392
  ```
370
393
 
371
394
  > As of `v0.16.12` the client has special support for the svelte reactive state system (runes). If you run a live query which deploys a subscription inside a svelte effect context, you will get reactivity without subscribing to anything right out of the box. Please see [here](https://github.com/DeutscheModelUnitedNations/munify-chase/blob/f70c4484a92551b564c70603ebfd48d5b8cac637/src/lib/api/customClient.ts#L12) and [here](https://github.com/DeutscheModelUnitedNations/munify-chase/blob/f70c4484a92551b564c70603ebfd48d5b8cac637/src/routes/app/(launcher)/%2Bpage.svelte#L9C9-L9C23) for real world examples.
372
395
  ### Alternative decoupled client generation
396
+
373
397
  As an alternative to use the client generator with a fully instanciated rumble instance, you can also import the `generateFromSchema` function from rumble and pass it a standard `GraphQLSchema` object to generate the client:
374
398
  ```ts
375
399
  import { generateFromSchema } from "@m1212e/rumble/client/generate";
@@ -1,3 +1,3 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_generate = require("../generate-D0CKBths.cjs");
2
+ const require_generate = require("../generate-Btr_u0Lr.cjs");
3
3
  exports.generateFromSchema = require_generate.generateFromSchema;
@@ -1,5 +1,4 @@
1
1
  import { GraphQLSchema } from "graphql";
2
- //#region lib/client/generate/generate.d.ts
3
2
  export declare function generateFromSchema({ outputPath, schema, rumbleImportPath, apiUrl, useExternalUrqlClient, removeExisting, forceReactivity, autoIncludeIdField }: {
4
3
  schema: GraphQLSchema;
5
4
  outputPath: string;
@@ -10,5 +9,4 @@ export declare function generateFromSchema({ outputPath, schema, rumbleImportPat
10
9
  forceReactivity?: boolean;
11
10
  autoIncludeIdField?: string | boolean;
12
11
  }): Promise<void>;
13
- //#endregion
14
12
  //# sourceMappingURL=generate.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generate.d.cts","names":[],"sources":["../../lib/client/generate/generate.ts"],"mappings":";;wBAoBsB,qBACpB,YACA,QACA,kBACA,QACA,uBACA,gBACA,iBACA;EAEA,QAAQ;EACR;EACA;EACA;EACA;EACA;EACA;EACA;IACD"}
1
+ {"version":3,"file":"generate.d.cts","names":[],"sources":["../../lib/client/generate/generate.ts"],"mappings":";wBAoBsB,qBACpB,YACA,QACA,kBACA,QACA,uBACA,gBACA,iBACA;EAEA,QAAQ;EACR;EACA;EACA;EACA;EACA;EACA;EACA;IACD"}
@@ -1,5 +1,4 @@
1
1
  import { GraphQLSchema } from "graphql";
2
- //#region lib/client/generate/generate.d.ts
3
2
  export declare function generateFromSchema({ outputPath, schema, rumbleImportPath, apiUrl, useExternalUrqlClient, removeExisting, forceReactivity, autoIncludeIdField }: {
4
3
  schema: GraphQLSchema;
5
4
  outputPath: string;
@@ -10,5 +9,4 @@ export declare function generateFromSchema({ outputPath, schema, rumbleImportPat
10
9
  forceReactivity?: boolean;
11
10
  autoIncludeIdField?: string | boolean;
12
11
  }): Promise<void>;
13
- //#endregion
14
12
  //# sourceMappingURL=generate.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generate.d.mts","names":[],"sources":["../../lib/client/generate/generate.ts"],"mappings":";;wBAoBsB,qBACpB,YACA,QACA,kBACA,QACA,uBACA,gBACA,iBACA;EAEA,QAAQ;EACR;EACA;EACA;EACA;EACA;EACA;EACA;IACD"}
1
+ {"version":3,"file":"generate.d.mts","names":[],"sources":["../../lib/client/generate/generate.ts"],"mappings":";wBAoBsB,qBACpB,YACA,QACA,kBACA,QACA,uBACA,gBACA,iBACA;EAEA,QAAQ;EACR;EACA;EACA;EACA;EACA;EACA;EACA;IACD"}
@@ -1,2 +1 @@
1
- import { t as generateFromSchema } from "../generate-BySnFVve.mjs";
2
- export { generateFromSchema };
1
+ export { t as generateFromSchema } from "../generate-Dzf2klR2.mjs";
package/out/client.cjs CHANGED
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let es_toolkit = require("es-toolkit");
3
3
  let graphql_scalars = require("graphql-scalars");
4
4
  let wonka = require("wonka");
5
- //#region lib/client/request.ts
5
+ // lib/client/request.ts
6
6
  let createSubscriber = () => () => {};
7
7
  const svelteReactivityReady = import("svelte/reactivity").then((m) => {
8
8
  createSubscriber = m.createSubscriber;
@@ -304,8 +304,7 @@ function serializePrimitive(arg) {
304
304
  if (typeof arg === "symbol" || typeof arg === "function") throw new Error(`Cannot stringify a ${typeof arg} to send as gql arg`);
305
305
  throw new Error("Cannot stringify an unknown type");
306
306
  }
307
- //#endregion
308
- //#region lib/client/liveQuery.ts
307
+ // lib/client/liveQuery.ts
309
308
  function makeLiveQuery({ urqlClient, availableSubscriptions, forceReactivity, schema, autoIncludeIdField }) {
310
309
  return new Proxy({}, { get: (_target, prop) => {
311
310
  return (input) => {
@@ -321,8 +320,7 @@ function makeLiveQuery({ urqlClient, availableSubscriptions, forceReactivity, sc
321
320
  };
322
321
  } });
323
322
  }
324
- //#endregion
325
- //#region lib/client/mutation.ts
323
+ // lib/client/mutation.ts
326
324
  function makeMutation({ urqlClient, schema, autoIncludeIdField }) {
327
325
  return new Proxy({}, { get: (_target, prop) => {
328
326
  return (input) => {
@@ -336,8 +334,7 @@ function makeMutation({ urqlClient, schema, autoIncludeIdField }) {
336
334
  };
337
335
  } });
338
336
  }
339
- //#endregion
340
- //#region lib/helpers/deepMap.ts
337
+ // lib/helpers/deepMap.ts
341
338
  /**
342
339
  * Recursively applies a mapping function to every value in a nested structure.
343
340
  *
@@ -385,8 +382,7 @@ function mapValuesDeep(input, fn) {
385
382
  };
386
383
  return recurse(input);
387
384
  }
388
- //#endregion
389
- //#region lib/client/nativeDateExchange.ts
385
+ // lib/client/nativeDateExchange.ts
390
386
  const dateIsoRegex = /^\d{4}-\d{2}-\d{2}(?:[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[Zz]|[+-]\d{2}:\d{2})?)?$/;
391
387
  const nativeDateExchange = ({ client: _client, forward }) => {
392
388
  return (operations$) => {
@@ -402,8 +398,7 @@ const nativeDateExchange = ({ client: _client, forward }) => {
402
398
  }));
403
399
  };
404
400
  };
405
- //#endregion
406
- //#region lib/client/query.ts
401
+ // lib/client/query.ts
407
402
  function makeQuery({ urqlClient, forceReactivity, schema, autoIncludeIdField }) {
408
403
  return new Proxy({}, { get: (_target, prop) => {
409
404
  return (input) => {
@@ -419,8 +414,7 @@ function makeQuery({ urqlClient, forceReactivity, schema, autoIncludeIdField })
419
414
  };
420
415
  } });
421
416
  }
422
- //#endregion
423
- //#region lib/client/subscription.ts
417
+ // lib/client/subscription.ts
424
418
  function makeSubscription({ urqlClient, schema, autoIncludeIdField }) {
425
419
  return new Proxy({}, { get: (_target, prop) => {
426
420
  return (input) => {
@@ -434,7 +428,6 @@ function makeSubscription({ urqlClient, schema, autoIncludeIdField }) {
434
428
  };
435
429
  } });
436
430
  }
437
- //#endregion
438
431
  exports.makeLiveQuery = makeLiveQuery;
439
432
  exports.makeMutation = makeMutation;
440
433
  exports.makeQuery = makeQuery;