@commitspark/graphql-api 2.0.0-beta.4 → 2.0.0-beta.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/CHANGELOG.md +32 -0
- package/README.md +147 -80
- package/dist/cjs/graphql/errors.d.ts +3 -2
- package/dist/cjs/graphql/errors.d.ts.map +1 -1
- package/dist/cjs/graphql/errors.js +11 -1
- package/dist/cjs/graphql/errors.js.map +1 -1
- package/dist/cjs/graphql/input-type-generator.d.ts.map +1 -1
- package/dist/cjs/graphql/input-type-generator.js +5 -10
- package/dist/cjs/graphql/input-type-generator.js.map +1 -1
- package/dist/cjs/graphql/queries-mutations-generator.d.ts +5 -5
- package/dist/cjs/graphql/queries-mutations-generator.d.ts.map +1 -1
- package/dist/cjs/graphql/queries-mutations-generator.js +45 -7
- package/dist/cjs/graphql/queries-mutations-generator.js.map +1 -1
- package/dist/cjs/graphql/resolvers/commit-message.d.ts +11 -0
- package/dist/cjs/graphql/resolvers/commit-message.d.ts.map +1 -0
- package/dist/cjs/graphql/resolvers/commit-message.js +22 -0
- package/dist/cjs/graphql/resolvers/commit-message.js.map +1 -0
- package/dist/cjs/graphql/resolvers/field-resolvers/entry-reference-resolver.js +2 -2
- package/dist/cjs/graphql/resolvers/field-resolvers/entry-reference-resolver.js.map +1 -1
- package/dist/cjs/graphql/resolvers/field-resolvers/field-default-value-resolver.js +3 -3
- package/dist/cjs/graphql/resolvers/field-resolvers/field-default-value-resolver.js.map +1 -1
- package/dist/cjs/graphql/resolvers/field-resolvers/version-resolver.d.ts +3 -0
- package/dist/cjs/graphql/resolvers/field-resolvers/version-resolver.d.ts.map +1 -0
- package/dist/cjs/graphql/resolvers/field-resolvers/version-resolver.js +7 -0
- package/dist/cjs/graphql/resolvers/field-resolvers/version-resolver.js.map +1 -0
- package/dist/cjs/graphql/resolvers/object-type-field-default-value-resolver-generator.js +1 -1
- package/dist/cjs/graphql/resolvers/object-type-field-default-value-resolver-generator.js.map +1 -1
- package/dist/cjs/graphql/resolvers/query-mutation-resolvers/mutation-create-resolver.d.ts.map +1 -1
- package/dist/cjs/graphql/resolvers/query-mutation-resolvers/mutation-create-resolver.js +34 -28
- package/dist/cjs/graphql/resolvers/query-mutation-resolvers/mutation-create-resolver.js.map +1 -1
- package/dist/cjs/graphql/resolvers/query-mutation-resolvers/mutation-delete-resolver.d.ts.map +1 -1
- package/dist/cjs/graphql/resolvers/query-mutation-resolvers/mutation-delete-resolver.js +28 -23
- package/dist/cjs/graphql/resolvers/query-mutation-resolvers/mutation-delete-resolver.js.map +1 -1
- package/dist/cjs/graphql/resolvers/query-mutation-resolvers/mutation-update-resolver.d.ts.map +1 -1
- package/dist/cjs/graphql/resolvers/query-mutation-resolvers/mutation-update-resolver.js +25 -18
- package/dist/cjs/graphql/resolvers/query-mutation-resolvers/mutation-update-resolver.js.map +1 -1
- package/dist/cjs/graphql/resolvers/query-mutation-resolvers/query-search-resolver.js +5 -5
- package/dist/cjs/graphql/resolvers/query-mutation-resolvers/query-search-resolver.js.map +1 -1
- package/dist/cjs/graphql/resolvers/query-mutation-resolvers/union-type-resolver.js +1 -1
- package/dist/cjs/graphql/resolvers/query-mutation-resolvers/union-type-resolver.js.map +1 -1
- package/dist/cjs/graphql/resolvers/types.d.ts +9 -1
- package/dist/cjs/graphql/resolvers/types.d.ts.map +1 -1
- package/dist/cjs/graphql/schema-generator.d.ts.map +1 -1
- package/dist/cjs/graphql/schema-generator.js +9 -8
- package/dist/cjs/graphql/schema-generator.js.map +1 -1
- package/dist/cjs/graphql/schema-utils/entry-reference-util.js +7 -7
- package/dist/cjs/graphql/schema-utils/entry-reference-util.js.map +1 -1
- package/dist/cjs/graphql/schema-utils/union-type-util.js +2 -2
- package/dist/cjs/graphql/schema-utils/union-type-util.js.map +1 -1
- package/dist/cjs/graphql/schema-validator.d.ts.map +1 -1
- package/dist/cjs/graphql/schema-validator.js +16 -0
- package/dist/cjs/graphql/schema-validator.js.map +1 -1
- package/dist/cjs/index.js +3 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/persistence/cache.d.ts +3 -0
- package/dist/cjs/persistence/cache.d.ts.map +1 -1
- package/dist/cjs/persistence/cache.js +8 -17
- package/dist/cjs/persistence/cache.js.map +1 -1
- package/dist/cjs/persistence/commit-with-retry.d.ts +16 -0
- package/dist/cjs/persistence/commit-with-retry.d.ts.map +1 -0
- package/dist/cjs/persistence/commit-with-retry.js +39 -0
- package/dist/cjs/persistence/commit-with-retry.js.map +1 -0
- package/dist/cjs/persistence/git-adapter-call.d.ts +10 -0
- package/dist/cjs/persistence/git-adapter-call.d.ts.map +1 -0
- package/dist/cjs/persistence/git-adapter-call.js +29 -0
- package/dist/cjs/persistence/git-adapter-call.js.map +1 -0
- package/dist/cjs/persistence/persistence.d.ts +11 -0
- package/dist/cjs/persistence/persistence.d.ts.map +1 -1
- package/dist/cjs/persistence/persistence.js +37 -2
- package/dist/cjs/persistence/persistence.js.map +1 -1
- package/dist/esm/graphql/errors.d.ts +3 -2
- package/dist/esm/graphql/errors.d.ts.map +1 -1
- package/dist/esm/graphql/errors.js +10 -0
- package/dist/esm/graphql/errors.js.map +1 -1
- package/dist/esm/graphql/input-type-generator.d.ts.map +1 -1
- package/dist/esm/graphql/input-type-generator.js +6 -11
- package/dist/esm/graphql/input-type-generator.js.map +1 -1
- package/dist/esm/graphql/queries-mutations-generator.d.ts +5 -5
- package/dist/esm/graphql/queries-mutations-generator.d.ts.map +1 -1
- package/dist/esm/graphql/queries-mutations-generator.js +43 -6
- package/dist/esm/graphql/queries-mutations-generator.js.map +1 -1
- package/dist/esm/graphql/resolvers/commit-message.d.ts +11 -0
- package/dist/esm/graphql/resolvers/commit-message.d.ts.map +1 -0
- package/dist/esm/graphql/resolvers/commit-message.js +19 -0
- package/dist/esm/graphql/resolvers/commit-message.js.map +1 -0
- package/dist/esm/graphql/resolvers/field-resolvers/entry-reference-resolver.js +3 -3
- package/dist/esm/graphql/resolvers/field-resolvers/entry-reference-resolver.js.map +1 -1
- package/dist/esm/graphql/resolvers/field-resolvers/field-default-value-resolver.js +4 -4
- package/dist/esm/graphql/resolvers/field-resolvers/field-default-value-resolver.js.map +1 -1
- package/dist/esm/graphql/resolvers/field-resolvers/version-resolver.d.ts +3 -0
- package/dist/esm/graphql/resolvers/field-resolvers/version-resolver.d.ts.map +1 -0
- package/dist/esm/graphql/resolvers/field-resolvers/version-resolver.js +3 -0
- package/dist/esm/graphql/resolvers/field-resolvers/version-resolver.js.map +1 -0
- package/dist/esm/graphql/resolvers/object-type-field-default-value-resolver-generator.js +2 -2
- package/dist/esm/graphql/resolvers/object-type-field-default-value-resolver-generator.js.map +1 -1
- package/dist/esm/graphql/resolvers/query-mutation-resolvers/mutation-create-resolver.d.ts.map +1 -1
- package/dist/esm/graphql/resolvers/query-mutation-resolvers/mutation-create-resolver.js +35 -29
- package/dist/esm/graphql/resolvers/query-mutation-resolvers/mutation-create-resolver.js.map +1 -1
- package/dist/esm/graphql/resolvers/query-mutation-resolvers/mutation-delete-resolver.d.ts.map +1 -1
- package/dist/esm/graphql/resolvers/query-mutation-resolvers/mutation-delete-resolver.js +30 -25
- package/dist/esm/graphql/resolvers/query-mutation-resolvers/mutation-delete-resolver.js.map +1 -1
- package/dist/esm/graphql/resolvers/query-mutation-resolvers/mutation-update-resolver.d.ts.map +1 -1
- package/dist/esm/graphql/resolvers/query-mutation-resolvers/mutation-update-resolver.js +27 -20
- package/dist/esm/graphql/resolvers/query-mutation-resolvers/mutation-update-resolver.js.map +1 -1
- package/dist/esm/graphql/resolvers/query-mutation-resolvers/query-search-resolver.js +6 -6
- package/dist/esm/graphql/resolvers/query-mutation-resolvers/query-search-resolver.js.map +1 -1
- package/dist/esm/graphql/resolvers/query-mutation-resolvers/union-type-resolver.js +2 -2
- package/dist/esm/graphql/resolvers/query-mutation-resolvers/union-type-resolver.js.map +1 -1
- package/dist/esm/graphql/resolvers/types.d.ts +9 -1
- package/dist/esm/graphql/resolvers/types.d.ts.map +1 -1
- package/dist/esm/graphql/schema-generator.d.ts.map +1 -1
- package/dist/esm/graphql/schema-generator.js +11 -10
- package/dist/esm/graphql/schema-generator.js.map +1 -1
- package/dist/esm/graphql/schema-utils/entry-reference-util.js +8 -8
- package/dist/esm/graphql/schema-utils/entry-reference-util.js.map +1 -1
- package/dist/esm/graphql/schema-utils/union-type-util.js +3 -3
- package/dist/esm/graphql/schema-utils/union-type-util.js.map +1 -1
- package/dist/esm/graphql/schema-validator.d.ts.map +1 -1
- package/dist/esm/graphql/schema-validator.js +17 -1
- package/dist/esm/graphql/schema-validator.js.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/persistence/cache.d.ts +3 -0
- package/dist/esm/persistence/cache.d.ts.map +1 -1
- package/dist/esm/persistence/cache.js +6 -15
- package/dist/esm/persistence/cache.js.map +1 -1
- package/dist/esm/persistence/commit-with-retry.d.ts +16 -0
- package/dist/esm/persistence/commit-with-retry.d.ts.map +1 -0
- package/dist/esm/persistence/commit-with-retry.js +35 -0
- package/dist/esm/persistence/commit-with-retry.js.map +1 -0
- package/dist/esm/persistence/git-adapter-call.d.ts +10 -0
- package/dist/esm/persistence/git-adapter-call.d.ts.map +1 -0
- package/dist/esm/persistence/git-adapter-call.js +24 -0
- package/dist/esm/persistence/git-adapter-call.js.map +1 -0
- package/dist/esm/persistence/persistence.d.ts +11 -0
- package/dist/esm/persistence/persistence.d.ts.map +1 -1
- package/dist/esm/persistence/persistence.js +36 -3
- package/dist/esm/persistence/persistence.js.map +1 -1
- package/package.json +8 -5
- package/src/graphql/errors.ts +2 -1
- package/src/graphql/input-type-generator.ts +5 -15
- package/src/graphql/queries-mutations-generator.ts +51 -10
- package/src/graphql/resolvers/commit-message.ts +34 -0
- package/src/graphql/resolvers/field-resolvers/version-resolver.ts +8 -0
- package/src/graphql/resolvers/query-mutation-resolvers/mutation-create-resolver.ts +40 -29
- package/src/graphql/resolvers/query-mutation-resolvers/mutation-delete-resolver.ts +44 -30
- package/src/graphql/resolvers/query-mutation-resolvers/mutation-update-resolver.ts +37 -18
- package/src/graphql/resolvers/types.ts +18 -1
- package/src/graphql/schema-generator.ts +12 -8
- package/src/graphql/schema-validator.ts +21 -0
- package/src/persistence/cache.ts +7 -13
- package/src/persistence/commit-with-retry.ts +47 -0
- package/src/persistence/git-adapter-call.ts +27 -0
- package/src/persistence/persistence.ts +50 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit-with-retry.d.ts","sourceRoot":"","sources":["../../../src/persistence/commit-with-retry.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EAGZ,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAK5C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,aAAa,EACtB,aAAa,MAAM,OAAO,CAAC,WAAW,CAAC,KACtC,OAAO,CAAC,IAAI,CAqBd,CAAA"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ErrorCode as GitAdapterErrorCode, GitAdapterError, } from '@commitspark/git-adapter';
|
|
2
|
+
import { callGitAdapter, toGraphQLError } from "./git-adapter-call.js";
|
|
3
|
+
const MAX_COMMIT_ATTEMPTS = 3;
|
|
4
|
+
/**
|
|
5
|
+
* Commits a draft that `createDraft` validates against the current commit of the request, guaranteeing that the
|
|
6
|
+
* draft is only committed on top of the exact commit it was validated against.
|
|
7
|
+
*
|
|
8
|
+
* Git adapters only move a branch if it still points to `parentSha` and otherwise throw `CONFLICT`. As most adapters
|
|
9
|
+
* can only check this for the branch as a whole, any concurrent commit to the branch between reading and committing
|
|
10
|
+
* leads to `CONFLICT`, even if it changed unrelated entries. In this case, the current commit of the request is moved
|
|
11
|
+
* to the new branch head and the draft is created and validated again, including expected entry versions and
|
|
12
|
+
* references, so that only actual conflicts are reported to the caller.
|
|
13
|
+
*
|
|
14
|
+
* Errors thrown by `createDraft` are not retried, as the draft was validated against the latest known state.
|
|
15
|
+
*/
|
|
16
|
+
export const commitWithRetry = async (context, createDraft) => {
|
|
17
|
+
for (let attempt = 1;; attempt++) {
|
|
18
|
+
const commitDraft = await createDraft();
|
|
19
|
+
try {
|
|
20
|
+
const commit = await context.gitAdapter.createCommit(commitDraft);
|
|
21
|
+
context.setCurrentHash(commit.commitHash);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
const isConflict = err instanceof GitAdapterError &&
|
|
26
|
+
err.code === GitAdapterErrorCode.CONFLICT;
|
|
27
|
+
if (!isConflict || attempt >= MAX_COMMIT_ATTEMPTS) {
|
|
28
|
+
throw toGraphQLError(err);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const headCommitHash = await callGitAdapter(() => context.gitAdapter.getLatestCommitHash(context.branch));
|
|
32
|
+
context.setCurrentHash(headCommitHash);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=commit-with-retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit-with-retry.js","sourceRoot":"","sources":["../../../src/persistence/commit-with-retry.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,SAAS,IAAI,mBAAmB,EAChC,eAAe,GAChB,MAAM,0BAA0B,CAAA;AAEjC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAEtE,MAAM,mBAAmB,GAAG,CAAC,CAAA;AAE7B;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAClC,OAAsB,EACtB,WAAuC,EACxB,EAAE;IACjB,KAAK,IAAI,OAAO,GAAG,CAAC,GAAI,OAAO,EAAE,EAAE,CAAC;QAClC,MAAM,WAAW,GAAG,MAAM,WAAW,EAAE,CAAA;QACvC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,WAAW,CAAC,CAAA;YACjE,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;YACzC,OAAM;QACR,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,UAAU,GACd,GAAG,YAAY,eAAe;gBAC9B,GAAG,CAAC,IAAI,KAAK,mBAAmB,CAAC,QAAQ,CAAA;YAC3C,IAAI,CAAC,UAAU,IAAI,OAAO,IAAI,mBAAmB,EAAE,CAAC;gBAClD,MAAM,cAAc,CAAC,GAAG,CAAC,CAAA;YAC3B,CAAC;QACH,CAAC;QAED,MAAM,cAAc,GAAG,MAAM,cAAc,CAAC,GAAG,EAAE,CAC/C,OAAO,CAAC,UAAU,CAAC,mBAAmB,CAAC,OAAO,CAAC,MAAM,CAAC,CACvD,CAAA;QACD,OAAO,CAAC,cAAc,CAAC,cAAc,CAAC,CAAA;IACxC,CAAC;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { GraphQLError } from 'graphql';
|
|
2
|
+
/**
|
|
3
|
+
* Converts an error thrown by a Git adapter to a GraphQL error, keeping the error code of a `GitAdapterError`.
|
|
4
|
+
*/
|
|
5
|
+
export declare const toGraphQLError: (err: unknown) => GraphQLError;
|
|
6
|
+
/**
|
|
7
|
+
* Runs a Git adapter operation and converts thrown errors to GraphQL errors.
|
|
8
|
+
*/
|
|
9
|
+
export declare const callGitAdapter: <T>(operation: () => Promise<T>) => Promise<T>;
|
|
10
|
+
//# sourceMappingURL=git-adapter-call.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-adapter-call.d.ts","sourceRoot":"","sources":["../../../src/persistence/git-adapter-call.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAGtC;;GAEG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,OAAO,KAAG,YAM7C,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,GAAU,CAAC,EACpC,WAAW,MAAM,OAAO,CAAC,CAAC,CAAC,KAC1B,OAAO,CAAC,CAAC,CAMX,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { GitAdapterError } from '@commitspark/git-adapter';
|
|
2
|
+
import { createError, ErrorCode } from "../graphql/errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Converts an error thrown by a Git adapter to a GraphQL error, keeping the error code of a `GitAdapterError`.
|
|
5
|
+
*/
|
|
6
|
+
export const toGraphQLError = (err) => {
|
|
7
|
+
if (err instanceof GitAdapterError) {
|
|
8
|
+
return createError(err.message, err.code, {});
|
|
9
|
+
}
|
|
10
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11
|
+
return createError(message, ErrorCode.INTERNAL_ERROR, {});
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Runs a Git adapter operation and converts thrown errors to GraphQL errors.
|
|
15
|
+
*/
|
|
16
|
+
export const callGitAdapter = async (operation) => {
|
|
17
|
+
try {
|
|
18
|
+
return await operation();
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
throw toGraphQLError(err);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=git-adapter-call.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-adapter-call.js","sourceRoot":"","sources":["../../../src/persistence/git-adapter-call.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAE1D,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAE7D;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,GAAY,EAAgB,EAAE;IAC3D,IAAI,GAAG,YAAY,eAAe,EAAE,CAAC;QACnC,OAAO,WAAW,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IAC/C,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAChE,OAAO,WAAW,CAAC,OAAO,EAAE,SAAS,CAAC,cAAc,EAAE,EAAE,CAAC,CAAA;AAC3D,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EACjC,SAA2B,EACf,EAAE;IACd,IAAI,CAAC;QACH,OAAO,MAAM,SAAS,EAAE,CAAA;IAC1B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,cAAc,CAAC,GAAG,CAAC,CAAA;IAC3B,CAAC;AACH,CAAC,CAAA"}
|
|
@@ -4,4 +4,15 @@ export declare function getTypeById(context: ApolloContext, id: string): Promise
|
|
|
4
4
|
export declare function findById(context: ApolloContext, id: string): Promise<Entry>;
|
|
5
5
|
export declare function findByType(context: ApolloContext, type: string): Promise<Entry[]>;
|
|
6
6
|
export declare function findByTypeId(context: ApolloContext, type: string, id: string): Promise<Entry>;
|
|
7
|
+
/**
|
|
8
|
+
* Returns the version of an entry, which is the content hash of the entry reported by the Git adapter. It changes
|
|
9
|
+
* whenever the stored entry changes.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getEntryVersion(context: ApolloContext, id: string): Promise<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Throws `CONFLICT` if an expected version is given and the entry has a different version at the current commit of
|
|
14
|
+
* the request. This lets callers detect that an entry was changed by someone else since they read it, e.g. while a
|
|
15
|
+
* user was editing the entry, instead of overwriting these changes.
|
|
16
|
+
*/
|
|
17
|
+
export declare function assertExpectedVersion(context: ApolloContext, id: string, expectedVersion: string | null | undefined): Promise<void>;
|
|
7
18
|
//# sourceMappingURL=persistence.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../../src/persistence/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAEhD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAE5C,wBAAsB,WAAW,CAC/B,OAAO,EAAE,aAAa,EACtB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,aAAa,EACtB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,KAAK,CAAC,CAchB;AAED,wBAAsB,UAAU,CAC9B,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,KAAK,EAAE,CAAC,CAMlB;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,KAAK,CAAC,CAehB"}
|
|
1
|
+
{"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../../src/persistence/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAEhD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAE5C,wBAAsB,WAAW,CAC/B,OAAO,EAAE,aAAa,EACtB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,aAAa,EACtB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,KAAK,CAAC,CAchB;AAED,wBAAsB,UAAU,CAC9B,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,KAAK,EAAE,CAAC,CAMlB;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,KAAK,CAAC,CAehB;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,aAAa,EACtB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,MAAM,CAAC,CAcjB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,aAAa,EACtB,EAAE,EAAE,MAAM,EACV,eAAe,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACzC,OAAO,CAAC,IAAI,CAAC,CAgBf"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createError } from "../graphql/errors.js";
|
|
1
|
+
import { createError, ErrorCode } from "../graphql/errors.js";
|
|
2
2
|
export async function getTypeById(context, id) {
|
|
3
3
|
const requestedEntry = await findById(context, id);
|
|
4
4
|
return requestedEntry.metadata.type;
|
|
@@ -7,7 +7,7 @@ export async function findById(context, id) {
|
|
|
7
7
|
const entriesRecord = await context.repositoryCache.getEntriesRecord(context, context.getCurrentHash());
|
|
8
8
|
const requestedEntry = entriesRecord.byId.get(id);
|
|
9
9
|
if (requestedEntry === undefined) {
|
|
10
|
-
throw createError(`No entry with ID "${id}" exists.`,
|
|
10
|
+
throw createError(`No entry with ID "${id}" exists.`, ErrorCode.NOT_FOUND, {
|
|
11
11
|
argumentName: 'id',
|
|
12
12
|
argumentValue: id,
|
|
13
13
|
});
|
|
@@ -21,7 +21,7 @@ export async function findByType(context, type) {
|
|
|
21
21
|
export async function findByTypeId(context, type, id) {
|
|
22
22
|
const entryById = await findById(context, id);
|
|
23
23
|
if (entryById === undefined || entryById.metadata.type !== type) {
|
|
24
|
-
throw createError(`No entry of type "${type}" with ID "${id}" exists.`,
|
|
24
|
+
throw createError(`No entry of type "${type}" with ID "${id}" exists.`, ErrorCode.NOT_FOUND, {
|
|
25
25
|
typeName: type,
|
|
26
26
|
argumentName: 'id',
|
|
27
27
|
argumentValue: id,
|
|
@@ -29,4 +29,37 @@ export async function findByTypeId(context, type, id) {
|
|
|
29
29
|
}
|
|
30
30
|
return entryById;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns the version of an entry, which is the content hash of the entry reported by the Git adapter. It changes
|
|
34
|
+
* whenever the stored entry changes.
|
|
35
|
+
*/
|
|
36
|
+
export async function getEntryVersion(context, id) {
|
|
37
|
+
const entriesRecord = await context.repositoryCache.getEntriesRecord(context, context.getCurrentHash());
|
|
38
|
+
const version = entriesRecord.hashById.get(id);
|
|
39
|
+
if (version === undefined) {
|
|
40
|
+
throw createError(`No entry with ID "${id}" exists.`, ErrorCode.NOT_FOUND, {
|
|
41
|
+
argumentName: 'id',
|
|
42
|
+
argumentValue: id,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return version;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Throws `CONFLICT` if an expected version is given and the entry has a different version at the current commit of
|
|
49
|
+
* the request. This lets callers detect that an entry was changed by someone else since they read it, e.g. while a
|
|
50
|
+
* user was editing the entry, instead of overwriting these changes.
|
|
51
|
+
*/
|
|
52
|
+
export async function assertExpectedVersion(context, id, expectedVersion) {
|
|
53
|
+
if (expectedVersion === undefined || expectedVersion === null) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const currentVersion = await getEntryVersion(context, id);
|
|
57
|
+
if (currentVersion !== expectedVersion) {
|
|
58
|
+
throw createError(`Entry with ID "${id}" has been changed: expected version "${expectedVersion}" but current version is ` +
|
|
59
|
+
`"${currentVersion}".`, ErrorCode.CONFLICT, {
|
|
60
|
+
argumentName: 'expectedVersion',
|
|
61
|
+
argumentValue: expectedVersion,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
32
65
|
//# sourceMappingURL=persistence.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["../../../src/persistence/persistence.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["../../../src/persistence/persistence.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -9,13 +9,16 @@
|
|
|
9
9
|
"gitops",
|
|
10
10
|
"api"
|
|
11
11
|
],
|
|
12
|
-
"version": "2.0.0-beta.
|
|
12
|
+
"version": "2.0.0-beta.5",
|
|
13
13
|
"license": "ISC",
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
16
|
"url": "git+https://github.com/commitspark/graphql-api.git"
|
|
17
17
|
},
|
|
18
18
|
"private": false,
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=22.18.0"
|
|
21
|
+
},
|
|
19
22
|
"files": [
|
|
20
23
|
"dist/**",
|
|
21
24
|
"!dist/**/*.tsbuildinfo",
|
|
@@ -53,13 +56,13 @@
|
|
|
53
56
|
},
|
|
54
57
|
"dependencies": {
|
|
55
58
|
"@apollo/server": "^5.2.0",
|
|
56
|
-
"@commitspark/git-adapter": "^2.0.0-beta.
|
|
59
|
+
"@commitspark/git-adapter": "^2.0.0-beta.4",
|
|
57
60
|
"@commitspark/search-adapter": "^1.0.0-beta.1",
|
|
58
|
-
"@graphql-tools/schema": "^
|
|
59
|
-
"@graphql-tools/utils": "^
|
|
61
|
+
"@graphql-tools/schema": "^11.0.8",
|
|
62
|
+
"@graphql-tools/utils": "^13.2.1"
|
|
60
63
|
},
|
|
61
64
|
"peerDependencies": {
|
|
62
|
-
"graphql": "^
|
|
65
|
+
"graphql": "^16.11.0"
|
|
63
66
|
},
|
|
64
67
|
"devDependencies": {
|
|
65
68
|
"@arethetypeswrong/cli": "^0.18.5",
|
package/src/graphql/errors.ts
CHANGED
|
@@ -2,13 +2,14 @@ import { GraphQLError } from 'graphql'
|
|
|
2
2
|
import { ErrorCode as GitAdapterErrorCode } from '@commitspark/git-adapter'
|
|
3
3
|
import { ErrorCode as SearchAdapterErrorCode } from '@commitspark/search-adapter'
|
|
4
4
|
|
|
5
|
-
export
|
|
5
|
+
export enum ErrorCode {
|
|
6
6
|
BAD_USER_INPUT = 'BAD_USER_INPUT',
|
|
7
7
|
NOT_FOUND = 'NOT_FOUND',
|
|
8
8
|
BAD_REPOSITORY_DATA = 'BAD_REPOSITORY_DATA',
|
|
9
9
|
BAD_SCHEMA = 'BAD_SCHEMA',
|
|
10
10
|
INTERNAL_ERROR = 'INTERNAL_ERROR',
|
|
11
11
|
IN_USE = 'IN_USE',
|
|
12
|
+
CONFLICT = 'CONFLICT',
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export interface ErrorMetadata {
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
isListType,
|
|
11
11
|
isNonNullType,
|
|
12
12
|
isObjectType,
|
|
13
|
-
isScalarType,
|
|
14
13
|
isUnionType,
|
|
15
14
|
} from 'graphql'
|
|
16
15
|
import { SchemaAnalyzerResult } from './schema-analyzer.ts'
|
|
@@ -72,16 +71,13 @@ export function generateObjectInputTypeStrings(
|
|
|
72
71
|
return objectTypes.map((objectType): string => {
|
|
73
72
|
const name = objectType.name
|
|
74
73
|
const inputTypeName = `${name}Input`
|
|
74
|
+
const isEntryType = hasDirective(objectType, ENTRY_DIRECTIVE_NAME)
|
|
75
75
|
let inputType = `input ${inputTypeName} {\n`
|
|
76
76
|
let inputTypeFieldStrings = ''
|
|
77
77
|
for (const fieldsKey in objectType.getFields()) {
|
|
78
78
|
const field = objectType.getFields()[fieldsKey]
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
isScalarType(field.type.ofType) &&
|
|
82
|
-
field.type.ofType.name === 'ID') ||
|
|
83
|
-
(isScalarType(field.type) && field.type.name === 'ID')
|
|
84
|
-
) {
|
|
79
|
+
// the ID of an entry is passed as mutation argument instead of as part of the entry data
|
|
80
|
+
if (isEntryType && field.name === 'id') {
|
|
85
81
|
continue
|
|
86
82
|
}
|
|
87
83
|
const inputTypeString = generateFieldInputTypeString(field.type)
|
|
@@ -103,12 +99,12 @@ export function generateObjectInputTypeStrings(
|
|
|
103
99
|
export function generateUnionInputTypeStrings(
|
|
104
100
|
unionTypes: GraphQLUnionType[],
|
|
105
101
|
): string[] {
|
|
106
|
-
|
|
102
|
+
return unionTypes.map((unionType): string => {
|
|
107
103
|
if (buildsOnTypeWithEntryDirective(unionType)) {
|
|
108
104
|
return ''
|
|
109
105
|
}
|
|
110
106
|
|
|
111
|
-
//
|
|
107
|
+
// `@oneOf` is a directive specified by GraphQL and built into graphql-js since 16.9
|
|
112
108
|
let unionInputType = `input ${unionType.name}Input @oneOf {\n`
|
|
113
109
|
for (const innerType of unionType.getTypes()) {
|
|
114
110
|
unionInputType += ` ${innerType.name}: ${innerType.name}Input\n`
|
|
@@ -116,10 +112,4 @@ export function generateUnionInputTypeStrings(
|
|
|
116
112
|
unionInputType += '}\n'
|
|
117
113
|
return unionInputType
|
|
118
114
|
})
|
|
119
|
-
|
|
120
|
-
if (typeStrings.join('').length > 0) {
|
|
121
|
-
typeStrings.push('directive @oneOf on INPUT_OBJECT\n')
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return typeStrings
|
|
125
115
|
}
|
|
@@ -8,12 +8,16 @@ import { mutationDeleteResolver } from './resolvers/query-mutation-resolvers/mut
|
|
|
8
8
|
import { queryTypeByIdResolver } from './resolvers/query-mutation-resolvers/query-type-by-id-resolver.ts'
|
|
9
9
|
import { querySearchResolver } from './resolvers/query-mutation-resolvers/query-search-resolver.ts'
|
|
10
10
|
import { referencedByResolver } from './resolvers/field-resolvers/referenced-by-resolver.ts'
|
|
11
|
+
import { versionResolver } from './resolvers/field-resolvers/version-resolver.ts'
|
|
11
12
|
import {
|
|
12
13
|
QueryMutationResolver,
|
|
13
14
|
ReferencedByResolver,
|
|
14
15
|
SearchQueryResolver,
|
|
16
|
+
VersionResolver,
|
|
15
17
|
} from './resolvers/types.ts'
|
|
16
18
|
|
|
19
|
+
const commitInputTypeName = '_CommitInput'
|
|
20
|
+
|
|
17
21
|
export function generateQueriesAndMutations(
|
|
18
22
|
entryDirectiveTypes: GraphQLObjectType[],
|
|
19
23
|
): GeneratedSchema[] {
|
|
@@ -42,8 +46,20 @@ export function generateQueriesAndMutations(
|
|
|
42
46
|
) => queryByIdResolver(source, args, { ...context, type: objectType }, info)
|
|
43
47
|
|
|
44
48
|
const inputTypeName = `${typeName}Input`
|
|
49
|
+
// mutations are printed with one argument per line, as indented within the generated root type
|
|
50
|
+
const commitArgument = (operation: string): string =>
|
|
51
|
+
' """\n' +
|
|
52
|
+
` Details of the commit. If no message is given, the message is '${operation} ${typeName} "<id>"'.\n` +
|
|
53
|
+
' """\n' +
|
|
54
|
+
` commit: ${commitInputTypeName}\n`
|
|
55
|
+
const expectedVersionArgument =
|
|
56
|
+
' """\n' +
|
|
57
|
+
' Version of the entry (field `_version`) that the change is based on. If given and the entry has a different\n' +
|
|
58
|
+
' version, the mutation fails with error code CONFLICT instead of overwriting changes made since.\n' +
|
|
59
|
+
' """\n' +
|
|
60
|
+
' expectedVersion: String\n'
|
|
45
61
|
const createMutationName = `create${typeName}`
|
|
46
|
-
const createMutationString = `${createMutationName}(id: ID
|
|
62
|
+
const createMutationString = `${createMutationName}(\n id: ID!\n data: ${inputTypeName}!\n${commitArgument('Create')} ): ${typeName}`
|
|
47
63
|
const createMutationResolver: QueryMutationResolver<EntryData> = (
|
|
48
64
|
source,
|
|
49
65
|
args,
|
|
@@ -58,7 +74,7 @@ export function generateQueriesAndMutations(
|
|
|
58
74
|
)
|
|
59
75
|
|
|
60
76
|
const updateMutationName = `update${typeName}`
|
|
61
|
-
const updateMutationString = `${updateMutationName}(id: ID
|
|
77
|
+
const updateMutationString = `${updateMutationName}(\n id: ID!\n data: ${inputTypeName}!\n${expectedVersionArgument}${commitArgument('Update')} ): ${typeName}`
|
|
62
78
|
const updateMutationResolver: QueryMutationResolver<EntryData> = (
|
|
63
79
|
source,
|
|
64
80
|
args,
|
|
@@ -73,7 +89,7 @@ export function generateQueriesAndMutations(
|
|
|
73
89
|
)
|
|
74
90
|
|
|
75
91
|
const deleteMutationName = `delete${typeName}`
|
|
76
|
-
const deleteMutationString = `${deleteMutationName}(id: ID
|
|
92
|
+
const deleteMutationString = `${deleteMutationName}(\n id: ID!\n${expectedVersionArgument}${commitArgument('Delete')} ): ID`
|
|
77
93
|
const deleteMutationResolver: QueryMutationResolver<string> = (
|
|
78
94
|
source,
|
|
79
95
|
args,
|
|
@@ -117,6 +133,24 @@ export function generateQueriesAndMutations(
|
|
|
117
133
|
})
|
|
118
134
|
}
|
|
119
135
|
|
|
136
|
+
export function generateCommitInputType(): string {
|
|
137
|
+
return `"""
|
|
138
|
+
Details of the commit created by a mutation.
|
|
139
|
+
"""
|
|
140
|
+
input ${commitInputTypeName} {
|
|
141
|
+
"""
|
|
142
|
+
Commit message summarizing the change in a single line. Leading and trailing whitespace is removed, and messages
|
|
143
|
+
containing line breaks are rejected with error code BAD_USER_INPUT. If omitted or empty, a message is generated.
|
|
144
|
+
"""
|
|
145
|
+
message: String
|
|
146
|
+
"""
|
|
147
|
+
Optional details of the change, which may span multiple lines. Leading and trailing whitespace is removed.
|
|
148
|
+
"""
|
|
149
|
+
body: String
|
|
150
|
+
}
|
|
151
|
+
`
|
|
152
|
+
}
|
|
153
|
+
|
|
120
154
|
export function generateTypeNameQuery(): GeneratedQuery {
|
|
121
155
|
const entryTypeQueryName = '_typeName'
|
|
122
156
|
const entryTypeQueryString = `${entryTypeQueryName}(id: ID!): String!`
|
|
@@ -147,27 +181,34 @@ export function generateSearchQuery(): GeneratedSearchQuery {
|
|
|
147
181
|
}
|
|
148
182
|
}
|
|
149
183
|
|
|
150
|
-
export function
|
|
184
|
+
export function generateEntryFields(
|
|
151
185
|
entryDirectiveTypes: GraphQLObjectType[],
|
|
152
|
-
):
|
|
186
|
+
): GeneratedEntryFields[] {
|
|
153
187
|
const referencedByFieldName = '_referencedBy'
|
|
188
|
+
const versionFieldName = '_version'
|
|
154
189
|
|
|
155
190
|
return entryDirectiveTypes.map((objectType) => ({
|
|
156
191
|
typeName: objectType.name,
|
|
157
|
-
name: referencedByFieldName,
|
|
158
192
|
typeExtensionString: `extend type ${objectType.name} {
|
|
159
193
|
${referencedByFieldName}: [ID!]!
|
|
194
|
+
"""
|
|
195
|
+
Version of the entry, which changes whenever the stored entry changes. Pass it as \`expectedVersion\` to update
|
|
196
|
+
and delete mutations to prevent overwriting changes made since the entry was read.
|
|
197
|
+
"""
|
|
198
|
+
${versionFieldName}: String!
|
|
160
199
|
}
|
|
161
200
|
`,
|
|
162
|
-
|
|
201
|
+
resolvers: {
|
|
202
|
+
[referencedByFieldName]: referencedByResolver,
|
|
203
|
+
[versionFieldName]: versionResolver,
|
|
204
|
+
},
|
|
163
205
|
}))
|
|
164
206
|
}
|
|
165
207
|
|
|
166
|
-
interface
|
|
208
|
+
interface GeneratedEntryFields {
|
|
167
209
|
typeName: string
|
|
168
|
-
name: string
|
|
169
210
|
typeExtensionString: string
|
|
170
|
-
|
|
211
|
+
resolvers: Record<string, ReferencedByResolver | VersionResolver>
|
|
171
212
|
}
|
|
172
213
|
|
|
173
214
|
export interface GeneratedSearchQuery {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { CommitDraft } from '@commitspark/git-adapter'
|
|
2
|
+
import { createError, ErrorCode } from '../errors.ts'
|
|
3
|
+
import { CommitInput } from './types.ts'
|
|
4
|
+
|
|
5
|
+
type MutationOperation = 'Create' | 'Update' | 'Delete'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Determines message and body of the commit of a mutation from mutation argument `commit`. The message is trimmed and
|
|
9
|
+
* must be a single line; an omitted or empty message is replaced by a message generated from the mutation. The body
|
|
10
|
+
* is trimmed and omitted if empty.
|
|
11
|
+
*/
|
|
12
|
+
export function getCommitMessage(
|
|
13
|
+
commit: CommitInput | null | undefined,
|
|
14
|
+
operation: MutationOperation,
|
|
15
|
+
typeName: string,
|
|
16
|
+
id: string,
|
|
17
|
+
): Pick<CommitDraft, 'message' | 'body'> {
|
|
18
|
+
const message = commit?.message?.trim() ?? ''
|
|
19
|
+
if (/[\r\n]/.test(message)) {
|
|
20
|
+
throw createError(
|
|
21
|
+
'Commit message must be a single line; use field "body" for details.',
|
|
22
|
+
ErrorCode.BAD_USER_INPUT,
|
|
23
|
+
{ argumentName: 'commit.message', argumentValue: commit?.message },
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
const body = commit?.body?.trim() ?? ''
|
|
27
|
+
|
|
28
|
+
const commitMessage =
|
|
29
|
+
message !== '' ? message : `${operation} ${typeName} "${id}"`
|
|
30
|
+
if (body === '') {
|
|
31
|
+
return { message: commitMessage }
|
|
32
|
+
}
|
|
33
|
+
return { message: commitMessage, body: body }
|
|
34
|
+
}
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
} from '@commitspark/git-adapter'
|
|
9
9
|
import { QueryMutationResolver } from '../types.ts'
|
|
10
10
|
import { createError, ErrorCode } from '../../errors.ts'
|
|
11
|
+
import { getCommitMessage } from '../commit-message.ts'
|
|
12
|
+
import { commitWithRetry } from '../../../persistence/commit-with-retry.ts'
|
|
11
13
|
|
|
12
14
|
export const mutationCreateResolver: QueryMutationResolver<EntryData> = async (
|
|
13
15
|
source,
|
|
@@ -23,6 +25,8 @@ export const mutationCreateResolver: QueryMutationResolver<EntryData> = async (
|
|
|
23
25
|
{},
|
|
24
26
|
)
|
|
25
27
|
}
|
|
28
|
+
// narrowed type, as narrowing does not apply within the callback below
|
|
29
|
+
const entryType = context.type
|
|
26
30
|
|
|
27
31
|
try {
|
|
28
32
|
validateEntryId(args.id)
|
|
@@ -36,40 +40,47 @@ export const mutationCreateResolver: QueryMutationResolver<EntryData> = async (
|
|
|
36
40
|
},
|
|
37
41
|
)
|
|
38
42
|
}
|
|
43
|
+
const commitMessage = getCommitMessage(
|
|
44
|
+
args.commit,
|
|
45
|
+
'Create',
|
|
46
|
+
context.type.name,
|
|
47
|
+
args.id,
|
|
48
|
+
)
|
|
39
49
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
await commitWithRetry(context, async () => {
|
|
51
|
+
let existingEntry
|
|
52
|
+
try {
|
|
53
|
+
existingEntry = await findById(context, args.id)
|
|
54
|
+
} catch {}
|
|
55
|
+
if (existingEntry) {
|
|
56
|
+
throw createError(
|
|
57
|
+
`An entry with id "${args.id}" already exists.`,
|
|
58
|
+
ErrorCode.BAD_USER_INPUT,
|
|
59
|
+
{
|
|
60
|
+
argumentName: 'id',
|
|
61
|
+
argumentValue: args.id,
|
|
62
|
+
},
|
|
63
|
+
)
|
|
64
|
+
}
|
|
54
65
|
|
|
55
|
-
|
|
66
|
+
await validateEntryReferences(entryType, context, args.data ?? null)
|
|
56
67
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
const newEntryDraft: EntryDraft = {
|
|
69
|
+
id: args.id,
|
|
70
|
+
metadata: {
|
|
71
|
+
type: context.type.name,
|
|
72
|
+
},
|
|
73
|
+
data: args.data ?? null,
|
|
74
|
+
deletion: false,
|
|
75
|
+
}
|
|
65
76
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
return {
|
|
78
|
+
ref: context.branch,
|
|
79
|
+
parentSha: context.getCurrentHash(),
|
|
80
|
+
entries: [newEntryDraft],
|
|
81
|
+
...commitMessage,
|
|
82
|
+
}
|
|
71
83
|
})
|
|
72
|
-
context.setCurrentHash(commit.commitHash)
|
|
73
84
|
|
|
74
85
|
const newEntry = await findByTypeId(context, context.type.name, args.id)
|
|
75
86
|
return { ...newEntry.data, id: newEntry.id }
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
assertExpectedVersion,
|
|
3
|
+
findByTypeId,
|
|
4
|
+
} from '../../../persistence/persistence.ts'
|
|
2
5
|
import { Entry } from '@commitspark/git-adapter'
|
|
3
6
|
import { getReferencingEntryIds } from '../../schema-utils/entry-reference-util.ts'
|
|
4
7
|
import { QueryMutationResolver } from '../types.ts'
|
|
5
8
|
import { createError, ErrorCode } from '../../errors.ts'
|
|
9
|
+
import { getCommitMessage } from '../commit-message.ts'
|
|
10
|
+
import { commitWithRetry } from '../../../persistence/commit-with-retry.ts'
|
|
6
11
|
|
|
7
12
|
export const mutationDeleteResolver: QueryMutationResolver<string> = async (
|
|
8
13
|
source,
|
|
@@ -10,38 +15,47 @@ export const mutationDeleteResolver: QueryMutationResolver<string> = async (
|
|
|
10
15
|
context,
|
|
11
16
|
info,
|
|
12
17
|
) => {
|
|
13
|
-
const
|
|
18
|
+
const commitMessage = getCommitMessage(
|
|
19
|
+
args.commit,
|
|
20
|
+
'Delete',
|
|
21
|
+
context.type.name,
|
|
22
|
+
args.id,
|
|
23
|
+
)
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
await
|
|
18
|
-
).filter((referencingId) => referencingId !== args.id)
|
|
19
|
-
if (otherReferencingIds.length > 0) {
|
|
20
|
-
const otherIds = otherReferencingIds
|
|
21
|
-
.map((referenceId) => `"${referenceId}"`)
|
|
22
|
-
.join(', ')
|
|
23
|
-
throw createError(
|
|
24
|
-
`Entry with ID "${args.id}" is still referenced by entries [${otherIds}].`,
|
|
25
|
-
ErrorCode.IN_USE,
|
|
26
|
-
{
|
|
27
|
-
argumentName: 'id',
|
|
28
|
-
argumentValue: args.id,
|
|
29
|
-
},
|
|
30
|
-
)
|
|
31
|
-
}
|
|
25
|
+
await commitWithRetry(context, async () => {
|
|
26
|
+
const entry: Entry = await findByTypeId(context, context.type.name, args.id)
|
|
27
|
+
await assertExpectedVersion(context, args.id, args.expectedVersion)
|
|
32
28
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
29
|
+
// references held by the entry itself are deleted together with the entry
|
|
30
|
+
const otherReferencingIds = (
|
|
31
|
+
await getReferencingEntryIds(context, info.schema, args.id)
|
|
32
|
+
).filter((referencingId) => referencingId !== args.id)
|
|
33
|
+
if (otherReferencingIds.length > 0) {
|
|
34
|
+
const otherIds = otherReferencingIds
|
|
35
|
+
.map((referenceId) => `"${referenceId}"`)
|
|
36
|
+
.join(', ')
|
|
37
|
+
throw createError(
|
|
38
|
+
`Entry with ID "${args.id}" is still referenced by entries [${otherIds}].`,
|
|
39
|
+
ErrorCode.IN_USE,
|
|
40
|
+
{
|
|
41
|
+
argumentName: 'id',
|
|
42
|
+
argumentValue: args.id,
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
ref: context.branch,
|
|
49
|
+
parentSha: context.getCurrentHash(),
|
|
50
|
+
entries: [
|
|
51
|
+
{
|
|
52
|
+
...entry,
|
|
53
|
+
deletion: true,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
...commitMessage,
|
|
57
|
+
}
|
|
43
58
|
})
|
|
44
|
-
context.setCurrentHash(commit.commitHash)
|
|
45
59
|
|
|
46
60
|
return args.id
|
|
47
61
|
}
|