Socket
Socket
Sign inDemoInstall

postgraphile-plugin-atomic-mutations

Package Overview
Dependencies
0
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.0.2 to 1.0.3

4

dist/atomic-mutations-plugin.d.ts
/// <reference types="node" />
import { IncomingMessage } from 'http';
import { PostGraphilePlugin } from 'postgraphile';
export declare const MutationAtomicityHeaderName = "x-mutation-atomicity";

@@ -18,5 +17,4 @@ export declare enum MutationAtomicityHeaderValue {

};
export declare const getMutationAtomicityContext: (req: any) => MutationAtomicityContext;
export declare const AtomicMutationsHook: PostGraphilePlugin;
export declare const getMutationAtomicityContext: (req: any, enablePluginByDefault?: boolean) => MutationAtomicityContext;
export declare const AtomicMutationsPlugin: import("postgraphile").Plugin;
//# sourceMappingURL=atomic-mutations-plugin.d.ts.map

@@ -11,4 +11,30 @@ "use strict";

})(MutationAtomicityHeaderValue = exports.MutationAtomicityHeaderValue || (exports.MutationAtomicityHeaderValue = {}));
exports.getMutationAtomicityContext = (req) => {
if (req.headers[exports.MutationAtomicityHeaderName] === 'on') {
exports.getMutationAtomicityContext = (req, enablePluginByDefault = false) => {
const body = req.body;
const paramsList = typeof body === 'string' ? { query: body } : body;
if (Array.isArray(paramsList) && paramsList.length > 1) {
throw Error('AtomicMutationsPlugin does not support GraphQL query batching');
}
const { query, operationName } = paramsList;
const parsedQuery = graphql_1.parse(query);
const [executedDefinition] = parsedQuery.definitions.filter((definition) => (definition.name === undefined && operationName === undefined) ||
definition.name.value === operationName);
if (executedDefinition &&
executedDefinition.operation === 'mutation' &&
((req.headers[exports.MutationAtomicityHeaderName] &&
req.headers[exports.MutationAtomicityHeaderName].toLowerCase() ===
MutationAtomicityHeaderValue.ON) ||
(req.headers[exports.MutationAtomicityHeaderName] === undefined &&
enablePluginByDefault))) {
req.headers[exports.MutationAtomicityHeaderName] = MutationAtomicityHeaderValue.ON;
const totalMutations = executedDefinition.selectionSet.selections.filter((selection) => selection.name.value !== '__typename').length;
req.mutationAtomicityContext = {
totalMutations,
executedMutations: [],
};
}
else {
req.headers[exports.MutationAtomicityHeaderName] = MutationAtomicityHeaderValue.OFF;
}
if (req.headers[exports.MutationAtomicityHeaderName] === MutationAtomicityHeaderValue.ON) {
return req.mutationAtomicityContext;

@@ -20,30 +46,2 @@ }

};
exports.AtomicMutationsHook = {
['postgraphile:httpParamsList'](paramsList, { req }) {
var _a;
if (paramsList.length > 1) {
throw Error('AtomicMutationsPlugin does not support GraphQL query batching');
}
if (paramsList.length === 1) {
const { query, operationName } = paramsList[0];
const parsedQuery = graphql_1.parse(query);
const [executedDefinition] = parsedQuery.definitions.filter((definition) => definition.name.value === operationName);
if (executedDefinition &&
executedDefinition.operation === 'mutation' &&
((_a = req.headers[exports.MutationAtomicityHeaderName]) === null || _a === void 0 ? void 0 : _a.toLowerCase()) ===
MutationAtomicityHeaderValue.ON) {
const totalMutations = executedDefinition.selectionSet.selections.filter((selection) => selection.name.value !== '__typename').length;
req.mutationAtomicityContext = {
totalMutations,
executedMutations: [],
};
}
else {
req.headers[exports.MutationAtomicityHeaderName] =
MutationAtomicityHeaderValue.OFF;
}
}
return paramsList;
},
};
exports.AtomicMutationsPlugin = postgraphile_1.makeWrapResolversPlugin((context) => {

@@ -59,2 +57,6 @@ if (context.scope.isRootMutation) {

try {
if (mutationAtomicityContext &&
mutationAtomicityContext.executedMutations.length === 0) {
await pgClient.query('SAVEPOINT atomic_mutations_tx_start');
}
const result = await resolver(source, args, context, resolveInfo);

@@ -75,7 +77,11 @@ if (mutationAtomicityContext) {

mutationAtomicityContext.executedMutations.length ===
mutationAtomicityContext.totalMutations &&
mutationAtomicityContext.executedMutations.filter((mutation) => mutation.hasErrors === true).length) {
pgClient.query(`rollback`);
mutationAtomicityContext.totalMutations) {
if (mutationAtomicityContext.executedMutations.filter((mutation) => mutation.hasErrors === true).length) {
await pgClient.query(`ROLLBACK TO SAVEPOINT atomic_mutations_tx_start`);
}
else {
await pgClient.query('RELEASE SAVEPOINT atomic_mutations_tx_start');
}
}
}
});
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const atomic_mutations_plugin_1 = require("./atomic-mutations-plugin");
const reqWithHeaderOff = {
const reqSuperSimpleQueryHeaderOn = {
headers: { [atomic_mutations_plugin_1.MutationAtomicityHeaderName]: atomic_mutations_plugin_1.MutationAtomicityHeaderValue.ON },
body: `{
someQuery
}`,
};
const reqSimpleQueryHeaderOn = {
headers: { [atomic_mutations_plugin_1.MutationAtomicityHeaderName]: atomic_mutations_plugin_1.MutationAtomicityHeaderValue.ON },
body: {
query: `{
someQuery
}`,
},
};
const reqSimpleMutationHeaderOn = {
headers: { [atomic_mutations_plugin_1.MutationAtomicityHeaderName]: atomic_mutations_plugin_1.MutationAtomicityHeaderValue.ON },
body: {
query: `mutation {
someMutation
}`,
},
};
const reqSimpleMutationHeaderOff = {
headers: { [atomic_mutations_plugin_1.MutationAtomicityHeaderName]: atomic_mutations_plugin_1.MutationAtomicityHeaderValue.OFF },
mutationAtomicityContext: {
totalMutations: 1,
executedMutations: [{ hasErrors: false }],
body: {
query: `mutation {
someMutation
}`,
},
};
const reqWithoutHeader = {
const reqSimpleMutationHeaderUndefined = {
headers: {},
mutationAtomicityContext: {
totalMutations: 1,
executedMutations: [{ hasErrors: false }],
body: {
query: `mutation {
someMutation
}`,
},
};
const reqMutationWithErrors = {
const reqSingleQueryHeaderOn = {
headers: { [atomic_mutations_plugin_1.MutationAtomicityHeaderName]: atomic_mutations_plugin_1.MutationAtomicityHeaderValue.ON },
mutationAtomicityContext: {
totalMutations: 1,
executedMutations: [{ hasErrors: false }],
body: {
query: `query QueryName {
someQuery
anotherQuery
}`,
operationName: 'QueryName',
},
};
const reqMutationWithoutErrors = {
const reqSingleMutationHeaderOn = {
headers: { [atomic_mutations_plugin_1.MutationAtomicityHeaderName]: atomic_mutations_plugin_1.MutationAtomicityHeaderValue.ON },
mutationAtomicityContext: {
totalMutations: 1,
executedMutations: [{ hasErrors: false }],
body: {
query: `mutation OperationName {
__typename
someMutation
anotherMutation
}`,
operationName: 'OperationName',
totalMutations: 2,
},
};
test('retrieve mutationAtomicityContext - reqMutationWithErrors', () => {
const mutationAtomicityContext = atomic_mutations_plugin_1.getMutationAtomicityContext(reqMutationWithErrors);
expect(mutationAtomicityContext).toBeTruthy();
const reqMixedQueryMutationHeaderOn = {
headers: { [atomic_mutations_plugin_1.MutationAtomicityHeaderName]: atomic_mutations_plugin_1.MutationAtomicityHeaderValue.ON },
body: {
query: `
query QueryName {
someQuery
}
mutation MutationName {
__typename
someMutation
anotherMutation
}`,
operationName: 'MutationName',
totalMutations: 2,
},
};
const reqMixedQueryMutationHeaderOff = {
headers: { [atomic_mutations_plugin_1.MutationAtomicityHeaderName]: atomic_mutations_plugin_1.MutationAtomicityHeaderValue.OFF },
body: {
query: `
query QueryName {
someQuery
}
mutation MutationName {
__typename
someMutation
anotherMutation
}`,
operationName: 'MutationName',
totalMutations: 2,
},
};
const reqMultipleMutationHeaderOn = {
headers: { [atomic_mutations_plugin_1.MutationAtomicityHeaderName]: atomic_mutations_plugin_1.MutationAtomicityHeaderValue.ON },
body: {
query: `
query QueryName {
someQuery
}
mutation MutationName {
__typename
someMutation
anotherMutation
}`,
operationName: 'MutationName',
totalMutations: 2,
},
};
const reqMultipleMutationHeaderOff = {
headers: { [atomic_mutations_plugin_1.MutationAtomicityHeaderName]: atomic_mutations_plugin_1.MutationAtomicityHeaderValue.OFF },
body: {
query: `
query QueryName {
someQuery
}
mutation MutationName {
__typename
someMutation
anotherMutation
}`,
operationName: 'MutationName',
totalMutations: 2,
},
};
const reqUnsupportedQueryBatching = {
headers: { [atomic_mutations_plugin_1.MutationAtomicityHeaderName]: atomic_mutations_plugin_1.MutationAtomicityHeaderValue.ON },
body: [
{
query: `query QueryName {
someQuery
}`,
},
{
query: `query QueryName {
someQuery
}`,
},
],
};
describe('query operations', () => {
test('super simple query - header on', () => {
const mutationAtomicityContext = atomic_mutations_plugin_1.getMutationAtomicityContext(reqSuperSimpleQueryHeaderOn);
expect(mutationAtomicityContext).toBeFalsy();
});
test('simple query - header on', () => {
const mutationAtomicityContext = atomic_mutations_plugin_1.getMutationAtomicityContext(reqSimpleQueryHeaderOn);
expect(mutationAtomicityContext).toBeFalsy();
});
test('single query - header on', () => {
const mutationAtomicityContext = atomic_mutations_plugin_1.getMutationAtomicityContext(reqSingleQueryHeaderOn);
expect(mutationAtomicityContext).toBeFalsy();
});
});
test('retrieve mutationAtomicityContext - reqMutationWithoutErrors', () => {
const mutationAtomicityContext = atomic_mutations_plugin_1.getMutationAtomicityContext(reqMutationWithoutErrors);
expect(mutationAtomicityContext).toBeTruthy();
describe('mutation operations', () => {
test('simple mutation - header on', () => {
const mutationAtomicityContext = atomic_mutations_plugin_1.getMutationAtomicityContext(reqSimpleMutationHeaderOn);
expect(mutationAtomicityContext).toBeTruthy();
});
test('simple mutation - header off', () => {
const mutationAtomicityContext = atomic_mutations_plugin_1.getMutationAtomicityContext(reqSimpleMutationHeaderOff);
expect(mutationAtomicityContext).toBeFalsy();
});
test('simple mutation - header undefined', () => {
const mutationAtomicityContext = atomic_mutations_plugin_1.getMutationAtomicityContext(reqSimpleMutationHeaderUndefined);
expect(mutationAtomicityContext).toBeFalsy();
});
test('single mutation - header on', () => {
const mutationAtomicityContext = atomic_mutations_plugin_1.getMutationAtomicityContext(reqSingleMutationHeaderOn);
expect(mutationAtomicityContext).toBeTruthy();
});
test('multiple mutation - header on', () => {
const mutationAtomicityContext = atomic_mutations_plugin_1.getMutationAtomicityContext(reqMultipleMutationHeaderOn);
expect(mutationAtomicityContext).toBeTruthy();
});
test('multiple mutation - header off', () => {
const mutationAtomicityContext = atomic_mutations_plugin_1.getMutationAtomicityContext(reqMultipleMutationHeaderOff);
expect(mutationAtomicityContext).toBeFalsy();
});
test('mixed query & mutation - header on', () => {
const mutationAtomicityContext = atomic_mutations_plugin_1.getMutationAtomicityContext(reqMixedQueryMutationHeaderOn);
expect(mutationAtomicityContext).toBeTruthy();
});
test('mixed query & mutation - header off', () => {
const mutationAtomicityContext = atomic_mutations_plugin_1.getMutationAtomicityContext(reqMixedQueryMutationHeaderOff);
expect(mutationAtomicityContext).toBeFalsy();
});
});
test('MutationAtomicityHeaderName - OFF', () => {
const mutationAtomicityContext = atomic_mutations_plugin_1.getMutationAtomicityContext(reqWithHeaderOff);
expect(mutationAtomicityContext).toBeFalsy();
describe('mutation counts', () => {
test('single mutation - total mutation count', () => {
const mutationAtomicityContext = atomic_mutations_plugin_1.getMutationAtomicityContext(reqSingleMutationHeaderOn);
expect(mutationAtomicityContext.totalMutations).toEqual(reqSingleMutationHeaderOn.body.totalMutations);
});
test('multiple mutation - total mutation count', () => {
const mutationAtomicityContext = atomic_mutations_plugin_1.getMutationAtomicityContext(reqMultipleMutationHeaderOn);
expect(mutationAtomicityContext.totalMutations).toEqual(reqMultipleMutationHeaderOn.body.totalMutations);
});
test('mixed query & mutation - total mutation count', () => {
const mutationAtomicityContext = atomic_mutations_plugin_1.getMutationAtomicityContext(reqMixedQueryMutationHeaderOn);
expect(mutationAtomicityContext.totalMutations).toEqual(reqMixedQueryMutationHeaderOn.body.totalMutations);
});
});
test('MutationAtomicityHeaderName - Missing', () => {
const mutationAtomicityContext = atomic_mutations_plugin_1.getMutationAtomicityContext(reqWithoutHeader);
expect(mutationAtomicityContext).toBeFalsy();
describe('unsupported', () => {
test('query batching exception', () => {
const unsupportedQueryBatching = () => {
atomic_mutations_plugin_1.getMutationAtomicityContext(reqUnsupportedQueryBatching);
};
expect(unsupportedQueryBatching).toThrow('AtomicMutationsPlugin does not support GraphQL query batching');
});
});
{
"name": "postgraphile-plugin-atomic-mutations",
"version": "1.0.2",
"version": "1.0.3",
"description": "Postgraphile plugin to enable mutation atomicty with GraphQL requests containing multiple mutations",

@@ -5,0 +5,0 @@ "main": "dist/index.js",

@@ -27,6 +27,3 @@ # postgraphile-plugin-atomic-mutations

```js
import { makePluginHook } from 'postgraphile';
import {
AtomicMutationsHook,
AtomicMutationsPlugin,

@@ -38,15 +35,10 @@ getMutationAtomicityContext,

// Used to detect the custom HTTP header which enables or disables the plugin dynamically.
const pluginHook = makePluginHook([AtomicMutationsHook]);
app.use(
postgraphile(pgConfig, schema, {
// The actual plugin which controls atomicity.
// Append the plugin to the array.
appendPlugins: [AtomicMutationsPlugin],
pluginHook,
async additionalGraphQLContextFromRequest(req, res) {
return {
// Additional context shared between AtomicMutationsHook and AtomicMutationsPlugin
// Additional context needed by the plugin
mutationAtomicityContext: getMutationAtomicityContext(req),

@@ -61,2 +53,16 @@ };

```js
const getMutationAtomicityContext = (
req,
enablePluginByDefault = false,
): MutationAtomicityContext;
```
This function will extract the HTTP headers from the `req` object passed as the
1st argument to determine if the mutation atomicity behavior will be enabled or
not. See _Usage from Client_ for more details.
The 2nd argument `enablePluginByDefault` will determine plugin behavior when the
HTTP header is not set (defaults to `false`).
## Usage from Client

@@ -103,8 +109,5 @@

`#3` will never be committed to the database since Mutation `#2` caused an
error, guaranteeing atomicity for the set of Mutations. If the client decides to
re-run the same request again, the outcome will be the same.
error, thus, guaranteeing atomicity for the set of Mutations. If the client
decides to re-run the same request again, the outcome will be the same.
If the `'X-Mutation-Atomicity'` is set to `'off` or not set at all, the plugin
will be disabled.
## Limitations

@@ -125,4 +128,2 @@

- Introduce plugin options to control default behavior.
- When the client does not specify a `'X-Mutation-Atomicity'` header.
- Improve tests to have more code coverage.

@@ -129,0 +130,0 @@

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc