@m1212e/rumble 0.12.4 → 0.12.5
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/LICENSE +201 -0
- package/README.md +286 -0
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# rumble
|
|
2
|
+
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
|
+
> 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.
|
|
5
|
+
|
|
6
|
+
> Using rumble and reading these docs requires some basic knowledge about the above mentioned tools. If you feel stuck, please make sure to familiarize yourself with those first! Especially familiarity with pothos and its drizzle plugin are very helpful!
|
|
7
|
+
|
|
8
|
+
## Getting started
|
|
9
|
+
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).
|
|
10
|
+
|
|
11
|
+
First, install rumble into your project:
|
|
12
|
+
```
|
|
13
|
+
bun add @m1212e/rumble
|
|
14
|
+
npm i @m1212e/rumble
|
|
15
|
+
```
|
|
16
|
+
then call the rumble creator:
|
|
17
|
+
```ts
|
|
18
|
+
import * as schema from "./db/schema";
|
|
19
|
+
import * as relations from "./db/relations";
|
|
20
|
+
import { rumble } from "@m1212e/rumble";
|
|
21
|
+
|
|
22
|
+
export const db = drizzle(
|
|
23
|
+
"postgres://postgres:postgres@localhost:5432/postgres",
|
|
24
|
+
{ schema, relations }
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const { abilityBuilder } = rumble({ db });
|
|
28
|
+
```
|
|
29
|
+
> If the creation of a drizzle instance with the schema definition seems unfamiliar to you, please see their excellent [getting started guide](https://orm.drizzle.team/docs/get-started)
|
|
30
|
+
|
|
31
|
+
The rumble creator returns some functions which you can use to implement your api. The concepts rumble uses are described in the following sections:
|
|
32
|
+
|
|
33
|
+
## Abilities
|
|
34
|
+
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:
|
|
35
|
+
|
|
36
|
+
### Wildcard
|
|
37
|
+
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.
|
|
38
|
+
```ts
|
|
39
|
+
// everyone can read posts
|
|
40
|
+
abilityBuilder.posts.allow("read");
|
|
41
|
+
|
|
42
|
+
// everyone can read and write posts
|
|
43
|
+
abilityBuilder.posts.allow(["read", "write"]);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Condition Object
|
|
47
|
+
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.__
|
|
48
|
+
```ts
|
|
49
|
+
// everyone can read published posts
|
|
50
|
+
abilityBuilder.posts.allow("read").when({
|
|
51
|
+
where: { published: true },
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Condition Function
|
|
56
|
+
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.
|
|
57
|
+
```ts
|
|
58
|
+
// only the author can update posts
|
|
59
|
+
abilityBuilder.posts
|
|
60
|
+
.allow(["update", "delete"])
|
|
61
|
+
.when(({ userId }) => ({ where: { authorId: userId } }));
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Application level filters
|
|
66
|
+
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:
|
|
67
|
+
```ts
|
|
68
|
+
abilityBuilder.users.filter("read").by(({ context, entities }) => {
|
|
69
|
+
// const allowed = await queryExternalAuthorizationService(context.user, entities);
|
|
70
|
+
|
|
71
|
+
// we could filter the list to only return the entities the user is allowed to see
|
|
72
|
+
// event mapping to prevent leakage of certain fields is possible
|
|
73
|
+
return entities;
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
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.
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
### Applying abilities
|
|
80
|
+
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.
|
|
81
|
+
```ts
|
|
82
|
+
schemaBuilder.queryFields((t) => {
|
|
83
|
+
return {
|
|
84
|
+
posts: t.drizzleField({
|
|
85
|
+
type: [PostRef],
|
|
86
|
+
resolve: (query, root, args, ctx, info) => {
|
|
87
|
+
return db.query.posts.findMany(
|
|
88
|
+
// here we apply our filter
|
|
89
|
+
query(ctx.abilities.posts.filter("read").query.many),
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
}),
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
> 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.
|
|
98
|
+
|
|
99
|
+
#### Applying filters
|
|
100
|
+
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:
|
|
101
|
+
```ts
|
|
102
|
+
const PostRef = schemaBuilder.drizzleObject("posts", {
|
|
103
|
+
name: "Post",
|
|
104
|
+
// apply the application level filters
|
|
105
|
+
applyFilters: abilityBuilder._.registeredFilters({
|
|
106
|
+
action: "read",
|
|
107
|
+
table: "posts",
|
|
108
|
+
}),
|
|
109
|
+
fields: (t) => ({
|
|
110
|
+
...
|
|
111
|
+
```
|
|
112
|
+
To apply filters in a custom handler implementation, like e.g. your mutations, you can use the `applyFilters` helper exported by rumble to easily filter a list of entities.
|
|
113
|
+
|
|
114
|
+
## Context & Configuration
|
|
115
|
+
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.
|
|
116
|
+
```ts
|
|
117
|
+
rumble({
|
|
118
|
+
db,
|
|
119
|
+
context(request) {
|
|
120
|
+
return {
|
|
121
|
+
// here you could instead read some cookies or HTTP headers to retrieve an actual userId
|
|
122
|
+
userId: 2,
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
> `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.
|
|
128
|
+
|
|
129
|
+
## Helpers
|
|
130
|
+
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.
|
|
131
|
+
|
|
132
|
+
### arg
|
|
133
|
+
`arg` 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 arg helper implements such a filter tailored to the specific entity which you then can directly pass on to the database query.
|
|
134
|
+
```ts
|
|
135
|
+
const WhereArgs = arg({
|
|
136
|
+
table: "posts",
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
schemaBuilder.queryFields((t) => {
|
|
140
|
+
return {
|
|
141
|
+
postsFiltered: t.drizzleField({
|
|
142
|
+
type: [PostRef],
|
|
143
|
+
args: {
|
|
144
|
+
// here we set our generated type as type for the where argument
|
|
145
|
+
where: t.arg({ type: WhereArgs }),
|
|
146
|
+
},
|
|
147
|
+
resolve: (query, root, args, ctx, info) => {
|
|
148
|
+
return db.query.posts.findMany(
|
|
149
|
+
query(
|
|
150
|
+
// here we apply the ability filter
|
|
151
|
+
ctx.abilities.users.filter("read")
|
|
152
|
+
// we can merge one time filters into the permission filter for this specific request
|
|
153
|
+
.merge({ where: args.where }).query.many,
|
|
154
|
+
),
|
|
155
|
+
);
|
|
156
|
+
},
|
|
157
|
+
}),
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### object
|
|
163
|
+
`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],`).
|
|
164
|
+
```ts
|
|
165
|
+
const UserRef = object({
|
|
166
|
+
table: "users",
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### query
|
|
171
|
+
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.
|
|
172
|
+
```ts
|
|
173
|
+
query({
|
|
174
|
+
table: "users",
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### pubsub
|
|
180
|
+
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.
|
|
181
|
+
```ts
|
|
182
|
+
const { updated, created, removed } = pubsub({
|
|
183
|
+
table: "users",
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
Now just call the functions whenever your application does the respective action and your subscriptions will get notified:
|
|
187
|
+
```ts
|
|
188
|
+
updateUsernameHandler() => {
|
|
189
|
+
await db.updateTheUsername();
|
|
190
|
+
// the pubsub function
|
|
191
|
+
updated(user.id);
|
|
192
|
+
}
|
|
193
|
+
// or if creating
|
|
194
|
+
createUserHandler() => {
|
|
195
|
+
await db.createTheUser();
|
|
196
|
+
// the pubsub function
|
|
197
|
+
created();
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
All `query` and `object` helper implementations will automatically update and work right out of the box, no additional config needed!
|
|
201
|
+
|
|
202
|
+
> 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
|
|
203
|
+
|
|
204
|
+
### enum_
|
|
205
|
+
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.
|
|
206
|
+
```ts
|
|
207
|
+
const enumRef = enum_({
|
|
208
|
+
tsName: "moodEnum",
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
> 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.
|
|
212
|
+
|
|
213
|
+
## Running the server
|
|
214
|
+
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).
|
|
215
|
+
```ts
|
|
216
|
+
import { createServer } from "node:http";
|
|
217
|
+
const server = createServer(createYoga());
|
|
218
|
+
server.listen(3000, () => {
|
|
219
|
+
console.info("Visit http://localhost:3000/graphql");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Usage & client generation
|
|
225
|
+
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:
|
|
226
|
+
```ts
|
|
227
|
+
|
|
228
|
+
const { clientCreator } = rumble(...);
|
|
229
|
+
|
|
230
|
+
await clientCreator({
|
|
231
|
+
// where should the client files be generated to
|
|
232
|
+
outputPath: "./example/src/generated-client",
|
|
233
|
+
// where will the client be able to reach your api
|
|
234
|
+
apiUrl: "http://localhost:3000/graphql",
|
|
235
|
+
|
|
236
|
+
// in case you do not want to specify a url yourself
|
|
237
|
+
// or you would like to perform some customization
|
|
238
|
+
// to the underlying urql client, you can set
|
|
239
|
+
|
|
240
|
+
// useExternalUrqlClient: "../client"
|
|
241
|
+
|
|
242
|
+
// to point to your custom client
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
> The client uses [urql](https://nearform.com/open-source/urql/docs/basics/core/) under the hood. If you would like more info on how the internals work, please see the docs.
|
|
246
|
+
|
|
247
|
+
An example usage might look like this:
|
|
248
|
+
```ts
|
|
249
|
+
import { client } from "./generated-client/client";
|
|
250
|
+
|
|
251
|
+
const users = client.liveQuery.users({
|
|
252
|
+
id: true,
|
|
253
|
+
name: true,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
users.subscribe((s) => s?.at(0));
|
|
257
|
+
```
|
|
258
|
+
Notice how the client offers `liveQuery` in addition to the traditional `query`, `mutation` and `subscription` operations. The live query is a hybrid query and subscription which basically performs a regular query and then checks if there is a subscription available with the same name (as it is the case when using the rumble query implementation helpers). In case there is one present, it will also subscribe. If no subscription with the same name can be found, it will perform a regular query.
|
|
259
|
+
|
|
260
|
+
If you would like to ensure that there is data present before subscribing to updates, you can await the result:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
// this waits for the first value to arrive
|
|
264
|
+
const users = await client.liveQuery.users({
|
|
265
|
+
id: true,
|
|
266
|
+
name: true,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// this will guaranteed to have a value set
|
|
270
|
+
users.subscribe((s) => s?.at(0));
|
|
271
|
+
|
|
272
|
+
// you can directly access the values of an awaited result
|
|
273
|
+
console.log(users.firstName)
|
|
274
|
+
```
|
|
275
|
+
### Alternative
|
|
276
|
+
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:
|
|
277
|
+
```ts
|
|
278
|
+
import { generateFromSchema } from "@m1212e/rumble";
|
|
279
|
+
|
|
280
|
+
await generateFromSchema({
|
|
281
|
+
// a schema object: https://github.com/graphql/graphql-js/blob/60ae6c48b9c78332bf3d6036e7d931a3617d0674/src/type/schema.ts#L130
|
|
282
|
+
schema: yourGraphQLSchemaObject
|
|
283
|
+
outputPath: "./generated";
|
|
284
|
+
})
|
|
285
|
+
```
|
|
286
|
+
This might become handy in separate code bases for api and client.
|