diff --git a/.gitattributes b/.gitattributes index 0b60a7d..8575f35 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,3 +5,10 @@ docs/* linguist-generated=true docs/**/* linguist-generated=true generated/* linguist-generated=true generated/**/* linguist-generated=true + +# Vendored from fragment-dev/graphql-queries -- see tests/template-schema/README.md. +# Marking these as generated collapses them by default in diffs and pull requests +# and excludes them from language statistics. +tests/template-schema/schema.json linguist-generated=true +tests/template-schema/queries.graphql linguist-generated=true +tests/template-schema/queries.runtime-args.graphql linguist-generated=true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c3618a3..877a77d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -90,5 +90,15 @@ jobs: run: | /home/linuxbrew/.linuxbrew/bin/fragment gen-graphql --path tests/fixtures/test-schema.json --output tests/fixtures/test-schema-queries.graphql yarn fragment-node-client-codegen -i tests/fixtures/test-schema-queries.graphql -o tests/fixtures/generated-test-client.ts - git diff --exit-code tests/fixtures/test-schema-queries.graphql tests/fixtures/generated-test-client.ts + yarn fragment-node-client-codegen -i tests/fixtures/edge-case-queries.graphql -o tests/fixtures/generated-edge-case-client.ts + git diff --exit-code tests/fixtures/test-schema-queries.graphql tests/fixtures/generated-test-client.ts tests/fixtures/generated-edge-case-client.ts + exit $? + + # tests/template-schema/*.graphql are vendored from fragment-dev/graphql-queries, + # so only the clients generated from them are regenerated here. + - name: Verify template schema generated clients are up-to-date + run: | + yarn fragment-node-client-codegen -i tests/template-schema/queries.graphql -o tests/fixtures/generated-template-client.ts + yarn fragment-node-client-codegen -i tests/template-schema/queries.runtime-args.graphql -o tests/fixtures/generated-template-runtime-args-client.ts + git diff --exit-code tests/fixtures/generated-template-client.ts tests/fixtures/generated-template-runtime-args-client.ts exit $? diff --git a/.github/workflows/updateSDKQueries.yml b/.github/workflows/updateSDKQueries.yml index 934874d..a0f9f77 100644 --- a/.github/workflows/updateSDKQueries.yml +++ b/.github/workflows/updateSDKQueries.yml @@ -53,6 +53,7 @@ jobs: working-directory: ./node-client run: | cp ../graphql-queries/queries.graphql src/queries/queries.graphql + cp ../graphql-queries/template-schema/schema.json ../graphql-queries/template-schema/*.graphql tests/template-schema/ - name: Build working-directory: ./node-client diff --git a/dist/cjs/src/bin.js b/dist/cjs/src/bin.js index 570d1dc..bb25601 100644 --- a/dist/cjs/src/bin.js +++ b/dist/cjs/src/bin.js @@ -23,6 +23,15 @@ var __importStar = (this && this.__importStar) || function (mod) { __setModuleDefault(result, mod); return result; }; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; @@ -31,6 +40,7 @@ const fs_1 = require("fs"); const yargs_1 = __importDefault(require("yargs")); const path = __importStar(require("path")); const cli_1 = require("@graphql-codegen/cli"); +const typedBatchEntries_js_1 = require("./typedBatchEntries.js"); const argv = (0, yargs_1.default)(process.argv.slice(2)) .options({ outputFilename: { @@ -55,44 +65,77 @@ const allowedInputExtensions = [".graphql", ".gql", ".ts"]; if (!allowedInputExtensions.includes(path.extname(argv.input))) { throw new Error(`Input filename must have a .graphql, .gql, or .ts extension. You provided ${path.extname(argv.input) || ""}`); } -(0, cli_1.generate)({ - schema: "https://api.us-west-2.fragment.dev/schema.graphql", - documents: argv.input, - config: { - scalars: { - AlphaNumericString: "string", - Date: "string", - DateTime: "string", - Int64: "string", - Int96: "string", - JSON: "Record", - JSONObject: "Record", - LastMoment: "string", - ParameterizedString: "string", - Period: "string", - SafeString: "string", - UTCOffset: "string", +/** + * Runs codegen for one input and writes the client, with the typed batch entry + * payloads appended. + * + * The payloads are derived from the same operations the plugins run over, so a + * document transform hands them (and the schema) to `sources` instead of loading + * either a second time. The transform leaves the documents themselves alone. + */ +const generateClient = (_a) => __awaiter(void 0, [_a], void 0, function* ({ input, outputFilename, }) { + const sources = []; + const [fileOutput] = yield (0, cli_1.generate)({ + schema: "https://api.us-west-2.fragment.dev/schema.graphql", + documents: input, + config: { + scalars: { + AlphaNumericString: "string", + Date: "string", + DateTime: "string", + Int64: "string", + Int96: "string", + JSON: "Record", + JSONObject: "Record", + LastMoment: "string", + ParameterizedString: "string", + Period: "string", + SafeString: "string", + UTCOffset: "string", + }, + gqlImport: "@fragment-dev/node-client#gql", }, - gqlImport: "@fragment-dev/node-client#gql", - }, - generates: { - [path.join(process.cwd(), argv.outputFilename)]: { - plugins: [ - "typescript", - "typescript-operations", - "typescript-graphql-request", - { - add: { - content: "/* This file is auto-generated by @fragment-dev/node-client. Don't edit it directly. */", + generates: { + [path.join(process.cwd(), outputFilename)]: { + documentTransforms: [ + { + transform: ({ documents, schema }) => { + sources.push({ + documents: documents + .map((document) => document.document) + .filter((document) => !!document), + schema, + }); + return documents; + }, }, - }, - ], + ], + plugins: [ + "typescript", + "typescript-operations", + "typescript-graphql-request", + { + add: { + content: "/* This file is auto-generated by @fragment-dev/node-client. Don't edit it directly. */", + }, + }, + ], + }, }, - }, -}, false) - .then(([fileOutput]) => { + }, false); const output = fileOutput.content.replace(/import .* from 'graphql-request'/g, "import { GraphQLClient, RequestOptions } from '@fragment-dev/node-client'"); - (0, fs_1.writeFileSync)(fileOutput.filename, output, "utf-8"); + const [source] = sources; + if (!source) { + console.warn("[@fragment-dev/node-client] Could not read the input operations, so no typed batch Ledger Entry payloads were generated."); + } + const typedBatchEntries = source ? (0, typedBatchEntries_js_1.generateTypedEntryPayloads)(source) : ""; + (0, fs_1.writeFileSync)(fileOutput.filename, typedBatchEntries ? `${output}\n${typedBatchEntries}` : output, "utf-8"); +}); +generateClient({ + input: argv.input, + outputFilename: argv.outputFilename, +}) + .then(() => { process.exit(0); }) .catch((error) => { diff --git a/dist/cjs/src/client.js b/dist/cjs/src/client.js index bed420d..42bb45e 100644 --- a/dist/cjs/src/client.js +++ b/dist/cjs/src/client.js @@ -57,6 +57,7 @@ const createRequestWrapper = (tokenCache, params, retryConfig) => (request, oper ["X-Fragment-Client"]: `node-client@${version_js_1.version}`, }; return (0, async_retry_1.default)((bail) => __awaiter(void 0, void 0, void 0, function* () { + var _a; if (operationType === "mutation") { const data = yield request(requestHeaders); const dataKeys = Object.keys(data); @@ -82,6 +83,25 @@ const createRequestWrapper = (tokenCache, params, retryConfig) => (request, oper else { throw err; } + break; + } + case "AddLedgerEntriesError": { + // The errors for each Ledger Entry responsible for the failure are + // what tell a caller which entries to fix, so they are surfaced + // rather than collapsed into the top-level message. + const err = new errors_js_1.AddLedgerEntriesError({ + code, + cause: data[graphQlOperationName], + message, + errors: ((_a = data[graphQlOperationName].errors) !== null && _a !== void 0 ? _a : []), + }); + if (!retryable) { + bail(err); + } + else { + throw err; + } + break; } case "BadRequestError": { const err = new errors_js_1.BadRequestError({ @@ -95,6 +115,7 @@ const createRequestWrapper = (tokenCache, params, retryConfig) => (request, oper else { throw err; } + break; } default: if (typeName.endsWith("Error")) { diff --git a/dist/cjs/src/errors.js b/dist/cjs/src/errors.js index a3d80fa..a063554 100644 --- a/dist/cjs/src/errors.js +++ b/dist/cjs/src/errors.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.BadRequestError = exports.InternalError = exports.FragmentError = void 0; +exports.AddLedgerEntriesError = exports.BadRequestError = exports.InternalError = exports.FragmentError = void 0; const graphql_request_1 = require("graphql-request"); class FragmentError extends Error { constructor({ cause, code, message: messageParam }) { @@ -36,3 +36,22 @@ class BadRequestError extends FragmentError { } } exports.BadRequestError = BadRequestError; +/** + * Thrown when one or more Ledger Entries in an `addLedgerEntries` batch could + * not be added. + * + * `addLedgerEntries` adds a batch of Ledger Entries in one synchronous and + * atomic transaction, so either every entry was added or none were. Nothing was + * written when this is thrown. + */ +class AddLedgerEntriesError extends FragmentError { + constructor({ cause, message, code, errors }) { + super({ + cause, + message, + code: code !== null && code !== void 0 ? code : "ledger_entry_batch_operation_failed", + }); + this.errors = errors !== null && errors !== void 0 ? errors : []; + } +} +exports.AddLedgerEntriesError = AddLedgerEntriesError; diff --git a/dist/cjs/src/typedBatchEntries.js b/dist/cjs/src/typedBatchEntries.js new file mode 100644 index 0000000..a16cac4 --- /dev/null +++ b/dist/cjs/src/typedBatchEntries.js @@ -0,0 +1,693 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.generateTypedEntryPayloads = exports.renderTypedEntryPayloads = exports.collectScalarNames = exports.nameTypedEntryPayloads = exports.deriveTypedEntryPayloads = void 0; +/** + * Derives typed batch Ledger Entry payloads from the per-entry-type + * `addLedgerEntry` operations the Fragment CLI generates for a Schema. + * + * `addLedgerEntries` takes one list of one input type (`AddLedgerEntryInput`), + * and `LedgerEntryInput.parameters` is an opaque `JSON` scalar, so GraphQL + * cannot express the parameter types of an individual entry in a batch. The + * single-entry operations can: they carry the entry type as a string literal and + * bind everything the caller may set to a typed operation variable. This module + * reads those operations and emits a typed payload per `(type, typeVersion)` + * pair, each of which builds an `AddLedgerEntryInput` for `addLedgerEntries`. + * + * What a payload exposes is fixed by `LedgerEntryInput`, not derived from the + * operation: every payload carries the same common fields (§2.3a), so the CLI + * changing which fields it binds never moves a payload's surface. + * + * Two things follow. A value the operation fixes to a literal is encoded in the + * Schema, so the API derives it and the payload neither exposes nor re-posts it. + * And an entry type whose Ledger Lines the caller supplies gets no payload at + * all, since `lines` is not a common field — post those with a raw + * `AddLedgerEntryInput`, which `addLedgerEntries` accepts in the same batch. + */ +const graphql_1 = require("graphql"); +const defaultWarn = (message) => { + // eslint-disable-next-line no-console + console.warn(`[@fragment-dev/node-client] ${message}`); +}; +/** + * `type` and `typeVersion` identify the payload and `parameters` is typed per + * payload, so none of the three is the caller's to set. + */ +const DERIVED_ENTRY_FIELDS = ["type", "typeVersion", "parameters"]; +/** + * The `LedgerEntryInput` fields every payload exposes, whatever its operation + * binds, with their declared types (spec 2.3a). + * + * Deliberately not derived from the operation. An operation binds only the entry + * fields the CLI version that generated it chose to expose, and that choice has + * already changed between versions -- so deriving the set would invent a + * restriction the API does not have, and would move a payload's surface whenever + * the CLI changed. A payload travels as an `AddLedgerEntryInput`, so what the + * operation binds places no limit on what the payload may carry. + * + * `ik` and `ledgerIk` are always present already. `lines` is excluded: it cannot + * be combined with an entry that has a `type`. + */ +const COMMON_ENTRY_FIELDS = [ + { name: "posted", type: "DateTime" }, + { name: "description", type: "String" }, + { name: "tags", type: "[LedgerEntryTagInput!]" }, + { name: "groups", type: "[LedgerEntryGroupInput!]" }, + { name: "conditions", type: "[LedgerEntryConditionInput!]" }, +]; +const findObjectField = (object, name) => object.fields.find((field) => field.name.value === name); +const findVariableDefinition = (operation, variableName) => { + var _a; + return (_a = operation.variableDefinitions) === null || _a === void 0 ? void 0 : _a.find((candidate) => candidate.variable.name.value === variableName); +}; +/** + * Returns the `addLedgerEntry` field of a recognised typed entry operation, or + * undefined if the operation is not one. Every failing condition is a silent + * skip, never an error: this is what excludes the SDK's own `addLedgerEntry` and + * `addLedgerEntryRuntime` operations, whose `type` is a variable. + */ +const getTypedEntryField = (operation) => { + var _a; + if (operation.operation !== "mutation") { + return undefined; + } + if (!operation.name) { + return undefined; + } + const selections = operation.selectionSet.selections; + if (selections.length !== 1) { + return undefined; + } + const [selection] = selections; + if (selection.kind !== graphql_1.Kind.FIELD || + selection.name.value !== "addLedgerEntry") { + return undefined; + } + const entryArgument = (_a = selection.arguments) === null || _a === void 0 ? void 0 : _a.find((argument) => argument.name.value === "entry"); + if (!entryArgument || entryArgument.value.kind !== graphql_1.Kind.OBJECT) { + return undefined; + } + const typeField = findObjectField(entryArgument.value, "type"); + if (!typeField || typeField.value.kind !== graphql_1.Kind.STRING) { + return undefined; + } + // The entry type comes back narrowed, so no caller has to re-check it. + return { + field: selection, + entry: entryArgument.value, + entryType: typeField.value.value, + /** + * An entry type whose Lines the Schema does not fix takes them from the + * caller, and a payload cannot carry them: `lines` is not a common field. + * Generating one anyway would hand the caller something that always posts + * an entry with no Lines, so no payload is generated at all. + */ + hasLines: !!findObjectField(entryArgument.value, "lines"), + }; +}; +/** + * The `typeVersion` the operation pins, or undefined when it pins one + * dynamically. `typeVersion` defaults to 1 when it is not set, so an operation + * that pins no version is normalised to 1 — for the payload's identity, its name + * and its wire payload alike. + * + * A version bound to a variable is different: the caller of the single-entry + * operation chooses it, and a payload — whose name states one version and whose + * builder posts it — cannot. Those get no payload rather than a silent 1. + */ +const getTypeVersion = (entry) => { + const field = findObjectField(entry, "typeVersion"); + if (!field || field.value.kind === graphql_1.Kind.NULL) { + return 1; + } + if (field.value.kind === graphql_1.Kind.INT) { + return Number.parseInt(field.value.value, 10); + } + return undefined; +}; +/** + * The entry fields the operation lets the caller set, in source order. A field + * bound to anything other than a variable is fixed by the operation, so it is + * not exposed. + */ +const getFields = (entry, operation, warn) => { + const fields = []; + entry.fields.forEach((entryField) => { + const wireName = entryField.name.value; + if (DERIVED_ENTRY_FIELDS.includes(wireName)) { + return; + } + const bind = ({ name, wireKey, variable, }) => { + var _a; + const variableName = variable.name.value; + const definition = findVariableDefinition(operation, variableName); + if (!definition) { + warn(`Operation \`${(_a = operation.name) === null || _a === void 0 ? void 0 : _a.value}\` binds \`${wireName}\` to undeclared variable \`$${variableName}\`. Skipping the field.`); + return; + } + fields.push({ + name, + wireName, + wireKey, + type: definition.type, + required: definition.type.kind === graphql_1.Kind.NON_NULL_TYPE, + }); + }; + if (entryField.value.kind === graphql_1.Kind.VARIABLE) { + bind({ name: wireName, variable: entryField.value }); + return; + } + // `ledger: { ik: $ledgerIk }` is the shape the CLI generates. The caller + // supplies the Idempotency Key, so the payload exposes it as `ledgerIk`. + if (entryField.value.kind === graphql_1.Kind.OBJECT) { + entryField.value.fields.forEach((matchField) => { + const key = matchField.name.value; + if (matchField.value.kind === graphql_1.Kind.VARIABLE) { + bind({ + name: `${wireName}${key.charAt(0).toUpperCase()}${key.slice(1)}`, + wireKey: key, + variable: matchField.value, + }); + return; + } + // Fixed by the operation, so encoded in the Schema: the API derives it. + }); + return; + } + // Fixed by the operation, so encoded in the Schema: the API derives it. + }); + // Spec 2.3a: every payload carries these, whether or not its operation binds + // them. Appended rather than interleaved, so the operation's own fields keep + // their source order. + COMMON_ENTRY_FIELDS.forEach(({ name, type }) => { + if (fields.some((field) => field.name === name)) { + return; + } + fields.push({ + name, + wireName: name, + type: (0, graphql_1.parseType)(type), + required: false, + }); + }); + return fields; +}; +const getParameters = (entry, operation, warn) => { + const parametersField = findObjectField(entry, "parameters"); + if (!parametersField) { + // The operation posts no parameters, so neither may the caller. + return { parametersMode: "absent" }; + } + if (parametersField.value.kind === graphql_1.Kind.VARIABLE) { + const definition = findVariableDefinition(operation, parametersField.value.name.value); + return { parametersMode: "untyped", parametersType: definition === null || definition === void 0 ? void 0 : definition.type }; + } + if (parametersField.value.kind !== graphql_1.Kind.OBJECT) { + return { parametersMode: "absent" }; + } + const parameters = []; + // Source order is the only ordering all SDKs can agree on, so it is preserved. + parametersField.value.fields.forEach((field) => { + var _a; + if (field.value.kind !== graphql_1.Kind.VARIABLE) { + // Fixed by the operation, so encoded in the Schema: the API derives it + // from there and the payload neither exposes nor re-posts it. + return; + } + const variableName = field.value.name.value; + const definition = findVariableDefinition(operation, variableName); + if (!definition) { + warn(`Operation \`${(_a = operation.name) === null || _a === void 0 ? void 0 : _a.value}\` binds parameter \`${field.name.value}\` to undeclared variable \`$${variableName}\`. Skipping the parameter.`); + return; + } + parameters.push({ + wireName: field.name.value, + // Escaped later, once the payload's other field names are known. + name: field.name.value, + type: definition.type, + required: definition.type.kind === graphql_1.Kind.NON_NULL_TYPE, + }); + }); + if (parameters.length === 0) { + // An empty object posts nothing, so the payload takes no parameters. + return { parametersMode: "absent" }; + } + return { parametersMode: "typed", parameters }; +}; +const identityOf = (payload) => JSON.stringify([payload.entryType, payload.typeVersion]); +const sameList = (a, b) => a.length === b.length && a.every((name, index) => name === b[index]); +const parameterNames = (payload) => payload.parametersMode === "typed" + ? payload.parameters.map((parameter) => parameter.wireName) + : []; +/** + * What the two operations disagree about, if anything. Both halves matter: the + * winning operation decides the payload's parameters *and* which entry fields a + * caller may set, so losing either silently is a surprise. + */ +const describeConflict = (a, b) => { + const conflicts = [ + !sameList(parameterNames(a), parameterNames(b)) + ? "parameters" + : undefined, + // Compare the name the caller writes: `ledger: { ik }` and `ledger: { id }` + // both set `ledger`, but they expose `ledgerIk` and `ledgerId`. + !sameList(a.fields.map((field) => field.name), b.fields.map((field) => field.name)) + ? "entry fields" + : undefined, + ].filter((conflict) => conflict !== undefined); + return conflicts.join(" and "); +}; +/** + * Derives one payload per `(type, typeVersion)` pair found in the given + * documents. Operations that are not typed entry operations are skipped + * silently. Duplicate identities are deduplicated, first occurrence winning. + */ +const deriveTypedEntryPayloads = (documents, { warn = defaultWarn } = {}) => { + const byIdentity = new Map(); + documents.forEach((document) => { + document.definitions.forEach((definition) => { + var _a, _b, _c, _d, _e; + if (definition.kind !== graphql_1.Kind.OPERATION_DEFINITION) { + return; + } + const recognised = getTypedEntryField(definition); + if (!recognised) { + return; + } + const { field, entry, entryType, hasLines } = recognised; + if (hasLines) { + // Not silent: the payload a caller would reach for is the one that is + // missing, so say which entry type it was and what to reach for instead. + warn(`Operation \`${(_a = definition.name) === null || _a === void 0 ? void 0 : _a.value}\` posts \`${entryType}\` with Ledger Lines, which a typed payload cannot carry. No payload is generated for it — post it with a raw \`AddLedgerEntryInput\`, which \`addLedgerEntries\` accepts alongside typed payloads.`); + return; + } + // `ik` is an argument of `addLedgerEntry`, not a field of `entry`. Every + // entry in a batch needs its own, so a payload always takes one. + const ikArgument = (_b = field.arguments) === null || _b === void 0 ? void 0 : _b.find((argument) => argument.name.value === "ik"); + const ikDefinition = (ikArgument === null || ikArgument === void 0 ? void 0 : ikArgument.value.kind) === graphql_1.Kind.VARIABLE + ? findVariableDefinition(definition, ikArgument.value.name.value) + : undefined; + const typeVersion = getTypeVersion(entry); + if (typeVersion === undefined) { + warn(`Operation \`${(_c = definition.name) === null || _c === void 0 ? void 0 : _c.value}\` binds \`typeVersion\` to a variable, so its version is the caller's to choose. A typed payload names one version and posts it, so none is generated for \`${entryType}\` — post it with a raw \`AddLedgerEntryInput\`, which \`addLedgerEntries\` accepts alongside typed payloads.`); + return; + } + const payload = Object.assign(Object.assign({ entryType, + typeVersion, operationName: (_e = (_d = definition.name) === null || _d === void 0 ? void 0 : _d.value) !== null && _e !== void 0 ? _e : "", fields: getFields(entry, definition, warn) }, getParameters(entry, definition, warn)), { ikType: ikDefinition === null || ikDefinition === void 0 ? void 0 : ikDefinition.type }); + const identity = identityOf(payload); + const existing = byIdentity.get(identity); + if (existing) { + // The CLI and API guarantee one Ledger Entry per (type, typeVersion), so + // two operations at one identity describe the same entry. Differing sets + // mean the .graphql is stale, or that two generations of it were fed in + // together — runtime-args operations bind fields the plain ones do not. + const conflict = describeConflict(existing, payload); + if (conflict) { + warn(`Operations \`${existing.operationName}\` and \`${payload.operationName}\` both describe \`${payload.entryType}\` (typeVersion ${payload.typeVersion}) but declare different ${conflict}. Using \`${existing.operationName}\`; check that your operations are all generated from the same Schema.`); + } + return; + } + byIdentity.set(identity, payload); + }); + }); + return [...byIdentity.values()]; +}; +exports.deriveTypedEntryPayloads = deriveTypedEntryPayloads; +const splitWords = (value) => value + .replace(/([a-z0-9])([A-Z])/g, "$1 $2") + .split(/[^a-zA-Z0-9]+/) + .filter(Boolean); +const pascalCase = (value) => splitWords(value) + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(""); +// A TypeScript identifier cannot start with a digit. +const escapeIdentifier = (value) => /^[0-9]/.test(value) ? `_${value}` : value; +/** + * Gives each parameter the field name it takes on the payload. Parameters share + * a namespace with the common fields, so a parameter called `posted` cannot keep + * that name: the first occupant keeps the plain name and later ones are + * suffixed. The wire name never changes, so each still carries its own value. + */ +const nameParameters = (payload, warn) => { + if (payload.parametersMode !== "typed") { + return payload; + } + const taken = new Set(["ik", ...payload.fields.map((field) => field.name)]); + return { + parametersMode: "typed", + parameters: payload.parameters.map((parameter) => { + let name = parameter.wireName; + let attempt = 1; + while (taken.has(name)) { + attempt += 1; + name = `${parameter.wireName}_${attempt}`; + } + if (name !== parameter.wireName) { + warn(`Parameter \`${parameter.wireName}\` of \`${payload.entryType}\` (typeVersion ${payload.typeVersion}) collides with another field of the payload, so it is called \`${name}\` there. It still posts as \`${parameter.wireName}\`.`); + } + taken.add(name); + return Object.assign(Object.assign({}, parameter), { name }); + }), + }; +}; +/** + * Assigns each payload its generated identifiers. A name depends only on its own + * payload's identity — never on which other operations are present — so adding + * an entry type or a new version of one never renames an existing payload. + */ +const nameTypedEntryPayloads = (payloads, { warn = defaultWarn } = {}) => { + const taken = new Set(); + return payloads.map((payload) => { + const pascal = `${pascalCase(payload.entryType)}V${payload.typeVersion}`; + const camel = pascal.charAt(0).toLowerCase() + pascal.slice(1); + // Two entry types can want one identifier — `user-funds` and `user_funds` + // both reduce to `UserFunds`. The first in source order keeps the plain + // name; later ones are suffixed. + let suffix = ""; + let attempt = 1; + while (taken.has(`${pascal}${suffix}`)) { + attempt += 1; + suffix = `_${attempt}`; + } + if (suffix) { + warn(`Ledger Entry type \`${payload.entryType}\` (typeVersion ${payload.typeVersion}) generates the identifier \`${pascal}\`, which is already taken. Using \`${pascal}${suffix}\` instead.`); + } + taken.add(`${pascal}${suffix}`); + return Object.assign(Object.assign(Object.assign({}, payload), nameParameters(payload, warn)), { typeName: escapeIdentifier(`${pascal}${suffix}`), builderName: escapeIdentifier(`${camel}${suffix}`) }); + }); +}; +exports.nameTypedEntryPayloads = nameTypedEntryPayloads; +const BUILT_IN_SCALARS = ["ID", "String", "Boolean", "Int", "Float"]; +/** The names of every scalar in a schema, including the built-in ones. */ +const collectScalarNames = (schema) => { + const names = new Set(BUILT_IN_SCALARS); + schema.definitions.forEach((definition) => { + if (definition.kind === graphql_1.Kind.SCALAR_TYPE_DEFINITION) { + names.add(definition.name.value); + } + }); + return names; +}; +exports.collectScalarNames = collectScalarNames; +/** A single-quoted TypeScript string literal, matching the codegen output style. */ +const quote = (value) => `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`; +const renderNamedType = (type, scalars) => { + const { value } = type.name; + // Anything that is not a scalar is an enum or an input object, both of which + // the typescript plugin emits into the same file under their schema name. + return scalars.has(value) ? `Scalars['${value}']['input']` : value; +}; +const renderMaybeType = (type, scalars) => { + if (type.kind === graphql_1.Kind.NON_NULL_TYPE) { + return renderInnerType(type.type, scalars); + } + return `${renderInnerType(type, scalars)} | null`; +}; +const renderInnerType = (type, scalars) => type.kind === graphql_1.Kind.LIST_TYPE + ? `Array<${renderMaybeType(type.type, scalars)}>` + : renderNamedType(type, scalars); +/** + * The TypeScript type of a value bound to a variable. Top-level nullability is + * carried by the optional marker instead: an unset field is omitted from the + * request, never serialized as `null`. + */ +const renderVariableType = (type, scalars) => type.kind === graphql_1.Kind.NON_NULL_TYPE + ? renderInnerType(type.type, scalars) + : renderInnerType(type, scalars); +const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/; +/** + * Wire names go into the generated type verbatim. A GraphQL name is always a + * valid TypeScript property key, so this only quotes defensively. + */ +const renderPropertyKey = (name) => IDENTIFIER.test(name) ? name : quote(name); +const FIELD_DOCS = { + ledgerIk: "The Idempotency Key of the Ledger to add this Ledger Entry to.", + posted: "ISO 8601 timestamp to post this Ledger Entry at.", + lines: "The Ledger Lines to create, for entry types whose lines the Schema does not fix.", +}; +const renderField = (field, scalars) => { + const optional = field.required ? "" : "?"; + const undefinable = field.required ? "" : " | undefined"; + const docs = FIELD_DOCS[field.name] + ? ` /** ${FIELD_DOCS[field.name]} */\n` + : ""; + return `${docs} ${renderPropertyKey(field.name)}${optional}: ${renderVariableType(field.type, scalars)}${undefinable};`; +}; +const renderParameters = (payload, scalars) => { + if (payload.parametersMode === "absent") { + // The operation posts no parameters, so the payload does not take any. + return []; + } + if (payload.parametersMode === "untyped") { + const { parametersType } = payload; + const type = parametersType + ? renderVariableType(parametersType, scalars) + : "Scalars['JSON']['input']"; + const required = (parametersType === null || parametersType === void 0 ? void 0 : parametersType.kind) === graphql_1.Kind.NON_NULL_TYPE ? "" : "?"; + const undefinable = required ? " | undefined" : ""; + return [ + [ + " /**", + " * This entry type's operation does not bind its parameters to typed", + " * variables, so they cannot be typed individually.", + " */", + ` parameters${required}: ${type}${undefinable};`, + ].join("\n"), + ]; + } + // Parameters sit alongside the common fields, in the order the operation + // declares them. + return payload.parameters.map((parameter) => { + const optional = parameter.required ? "" : "?"; + const undefinable = parameter.required ? "" : " | undefined"; + const renamed = parameter.name === parameter.wireName + ? "" + : ` /** Posts as \`${parameter.wireName}\`. */\n`; + return `${renamed} ${renderPropertyKey(parameter.name)}${optional}: ${renderVariableType(parameter.type, scalars)}${undefinable};`; + }); +}; +/** + * `value` when the caller must set it, and a conditional spread when they may + * not: an unset field is left out of the object rather than sent as `null`. + */ +const renderMember = ({ wireName, required, read, value = read, }) => ({ + wireName, + code: [ + required + ? ` ${wireName}: ${value},` + : ` ...(${read} !== undefined && { ${wireName}: ${value} }),`, + ], +}); +/** + * An `entry` key built from members that may each be absent — `parameters`, or a + * match object like `ledger`. When every member is optional the object is built + * first and spread only if it ended up with something in it, so an entry never + * carries an empty object the caller did not ask for. + */ +const renderObjectMember = ({ wireName, members, anyRequired, }) => { + if (anyRequired) { + return { + member: { + wireName, + code: [ + ` ${wireName}: {`, + ...members.map((member) => ` ${member}`), + " },", + ], + }, + }; + } + return { + prelude: [ + ` const ${wireName} = {`, + ...members.map((member) => ` ${member}`), + " };", + ].join("\n"), + member: { + wireName, + code: [` ...(Object.keys(${wireName}).length > 0 && { ${wireName} }),`], + }, + }; +}; +/** The `parameters` key of the emitted `entry` literal, if the payload has one. */ +const renderParametersMember = (payload) => { + var _a; + if (payload.parametersMode === "absent") { + return {}; + } + if (payload.parametersMode === "untyped") { + return { + member: renderMember({ + wireName: "parameters", + required: ((_a = payload.parametersType) === null || _a === void 0 ? void 0 : _a.kind) === graphql_1.Kind.NON_NULL_TYPE, + read: "input.parameters", + }), + }; + } + return renderObjectMember({ + wireName: "parameters", + // With a required parameter the object always has something in it; with + // none, it is built first and posted only if the caller set something. + anyRequired: payload.parameters.some((parameter) => parameter.required), + // Parameters keep the order the source operation declares them in, and each + // posts under its Schema name however it is spelled on the payload. + members: payload.parameters.map((parameter) => { + const key = renderPropertyKey(parameter.wireName); + const read = `input.${parameter.name}`; + return parameter.required + ? `${key}: ${read},` + : `...(${read} !== undefined && { ${key}: ${read} }),`; + }), + }); +}; +/** + * The `entry` keys the payload's own fields set. Fields are grouped by the + * `LedgerEntryInput` field they write, because a match object can be bound one + * key at a time — `ledger: { id: $id, ik: $ik }` is two payload fields writing + * one entry key, and they have to end up in one object rather than overwriting + * each other. + */ +const renderFieldMembers = (payload) => { + const groups = new Map(); + payload.fields.forEach((field) => { + var _a; + groups.set(field.wireName, [...((_a = groups.get(field.wireName)) !== null && _a !== void 0 ? _a : []), field]); + }); + const members = []; + const preludes = []; + groups.forEach((fields, wireName) => { + if (fields.length === 1) { + const [field] = fields; + members.push(renderMember({ + wireName, + required: field.required, + read: `input.${field.name}`, + value: field.wireKey + ? `{ ${field.wireKey}: input.${field.name} }` + : `input.${field.name}`, + })); + return; + } + const { member, prelude } = renderObjectMember({ + wireName, + anyRequired: fields.some((field) => field.required), + members: fields.map((field) => { + var _a; + // Grouping only happens for match objects, so every field has a key. + const key = renderPropertyKey((_a = field.wireKey) !== null && _a !== void 0 ? _a : field.name); + const read = `input.${field.name}`; + return field.required + ? `${key}: ${read},` + : `...(${read} !== undefined && { ${key}: ${read} }),`; + }), + }); + members.push(member); + if (prelude) { + preludes.push(prelude); + } + }); + return { members, preludes }; +}; +const renderBuilderBody = (payload) => { + const { member: parametersMember, prelude } = renderParametersMember(payload); + const fields = renderFieldMembers(payload); + const preludes = [...fields.preludes, ...(prelude ? [prelude] : [])]; + const members = [ + ...fields.members, + ...(parametersMember ? [parametersMember] : []), + { wireName: "type", code: [` type: ${quote(payload.entryType)},`] }, + { wireName: "typeVersion", code: [` typeVersion: ${payload.typeVersion},`] }, + ]; + // Keys are emitted in lexicographic order, which costs nothing to do here and + // makes two SDKs' requests comparable byte for byte. + const entry = [...members] + .sort((a, b) => a.wireName.localeCompare(b.wireName)) + .flatMap((member) => member.code); + const literal = (indent) => [ + "{", + " entry: {", + ...entry, + " },", + " ik: input.ik,", + "}", + ] + .map((line, index) => (index === 0 ? line : `${indent}${line}`)) + .join("\n"); + // An object whose members are all optional is built first, so the block body + // is only used where it earns its keep. + return preludes.length > 0 + ? ` => {\n${preludes.join("\n")}\n return ${literal(" ")};\n}` + : ` => (${literal("")})`; +}; +const renderPayload = (payload, scalars) => { + const description = `\`${payload.entryType}\` (typeVersion ${payload.typeVersion})`; + const ikType = payload.ikType + ? renderVariableType(payload.ikType, scalars) + : "Scalars['SafeString']['input']"; + const members = [ + ` /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */\n ik: ${ikType};`, + ...payload.fields.map((field) => renderField(field, scalars)), + ...renderParameters(payload, scalars), + ]; + return `/** + * Payload for the ${description} Ledger Entry, for use with \`addLedgerEntries\`. + * + * Derived from the \`${payload.operationName}\` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type ${payload.typeName} = { +${members.join("\n")} +}; + +/** Builds an \`addLedgerEntries\` entry for ${description}. */ +export const ${payload.builderName} = ( + input: ${payload.typeName}, +): AddLedgerEntryInput${renderBuilderBody(payload)};`; +}; +const renderRegistry = (payloads) => { + const entries = payloads.map((payload) => ` ${quote(`${payload.entryType}@${payload.typeVersion}`)}: ${payload.builderName},`); + return `/** + * Every typed payload builder, keyed by \`"@"\`. Useful + * when the entry type is only known at runtime. + */ +export const typedLedgerEntryBuilders = { +${entries.join("\n")} +} as const;`; +}; +/** + * Renders the typed batch entry section of a generated client. Returns an empty + * string when the input documents contain no typed entry operations. + */ +const renderTypedEntryPayloads = (payloads, scalars) => { + if (payloads.length === 0) { + return ""; + } + const sections = [ + `/** + * Typed payloads for batching Ledger Entries with \`addLedgerEntries\`. + * + * Each payload is derived from the single-entry \`addLedgerEntry\` operation for + * one \`(type, typeVersion)\` pair, and builds an \`AddLedgerEntryInput\` you can + * mix freely with raw, untyped entry inputs in the same batch: + * + * \`\`\`ts + * await client.addLedgerEntries({ + * entries: [${payloads[0].builderName}({ ik, ledgerIk, parameters: { ... } })], + * }); + * \`\`\` + * + * A payload takes exactly what its source operation binds, so what you can set + * here is what that entry type accepts — no more. A field you do not set is + * left out of the request rather than sent as \`null\`. + */`, + ...payloads.map((payload) => renderPayload(payload, scalars)), + renderRegistry(payloads), + ]; + return `${sections.join("\n\n")}\n`; +}; +exports.renderTypedEntryPayloads = renderTypedEntryPayloads; +/** Derives, names and renders typed payloads in one step. */ +const generateTypedEntryPayloads = ({ documents, schema, warn, }) => { + const payloads = (0, exports.nameTypedEntryPayloads)((0, exports.deriveTypedEntryPayloads)(documents, { warn }), { warn }); + return (0, exports.renderTypedEntryPayloads)(payloads, (0, exports.collectScalarNames)(schema)); +}; +exports.generateTypedEntryPayloads = generateTypedEntryPayloads; diff --git a/dist/cjs/types/src/client.d.ts.map b/dist/cjs/types/src/client.d.ts.map index 073dca3..afad477 100644 --- a/dist/cjs/types/src/client.d.ts.map +++ b/dist/cjs/types/src/client.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE7D,OAAO,EACL,kBAAkB,EAClB,MAAM,IAAI,aAAa,EACxB,MAAM,2BAA2B,CAAC;AAInC,OAAO,EAAwB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAa1E,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AAE9D,KAAK,0BAA0B,GAAG;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAqIF,KAAK,yBAAyB,CAC5B,CAAC,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,kBAAkB,KAAK,GAAG,IACnE;IACF,MAAM,EAAE,0BAA0B,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,CAAC;IACX,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B,CAAC;AAEF,KAAK,0BAA0B,CAC7B,CAAC,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,kBAAkB,KAAK,GAAG,IACnE,CAAC,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,kBAAkB,KAAK,MAAM,CAAC,GACzE,CAAC,SAAS,cAAc,GACtB,CAAC,GACD,cAAc,GAAG,CAAC,GACpB,KAAK,CAAC;AAEV,eAAO,MAAM,oBAAoB,sBAErB,aAAa,WACZ,kBAAkB,KACxB,GAAG,2DAKP,0BAA0B,CAAC,CAAC,KAAG,2BAA2B,CAAC,CAe7D,CAAC"} \ No newline at end of file +{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE7D,OAAO,EACL,kBAAkB,EAClB,MAAM,IAAI,aAAa,EACxB,MAAM,2BAA2B,CAAC;AAUnC,OAAO,EAAwB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAa1E,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AAE9D,KAAK,0BAA0B,GAAG;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAyJF,KAAK,yBAAyB,CAC5B,CAAC,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,kBAAkB,KAAK,GAAG,IACnE;IACF,MAAM,EAAE,0BAA0B,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,CAAC;IACX,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B,CAAC;AAEF,KAAK,0BAA0B,CAC7B,CAAC,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,kBAAkB,KAAK,GAAG,IACnE,CAAC,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,kBAAkB,KAAK,MAAM,CAAC,GACzE,CAAC,SAAS,cAAc,GACtB,CAAC,GACD,cAAc,GAAG,CAAC,GACpB,KAAK,CAAC;AAEV,eAAO,MAAM,oBAAoB,sBAErB,aAAa,WACZ,kBAAkB,KACxB,GAAG,2DAKP,0BAA0B,CAAC,CAAC,KAAG,2BAA2B,CAAC,CAe7D,CAAC"} \ No newline at end of file diff --git a/dist/cjs/types/src/errors.d.ts b/dist/cjs/types/src/errors.d.ts index 2de044b..1797494 100644 --- a/dist/cjs/types/src/errors.d.ts +++ b/dist/cjs/types/src/errors.d.ts @@ -18,5 +18,36 @@ export type BadRequestErrorParams = FragmentErrorParams; export declare class BadRequestError extends FragmentError { constructor({ cause, message, code }: BadRequestErrorParams); } +/** + * Error details for a single Ledger Entry that was responsible for the batch's + * failure. + */ +export type LedgerEntryBatchError = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) of the Ledger Entry */ + ik: string; + /** The status code of error. For example, 'ledger_entry_too_many_lines'. */ + code: string; + /** The error message */ + message: string; + /** Whether or not the operation is retryable */ + retryable: boolean; +}; +export type AddLedgerEntriesErrorParams = FragmentErrorParams & { + /** The list of errors for each Ledger Entry that was responsible for the batch's failure. */ + errors?: LedgerEntryBatchError[]; +}; +/** + * Thrown when one or more Ledger Entries in an `addLedgerEntries` batch could + * not be added. + * + * `addLedgerEntries` adds a batch of Ledger Entries in one synchronous and + * atomic transaction, so either every entry was added or none were. Nothing was + * written when this is thrown. + */ +export declare class AddLedgerEntriesError extends FragmentError { + /** The list of errors for each Ledger Entry that was responsible for the batch's failure. */ + readonly errors: LedgerEntryBatchError[]; + constructor({ cause, message, code, errors }: AddLedgerEntriesErrorParams); +} export {}; //# sourceMappingURL=errors.d.ts.map \ No newline at end of file diff --git a/dist/cjs/types/src/errors.d.ts.map b/dist/cjs/types/src/errors.d.ts.map index c9f22ca..c9eddc0 100644 --- a/dist/cjs/types/src/errors.d.ts.map +++ b/dist/cjs/types/src/errors.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,KAAK,mBAAmB,GAAG;IACzB,KAAK,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;gBAEX,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,mBAAmB;CAmBxE;AAED,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAEzD,qBAAa,aAAc,SAAQ,aAAa;gBAClC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,sBAAsB;CAGvD;AAED,MAAM,MAAM,qBAAqB,GAAG,mBAAmB,CAAC;AAExD,qBAAa,eAAgB,SAAQ,aAAa;gBACpC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,qBAAqB;CAG5D"} \ No newline at end of file +{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,KAAK,mBAAmB,GAAG;IACzB,KAAK,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;gBAEX,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,mBAAmB;CAmBxE;AAED,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAEzD,qBAAa,aAAc,SAAQ,aAAa;gBAClC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,sBAAsB;CAGvD;AAED,MAAM,MAAM,qBAAqB,GAAG,mBAAmB,CAAC;AAExD,qBAAa,eAAgB,SAAQ,aAAa;gBACpC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,qBAAqB;CAG5D;AAED;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,6GAA6G;IAC7G,EAAE,EAAE,MAAM,CAAC;IACX,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG,mBAAmB,GAAG;IAC9D,6FAA6F;IAC7F,MAAM,CAAC,EAAE,qBAAqB,EAAE,CAAC;CAClC,CAAC;AAEF;;;;;;;GAOG;AACH,qBAAa,qBAAsB,SAAQ,aAAa;IACtD,6FAA6F;IAC7F,QAAQ,CAAC,MAAM,EAAE,qBAAqB,EAAE,CAAC;gBAE7B,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,2BAA2B;CAQ1E"} \ No newline at end of file diff --git a/dist/cjs/types/src/typedBatchEntries.d.ts b/dist/cjs/types/src/typedBatchEntries.d.ts new file mode 100644 index 0000000..2656499 --- /dev/null +++ b/dist/cjs/types/src/typedBatchEntries.d.ts @@ -0,0 +1,120 @@ +/** + * Derives typed batch Ledger Entry payloads from the per-entry-type + * `addLedgerEntry` operations the Fragment CLI generates for a Schema. + * + * `addLedgerEntries` takes one list of one input type (`AddLedgerEntryInput`), + * and `LedgerEntryInput.parameters` is an opaque `JSON` scalar, so GraphQL + * cannot express the parameter types of an individual entry in a batch. The + * single-entry operations can: they carry the entry type as a string literal and + * bind everything the caller may set to a typed operation variable. This module + * reads those operations and emits a typed payload per `(type, typeVersion)` + * pair, each of which builds an `AddLedgerEntryInput` for `addLedgerEntries`. + * + * What a payload exposes is fixed by `LedgerEntryInput`, not derived from the + * operation: every payload carries the same common fields (§2.3a), so the CLI + * changing which fields it binds never moves a payload's surface. + * + * Two things follow. A value the operation fixes to a literal is encoded in the + * Schema, so the API derives it and the payload neither exposes nor re-posts it. + * And an entry type whose Ledger Lines the caller supplies gets no payload at + * all, since `lines` is not a common field — post those with a raw + * `AddLedgerEntryInput`, which `addLedgerEntries` accepts in the same batch. + */ +import { type DocumentNode, type TypeNode } from "graphql"; +/** A value the caller supplies, typed by the variable the operation binds. */ +export type BoundValue = { + /** The variable's declared type, which is where the payload's type comes from. */ + type: TypeNode; + /** True when the variable's type is non-null. */ + required: boolean; +}; +/** A single parameter of a typed entry payload. */ +export type TypedEntryParameter = { + /** The parameter name from the Schema. Goes on the wire verbatim. */ + wireName: string; + /** + * The field name on the generated payload. The wire name unless it collided + * with a common field or an earlier parameter (§2.5). + */ + name: string; +} & BoundValue; +/** + * An entry field a typed payload lets the caller set, derived from the source + * operation binding it to a variable. + */ +export type TypedEntryField = { + /** The field name on the generated payload, for a value the caller supplies. */ + name: string; + /** The `LedgerEntryInput` field it sets. */ + wireName: string; + /** + * Set when the value is a match key nested inside the wire field — `ledgerIk` + * sets `ledger: { ik }`, so this is `"ik"` there. + */ + wireKey?: string; +} & BoundValue; +/** + * How the source operation exposes `parameters`, if at all: as an inline object + * literal, so each parameter is typed individually; bound to a variable, so the + * payload falls back to an untyped map; or not at all, so the caller cannot set + * parameters. Each carries only what that case has. + */ +export type PayloadParameters = { + parametersMode: "typed"; + parameters: TypedEntryParameter[]; +} | { + parametersMode: "untyped"; + parametersType: TypeNode | undefined; +} | { + parametersMode: "absent"; +}; +/** A typed payload for one `(entry type, typeVersion)` pair. */ +export type TypedEntryPayload = { + entryType: string; + typeVersion: number; + /** The operation the payload was derived from. Informative only. */ + operationName: string; + /** Entry fields the caller may set, in the operation's source order. */ + fields: TypedEntryField[]; + /** The declared type of the entry's own `ik`, when bound to a variable. */ + ikType?: TypeNode; +} & PayloadParameters; +/** A payload with its generated TypeScript identifiers assigned. */ +export type NamedTypedEntryPayload = TypedEntryPayload & { + /** Name of the exported payload type, e.g. `UserFundsAccountV1`. */ + typeName: string; + /** Name of the exported builder function, e.g. `userFundsAccountV1`. */ + builderName: string; +}; +type Warn = (message: string) => void; +/** + * Derives one payload per `(type, typeVersion)` pair found in the given + * documents. Operations that are not typed entry operations are skipped + * silently. Duplicate identities are deduplicated, first occurrence winning. + */ +export declare const deriveTypedEntryPayloads: (documents: ReadonlyArray, { warn }?: { + warn?: Warn; +}) => TypedEntryPayload[]; +/** + * Assigns each payload its generated identifiers. A name depends only on its own + * payload's identity — never on which other operations are present — so adding + * an entry type or a new version of one never renames an existing payload. + */ +export declare const nameTypedEntryPayloads: (payloads: ReadonlyArray, { warn }?: { + warn?: Warn; +}) => NamedTypedEntryPayload[]; +/** The names of every scalar in a schema, including the built-in ones. */ +export declare const collectScalarNames: (schema: DocumentNode) => Set; +/** + * Renders the typed batch entry section of a generated client. Returns an empty + * string when the input documents contain no typed entry operations. + */ +export declare const renderTypedEntryPayloads: (payloads: ReadonlyArray, scalars: ReadonlySet) => string; +/** Derives, names and renders typed payloads in one step. */ +export declare const generateTypedEntryPayloads: ({ documents, schema, warn, }: { + documents: ReadonlyArray; + schema: DocumentNode; + warn?: Warn; +}) => string; +export {}; +//# sourceMappingURL=typedBatchEntries.d.ts.map \ No newline at end of file diff --git a/dist/cjs/types/src/typedBatchEntries.d.ts.map b/dist/cjs/types/src/typedBatchEntries.d.ts.map new file mode 100644 index 0000000..ee5f327 --- /dev/null +++ b/dist/cjs/types/src/typedBatchEntries.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"typedBatchEntries.d.ts","sourceRoot":"","sources":["../../../../src/typedBatchEntries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAGL,KAAK,YAAY,EAKjB,KAAK,QAAQ,EAGd,MAAM,SAAS,CAAC;AAEjB,8EAA8E;AAC9E,MAAM,MAAM,UAAU,GAAG;IACvB,kFAAkF;IAClF,IAAI,EAAE,QAAQ,CAAC;IACf,iDAAiD;IACjD,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,mDAAmD;AACnD,MAAM,MAAM,mBAAmB,GAAG;IAChC,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,UAAU,CAAC;AAEf;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,UAAU,CAAC;AAEf;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,cAAc,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,mBAAmB,EAAE,CAAA;CAAE,GAC9D;IAAE,cAAc,EAAE,SAAS,CAAC;IAAC,cAAc,EAAE,QAAQ,GAAG,SAAS,CAAA;CAAE,GACnE;IAAE,cAAc,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEjC,gEAAgE;AAChE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,2EAA2E;IAC3E,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB,GAAG,iBAAiB,CAAC;AAEtB,oEAAoE;AACpE,MAAM,MAAM,sBAAsB,GAAG,iBAAiB,GAAG;IACvD,oEAAoE;IACpE,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,KAAK,IAAI,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAoStC;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,cACxB,cAAc,YAAY,CAAC,aACd;IAAE,IAAI,CAAC,EAAE,IAAI,CAAA;CAAE,KACtC,iBAAiB,EAsEnB,CAAC;AAqDF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,aACvB,cAAc,iBAAiB,CAAC,aAClB;IAAE,IAAI,CAAC,EAAE,IAAI,CAAA;CAAE,KACtC,sBAAsB,EA6BxB,CAAC;AAIF,0EAA0E;AAC1E,eAAO,MAAM,kBAAkB,WAAY,YAAY,KAAG,IAAI,MAAM,CAQnE,CAAC;AA+WF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,aACzB,cAAc,sBAAsB,CAAC,WACtC,YAAY,MAAM,CAAC,KAC3B,MA4BF,CAAC;AAEF,6DAA6D;AAC7D,eAAO,MAAM,0BAA0B,iCAIpC;IACD,SAAS,EAAE,cAAc,YAAY,CAAC,CAAC;IACvC,MAAM,EAAE,YAAY,CAAC;IACrB,IAAI,CAAC,EAAE,IAAI,CAAC;CACb,KAAG,MAMH,CAAC"} \ No newline at end of file diff --git a/dist/esm/src/bin.js b/dist/esm/src/bin.js index b08b8ed..4971798 100644 --- a/dist/esm/src/bin.js +++ b/dist/esm/src/bin.js @@ -3,6 +3,7 @@ import { writeFileSync } from "fs"; import yargs from "yargs"; import * as path from "path"; import { generate, cliError } from "@graphql-codegen/cli"; +import { generateTypedEntryPayloads } from "./typedBatchEntries.js"; const argv = yargs(process.argv.slice(2)) .options({ outputFilename: { @@ -27,44 +28,77 @@ const allowedInputExtensions = [".graphql", ".gql", ".ts"]; if (!allowedInputExtensions.includes(path.extname(argv.input))) { throw new Error(`Input filename must have a .graphql, .gql, or .ts extension. You provided ${path.extname(argv.input) || ""}`); } -generate({ - schema: "https://api.us-west-2.fragment.dev/schema.graphql", - documents: argv.input, - config: { - scalars: { - AlphaNumericString: "string", - Date: "string", - DateTime: "string", - Int64: "string", - Int96: "string", - JSON: "Record", - JSONObject: "Record", - LastMoment: "string", - ParameterizedString: "string", - Period: "string", - SafeString: "string", - UTCOffset: "string", +/** + * Runs codegen for one input and writes the client, with the typed batch entry + * payloads appended. + * + * The payloads are derived from the same operations the plugins run over, so a + * document transform hands them (and the schema) to `sources` instead of loading + * either a second time. The transform leaves the documents themselves alone. + */ +const generateClient = async ({ input, outputFilename, }) => { + const sources = []; + const [fileOutput] = await generate({ + schema: "https://api.us-west-2.fragment.dev/schema.graphql", + documents: input, + config: { + scalars: { + AlphaNumericString: "string", + Date: "string", + DateTime: "string", + Int64: "string", + Int96: "string", + JSON: "Record", + JSONObject: "Record", + LastMoment: "string", + ParameterizedString: "string", + Period: "string", + SafeString: "string", + UTCOffset: "string", + }, + gqlImport: "@fragment-dev/node-client#gql", }, - gqlImport: "@fragment-dev/node-client#gql", - }, - generates: { - [path.join(process.cwd(), argv.outputFilename)]: { - plugins: [ - "typescript", - "typescript-operations", - "typescript-graphql-request", - { - add: { - content: "/* This file is auto-generated by @fragment-dev/node-client. Don't edit it directly. */", + generates: { + [path.join(process.cwd(), outputFilename)]: { + documentTransforms: [ + { + transform: ({ documents, schema }) => { + sources.push({ + documents: documents + .map((document) => document.document) + .filter((document) => !!document), + schema, + }); + return documents; + }, + }, + ], + plugins: [ + "typescript", + "typescript-operations", + "typescript-graphql-request", + { + add: { + content: "/* This file is auto-generated by @fragment-dev/node-client. Don't edit it directly. */", + }, }, - }, - ], + ], + }, }, - }, -}, false) - .then(([fileOutput]) => { + }, false); const output = fileOutput.content.replace(/import .* from 'graphql-request'/g, "import { GraphQLClient, RequestOptions } from '@fragment-dev/node-client'"); - writeFileSync(fileOutput.filename, output, "utf-8"); + const [source] = sources; + if (!source) { + console.warn("[@fragment-dev/node-client] Could not read the input operations, so no typed batch Ledger Entry payloads were generated."); + } + const typedBatchEntries = source ? generateTypedEntryPayloads(source) : ""; + writeFileSync(fileOutput.filename, typedBatchEntries ? `${output}\n${typedBatchEntries}` : output, "utf-8"); +}; +generateClient({ + input: argv.input, + outputFilename: argv.outputFilename, +}) + .then(() => { process.exit(0); }) .catch((error) => { diff --git a/dist/esm/src/client.js b/dist/esm/src/client.js index 8ab9a01..22fcc7c 100644 --- a/dist/esm/src/client.js +++ b/dist/esm/src/client.js @@ -3,7 +3,7 @@ import { ClientError, GraphQLClient } from "graphql-request"; import { getSdk as getDefaultSdk, } from "../generated/generated.js"; import { version } from "../generated/version.js"; import { getToken } from "./getToken.js"; -import { BadRequestError, FragmentError, InternalError } from "./errors.js"; +import { AddLedgerEntriesError, BadRequestError, FragmentError, InternalError, } from "./errors.js"; import { DEFAULT_RETRY_CONFIG } from "./retryConfig.js"; const getRetryableField = (error) => { if (Object.prototype.hasOwnProperty.call(error, "retryable")) { @@ -67,6 +67,26 @@ const createRequestWrapper = (tokenCache, params, retryConfig) => async (request else { throw err; } + break; + } + case "AddLedgerEntriesError": { + // The errors for each Ledger Entry responsible for the failure are + // what tell a caller which entries to fix, so they are surfaced + // rather than collapsed into the top-level message. + const err = new AddLedgerEntriesError({ + code, + cause: data[graphQlOperationName], + message, + errors: (data[graphQlOperationName].errors ?? + []), + }); + if (!retryable) { + bail(err); + } + else { + throw err; + } + break; } case "BadRequestError": { const err = new BadRequestError({ @@ -80,6 +100,7 @@ const createRequestWrapper = (tokenCache, params, retryConfig) => async (request else { throw err; } + break; } default: if (typeName.endsWith("Error")) { diff --git a/dist/esm/src/errors.js b/dist/esm/src/errors.js index 62fc865..ef39f27 100644 --- a/dist/esm/src/errors.js +++ b/dist/esm/src/errors.js @@ -32,3 +32,23 @@ export class BadRequestError extends FragmentError { super({ cause, message, code: code ?? "bad_request_error" }); } } +/** + * Thrown when one or more Ledger Entries in an `addLedgerEntries` batch could + * not be added. + * + * `addLedgerEntries` adds a batch of Ledger Entries in one synchronous and + * atomic transaction, so either every entry was added or none were. Nothing was + * written when this is thrown. + */ +export class AddLedgerEntriesError extends FragmentError { + /** The list of errors for each Ledger Entry that was responsible for the batch's failure. */ + errors; + constructor({ cause, message, code, errors }) { + super({ + cause, + message, + code: code ?? "ledger_entry_batch_operation_failed", + }); + this.errors = errors ?? []; + } +} diff --git a/dist/esm/src/typedBatchEntries.js b/dist/esm/src/typedBatchEntries.js new file mode 100644 index 0000000..110cfbb --- /dev/null +++ b/dist/esm/src/typedBatchEntries.js @@ -0,0 +1,686 @@ +/** + * Derives typed batch Ledger Entry payloads from the per-entry-type + * `addLedgerEntry` operations the Fragment CLI generates for a Schema. + * + * `addLedgerEntries` takes one list of one input type (`AddLedgerEntryInput`), + * and `LedgerEntryInput.parameters` is an opaque `JSON` scalar, so GraphQL + * cannot express the parameter types of an individual entry in a batch. The + * single-entry operations can: they carry the entry type as a string literal and + * bind everything the caller may set to a typed operation variable. This module + * reads those operations and emits a typed payload per `(type, typeVersion)` + * pair, each of which builds an `AddLedgerEntryInput` for `addLedgerEntries`. + * + * What a payload exposes is fixed by `LedgerEntryInput`, not derived from the + * operation: every payload carries the same common fields (§2.3a), so the CLI + * changing which fields it binds never moves a payload's surface. + * + * Two things follow. A value the operation fixes to a literal is encoded in the + * Schema, so the API derives it and the payload neither exposes nor re-posts it. + * And an entry type whose Ledger Lines the caller supplies gets no payload at + * all, since `lines` is not a common field — post those with a raw + * `AddLedgerEntryInput`, which `addLedgerEntries` accepts in the same batch. + */ +import { Kind, parseType, } from "graphql"; +const defaultWarn = (message) => { + // eslint-disable-next-line no-console + console.warn(`[@fragment-dev/node-client] ${message}`); +}; +/** + * `type` and `typeVersion` identify the payload and `parameters` is typed per + * payload, so none of the three is the caller's to set. + */ +const DERIVED_ENTRY_FIELDS = ["type", "typeVersion", "parameters"]; +/** + * The `LedgerEntryInput` fields every payload exposes, whatever its operation + * binds, with their declared types (spec 2.3a). + * + * Deliberately not derived from the operation. An operation binds only the entry + * fields the CLI version that generated it chose to expose, and that choice has + * already changed between versions -- so deriving the set would invent a + * restriction the API does not have, and would move a payload's surface whenever + * the CLI changed. A payload travels as an `AddLedgerEntryInput`, so what the + * operation binds places no limit on what the payload may carry. + * + * `ik` and `ledgerIk` are always present already. `lines` is excluded: it cannot + * be combined with an entry that has a `type`. + */ +const COMMON_ENTRY_FIELDS = [ + { name: "posted", type: "DateTime" }, + { name: "description", type: "String" }, + { name: "tags", type: "[LedgerEntryTagInput!]" }, + { name: "groups", type: "[LedgerEntryGroupInput!]" }, + { name: "conditions", type: "[LedgerEntryConditionInput!]" }, +]; +const findObjectField = (object, name) => object.fields.find((field) => field.name.value === name); +const findVariableDefinition = (operation, variableName) => operation.variableDefinitions?.find((candidate) => candidate.variable.name.value === variableName); +/** + * Returns the `addLedgerEntry` field of a recognised typed entry operation, or + * undefined if the operation is not one. Every failing condition is a silent + * skip, never an error: this is what excludes the SDK's own `addLedgerEntry` and + * `addLedgerEntryRuntime` operations, whose `type` is a variable. + */ +const getTypedEntryField = (operation) => { + if (operation.operation !== "mutation") { + return undefined; + } + if (!operation.name) { + return undefined; + } + const selections = operation.selectionSet.selections; + if (selections.length !== 1) { + return undefined; + } + const [selection] = selections; + if (selection.kind !== Kind.FIELD || + selection.name.value !== "addLedgerEntry") { + return undefined; + } + const entryArgument = selection.arguments?.find((argument) => argument.name.value === "entry"); + if (!entryArgument || entryArgument.value.kind !== Kind.OBJECT) { + return undefined; + } + const typeField = findObjectField(entryArgument.value, "type"); + if (!typeField || typeField.value.kind !== Kind.STRING) { + return undefined; + } + // The entry type comes back narrowed, so no caller has to re-check it. + return { + field: selection, + entry: entryArgument.value, + entryType: typeField.value.value, + /** + * An entry type whose Lines the Schema does not fix takes them from the + * caller, and a payload cannot carry them: `lines` is not a common field. + * Generating one anyway would hand the caller something that always posts + * an entry with no Lines, so no payload is generated at all. + */ + hasLines: !!findObjectField(entryArgument.value, "lines"), + }; +}; +/** + * The `typeVersion` the operation pins, or undefined when it pins one + * dynamically. `typeVersion` defaults to 1 when it is not set, so an operation + * that pins no version is normalised to 1 — for the payload's identity, its name + * and its wire payload alike. + * + * A version bound to a variable is different: the caller of the single-entry + * operation chooses it, and a payload — whose name states one version and whose + * builder posts it — cannot. Those get no payload rather than a silent 1. + */ +const getTypeVersion = (entry) => { + const field = findObjectField(entry, "typeVersion"); + if (!field || field.value.kind === Kind.NULL) { + return 1; + } + if (field.value.kind === Kind.INT) { + return Number.parseInt(field.value.value, 10); + } + return undefined; +}; +/** + * The entry fields the operation lets the caller set, in source order. A field + * bound to anything other than a variable is fixed by the operation, so it is + * not exposed. + */ +const getFields = (entry, operation, warn) => { + const fields = []; + entry.fields.forEach((entryField) => { + const wireName = entryField.name.value; + if (DERIVED_ENTRY_FIELDS.includes(wireName)) { + return; + } + const bind = ({ name, wireKey, variable, }) => { + const variableName = variable.name.value; + const definition = findVariableDefinition(operation, variableName); + if (!definition) { + warn(`Operation \`${operation.name?.value}\` binds \`${wireName}\` to undeclared variable \`$${variableName}\`. Skipping the field.`); + return; + } + fields.push({ + name, + wireName, + wireKey, + type: definition.type, + required: definition.type.kind === Kind.NON_NULL_TYPE, + }); + }; + if (entryField.value.kind === Kind.VARIABLE) { + bind({ name: wireName, variable: entryField.value }); + return; + } + // `ledger: { ik: $ledgerIk }` is the shape the CLI generates. The caller + // supplies the Idempotency Key, so the payload exposes it as `ledgerIk`. + if (entryField.value.kind === Kind.OBJECT) { + entryField.value.fields.forEach((matchField) => { + const key = matchField.name.value; + if (matchField.value.kind === Kind.VARIABLE) { + bind({ + name: `${wireName}${key.charAt(0).toUpperCase()}${key.slice(1)}`, + wireKey: key, + variable: matchField.value, + }); + return; + } + // Fixed by the operation, so encoded in the Schema: the API derives it. + }); + return; + } + // Fixed by the operation, so encoded in the Schema: the API derives it. + }); + // Spec 2.3a: every payload carries these, whether or not its operation binds + // them. Appended rather than interleaved, so the operation's own fields keep + // their source order. + COMMON_ENTRY_FIELDS.forEach(({ name, type }) => { + if (fields.some((field) => field.name === name)) { + return; + } + fields.push({ + name, + wireName: name, + type: parseType(type), + required: false, + }); + }); + return fields; +}; +const getParameters = (entry, operation, warn) => { + const parametersField = findObjectField(entry, "parameters"); + if (!parametersField) { + // The operation posts no parameters, so neither may the caller. + return { parametersMode: "absent" }; + } + if (parametersField.value.kind === Kind.VARIABLE) { + const definition = findVariableDefinition(operation, parametersField.value.name.value); + return { parametersMode: "untyped", parametersType: definition?.type }; + } + if (parametersField.value.kind !== Kind.OBJECT) { + return { parametersMode: "absent" }; + } + const parameters = []; + // Source order is the only ordering all SDKs can agree on, so it is preserved. + parametersField.value.fields.forEach((field) => { + if (field.value.kind !== Kind.VARIABLE) { + // Fixed by the operation, so encoded in the Schema: the API derives it + // from there and the payload neither exposes nor re-posts it. + return; + } + const variableName = field.value.name.value; + const definition = findVariableDefinition(operation, variableName); + if (!definition) { + warn(`Operation \`${operation.name?.value}\` binds parameter \`${field.name.value}\` to undeclared variable \`$${variableName}\`. Skipping the parameter.`); + return; + } + parameters.push({ + wireName: field.name.value, + // Escaped later, once the payload's other field names are known. + name: field.name.value, + type: definition.type, + required: definition.type.kind === Kind.NON_NULL_TYPE, + }); + }); + if (parameters.length === 0) { + // An empty object posts nothing, so the payload takes no parameters. + return { parametersMode: "absent" }; + } + return { parametersMode: "typed", parameters }; +}; +const identityOf = (payload) => JSON.stringify([payload.entryType, payload.typeVersion]); +const sameList = (a, b) => a.length === b.length && a.every((name, index) => name === b[index]); +const parameterNames = (payload) => payload.parametersMode === "typed" + ? payload.parameters.map((parameter) => parameter.wireName) + : []; +/** + * What the two operations disagree about, if anything. Both halves matter: the + * winning operation decides the payload's parameters *and* which entry fields a + * caller may set, so losing either silently is a surprise. + */ +const describeConflict = (a, b) => { + const conflicts = [ + !sameList(parameterNames(a), parameterNames(b)) + ? "parameters" + : undefined, + // Compare the name the caller writes: `ledger: { ik }` and `ledger: { id }` + // both set `ledger`, but they expose `ledgerIk` and `ledgerId`. + !sameList(a.fields.map((field) => field.name), b.fields.map((field) => field.name)) + ? "entry fields" + : undefined, + ].filter((conflict) => conflict !== undefined); + return conflicts.join(" and "); +}; +/** + * Derives one payload per `(type, typeVersion)` pair found in the given + * documents. Operations that are not typed entry operations are skipped + * silently. Duplicate identities are deduplicated, first occurrence winning. + */ +export const deriveTypedEntryPayloads = (documents, { warn = defaultWarn } = {}) => { + const byIdentity = new Map(); + documents.forEach((document) => { + document.definitions.forEach((definition) => { + if (definition.kind !== Kind.OPERATION_DEFINITION) { + return; + } + const recognised = getTypedEntryField(definition); + if (!recognised) { + return; + } + const { field, entry, entryType, hasLines } = recognised; + if (hasLines) { + // Not silent: the payload a caller would reach for is the one that is + // missing, so say which entry type it was and what to reach for instead. + warn(`Operation \`${definition.name?.value}\` posts \`${entryType}\` with Ledger Lines, which a typed payload cannot carry. No payload is generated for it — post it with a raw \`AddLedgerEntryInput\`, which \`addLedgerEntries\` accepts alongside typed payloads.`); + return; + } + // `ik` is an argument of `addLedgerEntry`, not a field of `entry`. Every + // entry in a batch needs its own, so a payload always takes one. + const ikArgument = field.arguments?.find((argument) => argument.name.value === "ik"); + const ikDefinition = ikArgument?.value.kind === Kind.VARIABLE + ? findVariableDefinition(definition, ikArgument.value.name.value) + : undefined; + const typeVersion = getTypeVersion(entry); + if (typeVersion === undefined) { + warn(`Operation \`${definition.name?.value}\` binds \`typeVersion\` to a variable, so its version is the caller's to choose. A typed payload names one version and posts it, so none is generated for \`${entryType}\` — post it with a raw \`AddLedgerEntryInput\`, which \`addLedgerEntries\` accepts alongside typed payloads.`); + return; + } + const payload = { + entryType, + typeVersion, + operationName: definition.name?.value ?? "", + fields: getFields(entry, definition, warn), + ...getParameters(entry, definition, warn), + ikType: ikDefinition?.type, + }; + const identity = identityOf(payload); + const existing = byIdentity.get(identity); + if (existing) { + // The CLI and API guarantee one Ledger Entry per (type, typeVersion), so + // two operations at one identity describe the same entry. Differing sets + // mean the .graphql is stale, or that two generations of it were fed in + // together — runtime-args operations bind fields the plain ones do not. + const conflict = describeConflict(existing, payload); + if (conflict) { + warn(`Operations \`${existing.operationName}\` and \`${payload.operationName}\` both describe \`${payload.entryType}\` (typeVersion ${payload.typeVersion}) but declare different ${conflict}. Using \`${existing.operationName}\`; check that your operations are all generated from the same Schema.`); + } + return; + } + byIdentity.set(identity, payload); + }); + }); + return [...byIdentity.values()]; +}; +const splitWords = (value) => value + .replace(/([a-z0-9])([A-Z])/g, "$1 $2") + .split(/[^a-zA-Z0-9]+/) + .filter(Boolean); +const pascalCase = (value) => splitWords(value) + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(""); +// A TypeScript identifier cannot start with a digit. +const escapeIdentifier = (value) => /^[0-9]/.test(value) ? `_${value}` : value; +/** + * Gives each parameter the field name it takes on the payload. Parameters share + * a namespace with the common fields, so a parameter called `posted` cannot keep + * that name: the first occupant keeps the plain name and later ones are + * suffixed. The wire name never changes, so each still carries its own value. + */ +const nameParameters = (payload, warn) => { + if (payload.parametersMode !== "typed") { + return payload; + } + const taken = new Set(["ik", ...payload.fields.map((field) => field.name)]); + return { + parametersMode: "typed", + parameters: payload.parameters.map((parameter) => { + let name = parameter.wireName; + let attempt = 1; + while (taken.has(name)) { + attempt += 1; + name = `${parameter.wireName}_${attempt}`; + } + if (name !== parameter.wireName) { + warn(`Parameter \`${parameter.wireName}\` of \`${payload.entryType}\` (typeVersion ${payload.typeVersion}) collides with another field of the payload, so it is called \`${name}\` there. It still posts as \`${parameter.wireName}\`.`); + } + taken.add(name); + return { ...parameter, name }; + }), + }; +}; +/** + * Assigns each payload its generated identifiers. A name depends only on its own + * payload's identity — never on which other operations are present — so adding + * an entry type or a new version of one never renames an existing payload. + */ +export const nameTypedEntryPayloads = (payloads, { warn = defaultWarn } = {}) => { + const taken = new Set(); + return payloads.map((payload) => { + const pascal = `${pascalCase(payload.entryType)}V${payload.typeVersion}`; + const camel = pascal.charAt(0).toLowerCase() + pascal.slice(1); + // Two entry types can want one identifier — `user-funds` and `user_funds` + // both reduce to `UserFunds`. The first in source order keeps the plain + // name; later ones are suffixed. + let suffix = ""; + let attempt = 1; + while (taken.has(`${pascal}${suffix}`)) { + attempt += 1; + suffix = `_${attempt}`; + } + if (suffix) { + warn(`Ledger Entry type \`${payload.entryType}\` (typeVersion ${payload.typeVersion}) generates the identifier \`${pascal}\`, which is already taken. Using \`${pascal}${suffix}\` instead.`); + } + taken.add(`${pascal}${suffix}`); + return { + ...payload, + ...nameParameters(payload, warn), + typeName: escapeIdentifier(`${pascal}${suffix}`), + builderName: escapeIdentifier(`${camel}${suffix}`), + }; + }); +}; +const BUILT_IN_SCALARS = ["ID", "String", "Boolean", "Int", "Float"]; +/** The names of every scalar in a schema, including the built-in ones. */ +export const collectScalarNames = (schema) => { + const names = new Set(BUILT_IN_SCALARS); + schema.definitions.forEach((definition) => { + if (definition.kind === Kind.SCALAR_TYPE_DEFINITION) { + names.add(definition.name.value); + } + }); + return names; +}; +/** A single-quoted TypeScript string literal, matching the codegen output style. */ +const quote = (value) => `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`; +const renderNamedType = (type, scalars) => { + const { value } = type.name; + // Anything that is not a scalar is an enum or an input object, both of which + // the typescript plugin emits into the same file under their schema name. + return scalars.has(value) ? `Scalars['${value}']['input']` : value; +}; +const renderMaybeType = (type, scalars) => { + if (type.kind === Kind.NON_NULL_TYPE) { + return renderInnerType(type.type, scalars); + } + return `${renderInnerType(type, scalars)} | null`; +}; +const renderInnerType = (type, scalars) => type.kind === Kind.LIST_TYPE + ? `Array<${renderMaybeType(type.type, scalars)}>` + : renderNamedType(type, scalars); +/** + * The TypeScript type of a value bound to a variable. Top-level nullability is + * carried by the optional marker instead: an unset field is omitted from the + * request, never serialized as `null`. + */ +const renderVariableType = (type, scalars) => type.kind === Kind.NON_NULL_TYPE + ? renderInnerType(type.type, scalars) + : renderInnerType(type, scalars); +const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/; +/** + * Wire names go into the generated type verbatim. A GraphQL name is always a + * valid TypeScript property key, so this only quotes defensively. + */ +const renderPropertyKey = (name) => IDENTIFIER.test(name) ? name : quote(name); +const FIELD_DOCS = { + ledgerIk: "The Idempotency Key of the Ledger to add this Ledger Entry to.", + posted: "ISO 8601 timestamp to post this Ledger Entry at.", + lines: "The Ledger Lines to create, for entry types whose lines the Schema does not fix.", +}; +const renderField = (field, scalars) => { + const optional = field.required ? "" : "?"; + const undefinable = field.required ? "" : " | undefined"; + const docs = FIELD_DOCS[field.name] + ? ` /** ${FIELD_DOCS[field.name]} */\n` + : ""; + return `${docs} ${renderPropertyKey(field.name)}${optional}: ${renderVariableType(field.type, scalars)}${undefinable};`; +}; +const renderParameters = (payload, scalars) => { + if (payload.parametersMode === "absent") { + // The operation posts no parameters, so the payload does not take any. + return []; + } + if (payload.parametersMode === "untyped") { + const { parametersType } = payload; + const type = parametersType + ? renderVariableType(parametersType, scalars) + : "Scalars['JSON']['input']"; + const required = parametersType?.kind === Kind.NON_NULL_TYPE ? "" : "?"; + const undefinable = required ? " | undefined" : ""; + return [ + [ + " /**", + " * This entry type's operation does not bind its parameters to typed", + " * variables, so they cannot be typed individually.", + " */", + ` parameters${required}: ${type}${undefinable};`, + ].join("\n"), + ]; + } + // Parameters sit alongside the common fields, in the order the operation + // declares them. + return payload.parameters.map((parameter) => { + const optional = parameter.required ? "" : "?"; + const undefinable = parameter.required ? "" : " | undefined"; + const renamed = parameter.name === parameter.wireName + ? "" + : ` /** Posts as \`${parameter.wireName}\`. */\n`; + return `${renamed} ${renderPropertyKey(parameter.name)}${optional}: ${renderVariableType(parameter.type, scalars)}${undefinable};`; + }); +}; +/** + * `value` when the caller must set it, and a conditional spread when they may + * not: an unset field is left out of the object rather than sent as `null`. + */ +const renderMember = ({ wireName, required, read, value = read, }) => ({ + wireName, + code: [ + required + ? ` ${wireName}: ${value},` + : ` ...(${read} !== undefined && { ${wireName}: ${value} }),`, + ], +}); +/** + * An `entry` key built from members that may each be absent — `parameters`, or a + * match object like `ledger`. When every member is optional the object is built + * first and spread only if it ended up with something in it, so an entry never + * carries an empty object the caller did not ask for. + */ +const renderObjectMember = ({ wireName, members, anyRequired, }) => { + if (anyRequired) { + return { + member: { + wireName, + code: [ + ` ${wireName}: {`, + ...members.map((member) => ` ${member}`), + " },", + ], + }, + }; + } + return { + prelude: [ + ` const ${wireName} = {`, + ...members.map((member) => ` ${member}`), + " };", + ].join("\n"), + member: { + wireName, + code: [` ...(Object.keys(${wireName}).length > 0 && { ${wireName} }),`], + }, + }; +}; +/** The `parameters` key of the emitted `entry` literal, if the payload has one. */ +const renderParametersMember = (payload) => { + if (payload.parametersMode === "absent") { + return {}; + } + if (payload.parametersMode === "untyped") { + return { + member: renderMember({ + wireName: "parameters", + required: payload.parametersType?.kind === Kind.NON_NULL_TYPE, + read: "input.parameters", + }), + }; + } + return renderObjectMember({ + wireName: "parameters", + // With a required parameter the object always has something in it; with + // none, it is built first and posted only if the caller set something. + anyRequired: payload.parameters.some((parameter) => parameter.required), + // Parameters keep the order the source operation declares them in, and each + // posts under its Schema name however it is spelled on the payload. + members: payload.parameters.map((parameter) => { + const key = renderPropertyKey(parameter.wireName); + const read = `input.${parameter.name}`; + return parameter.required + ? `${key}: ${read},` + : `...(${read} !== undefined && { ${key}: ${read} }),`; + }), + }); +}; +/** + * The `entry` keys the payload's own fields set. Fields are grouped by the + * `LedgerEntryInput` field they write, because a match object can be bound one + * key at a time — `ledger: { id: $id, ik: $ik }` is two payload fields writing + * one entry key, and they have to end up in one object rather than overwriting + * each other. + */ +const renderFieldMembers = (payload) => { + const groups = new Map(); + payload.fields.forEach((field) => { + groups.set(field.wireName, [...(groups.get(field.wireName) ?? []), field]); + }); + const members = []; + const preludes = []; + groups.forEach((fields, wireName) => { + if (fields.length === 1) { + const [field] = fields; + members.push(renderMember({ + wireName, + required: field.required, + read: `input.${field.name}`, + value: field.wireKey + ? `{ ${field.wireKey}: input.${field.name} }` + : `input.${field.name}`, + })); + return; + } + const { member, prelude } = renderObjectMember({ + wireName, + anyRequired: fields.some((field) => field.required), + members: fields.map((field) => { + // Grouping only happens for match objects, so every field has a key. + const key = renderPropertyKey(field.wireKey ?? field.name); + const read = `input.${field.name}`; + return field.required + ? `${key}: ${read},` + : `...(${read} !== undefined && { ${key}: ${read} }),`; + }), + }); + members.push(member); + if (prelude) { + preludes.push(prelude); + } + }); + return { members, preludes }; +}; +const renderBuilderBody = (payload) => { + const { member: parametersMember, prelude } = renderParametersMember(payload); + const fields = renderFieldMembers(payload); + const preludes = [...fields.preludes, ...(prelude ? [prelude] : [])]; + const members = [ + ...fields.members, + ...(parametersMember ? [parametersMember] : []), + { wireName: "type", code: [` type: ${quote(payload.entryType)},`] }, + { wireName: "typeVersion", code: [` typeVersion: ${payload.typeVersion},`] }, + ]; + // Keys are emitted in lexicographic order, which costs nothing to do here and + // makes two SDKs' requests comparable byte for byte. + const entry = [...members] + .sort((a, b) => a.wireName.localeCompare(b.wireName)) + .flatMap((member) => member.code); + const literal = (indent) => [ + "{", + " entry: {", + ...entry, + " },", + " ik: input.ik,", + "}", + ] + .map((line, index) => (index === 0 ? line : `${indent}${line}`)) + .join("\n"); + // An object whose members are all optional is built first, so the block body + // is only used where it earns its keep. + return preludes.length > 0 + ? ` => {\n${preludes.join("\n")}\n return ${literal(" ")};\n}` + : ` => (${literal("")})`; +}; +const renderPayload = (payload, scalars) => { + const description = `\`${payload.entryType}\` (typeVersion ${payload.typeVersion})`; + const ikType = payload.ikType + ? renderVariableType(payload.ikType, scalars) + : "Scalars['SafeString']['input']"; + const members = [ + ` /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */\n ik: ${ikType};`, + ...payload.fields.map((field) => renderField(field, scalars)), + ...renderParameters(payload, scalars), + ]; + return `/** + * Payload for the ${description} Ledger Entry, for use with \`addLedgerEntries\`. + * + * Derived from the \`${payload.operationName}\` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type ${payload.typeName} = { +${members.join("\n")} +}; + +/** Builds an \`addLedgerEntries\` entry for ${description}. */ +export const ${payload.builderName} = ( + input: ${payload.typeName}, +): AddLedgerEntryInput${renderBuilderBody(payload)};`; +}; +const renderRegistry = (payloads) => { + const entries = payloads.map((payload) => ` ${quote(`${payload.entryType}@${payload.typeVersion}`)}: ${payload.builderName},`); + return `/** + * Every typed payload builder, keyed by \`"@"\`. Useful + * when the entry type is only known at runtime. + */ +export const typedLedgerEntryBuilders = { +${entries.join("\n")} +} as const;`; +}; +/** + * Renders the typed batch entry section of a generated client. Returns an empty + * string when the input documents contain no typed entry operations. + */ +export const renderTypedEntryPayloads = (payloads, scalars) => { + if (payloads.length === 0) { + return ""; + } + const sections = [ + `/** + * Typed payloads for batching Ledger Entries with \`addLedgerEntries\`. + * + * Each payload is derived from the single-entry \`addLedgerEntry\` operation for + * one \`(type, typeVersion)\` pair, and builds an \`AddLedgerEntryInput\` you can + * mix freely with raw, untyped entry inputs in the same batch: + * + * \`\`\`ts + * await client.addLedgerEntries({ + * entries: [${payloads[0].builderName}({ ik, ledgerIk, parameters: { ... } })], + * }); + * \`\`\` + * + * A payload takes exactly what its source operation binds, so what you can set + * here is what that entry type accepts — no more. A field you do not set is + * left out of the request rather than sent as \`null\`. + */`, + ...payloads.map((payload) => renderPayload(payload, scalars)), + renderRegistry(payloads), + ]; + return `${sections.join("\n\n")}\n`; +}; +/** Derives, names and renders typed payloads in one step. */ +export const generateTypedEntryPayloads = ({ documents, schema, warn, }) => { + const payloads = nameTypedEntryPayloads(deriveTypedEntryPayloads(documents, { warn }), { warn }); + return renderTypedEntryPayloads(payloads, collectScalarNames(schema)); +}; diff --git a/dist/esm/types/src/client.d.ts.map b/dist/esm/types/src/client.d.ts.map index 073dca3..afad477 100644 --- a/dist/esm/types/src/client.d.ts.map +++ b/dist/esm/types/src/client.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE7D,OAAO,EACL,kBAAkB,EAClB,MAAM,IAAI,aAAa,EACxB,MAAM,2BAA2B,CAAC;AAInC,OAAO,EAAwB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAa1E,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AAE9D,KAAK,0BAA0B,GAAG;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAqIF,KAAK,yBAAyB,CAC5B,CAAC,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,kBAAkB,KAAK,GAAG,IACnE;IACF,MAAM,EAAE,0BAA0B,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,CAAC;IACX,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B,CAAC;AAEF,KAAK,0BAA0B,CAC7B,CAAC,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,kBAAkB,KAAK,GAAG,IACnE,CAAC,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,kBAAkB,KAAK,MAAM,CAAC,GACzE,CAAC,SAAS,cAAc,GACtB,CAAC,GACD,cAAc,GAAG,CAAC,GACpB,KAAK,CAAC;AAEV,eAAO,MAAM,oBAAoB,sBAErB,aAAa,WACZ,kBAAkB,KACxB,GAAG,2DAKP,0BAA0B,CAAC,CAAC,KAAG,2BAA2B,CAAC,CAe7D,CAAC"} \ No newline at end of file +{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE7D,OAAO,EACL,kBAAkB,EAClB,MAAM,IAAI,aAAa,EACxB,MAAM,2BAA2B,CAAC;AAUnC,OAAO,EAAwB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAa1E,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AAE9D,KAAK,0BAA0B,GAAG;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAyJF,KAAK,yBAAyB,CAC5B,CAAC,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,kBAAkB,KAAK,GAAG,IACnE;IACF,MAAM,EAAE,0BAA0B,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,CAAC;IACX,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B,CAAC;AAEF,KAAK,0BAA0B,CAC7B,CAAC,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,kBAAkB,KAAK,GAAG,IACnE,CAAC,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,kBAAkB,KAAK,MAAM,CAAC,GACzE,CAAC,SAAS,cAAc,GACtB,CAAC,GACD,cAAc,GAAG,CAAC,GACpB,KAAK,CAAC;AAEV,eAAO,MAAM,oBAAoB,sBAErB,aAAa,WACZ,kBAAkB,KACxB,GAAG,2DAKP,0BAA0B,CAAC,CAAC,KAAG,2BAA2B,CAAC,CAe7D,CAAC"} \ No newline at end of file diff --git a/dist/esm/types/src/errors.d.ts b/dist/esm/types/src/errors.d.ts index 2de044b..1797494 100644 --- a/dist/esm/types/src/errors.d.ts +++ b/dist/esm/types/src/errors.d.ts @@ -18,5 +18,36 @@ export type BadRequestErrorParams = FragmentErrorParams; export declare class BadRequestError extends FragmentError { constructor({ cause, message, code }: BadRequestErrorParams); } +/** + * Error details for a single Ledger Entry that was responsible for the batch's + * failure. + */ +export type LedgerEntryBatchError = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) of the Ledger Entry */ + ik: string; + /** The status code of error. For example, 'ledger_entry_too_many_lines'. */ + code: string; + /** The error message */ + message: string; + /** Whether or not the operation is retryable */ + retryable: boolean; +}; +export type AddLedgerEntriesErrorParams = FragmentErrorParams & { + /** The list of errors for each Ledger Entry that was responsible for the batch's failure. */ + errors?: LedgerEntryBatchError[]; +}; +/** + * Thrown when one or more Ledger Entries in an `addLedgerEntries` batch could + * not be added. + * + * `addLedgerEntries` adds a batch of Ledger Entries in one synchronous and + * atomic transaction, so either every entry was added or none were. Nothing was + * written when this is thrown. + */ +export declare class AddLedgerEntriesError extends FragmentError { + /** The list of errors for each Ledger Entry that was responsible for the batch's failure. */ + readonly errors: LedgerEntryBatchError[]; + constructor({ cause, message, code, errors }: AddLedgerEntriesErrorParams); +} export {}; //# sourceMappingURL=errors.d.ts.map \ No newline at end of file diff --git a/dist/esm/types/src/errors.d.ts.map b/dist/esm/types/src/errors.d.ts.map index c9f22ca..c9eddc0 100644 --- a/dist/esm/types/src/errors.d.ts.map +++ b/dist/esm/types/src/errors.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,KAAK,mBAAmB,GAAG;IACzB,KAAK,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;gBAEX,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,mBAAmB;CAmBxE;AAED,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAEzD,qBAAa,aAAc,SAAQ,aAAa;gBAClC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,sBAAsB;CAGvD;AAED,MAAM,MAAM,qBAAqB,GAAG,mBAAmB,CAAC;AAExD,qBAAa,eAAgB,SAAQ,aAAa;gBACpC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,qBAAqB;CAG5D"} \ No newline at end of file +{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,KAAK,mBAAmB,GAAG;IACzB,KAAK,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;gBAEX,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,mBAAmB;CAmBxE;AAED,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,CAAC;AAEzD,qBAAa,aAAc,SAAQ,aAAa;gBAClC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,sBAAsB;CAGvD;AAED,MAAM,MAAM,qBAAqB,GAAG,mBAAmB,CAAC;AAExD,qBAAa,eAAgB,SAAQ,aAAa;gBACpC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,qBAAqB;CAG5D;AAED;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,6GAA6G;IAC7G,EAAE,EAAE,MAAM,CAAC;IACX,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG,mBAAmB,GAAG;IAC9D,6FAA6F;IAC7F,MAAM,CAAC,EAAE,qBAAqB,EAAE,CAAC;CAClC,CAAC;AAEF;;;;;;;GAOG;AACH,qBAAa,qBAAsB,SAAQ,aAAa;IACtD,6FAA6F;IAC7F,QAAQ,CAAC,MAAM,EAAE,qBAAqB,EAAE,CAAC;gBAE7B,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,2BAA2B;CAQ1E"} \ No newline at end of file diff --git a/dist/esm/types/src/typedBatchEntries.d.ts b/dist/esm/types/src/typedBatchEntries.d.ts new file mode 100644 index 0000000..2656499 --- /dev/null +++ b/dist/esm/types/src/typedBatchEntries.d.ts @@ -0,0 +1,120 @@ +/** + * Derives typed batch Ledger Entry payloads from the per-entry-type + * `addLedgerEntry` operations the Fragment CLI generates for a Schema. + * + * `addLedgerEntries` takes one list of one input type (`AddLedgerEntryInput`), + * and `LedgerEntryInput.parameters` is an opaque `JSON` scalar, so GraphQL + * cannot express the parameter types of an individual entry in a batch. The + * single-entry operations can: they carry the entry type as a string literal and + * bind everything the caller may set to a typed operation variable. This module + * reads those operations and emits a typed payload per `(type, typeVersion)` + * pair, each of which builds an `AddLedgerEntryInput` for `addLedgerEntries`. + * + * What a payload exposes is fixed by `LedgerEntryInput`, not derived from the + * operation: every payload carries the same common fields (§2.3a), so the CLI + * changing which fields it binds never moves a payload's surface. + * + * Two things follow. A value the operation fixes to a literal is encoded in the + * Schema, so the API derives it and the payload neither exposes nor re-posts it. + * And an entry type whose Ledger Lines the caller supplies gets no payload at + * all, since `lines` is not a common field — post those with a raw + * `AddLedgerEntryInput`, which `addLedgerEntries` accepts in the same batch. + */ +import { type DocumentNode, type TypeNode } from "graphql"; +/** A value the caller supplies, typed by the variable the operation binds. */ +export type BoundValue = { + /** The variable's declared type, which is where the payload's type comes from. */ + type: TypeNode; + /** True when the variable's type is non-null. */ + required: boolean; +}; +/** A single parameter of a typed entry payload. */ +export type TypedEntryParameter = { + /** The parameter name from the Schema. Goes on the wire verbatim. */ + wireName: string; + /** + * The field name on the generated payload. The wire name unless it collided + * with a common field or an earlier parameter (§2.5). + */ + name: string; +} & BoundValue; +/** + * An entry field a typed payload lets the caller set, derived from the source + * operation binding it to a variable. + */ +export type TypedEntryField = { + /** The field name on the generated payload, for a value the caller supplies. */ + name: string; + /** The `LedgerEntryInput` field it sets. */ + wireName: string; + /** + * Set when the value is a match key nested inside the wire field — `ledgerIk` + * sets `ledger: { ik }`, so this is `"ik"` there. + */ + wireKey?: string; +} & BoundValue; +/** + * How the source operation exposes `parameters`, if at all: as an inline object + * literal, so each parameter is typed individually; bound to a variable, so the + * payload falls back to an untyped map; or not at all, so the caller cannot set + * parameters. Each carries only what that case has. + */ +export type PayloadParameters = { + parametersMode: "typed"; + parameters: TypedEntryParameter[]; +} | { + parametersMode: "untyped"; + parametersType: TypeNode | undefined; +} | { + parametersMode: "absent"; +}; +/** A typed payload for one `(entry type, typeVersion)` pair. */ +export type TypedEntryPayload = { + entryType: string; + typeVersion: number; + /** The operation the payload was derived from. Informative only. */ + operationName: string; + /** Entry fields the caller may set, in the operation's source order. */ + fields: TypedEntryField[]; + /** The declared type of the entry's own `ik`, when bound to a variable. */ + ikType?: TypeNode; +} & PayloadParameters; +/** A payload with its generated TypeScript identifiers assigned. */ +export type NamedTypedEntryPayload = TypedEntryPayload & { + /** Name of the exported payload type, e.g. `UserFundsAccountV1`. */ + typeName: string; + /** Name of the exported builder function, e.g. `userFundsAccountV1`. */ + builderName: string; +}; +type Warn = (message: string) => void; +/** + * Derives one payload per `(type, typeVersion)` pair found in the given + * documents. Operations that are not typed entry operations are skipped + * silently. Duplicate identities are deduplicated, first occurrence winning. + */ +export declare const deriveTypedEntryPayloads: (documents: ReadonlyArray, { warn }?: { + warn?: Warn; +}) => TypedEntryPayload[]; +/** + * Assigns each payload its generated identifiers. A name depends only on its own + * payload's identity — never on which other operations are present — so adding + * an entry type or a new version of one never renames an existing payload. + */ +export declare const nameTypedEntryPayloads: (payloads: ReadonlyArray, { warn }?: { + warn?: Warn; +}) => NamedTypedEntryPayload[]; +/** The names of every scalar in a schema, including the built-in ones. */ +export declare const collectScalarNames: (schema: DocumentNode) => Set; +/** + * Renders the typed batch entry section of a generated client. Returns an empty + * string when the input documents contain no typed entry operations. + */ +export declare const renderTypedEntryPayloads: (payloads: ReadonlyArray, scalars: ReadonlySet) => string; +/** Derives, names and renders typed payloads in one step. */ +export declare const generateTypedEntryPayloads: ({ documents, schema, warn, }: { + documents: ReadonlyArray; + schema: DocumentNode; + warn?: Warn; +}) => string; +export {}; +//# sourceMappingURL=typedBatchEntries.d.ts.map \ No newline at end of file diff --git a/dist/esm/types/src/typedBatchEntries.d.ts.map b/dist/esm/types/src/typedBatchEntries.d.ts.map new file mode 100644 index 0000000..ee5f327 --- /dev/null +++ b/dist/esm/types/src/typedBatchEntries.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"typedBatchEntries.d.ts","sourceRoot":"","sources":["../../../../src/typedBatchEntries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAGL,KAAK,YAAY,EAKjB,KAAK,QAAQ,EAGd,MAAM,SAAS,CAAC;AAEjB,8EAA8E;AAC9E,MAAM,MAAM,UAAU,GAAG;IACvB,kFAAkF;IAClF,IAAI,EAAE,QAAQ,CAAC;IACf,iDAAiD;IACjD,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,mDAAmD;AACnD,MAAM,MAAM,mBAAmB,GAAG;IAChC,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,UAAU,CAAC;AAEf;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,UAAU,CAAC;AAEf;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,cAAc,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,mBAAmB,EAAE,CAAA;CAAE,GAC9D;IAAE,cAAc,EAAE,SAAS,CAAC;IAAC,cAAc,EAAE,QAAQ,GAAG,SAAS,CAAA;CAAE,GACnE;IAAE,cAAc,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEjC,gEAAgE;AAChE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,2EAA2E;IAC3E,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB,GAAG,iBAAiB,CAAC;AAEtB,oEAAoE;AACpE,MAAM,MAAM,sBAAsB,GAAG,iBAAiB,GAAG;IACvD,oEAAoE;IACpE,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,KAAK,IAAI,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAoStC;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,cACxB,cAAc,YAAY,CAAC,aACd;IAAE,IAAI,CAAC,EAAE,IAAI,CAAA;CAAE,KACtC,iBAAiB,EAsEnB,CAAC;AAqDF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,aACvB,cAAc,iBAAiB,CAAC,aAClB;IAAE,IAAI,CAAC,EAAE,IAAI,CAAA;CAAE,KACtC,sBAAsB,EA6BxB,CAAC;AAIF,0EAA0E;AAC1E,eAAO,MAAM,kBAAkB,WAAY,YAAY,KAAG,IAAI,MAAM,CAQnE,CAAC;AA+WF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,aACzB,cAAc,sBAAsB,CAAC,WACtC,YAAY,MAAM,CAAC,KAC3B,MA4BF,CAAC;AAEF,6DAA6D;AAC7D,eAAO,MAAM,0BAA0B,iCAIpC;IACD,SAAS,EAAE,cAAc,YAAY,CAAC,CAAC;IACvC,MAAM,EAAE,YAAY,CAAC;IACrB,IAAI,CAAC,EAAE,IAAI,CAAC;CACb,KAAG,MAMH,CAAC"} \ No newline at end of file diff --git a/scripts/update-test-schema.sh b/scripts/update-test-schema.sh index a128dc0..d57228f 100755 --- a/scripts/update-test-schema.sh +++ b/scripts/update-test-schema.sh @@ -14,5 +14,22 @@ yarn fragment-node-client-codegen \ -i tests/fixtures/test-schema-queries.graphql \ -o tests/fixtures/generated-test-client.ts -echo "Done! Don't forget to commit the updated files." +# tests/template-schema/ is vendored from fragment-dev/graphql-queries. Its +# .graphql files are generated there by the Fragment CLI and copied in as-is, so +# they are never regenerated here — only the clients built from them are. +echo "Generating SDK from the template schema queries..." +yarn fragment-node-client-codegen \ + -i tests/template-schema/queries.graphql \ + -o tests/fixtures/generated-template-client.ts + +yarn fragment-node-client-codegen \ + -i tests/template-schema/queries.runtime-args.graphql \ + -o tests/fixtures/generated-template-runtime-args-client.ts +# Hand-written operations covering shapes the CLI does not generate. +echo "Generating SDK from the edge case queries..." +yarn fragment-node-client-codegen \ + -i tests/fixtures/edge-case-queries.graphql \ + -o tests/fixtures/generated-edge-case-client.ts + +echo "Done! Don't forget to commit the updated files." diff --git a/spec.md b/spec.md new file mode 100644 index 0000000..9675fe0 --- /dev/null +++ b/spec.md @@ -0,0 +1,348 @@ +# Typed Batch Ledger Entries — SDK Specification + +Status: draft +Applies to: `fragment-python`, `node-client`, `fragment-go`, `fragment-ruby` + +"MUST", "MUST NOT", "SHOULD", and "MAY" are used in the RFC 2119 sense. +Sections marked **[Normative]** are binding and conformance-tested. Sections +marked **[Informative]** are explanatory and bind nothing. + +--- + +## 1. Problem and scope + +`addLedgerEntries(entries: [AddLedgerEntryInput!]!)` commits a batch of Ledger +Entries atomically. `AddLedgerEntryInput` is `{ entry: LedgerEntryInput!, ik: +SafeString! }`, and `LedgerEntryInput.parameters` is an opaque `JSON` scalar. + +A batch therefore takes one list of one input type, and GraphQL cannot express +the parameter types of an individual entry within it. Callers are left passing +untyped maps. + +Each SDK closes this gap at code-generation time by deriving typed payloads from +the per-entry-type `addLedgerEntry` operations already generated for a Schema. +Those operations encode both missing facts: the entry type as a string literal, +and each parameter bound to a typed operation variable. + +**In scope:** the rules for deriving typed payloads from `.graphql` operations, +and the JSON those payloads produce. + +**Out of scope:** the shape of each language's API. Classes, discriminated +unions, interfaces, and structs are all conforming, and SDKs SHOULD choose +whatever is idiomatic. Nothing in this document constrains the surface a caller +touches, only what goes on the wire and what gets derived from what. + +--- + +## 2. Derivation **[Normative]** + +### 2.1 Recognition + +An operation is a **typed entry operation** if and only if all hold: + +1. It is a `mutation`. +2. It has a name. +3. Its selection set contains exactly one selection. +4. That selection is a field named `addLedgerEntry`. +5. That field has an `entry` argument whose value is an inline object literal. +6. That object has a `type` field whose value is a **string literal**. + +An operation that fails any condition MUST be skipped silently. It MUST NOT be +an error. This is what excludes the SDKs' own `addLedgerEntry` and +`addLedgerEntryRuntime`, whose `type` is a variable. + +Generators MUST NOT require a particular operation name. Naming conventions +(`PostAuthCapture`, etc.) are informative only. + +### 2.2 Identity + +A payload's identity is the pair **(`type`, `typeVersion`)**. + +`typeVersion` is the integer literal in the `entry` object if present, otherwise +**1** (§2.5). Identity MUST NOT be the entry type alone: the same type at two +versions has different parameter sets, and collapsing them drops one and posts +the wrong version. Because unpinned normalises to 1, an unpinned operation and +one pinning `typeVersion: 1` share an identity and describe the same model. + +Two operations MAY map to the same identity — for example a second operation +with a different selection set. Because the Fragment CLI and API guarantee that +no two Ledger Entries share a `(type, typeVersion)` pair, such operations +necessarily declare the same parameters. Generators MUST deduplicate them; the +first occurrence in input order wins. Generators MAY additionally error if the +parameter sets differ, since that indicates the `.graphql` is stale relative to +the Schema. + +### 2.3 Parameters + +If the `entry` object has a `parameters` field whose value is an inline object +literal, each of its fields is considered in source order: + +- If the field's value is **not** a variable reference, it MUST be skipped. The + value is fixed by the operation and MUST NOT become a caller-supplied field. +- Otherwise it yields a parameter whose **wire name** is the field name, + verbatim. +- The parameter's type and required-ness come from the matching **variable + definition**, not from the field name. A non-null variable type + (`String!`) is required; anything else is optional. + +If `parameters` is absent, or is a variable rather than an inline object, the +payload has no typed parameters. Generators SHOULD still emit a payload for the +entry type, with parameters falling back to the language's untyped map. + +### 2.3a Common fields + +Everything above is derived from the source operation, because only the +operation knows it. The rest of a payload is fixed by `LedgerEntryInput` and does +not vary by entry type. + +A payload MUST expose all of: + +| Field | Notes | +| --- | --- | +| `ik` | argument of `addLedgerEntry`, not a field of `entry` | +| `ledger` | as an idempotency key, e.g. a `ledger_ik` field | +| `posted` | | +| `description` | | +| `tags` | | +| `groups` | | +| `conditions` | | + +`lines` is the one `LedgerEntryInput` field a payload MUST NOT expose, because it +cannot be combined with an entry that has a `type`. `type`, `typeVersion` and +`parameters` are derived (§2.1, §2.2, §2.3) and MUST NOT be caller-supplied. + +**Do not derive this set from the operation.** A source operation binds only the +entry fields the CLI chose to expose, and that choice has already changed between +CLI versions: one generation binds `tags`, `groups` and `conditions` while +another binds `typeVersion` instead, and neither binds `description`. The +operation is a codegen input, never the transport. A typed payload travels as an +`AddLedgerEntryInput` on `addLedgerEntries`, so what the operation binds places no +limit on what the payload may carry. Deriving the set would invent a restriction +the API does not have and would move a payload's surface whenever the CLI +changed. + +When `LedgerEntryInput` gains a field, this list is what has to be updated, in +one place, for all four SDKs. + +### 2.4 Ordering + +Parameters MUST retain the order in which they appear in the source +`parameters: {...}` literal. Generators MUST NOT reorder them — not +alphabetically, and not required-first. All four generators read the same +document, so source order is the only ordering they can agree on without +coordination. + +### 2.5 Naming and escaping + +The payload's name is derived from the entry type and MUST always carry the +version it resolves to. + +A name therefore depends only on that payload's own identity, never on which +other operations are in the input. Suffixing only when versions collide would +mean adding a second version later renames the first, breaking every existing +call site for a purely additive Schema change (§2.6). + +An operation that pins no `typeVersion` MUST be normalised to **1** — for its +identity (§2.2), its name, and its wire payload alike. An entry with no +`typeVersion` resolves to version 1 server-side, never to the latest version, so +`1` and unspecified are equivalent and the SDK may state the resolved value. +Normalising early is what keeps the name and the wire from disagreeing: a model +called `V1` that posted no version at all would mislead every reader of it. + +This is the one case where an SDK supplies a value the operation did not state. +It is permitted only because the resolution rule is fixed; §3.2's requirement to +omit unset fields still applies to every other field. + +Local identifiers MAY be escaped however the language requires — reserved words, +casing, export rules, collisions with SDK-provided members. Escaping is local. +The wire name MUST remain the verbatim parameter name from the Schema (§3.3). + +Escaping can make two parameters land on one identifier: `user_id` and `userId` +both reduce to `user_id`. Within a payload, local field names MUST be unique. +The first occurrence in source order keeps the plain name and later ones are +suffixed, the same way model names are disambiguated above. Escaping a +parameter this way MUST NOT change its wire name, so each still carries its own +value. SDKs SHOULD warn when it happens, because the caller ends up using a name +they did not choose. + +A language whose declaration form silently accepts a duplicate is the dangerous +case. Python is one: a repeated pydantic field declaration keeps the last, and +both wire keys then take a single value. + +### 2.6 Backward compatibility + +Code generation MUST be backward-compatible across additive and +order-only Schema changes. Specifically, none of the following may break caller +source code: + +- **Adding a new entry type**, or a new version of an existing one. +- **Reordering the parameters** of an existing entry type (§2.4). +- **Adding a new optional parameter** to an existing entry type. + +Renaming or removing a parameter, and removing an entry type, are breaking +Schema changes and are outside this guarantee. + +Two consequences worth stating, since neither is obvious: + +- A generated identifier MUST depend only on its own payload's identity, never + on which other operations are present in the input (§2.5). Context-sensitive + naming turns an addition into a rename. +- Parameters MUST be caller-supplied **by name**, not by position. Python, Node, + and Ruby get this from the language. Go permits unkeyed struct literals, where + reordering two same-typed parameters silently swaps values rather than failing + to compile; the Go SDK MUST require keyed literals. + +**Verification:** not expressible in the shared fixtures, since generated source +is language-specific. Each SDK MUST instead carry a **snapshot test** over its +generated output, so any change to generated identifiers or signatures surfaces +as a reviewable diff rather than silently. Snapshots are language-specific and +live in the SDK repo, not here. + +--- + +## 3. Wire contract **[Normative]** + +### 3.1 Shape + +A batch serializes as: + +```json +{"entries": [ + {"ik": "", + "entry": {"ledger": {"ik": ""}, + "type": "", + "typeVersion": 2, + "posted": "", + "description": "", + "parameters": {"": ""}, + "tags": [], "groups": [], "conditions": []}} +]} +``` + +Entry order MUST be preserved. The API returns results in the same order. + +### 3.2 Omission + +A field the caller did not set MUST be omitted. It MUST NOT be serialized as +`null`. `null` is a meaningful value the caller may pass deliberately, and the +two MUST remain distinguishable. + +Language hazards: Go's `omitempty` conflates zero values (`""`, `0`, `false`) +with unset and MUST NOT be relied on — use pointers or a custom marshaller. +TypeScript's `InputMaybe = T | null` invites wire-visible `null` and MUST NOT +be used for these types; use `?:` with `| undefined`. + +### 3.3 Names + +Schema parameter names go on the wire verbatim. Any local escaping (§2.5) MUST +be reversed during serialization via an explicit mapping. No SDK may camelCase, +snake_case, or otherwise normalize a parameter name on the wire. + +### 3.4 Equivalence profile + +**Baseline (required):** *semantic* equivalence. For the same logical batch, +every SDK MUST produce the same key set, nesting, and values, with unset fields +omitted. Key ordering is unconstrained. + +**Strict (optional):** *byte* equivalence. Additionally requires: + +- **Canonical key order:** keys are emitted in lexicographic order by wire name + at every level, *except* `parameters`, which uses source order per §2.4. + Chosen because it is mechanical and needs no coordination beyond this + sentence. `fragment-python` satisfies it incidentally, since ariadne-codegen + emits input-type fields alphabetically — an artifact, not a guarantee, so it + is fixture-enforced rather than assumed. +- **Encoder settings:** Python `json.dumps(ensure_ascii=False, + separators=(',', ':'))`; Go a replacement `graphql.Client` using + `SetEscapeHTML(false)`, because `encoding/json` escapes `<`, `>`, `&` even + inside a custom `MarshalJSON`. +- **No float-typed parameters** — Go renders `float64(1)` as `1`, Python as + `1.0`. Fragment already binds `Int64`/`Int96` to strings, so this is mostly + moot. + +> **Open decision.** The strict profile conflicts with the Node design in which +> the generated types describe the wire shape directly and the caller's object +> literal *is* the request body — key order there is caller-controlled and can +> only be canonicalized by adding the runtime transform that design exists to +> avoid. Adopt strict only if byte-equality is genuinely required (request +> signing, cross-SDK golden diffs). The baseline is recommended. + +### 3.5 Mixing + +An SDK MUST allow raw, untyped entry inputs and typed payloads in the same +batch. Raw inputs bypass §3.2 — a caller who explicitly passes `null` gets +`null` — and this asymmetry is accepted. + +### 3.6 Accepting and serialising + +Everything a batch method accepts MUST serialise. Widening the accepted type +past what the transport actually converts turns a compile-time rejection into a +runtime failure, and the wider signature is what makes the failure reachable. + +Language hazard: the Python SDK's transport recurses into request variables with +`isinstance(value, list)`, so an argument typed as a broader sequence has to be +narrowed to a list before it reaches the encoder. A tuple otherwise satisfies +the signature, skips conversion, and reaches the JSON encoder holding model +objects. + +--- + +## 4. Batch semantics **[Normative]** + +- The batch is **atomic**: either every entry commits or none do. On error, + nothing was written, and callers MUST NOT implement partial-batch + reconciliation. +- Idempotency keys are **per entry**, not per batch. `isIkReplay` is reported + per result, so a retried partially-replayed batch reports which entries had + already committed. +- The response is the union `AddLedgerEntriesResult | AddLedgerEntriesError | + BadRequestError | InternalError`, discriminated on `__typename`. SDKs MUST + require callers to narrow before reading `results`. +- `AddLedgerEntriesError` reports per-entry failures. Alongside the usual `code`, + `message` and `retryable` it carries `errors`, one element per failing entry, + each with the `ik` that identifies it. An SDK MUST surface that list rather + than collapsing it to the top-level message, since the `ik` is what tells a + caller which entry to fix. + +--- + +## 5. Conformance + +Each SDK MUST implement a runner over the shared fixtures in +`spec/conformance/`. A fixture is a directory containing: + +| File | Purpose | +| --- | --- | +| `input.graphql` | operations fed to the generator | +| `case.json` | the logical batch: which payloads, with which values | +| `expected.json` | the JSON the SDK must produce | + +`case.json` names payloads by **entry type and version**, not by generated +identifier, so it stays language-neutral. + +| Fixture | Asserts | +| --- | --- | +| `001-basic` | recognition, nesting, parameter extraction | +| `002-type-versions` | §2.2 — v1 and v2 produce distinct payloads | +| `003-reserved-names` | §2.5/§3.3 — escaping never reaches the wire | +| `004-param-order` | §2.4 — source order, not required-first | +| `005-unset-omitted` | §3.2 — omitted, never `null` | +| `006-non-ascii` | §3.4 — encoding agreement | + +Comparison is by parsed-JSON equality under the baseline profile, and by byte +equality under the strict profile. + +--- + +## 6. Known gaps **[Informative]** + +- No behavior here has been verified against a live API. Server tolerance for an + entry object with `lines` absent and `type` present is untested in every SDK. +- `addLedgerEntries` exists in the published schema but is absent from the Ruby + SDK's pinned `fragment.schema.json`, where `graphql-client` validates at parse + time and so fails at client construction. +- In Go and Ruby the operation documents are synced from + `fragment-dev/graphql-queries`; the batch operation must land there or be + overwritten by the next sync. +- The built-in single-entry `addLedgerEntry` operation declares no `$conditions` + variable, so it cannot express everything a typed payload carries. diff --git a/src/bin.ts b/src/bin.ts index 2e402b7..5894cca 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -4,6 +4,9 @@ import yargs from "yargs"; import * as path from "path"; import { generate, cliError } from "@graphql-codegen/cli"; import type { Types } from "@graphql-codegen/plugin-helpers"; +import type { DocumentNode } from "graphql"; + +import { generateTypedEntryPayloads } from "./typedBatchEntries.js"; const argv = yargs(process.argv.slice(2)) .options({ @@ -39,51 +42,101 @@ if (!allowedInputExtensions.includes(path.extname(argv.input))) { ); } -generate( - { - schema: "https://api.us-west-2.fragment.dev/schema.graphql", - documents: argv.input, - config: { - scalars: { - AlphaNumericString: "string", - Date: "string", - DateTime: "string", - Int64: "string", - Int96: "string", - JSON: "Record", - JSONObject: "Record", - LastMoment: "string", - ParameterizedString: "string", - Period: "string", - SafeString: "string", - UTCOffset: "string", +/** + * Runs codegen for one input and writes the client, with the typed batch entry + * payloads appended. + * + * The payloads are derived from the same operations the plugins run over, so a + * document transform hands them (and the schema) to `sources` instead of loading + * either a second time. The transform leaves the documents themselves alone. + */ +const generateClient = async ({ + input, + outputFilename, +}: { + input: string; + outputFilename: string; +}): Promise => { + const sources: { documents: DocumentNode[]; schema: DocumentNode }[] = []; + + const [fileOutput]: Types.FileOutput[] = await generate( + { + schema: "https://api.us-west-2.fragment.dev/schema.graphql", + documents: input, + config: { + scalars: { + AlphaNumericString: "string", + Date: "string", + DateTime: "string", + Int64: "string", + Int96: "string", + JSON: "Record", + JSONObject: "Record", + LastMoment: "string", + ParameterizedString: "string", + Period: "string", + SafeString: "string", + UTCOffset: "string", + }, + gqlImport: "@fragment-dev/node-client#gql", }, - gqlImport: "@fragment-dev/node-client#gql", - }, - generates: { - [path.join(process.cwd(), argv.outputFilename as string)]: { - plugins: [ - "typescript", - "typescript-operations", - "typescript-graphql-request", - { - add: { - content: - "/* This file is auto-generated by @fragment-dev/node-client. Don't edit it directly. */", + generates: { + [path.join(process.cwd(), outputFilename)]: { + documentTransforms: [ + { + transform: ({ documents, schema }) => { + sources.push({ + documents: documents + .map((document) => document.document) + .filter((document): document is DocumentNode => !!document), + schema, + }); + return documents; + }, }, - }, - ], + ], + plugins: [ + "typescript", + "typescript-operations", + "typescript-graphql-request", + { + add: { + content: + "/* This file is auto-generated by @fragment-dev/node-client. Don't edit it directly. */", + }, + }, + ], + }, }, }, - }, - false -) - .then(([fileOutput]: Types.FileOutput[]) => { - const output = fileOutput.content.replace( - /import .* from 'graphql-request'/g, - "import { GraphQLClient, RequestOptions } from '@fragment-dev/node-client'" + false + ); + + const output = fileOutput.content.replace( + /import .* from 'graphql-request'/g, + "import { GraphQLClient, RequestOptions } from '@fragment-dev/node-client'" + ); + + const [source] = sources; + if (!source) { + console.warn( + "[@fragment-dev/node-client] Could not read the input operations, so no typed batch Ledger Entry payloads were generated." ); - writeFileSync(fileOutput.filename, output, "utf-8"); + } + const typedBatchEntries = source ? generateTypedEntryPayloads(source) : ""; + + writeFileSync( + fileOutput.filename, + typedBatchEntries ? `${output}\n${typedBatchEntries}` : output, + "utf-8" + ); +}; + +generateClient({ + input: argv.input, + outputFilename: argv.outputFilename as string, +}) + .then(() => { process.exit(0); }) .catch((error) => { diff --git a/src/client.ts b/src/client.ts index f053784..d3e304c 100644 --- a/src/client.ts +++ b/src/client.ts @@ -7,7 +7,13 @@ import { } from "../generated/generated.js"; import { version } from "../generated/version.js"; import { getToken } from "./getToken.js"; -import { BadRequestError, FragmentError, InternalError } from "./errors.js"; +import { + AddLedgerEntriesError, + BadRequestError, + FragmentError, + InternalError, + type LedgerEntryBatchError, +} from "./errors.js"; import { DEFAULT_RETRY_CONFIG, type RetryConfig } from "./retryConfig.js"; type RetryableErrorObject = T & { retryable: boolean }; @@ -108,6 +114,25 @@ const createRequestWrapper = } else { throw err; } + break; + } + case "AddLedgerEntriesError": { + // The errors for each Ledger Entry responsible for the failure are + // what tell a caller which entries to fix, so they are surfaced + // rather than collapsed into the top-level message. + const err = new AddLedgerEntriesError({ + code, + cause: (data as any)[graphQlOperationName], + message, + errors: ((data as any)[graphQlOperationName].errors ?? + []) as LedgerEntryBatchError[], + }); + if (!retryable) { + bail(err); + } else { + throw err; + } + break; } case "BadRequestError": { const err = new BadRequestError({ @@ -120,6 +145,7 @@ const createRequestWrapper = } else { throw err; } + break; } default: if (typeName.endsWith("Error")) { diff --git a/src/errors.ts b/src/errors.ts index 62b631e..07aeba4 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -48,3 +48,45 @@ export class BadRequestError extends FragmentError { super({ cause, message, code: code ?? "bad_request_error" }); } } + +/** + * Error details for a single Ledger Entry that was responsible for the batch's + * failure. + */ +export type LedgerEntryBatchError = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) of the Ledger Entry */ + ik: string; + /** The status code of error. For example, 'ledger_entry_too_many_lines'. */ + code: string; + /** The error message */ + message: string; + /** Whether or not the operation is retryable */ + retryable: boolean; +}; + +export type AddLedgerEntriesErrorParams = FragmentErrorParams & { + /** The list of errors for each Ledger Entry that was responsible for the batch's failure. */ + errors?: LedgerEntryBatchError[]; +}; + +/** + * Thrown when one or more Ledger Entries in an `addLedgerEntries` batch could + * not be added. + * + * `addLedgerEntries` adds a batch of Ledger Entries in one synchronous and + * atomic transaction, so either every entry was added or none were. Nothing was + * written when this is thrown. + */ +export class AddLedgerEntriesError extends FragmentError { + /** The list of errors for each Ledger Entry that was responsible for the batch's failure. */ + readonly errors: LedgerEntryBatchError[]; + + constructor({ cause, message, code, errors }: AddLedgerEntriesErrorParams) { + super({ + cause, + message, + code: code ?? "ledger_entry_batch_operation_failed", + }); + this.errors = errors ?? []; + } +} diff --git a/src/typedBatchEntries.ts b/src/typedBatchEntries.ts new file mode 100644 index 0000000..1624d74 --- /dev/null +++ b/src/typedBatchEntries.ts @@ -0,0 +1,994 @@ +/** + * Derives typed batch Ledger Entry payloads from the per-entry-type + * `addLedgerEntry` operations the Fragment CLI generates for a Schema. + * + * `addLedgerEntries` takes one list of one input type (`AddLedgerEntryInput`), + * and `LedgerEntryInput.parameters` is an opaque `JSON` scalar, so GraphQL + * cannot express the parameter types of an individual entry in a batch. The + * single-entry operations can: they carry the entry type as a string literal and + * bind everything the caller may set to a typed operation variable. This module + * reads those operations and emits a typed payload per `(type, typeVersion)` + * pair, each of which builds an `AddLedgerEntryInput` for `addLedgerEntries`. + * + * What a payload exposes is fixed by `LedgerEntryInput`, not derived from the + * operation: every payload carries the same common fields (§2.3a), so the CLI + * changing which fields it binds never moves a payload's surface. + * + * Two things follow. A value the operation fixes to a literal is encoded in the + * Schema, so the API derives it and the payload neither exposes nor re-posts it. + * And an entry type whose Ledger Lines the caller supplies gets no payload at + * all, since `lines` is not a common field — post those with a raw + * `AddLedgerEntryInput`, which `addLedgerEntries` accepts in the same batch. + */ +import { + Kind, + parseType, + type DocumentNode, + type ListTypeNode, + type NamedTypeNode, + type ObjectValueNode, + type OperationDefinitionNode, + type TypeNode, + type VariableDefinitionNode, + type VariableNode, +} from "graphql"; + +/** A value the caller supplies, typed by the variable the operation binds. */ +export type BoundValue = { + /** The variable's declared type, which is where the payload's type comes from. */ + type: TypeNode; + /** True when the variable's type is non-null. */ + required: boolean; +}; + +/** A single parameter of a typed entry payload. */ +export type TypedEntryParameter = { + /** The parameter name from the Schema. Goes on the wire verbatim. */ + wireName: string; + /** + * The field name on the generated payload. The wire name unless it collided + * with a common field or an earlier parameter (§2.5). + */ + name: string; +} & BoundValue; + +/** + * An entry field a typed payload lets the caller set, derived from the source + * operation binding it to a variable. + */ +export type TypedEntryField = { + /** The field name on the generated payload, for a value the caller supplies. */ + name: string; + /** The `LedgerEntryInput` field it sets. */ + wireName: string; + /** + * Set when the value is a match key nested inside the wire field — `ledgerIk` + * sets `ledger: { ik }`, so this is `"ik"` there. + */ + wireKey?: string; +} & BoundValue; + +/** + * How the source operation exposes `parameters`, if at all: as an inline object + * literal, so each parameter is typed individually; bound to a variable, so the + * payload falls back to an untyped map; or not at all, so the caller cannot set + * parameters. Each carries only what that case has. + */ +export type PayloadParameters = + | { parametersMode: "typed"; parameters: TypedEntryParameter[] } + | { parametersMode: "untyped"; parametersType: TypeNode | undefined } + | { parametersMode: "absent" }; + +/** A typed payload for one `(entry type, typeVersion)` pair. */ +export type TypedEntryPayload = { + entryType: string; + typeVersion: number; + /** The operation the payload was derived from. Informative only. */ + operationName: string; + /** Entry fields the caller may set, in the operation's source order. */ + fields: TypedEntryField[]; + /** The declared type of the entry's own `ik`, when bound to a variable. */ + ikType?: TypeNode; +} & PayloadParameters; + +/** A payload with its generated TypeScript identifiers assigned. */ +export type NamedTypedEntryPayload = TypedEntryPayload & { + /** Name of the exported payload type, e.g. `UserFundsAccountV1`. */ + typeName: string; + /** Name of the exported builder function, e.g. `userFundsAccountV1`. */ + builderName: string; +}; + +type Warn = (message: string) => void; + +const defaultWarn: Warn = (message) => { + // eslint-disable-next-line no-console + console.warn(`[@fragment-dev/node-client] ${message}`); +}; + +/** + * `type` and `typeVersion` identify the payload and `parameters` is typed per + * payload, so none of the three is the caller's to set. + */ +const DERIVED_ENTRY_FIELDS = ["type", "typeVersion", "parameters"]; + +/** + * The `LedgerEntryInput` fields every payload exposes, whatever its operation + * binds, with their declared types (spec 2.3a). + * + * Deliberately not derived from the operation. An operation binds only the entry + * fields the CLI version that generated it chose to expose, and that choice has + * already changed between versions -- so deriving the set would invent a + * restriction the API does not have, and would move a payload's surface whenever + * the CLI changed. A payload travels as an `AddLedgerEntryInput`, so what the + * operation binds places no limit on what the payload may carry. + * + * `ik` and `ledgerIk` are always present already. `lines` is excluded: it cannot + * be combined with an entry that has a `type`. + */ +const COMMON_ENTRY_FIELDS: ReadonlyArray<{ name: string; type: string }> = [ + { name: "posted", type: "DateTime" }, + { name: "description", type: "String" }, + { name: "tags", type: "[LedgerEntryTagInput!]" }, + { name: "groups", type: "[LedgerEntryGroupInput!]" }, + { name: "conditions", type: "[LedgerEntryConditionInput!]" }, +]; + +const findObjectField = (object: ObjectValueNode, name: string) => + object.fields.find((field) => field.name.value === name); + +const findVariableDefinition = ( + operation: OperationDefinitionNode, + variableName: string, +): VariableDefinitionNode | undefined => + operation.variableDefinitions?.find( + (candidate) => candidate.variable.name.value === variableName, + ); + +/** + * Returns the `addLedgerEntry` field of a recognised typed entry operation, or + * undefined if the operation is not one. Every failing condition is a silent + * skip, never an error: this is what excludes the SDK's own `addLedgerEntry` and + * `addLedgerEntryRuntime` operations, whose `type` is a variable. + */ +const getTypedEntryField = (operation: OperationDefinitionNode) => { + if (operation.operation !== "mutation") { + return undefined; + } + if (!operation.name) { + return undefined; + } + const selections = operation.selectionSet.selections; + if (selections.length !== 1) { + return undefined; + } + const [selection] = selections; + if ( + selection.kind !== Kind.FIELD || + selection.name.value !== "addLedgerEntry" + ) { + return undefined; + } + const entryArgument = selection.arguments?.find( + (argument) => argument.name.value === "entry", + ); + if (!entryArgument || entryArgument.value.kind !== Kind.OBJECT) { + return undefined; + } + const typeField = findObjectField(entryArgument.value, "type"); + if (!typeField || typeField.value.kind !== Kind.STRING) { + return undefined; + } + // The entry type comes back narrowed, so no caller has to re-check it. + return { + field: selection, + entry: entryArgument.value, + entryType: typeField.value.value, + /** + * An entry type whose Lines the Schema does not fix takes them from the + * caller, and a payload cannot carry them: `lines` is not a common field. + * Generating one anyway would hand the caller something that always posts + * an entry with no Lines, so no payload is generated at all. + */ + hasLines: !!findObjectField(entryArgument.value, "lines"), + }; +}; + +/** + * The `typeVersion` the operation pins, or undefined when it pins one + * dynamically. `typeVersion` defaults to 1 when it is not set, so an operation + * that pins no version is normalised to 1 — for the payload's identity, its name + * and its wire payload alike. + * + * A version bound to a variable is different: the caller of the single-entry + * operation chooses it, and a payload — whose name states one version and whose + * builder posts it — cannot. Those get no payload rather than a silent 1. + */ +const getTypeVersion = (entry: ObjectValueNode): number | undefined => { + const field = findObjectField(entry, "typeVersion"); + if (!field || field.value.kind === Kind.NULL) { + return 1; + } + if (field.value.kind === Kind.INT) { + return Number.parseInt(field.value.value, 10); + } + return undefined; +}; + +/** + * The entry fields the operation lets the caller set, in source order. A field + * bound to anything other than a variable is fixed by the operation, so it is + * not exposed. + */ +const getFields = ( + entry: ObjectValueNode, + operation: OperationDefinitionNode, + warn: Warn, +): TypedEntryField[] => { + const fields: TypedEntryField[] = []; + + entry.fields.forEach((entryField) => { + const wireName = entryField.name.value; + if (DERIVED_ENTRY_FIELDS.includes(wireName)) { + return; + } + + const bind = ({ + name, + wireKey, + variable, + }: { + name: string; + wireKey?: string; + variable: VariableNode; + }) => { + const variableName = variable.name.value; + const definition = findVariableDefinition(operation, variableName); + if (!definition) { + warn( + `Operation \`${operation.name?.value}\` binds \`${wireName}\` to undeclared variable \`$${variableName}\`. Skipping the field.`, + ); + return; + } + fields.push({ + name, + wireName, + wireKey, + type: definition.type, + required: definition.type.kind === Kind.NON_NULL_TYPE, + }); + }; + + if (entryField.value.kind === Kind.VARIABLE) { + bind({ name: wireName, variable: entryField.value }); + return; + } + + // `ledger: { ik: $ledgerIk }` is the shape the CLI generates. The caller + // supplies the Idempotency Key, so the payload exposes it as `ledgerIk`. + if (entryField.value.kind === Kind.OBJECT) { + entryField.value.fields.forEach((matchField) => { + const key = matchField.name.value; + if (matchField.value.kind === Kind.VARIABLE) { + bind({ + name: `${wireName}${key.charAt(0).toUpperCase()}${key.slice(1)}`, + wireKey: key, + variable: matchField.value, + }); + return; + } + // Fixed by the operation, so encoded in the Schema: the API derives it. + }); + return; + } + + // Fixed by the operation, so encoded in the Schema: the API derives it. + }); + + // Spec 2.3a: every payload carries these, whether or not its operation binds + // them. Appended rather than interleaved, so the operation's own fields keep + // their source order. + COMMON_ENTRY_FIELDS.forEach(({ name, type }) => { + if (fields.some((field) => field.name === name)) { + return; + } + fields.push({ + name, + wireName: name, + type: parseType(type), + required: false, + }); + }); + + return fields; +}; + +const getParameters = ( + entry: ObjectValueNode, + operation: OperationDefinitionNode, + warn: Warn, +): PayloadParameters => { + const parametersField = findObjectField(entry, "parameters"); + if (!parametersField) { + // The operation posts no parameters, so neither may the caller. + return { parametersMode: "absent" }; + } + if (parametersField.value.kind === Kind.VARIABLE) { + const definition = findVariableDefinition( + operation, + parametersField.value.name.value, + ); + return { parametersMode: "untyped", parametersType: definition?.type }; + } + if (parametersField.value.kind !== Kind.OBJECT) { + return { parametersMode: "absent" }; + } + + const parameters: TypedEntryParameter[] = []; + // Source order is the only ordering all SDKs can agree on, so it is preserved. + parametersField.value.fields.forEach((field) => { + if (field.value.kind !== Kind.VARIABLE) { + // Fixed by the operation, so encoded in the Schema: the API derives it + // from there and the payload neither exposes nor re-posts it. + return; + } + const variableName = field.value.name.value; + const definition = findVariableDefinition(operation, variableName); + if (!definition) { + warn( + `Operation \`${operation.name?.value}\` binds parameter \`${field.name.value}\` to undeclared variable \`$${variableName}\`. Skipping the parameter.`, + ); + return; + } + parameters.push({ + wireName: field.name.value, + // Escaped later, once the payload's other field names are known. + name: field.name.value, + type: definition.type, + required: definition.type.kind === Kind.NON_NULL_TYPE, + }); + }); + + if (parameters.length === 0) { + // An empty object posts nothing, so the payload takes no parameters. + return { parametersMode: "absent" }; + } + + return { parametersMode: "typed", parameters }; +}; + +const identityOf = ( + payload: Pick, +) => JSON.stringify([payload.entryType, payload.typeVersion]); + +const sameList = (a: ReadonlyArray, b: ReadonlyArray) => + a.length === b.length && a.every((name, index) => name === b[index]); + +const parameterNames = (payload: TypedEntryPayload) => + payload.parametersMode === "typed" + ? payload.parameters.map((parameter) => parameter.wireName) + : []; + +/** + * What the two operations disagree about, if anything. Both halves matter: the + * winning operation decides the payload's parameters *and* which entry fields a + * caller may set, so losing either silently is a surprise. + */ +const describeConflict = (a: TypedEntryPayload, b: TypedEntryPayload) => { + const conflicts = [ + !sameList(parameterNames(a), parameterNames(b)) + ? "parameters" + : undefined, + // Compare the name the caller writes: `ledger: { ik }` and `ledger: { id }` + // both set `ledger`, but they expose `ledgerIk` and `ledgerId`. + !sameList( + a.fields.map((field) => field.name), + b.fields.map((field) => field.name), + ) + ? "entry fields" + : undefined, + ].filter((conflict): conflict is string => conflict !== undefined); + return conflicts.join(" and "); +}; + +/** + * Derives one payload per `(type, typeVersion)` pair found in the given + * documents. Operations that are not typed entry operations are skipped + * silently. Duplicate identities are deduplicated, first occurrence winning. + */ +export const deriveTypedEntryPayloads = ( + documents: ReadonlyArray, + { warn = defaultWarn }: { warn?: Warn } = {}, +): TypedEntryPayload[] => { + const byIdentity = new Map(); + + documents.forEach((document) => { + document.definitions.forEach((definition) => { + if (definition.kind !== Kind.OPERATION_DEFINITION) { + return; + } + const recognised = getTypedEntryField(definition); + if (!recognised) { + return; + } + const { field, entry, entryType, hasLines } = recognised; + + if (hasLines) { + // Not silent: the payload a caller would reach for is the one that is + // missing, so say which entry type it was and what to reach for instead. + warn( + `Operation \`${definition.name?.value}\` posts \`${entryType}\` with Ledger Lines, which a typed payload cannot carry. No payload is generated for it — post it with a raw \`AddLedgerEntryInput\`, which \`addLedgerEntries\` accepts alongside typed payloads.`, + ); + return; + } + + // `ik` is an argument of `addLedgerEntry`, not a field of `entry`. Every + // entry in a batch needs its own, so a payload always takes one. + const ikArgument = field.arguments?.find( + (argument) => argument.name.value === "ik", + ); + const ikDefinition = + ikArgument?.value.kind === Kind.VARIABLE + ? findVariableDefinition(definition, ikArgument.value.name.value) + : undefined; + + const typeVersion = getTypeVersion(entry); + if (typeVersion === undefined) { + warn( + `Operation \`${definition.name?.value}\` binds \`typeVersion\` to a variable, so its version is the caller's to choose. A typed payload names one version and posts it, so none is generated for \`${entryType}\` — post it with a raw \`AddLedgerEntryInput\`, which \`addLedgerEntries\` accepts alongside typed payloads.`, + ); + return; + } + + const payload: TypedEntryPayload = { + entryType, + typeVersion, + operationName: definition.name?.value ?? "", + fields: getFields(entry, definition, warn), + ...getParameters(entry, definition, warn), + ikType: ikDefinition?.type, + }; + + const identity = identityOf(payload); + const existing = byIdentity.get(identity); + if (existing) { + // The CLI and API guarantee one Ledger Entry per (type, typeVersion), so + // two operations at one identity describe the same entry. Differing sets + // mean the .graphql is stale, or that two generations of it were fed in + // together — runtime-args operations bind fields the plain ones do not. + const conflict = describeConflict(existing, payload); + if (conflict) { + warn( + `Operations \`${existing.operationName}\` and \`${payload.operationName}\` both describe \`${payload.entryType}\` (typeVersion ${payload.typeVersion}) but declare different ${conflict}. Using \`${existing.operationName}\`; check that your operations are all generated from the same Schema.`, + ); + } + return; + } + byIdentity.set(identity, payload); + }); + }); + + return [...byIdentity.values()]; +}; + +const splitWords = (value: string): string[] => + value + .replace(/([a-z0-9])([A-Z])/g, "$1 $2") + .split(/[^a-zA-Z0-9]+/) + .filter(Boolean); + +const pascalCase = (value: string): string => + splitWords(value) + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(""); + +// A TypeScript identifier cannot start with a digit. +const escapeIdentifier = (value: string): string => + /^[0-9]/.test(value) ? `_${value}` : value; + +/** + * Gives each parameter the field name it takes on the payload. Parameters share + * a namespace with the common fields, so a parameter called `posted` cannot keep + * that name: the first occupant keeps the plain name and later ones are + * suffixed. The wire name never changes, so each still carries its own value. + */ +const nameParameters = ( + payload: TypedEntryPayload, + warn: Warn, +): PayloadParameters => { + if (payload.parametersMode !== "typed") { + return payload; + } + + const taken = new Set(["ik", ...payload.fields.map((field) => field.name)]); + + return { + parametersMode: "typed", + parameters: payload.parameters.map((parameter) => { + let name = parameter.wireName; + let attempt = 1; + while (taken.has(name)) { + attempt += 1; + name = `${parameter.wireName}_${attempt}`; + } + if (name !== parameter.wireName) { + warn( + `Parameter \`${parameter.wireName}\` of \`${payload.entryType}\` (typeVersion ${payload.typeVersion}) collides with another field of the payload, so it is called \`${name}\` there. It still posts as \`${parameter.wireName}\`.`, + ); + } + taken.add(name); + return { ...parameter, name }; + }), + }; +}; + +/** + * Assigns each payload its generated identifiers. A name depends only on its own + * payload's identity — never on which other operations are present — so adding + * an entry type or a new version of one never renames an existing payload. + */ +export const nameTypedEntryPayloads = ( + payloads: ReadonlyArray, + { warn = defaultWarn }: { warn?: Warn } = {}, +): NamedTypedEntryPayload[] => { + const taken = new Set(); + + return payloads.map((payload) => { + const pascal = `${pascalCase(payload.entryType)}V${payload.typeVersion}`; + const camel = pascal.charAt(0).toLowerCase() + pascal.slice(1); + // Two entry types can want one identifier — `user-funds` and `user_funds` + // both reduce to `UserFunds`. The first in source order keeps the plain + // name; later ones are suffixed. + let suffix = ""; + let attempt = 1; + while (taken.has(`${pascal}${suffix}`)) { + attempt += 1; + suffix = `_${attempt}`; + } + if (suffix) { + warn( + `Ledger Entry type \`${payload.entryType}\` (typeVersion ${payload.typeVersion}) generates the identifier \`${pascal}\`, which is already taken. Using \`${pascal}${suffix}\` instead.`, + ); + } + taken.add(`${pascal}${suffix}`); + + return { + ...payload, + ...nameParameters(payload, warn), + typeName: escapeIdentifier(`${pascal}${suffix}`), + builderName: escapeIdentifier(`${camel}${suffix}`), + }; + }); +}; + +const BUILT_IN_SCALARS = ["ID", "String", "Boolean", "Int", "Float"]; + +/** The names of every scalar in a schema, including the built-in ones. */ +export const collectScalarNames = (schema: DocumentNode): Set => { + const names = new Set(BUILT_IN_SCALARS); + schema.definitions.forEach((definition) => { + if (definition.kind === Kind.SCALAR_TYPE_DEFINITION) { + names.add(definition.name.value); + } + }); + return names; +}; + +/** A single-quoted TypeScript string literal, matching the codegen output style. */ +const quote = (value: string): string => + `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`; + +const renderNamedType = ( + type: NamedTypeNode, + scalars: ReadonlySet, +): string => { + const { value } = type.name; + // Anything that is not a scalar is an enum or an input object, both of which + // the typescript plugin emits into the same file under their schema name. + return scalars.has(value) ? `Scalars['${value}']['input']` : value; +}; + +const renderMaybeType = ( + type: TypeNode, + scalars: ReadonlySet, +): string => { + if (type.kind === Kind.NON_NULL_TYPE) { + return renderInnerType(type.type, scalars); + } + return `${renderInnerType(type, scalars)} | null`; +}; + +const renderInnerType = ( + type: NamedTypeNode | ListTypeNode, + scalars: ReadonlySet, +): string => + type.kind === Kind.LIST_TYPE + ? `Array<${renderMaybeType(type.type, scalars)}>` + : renderNamedType(type, scalars); + +/** + * The TypeScript type of a value bound to a variable. Top-level nullability is + * carried by the optional marker instead: an unset field is omitted from the + * request, never serialized as `null`. + */ +const renderVariableType = ( + type: TypeNode, + scalars: ReadonlySet, +): string => + type.kind === Kind.NON_NULL_TYPE + ? renderInnerType(type.type, scalars) + : renderInnerType(type, scalars); + +const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/; + +/** + * Wire names go into the generated type verbatim. A GraphQL name is always a + * valid TypeScript property key, so this only quotes defensively. + */ +const renderPropertyKey = (name: string): string => + IDENTIFIER.test(name) ? name : quote(name); + +const FIELD_DOCS: Record = { + ledgerIk: + "The Idempotency Key of the Ledger to add this Ledger Entry to.", + posted: "ISO 8601 timestamp to post this Ledger Entry at.", + lines: + "The Ledger Lines to create, for entry types whose lines the Schema does not fix.", +}; + +const renderField = ( + field: TypedEntryField, + scalars: ReadonlySet, +): string => { + const optional = field.required ? "" : "?"; + const undefinable = field.required ? "" : " | undefined"; + const docs = FIELD_DOCS[field.name] + ? ` /** ${FIELD_DOCS[field.name]} */\n` + : ""; + return `${docs} ${renderPropertyKey(field.name)}${optional}: ${renderVariableType( + field.type, + scalars, + )}${undefinable};`; +}; + +const renderParameters = ( + payload: NamedTypedEntryPayload, + scalars: ReadonlySet, +): string[] => { + if (payload.parametersMode === "absent") { + // The operation posts no parameters, so the payload does not take any. + return []; + } + if (payload.parametersMode === "untyped") { + const { parametersType } = payload; + const type = parametersType + ? renderVariableType(parametersType, scalars) + : "Scalars['JSON']['input']"; + const required = parametersType?.kind === Kind.NON_NULL_TYPE ? "" : "?"; + const undefinable = required ? " | undefined" : ""; + return [ + [ + " /**", + " * This entry type's operation does not bind its parameters to typed", + " * variables, so they cannot be typed individually.", + " */", + ` parameters${required}: ${type}${undefinable};`, + ].join("\n"), + ]; + } + + // Parameters sit alongside the common fields, in the order the operation + // declares them. + return payload.parameters.map((parameter) => { + const optional = parameter.required ? "" : "?"; + const undefinable = parameter.required ? "" : " | undefined"; + const renamed = + parameter.name === parameter.wireName + ? "" + : ` /** Posts as \`${parameter.wireName}\`. */\n`; + return `${renamed} ${renderPropertyKey(parameter.name)}${optional}: ${renderVariableType( + parameter.type, + scalars, + )}${undefinable};`; + }); +}; + +/** + * One key of the emitted `entry` literal, as the lines it occupies, with the + * wire name it sorts by. Lines stay a list until the whole body is joined, so + * nothing has to be re-split to indent it. + */ +type EntryMember = { wireName: string; code: string[] }; + +/** + * `value` when the caller must set it, and a conditional spread when they may + * not: an unset field is left out of the object rather than sent as `null`. + */ +const renderMember = ({ + wireName, + required, + read, + value = read, +}: { + wireName: string; + required: boolean; + /** The expression that is checked for `undefined`. */ + read: string; + /** The expression that is assigned, if it differs from `read`. */ + value?: string; +}): EntryMember => ({ + wireName, + code: [ + required + ? ` ${wireName}: ${value},` + : ` ...(${read} !== undefined && { ${wireName}: ${value} }),`, + ], +}); + +/** + * An `entry` key built from members that may each be absent — `parameters`, or a + * match object like `ledger`. When every member is optional the object is built + * first and spread only if it ended up with something in it, so an entry never + * carries an empty object the caller did not ask for. + */ +const renderObjectMember = ({ + wireName, + members, + anyRequired, +}: { + wireName: string; + /** Rendered member lines, without indentation. */ + members: string[]; + anyRequired: boolean; +}): { member: EntryMember; prelude?: string } => { + if (anyRequired) { + return { + member: { + wireName, + code: [ + ` ${wireName}: {`, + ...members.map((member) => ` ${member}`), + " },", + ], + }, + }; + } + + return { + prelude: [ + ` const ${wireName} = {`, + ...members.map((member) => ` ${member}`), + " };", + ].join("\n"), + member: { + wireName, + code: [` ...(Object.keys(${wireName}).length > 0 && { ${wireName} }),`], + }, + }; +}; + +/** The `parameters` key of the emitted `entry` literal, if the payload has one. */ +const renderParametersMember = ( + payload: NamedTypedEntryPayload, +): { member?: EntryMember; prelude?: string } => { + if (payload.parametersMode === "absent") { + return {}; + } + if (payload.parametersMode === "untyped") { + return { + member: renderMember({ + wireName: "parameters", + required: payload.parametersType?.kind === Kind.NON_NULL_TYPE, + read: "input.parameters", + }), + }; + } + + return renderObjectMember({ + wireName: "parameters", + // With a required parameter the object always has something in it; with + // none, it is built first and posted only if the caller set something. + anyRequired: payload.parameters.some((parameter) => parameter.required), + // Parameters keep the order the source operation declares them in, and each + // posts under its Schema name however it is spelled on the payload. + members: payload.parameters.map((parameter) => { + const key = renderPropertyKey(parameter.wireName); + const read = `input.${parameter.name}`; + return parameter.required + ? `${key}: ${read},` + : `...(${read} !== undefined && { ${key}: ${read} }),`; + }), + }); +}; + +/** + * The `entry` keys the payload's own fields set. Fields are grouped by the + * `LedgerEntryInput` field they write, because a match object can be bound one + * key at a time — `ledger: { id: $id, ik: $ik }` is two payload fields writing + * one entry key, and they have to end up in one object rather than overwriting + * each other. + */ +const renderFieldMembers = ( + payload: NamedTypedEntryPayload, +): { members: EntryMember[]; preludes: string[] } => { + const groups = new Map(); + payload.fields.forEach((field) => { + groups.set(field.wireName, [...(groups.get(field.wireName) ?? []), field]); + }); + + const members: EntryMember[] = []; + const preludes: string[] = []; + + groups.forEach((fields, wireName) => { + if (fields.length === 1) { + const [field] = fields; + members.push( + renderMember({ + wireName, + required: field.required, + read: `input.${field.name}`, + value: field.wireKey + ? `{ ${field.wireKey}: input.${field.name} }` + : `input.${field.name}`, + }), + ); + return; + } + + const { member, prelude } = renderObjectMember({ + wireName, + anyRequired: fields.some((field) => field.required), + members: fields.map((field) => { + // Grouping only happens for match objects, so every field has a key. + const key = renderPropertyKey(field.wireKey ?? field.name); + const read = `input.${field.name}`; + return field.required + ? `${key}: ${read},` + : `...(${read} !== undefined && { ${key}: ${read} }),`; + }), + }); + members.push(member); + if (prelude) { + preludes.push(prelude); + } + }); + + return { members, preludes }; +}; + +const renderBuilderBody = (payload: NamedTypedEntryPayload): string => { + const { member: parametersMember, prelude } = renderParametersMember(payload); + const fields = renderFieldMembers(payload); + const preludes = [...fields.preludes, ...(prelude ? [prelude] : [])]; + + const members: EntryMember[] = [ + ...fields.members, + ...(parametersMember ? [parametersMember] : []), + { wireName: "type", code: [` type: ${quote(payload.entryType)},`] }, + { wireName: "typeVersion", code: [` typeVersion: ${payload.typeVersion},`] }, + ]; + + // Keys are emitted in lexicographic order, which costs nothing to do here and + // makes two SDKs' requests comparable byte for byte. + const entry = [...members] + .sort((a, b) => a.wireName.localeCompare(b.wireName)) + .flatMap((member) => member.code); + + const literal = (indent: string) => + [ + "{", + " entry: {", + ...entry, + " },", + " ik: input.ik,", + "}", + ] + .map((line, index) => (index === 0 ? line : `${indent}${line}`)) + .join("\n"); + + // An object whose members are all optional is built first, so the block body + // is only used where it earns its keep. + return preludes.length > 0 + ? ` => {\n${preludes.join("\n")}\n return ${literal(" ")};\n}` + : ` => (${literal("")})`; +}; + +const renderPayload = ( + payload: NamedTypedEntryPayload, + scalars: ReadonlySet, +): string => { + const description = `\`${payload.entryType}\` (typeVersion ${payload.typeVersion})`; + const ikType = payload.ikType + ? renderVariableType(payload.ikType, scalars) + : "Scalars['SafeString']['input']"; + const members = [ + ` /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */\n ik: ${ikType};`, + ...payload.fields.map((field) => renderField(field, scalars)), + ...renderParameters(payload, scalars), + ]; + + return `/** + * Payload for the ${description} Ledger Entry, for use with \`addLedgerEntries\`. + * + * Derived from the \`${payload.operationName}\` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type ${payload.typeName} = { +${members.join("\n")} +}; + +/** Builds an \`addLedgerEntries\` entry for ${description}. */ +export const ${payload.builderName} = ( + input: ${payload.typeName}, +): AddLedgerEntryInput${renderBuilderBody(payload)};`; +}; + +const renderRegistry = ( + payloads: ReadonlyArray, +): string => { + const entries = payloads.map( + (payload) => + ` ${quote(`${payload.entryType}@${payload.typeVersion}`)}: ${payload.builderName},`, + ); + return `/** + * Every typed payload builder, keyed by \`"@"\`. Useful + * when the entry type is only known at runtime. + */ +export const typedLedgerEntryBuilders = { +${entries.join("\n")} +} as const;`; +}; + +/** + * Renders the typed batch entry section of a generated client. Returns an empty + * string when the input documents contain no typed entry operations. + */ +export const renderTypedEntryPayloads = ( + payloads: ReadonlyArray, + scalars: ReadonlySet, +): string => { + if (payloads.length === 0) { + return ""; + } + + const sections = [ + `/** + * Typed payloads for batching Ledger Entries with \`addLedgerEntries\`. + * + * Each payload is derived from the single-entry \`addLedgerEntry\` operation for + * one \`(type, typeVersion)\` pair, and builds an \`AddLedgerEntryInput\` you can + * mix freely with raw, untyped entry inputs in the same batch: + * + * \`\`\`ts + * await client.addLedgerEntries({ + * entries: [${payloads[0].builderName}({ ik, ledgerIk, parameters: { ... } })], + * }); + * \`\`\` + * + * A payload takes exactly what its source operation binds, so what you can set + * here is what that entry type accepts — no more. A field you do not set is + * left out of the request rather than sent as \`null\`. + */`, + ...payloads.map((payload) => renderPayload(payload, scalars)), + renderRegistry(payloads), + ]; + + return `${sections.join("\n\n")}\n`; +}; + +/** Derives, names and renders typed payloads in one step. */ +export const generateTypedEntryPayloads = ({ + documents, + schema, + warn, +}: { + documents: ReadonlyArray; + schema: DocumentNode; + warn?: Warn; +}): string => { + const payloads = nameTypedEntryPayloads( + deriveTypedEntryPayloads(documents, { warn }), + { warn }, + ); + return renderTypedEntryPayloads(payloads, collectScalarNames(schema)); +}; diff --git a/tests/__snapshots__/typed-batch-entries.test.ts.snap b/tests/__snapshots__/typed-batch-entries.test.ts.snap new file mode 100644 index 0000000..1fad461 --- /dev/null +++ b/tests/__snapshots__/typed-batch-entries.test.ts.snap @@ -0,0 +1,158 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`rendering > matches the committed snapshot of generated output 1`] = ` +"/** + * Typed payloads for batching Ledger Entries with \`addLedgerEntries\`. + * + * Each payload is derived from the single-entry \`addLedgerEntry\` operation for + * one \`(type, typeVersion)\` pair, and builds an \`AddLedgerEntryInput\` you can + * mix freely with raw, untyped entry inputs in the same batch: + * + * \`\`\`ts + * await client.addLedgerEntries({ + * entries: [userFundsAccountV1({ ik, ledgerIk, parameters: { ... } })], + * }); + * \`\`\` + * + * A payload takes exactly what its source operation binds, so what you can set + * here is what that entry type accepts — no more. A field you do not set is + * left out of the request rather than sent as \`null\`. + */ + +/** + * Payload for the \`user-funds-account\` (typeVersion 1) Ledger Entry, for use with \`addLedgerEntries\`. + * + * Derived from the \`PostUserFundsAccount\` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type UserFundsAccountV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + amount: Scalars['String']['input']; +}; + +/** Builds an \`addLedgerEntries\` entry for \`user-funds-account\` (typeVersion 1). */ +export const userFundsAccountV1 = ( + input: UserFundsAccountV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + amount: input.amount, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'user-funds-account', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Payload for the \`user-funds-account\` (typeVersion 2) Ledger Entry, for use with \`addLedgerEntries\`. + * + * Derived from the \`PostUserFundsAccount_v2\` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type UserFundsAccountV2 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + amount: Scalars['String']['input']; + feeAmount: Scalars['Int64']['input']; + memo?: Scalars['String']['input'] | undefined; +}; + +/** Builds an \`addLedgerEntries\` entry for \`user-funds-account\` (typeVersion 2). */ +export const userFundsAccountV2 = ( + input: UserFundsAccountV2, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + amount: input.amount, + feeAmount: input.feeAmount, + ...(input.memo !== undefined && { memo: input.memo }), + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'user-funds-account', + typeVersion: 2, + }, + ik: input.ik, +}); + +/** + * Payload for the \`runtime-thing\` (typeVersion 1) Ledger Entry, for use with \`addLedgerEntries\`. + * + * Derived from the \`PostRuntimeThing\` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type RuntimeThingV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + /** + * This entry type's operation does not bind its parameters to typed + * variables, so they cannot be typed individually. + */ + parameters: Scalars['JSON']['input']; +}; + +/** Builds an \`addLedgerEntries\` entry for \`runtime-thing\` (typeVersion 1). */ +export const runtimeThingV1 = ( + input: RuntimeThingV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: input.parameters, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'runtime-thing', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Every typed payload builder, keyed by \`"@"\`. Useful + * when the entry type is only known at runtime. + */ +export const typedLedgerEntryBuilders = { + 'user-funds-account@1': userFundsAccountV1, + 'user-funds-account@2': userFundsAccountV2, + 'runtime-thing@1': runtimeThingV1, +} as const; +" +`; diff --git a/tests/batch-ledger-entries.test.ts b/tests/batch-ledger-entries.test.ts new file mode 100644 index 0000000..5a4bb4c --- /dev/null +++ b/tests/batch-ledger-entries.test.ts @@ -0,0 +1,544 @@ +import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; +import { setupServer } from "msw/node"; +import { http, graphql, HttpResponse } from "msw"; + +import { createFragmentClient } from "../src/client.js"; +import { AddLedgerEntriesError } from "../src/errors.js"; +import { + getSdk, + paymentProcessingV1, + typedLedgerEntryBuilders, + userFundsAccountV1, + userFundsAccountV2, +} from "./fixtures/generated-test-client.js"; +// This entry type's operation binds tags, groups and conditions, so its payload +// is the one that can exercise them. +import { disputePayoutInitiateV1 } from "./fixtures/generated-template-runtime-args-client.js"; +import { + allOptionalV2, + eitherLedgerKeyV1, + fixedValuesV1, + untypedParametersV1, +} from "./fixtures/generated-edge-case-client.js"; + +const API_URL = "https://fragment-api.example.com/graphql"; +const fragmentApi = graphql.link(API_URL); + +/** An entry as it appeared in the request body. */ +type PostedEntry = { + ik: string; + entry: { + ledger?: { ik: string }; + type?: string; + typeVersion?: number; + posted?: string; + description?: string | null; + parameters?: Record; + tags?: unknown[]; + groups?: unknown[]; + conditions?: unknown[]; + lines?: unknown[]; + }; +}; + +type Recorded = { body: string; variables: { entries: PostedEntry[] } }; + +let recorded: Recorded | undefined; +let respondWith: Record = { + __typename: "AddLedgerEntriesResult", + results: [], +}; + +const server = setupServer( + fragmentApi.mutation("addLedgerEntries", async ({ variables, request }) => { + recorded = { + body: await request.clone().text(), + variables: variables as Recorded["variables"], + }; + return HttpResponse.json({ data: { addLedgerEntries: respondWith } }); + }), + http.post("https://fragment-auth.example.com/oauth2/token", () => + HttpResponse.json({ access_token: "mocked-access-token", expires_in: 3600 }), + ), +); + +const client = createFragmentClient({ + params: { + clientId: "test", + clientSecret: "test", + scope: "*", + authUrl: "https://fragment-auth.example.com/oauth2/token", + apiUrl: API_URL, + }, + getSdk, + retryConfig: { retries: 0, minTimeout: 1, maxTimeout: 1 }, +}); + +/** The `entries` the SDK actually put on the wire. */ +const postedEntries = (): PostedEntry[] => recorded?.variables.entries ?? []; + +beforeAll(() => server.listen()); +afterEach(() => { + recorded = undefined; + respondWith = { __typename: "AddLedgerEntriesResult", results: [] }; +}); +afterAll(() => server.close()); + +describe("wire contract", () => { + it("nests a typed payload as an AddLedgerEntryInput", async () => { + await client.addLedgerEntries({ + entries: [ + disputePayoutInitiateV1({ + ik: "entry-ik", + ledgerIk: "ledger-ik", + posted: "2024-01-01T00:00:00Z", + tags: [{ key: "channel", value: "web" }], + groups: [{ key: "batch", value: "1" }], + conditions: [ + { + account: { path: "asset-root" }, + precondition: { ownBalance: { gte: "0" } }, + }, + ], + user_id: "user-1", + disputes_id: "dispute-1", + amount: "500", + currency: "USD", + payout_id: "payout-1", + order_id: "order-1", + }), + ], + }); + + expect(postedEntries()).toEqual([ + { + ik: "entry-ik", + entry: { + ledger: { ik: "ledger-ik" }, + type: "dispute_payout_initiate", + typeVersion: 1, + posted: "2024-01-01T00:00:00Z", + // Flat on the payload, nested on the wire. + parameters: { + user_id: "user-1", + disputes_id: "dispute-1", + amount: "500", + currency: "USD", + payout_id: "payout-1", + order_id: "order-1", + }, + tags: [{ key: "channel", value: "web" }], + groups: [{ key: "batch", value: "1" }], + conditions: [ + { account: { path: "asset-root" }, precondition: { ownBalance: { gte: "0" } } }, + ], + }, + }, + ]); + }); + + it("omits fields the caller did not set instead of sending null", async () => { + await client.addLedgerEntries({ + entries: [ + userFundsAccountV1({ + ik: "entry-ik", + ledgerIk: "ledger-ik", + // `posted` is the only other field this entry type's operation binds, + // and it is unset. + amount: "200", + }), + ], + }); + + const [{ entry }] = postedEntries(); + expect(Object.keys(entry).sort()).toEqual([ + "ledger", + "parameters", + "type", + "typeVersion", + ]); + // Explicitly passing undefined is the same as not passing the field. + expect(recorded?.body).not.toContain("null"); + }); + + it("treats an explicit undefined the same as an unset field", async () => { + await client.addLedgerEntries({ + entries: [ + userFundsAccountV1({ + ik: "entry-ik", + ledgerIk: "ledger-ik", + posted: undefined, + amount: "200", + }), + ], + }); + + const [{ entry }] = postedEntries(); + expect("posted" in entry).toBe(false); + }); + + it("normalises an unpinned typeVersion to 1 on the wire", async () => { + // `payment_processing` pins no typeVersion in its source operation. + await client.addLedgerEntries({ + entries: [ + paymentProcessingV1({ + ik: "entry-ik", + ledgerIk: "ledger-ik", + amount: "400", + }), + ], + }); + + expect(postedEntries()[0].entry.typeVersion).toEqual(1); + }); + + it("never puts lines on a typed entry", async () => { + await client.addLedgerEntries({ + entries: [ + userFundsAccountV1({ + ik: "entry-ik", + ledgerIk: "ledger-ik", + amount: "200", + }), + ], + }); + + expect("lines" in postedEntries()[0].entry).toBe(false); + }); + + it("preserves entry order and parameter order", async () => { + await client.addLedgerEntries({ + entries: [ + userFundsAccountV2({ + ik: "second-shaped-first", + ledgerIk: "ledger-ik", + // Given out of source order by the caller... + feeAmount: "10", + amount: "200", + }), + userFundsAccountV1({ + ik: "then-this-one", + ledgerIk: "ledger-ik", + amount: "1", + }), + ], + }); + + const entries = postedEntries(); + expect(entries.map((entry) => entry.ik)).toEqual([ + "second-shaped-first", + "then-this-one", + ]); + // ...and serialized in the order the source operation declares them. + expect(Object.keys(entries[0].entry.parameters ?? {})).toEqual([ + "amount", + "feeAmount", + ]); + }); + + it("sends parameter names and non-ASCII values verbatim", async () => { + await client.addLedgerEntries({ + entries: [ + userFundsAccountV1({ + ik: "entry-ik", + ledgerIk: "ledger-ik", + amount: "café — 元气 🎉", + }), + ], + }); + + expect(postedEntries()[0].entry.parameters).toEqual({ + amount: "café — 元气 🎉", + }); + }); + + it("builds a payload looked up by entry type and version at runtime", async () => { + // The registry is keyed by entry type and version, not by generated name. + const build = typedLedgerEntryBuilders["fundingSettlement@1"]; + await client.addLedgerEntries({ + entries: [ + build({ ik: "entry-ik", ledgerIk: "ledger-ik", amount: "5", }), + ], + }); + + expect(postedEntries()[0].entry.parameters).toEqual({ amount: "5" }); + }); + + it("mixes typed payloads with raw untyped inputs in one batch", async () => { + await client.addLedgerEntries({ + entries: [ + userFundsAccountV1({ + ik: "typed", + ledgerIk: "ledger-ik", + amount: "200", + }), + { + ik: "raw", + entry: { + ledger: { ik: "ledger-ik" }, + type: "fundingSettlement", + parameters: { amount: "300" }, + // A raw input is passed through as written, nulls included. + description: null, + }, + }, + ], + }); + + const entries = postedEntries(); + expect(entries.map((entry) => entry.ik)).toEqual(["typed", "raw"]); + expect(entries[1].entry.description).toBeNull(); + }); +}); + +describe("batch semantics", () => { + it("requires narrowing before reading results", async () => { + respondWith = { + __typename: "AddLedgerEntriesResult", + results: [ + { + __typename: "AddLedgerEntryResult", + isIkReplay: false, + entry: { + id: "entry-id", + ik: "entry-ik", + type: "user-funds-account", + posted: "2024-01-01T00:00:00Z", + created: "2024-01-01T00:00:00Z", + }, + lines: [], + }, + ], + }; + + const response = await client.addLedgerEntries({ + entries: [ + userFundsAccountV1({ + ik: "entry-ik", + ledgerIk: "ledger-ik", + amount: "200", + }), + ], + }); + + // @ts-expect-error results is only readable once the union is narrowed. + expect(response.addLedgerEntries.results).toBeDefined(); + + if (response.addLedgerEntries.__typename === "AddLedgerEntriesResult") { + expect(response.addLedgerEntries.results[0].isIkReplay).toBe(false); + expect(response.addLedgerEntries.results[0].entry.ik).toEqual("entry-ik"); + } + }); + + it("surfaces the per-entry errors of a failed batch", async () => { + respondWith = { + __typename: "AddLedgerEntriesError", + code: "ledger_entry_batch_operation_failed", + message: "One or more entries could not be committed", + retryable: false, + errors: [ + { + __typename: "AddLedgerEntryError", + ik: "second", + code: "ledger_entry_too_many_lines", + message: "Too many lines", + retryable: false, + }, + ], + }; + + const onRetry = vi.fn(); + const failing = createFragmentClient({ + params: { + clientId: "test", + clientSecret: "test", + scope: "*", + authUrl: "https://fragment-auth.example.com/oauth2/token", + apiUrl: API_URL, + }, + retryConfig: { retries: 3, minTimeout: 1, maxTimeout: 1, onRetry }, + }); + + let error: AddLedgerEntriesError | undefined; + try { + await failing.addLedgerEntries({ + entries: [ + userFundsAccountV1({ + ik: "first", + ledgerIk: "ledger-ik", + amount: "200", + }), + ], + }); + } catch (thrown) { + error = thrown as AddLedgerEntriesError; + } + + expect(error).toBeInstanceOf(AddLedgerEntriesError); + expect(error?.code).toEqual("ledger_entry_batch_operation_failed"); + expect(error?.errors).toEqual([ + { + __typename: "AddLedgerEntryError", + ik: "second", + code: "ledger_entry_too_many_lines", + message: "Too many lines", + retryable: false, + }, + ]); + // A non-retryable batch error is not retried. + expect(onRetry).not.toHaveBeenCalled(); + }); + + it("retries a retryable batch error", async () => { + respondWith = { + __typename: "AddLedgerEntriesError", + code: "ledger_entry_batch_operation_failed", + message: "Try again", + retryable: true, + errors: [], + }; + + const onRetry = vi.fn(); + const retrying = createFragmentClient({ + params: { + clientId: "test", + clientSecret: "test", + scope: "*", + authUrl: "https://fragment-auth.example.com/oauth2/token", + apiUrl: API_URL, + }, + retryConfig: { retries: 2, minTimeout: 1, maxTimeout: 1, onRetry }, + }); + + await expect( + retrying.addLedgerEntries({ + entries: [ + userFundsAccountV1({ + ik: "first", + ledgerIk: "ledger-ik", + amount: "200", + }), + ], + }), + ).rejects.toBeInstanceOf(AddLedgerEntriesError); + expect(onRetry).toHaveBeenCalledTimes(2); + }); +}); + +describe("payload shapes the Fragment CLI does not generate", () => { + // These come from tests/fixtures/edge-case-queries.graphql, hand-written to + // cover operation shapes the CLI has no reason to emit but a caller may write. + + it("generates no payload for an entry type that takes Ledger Lines", async () => { + const client = await import("./fixtures/generated-edge-case-client.js"); + + // `runtime_lines` binds `lines`, which no payload can carry, so the codegen + // leaves it out rather than generating one that always posts without Lines. + expect("runtimeLinesV1" in client).toBe(false); + expect(Object.keys(client.typedLedgerEntryBuilders)).not.toContain( + "runtime_lines@1", + ); + }); + it("passes an untyped parameters map straight through", () => { + const built = untypedParametersV1({ + ik: "entry-ik", + ledgerIk: "ledger-ik", + parameters: { anything: "goes", nested: { deep: true } }, + }); + + expect(built.entry.parameters).toEqual({ + anything: "goes", + nested: { deep: true }, + }); + }); + + it("omits an all-optional parameters object the caller left empty", () => { + const empty = allOptionalV2({ ik: "entry-ik", ledgerIk: "ledger-ik" }); + expect("parameters" in empty.entry).toBe(false); + // The version still reaches the wire. + expect(empty.entry.typeVersion).toEqual(2); + + const partial = allOptionalV2({ + ik: "entry-ik", + ledgerIk: "ledger-ik", + note: "just this one", + }); + expect(partial.entry.parameters).toEqual({ note: "just this one" }); + }); + + it("merges two payload fields that write one match object", () => { + // `ledger: { id: $ledgerId, ik: $ledgerIk }` is two payload fields writing + // one entry field, so setting both has to keep both. + const both = eitherLedgerKeyV1({ + ik: "entry-ik", + ledgerId: "ledger-id", + ledgerIk: "ledger-ik", + amount: "100", + }); + expect(both.entry.ledger).toEqual({ id: "ledger-id", ik: "ledger-ik" }); + + const byId = eitherLedgerKeyV1({ + ik: "entry-ik", + ledgerId: "ledger-id", + amount: "100", + }); + expect(byId.entry.ledger).toEqual({ id: "ledger-id" }); + + const neither = eitherLedgerKeyV1({ + ik: "entry-ik", + amount: "100", + }); + expect("ledger" in neither.entry).toBe(false); + }); + + it("sends an edge-case payload through a batch alongside a raw input", async () => { + await client.addLedgerEntries({ + entries: [ + fixedValuesV1({ + ik: "typed", + ledgerIk: "ledger-ik", + amount: "100", + }), + { + ik: "raw", + entry: { + ledger: { ik: "ledger-ik" }, + lines: [{ account: { path: "asset-root" }, amount: "100" }], + }, + }, + ], + }); + + expect(postedEntries().map((entry) => entry.ik)).toEqual(["typed", "raw"]); + expect(recorded?.body).not.toContain("null"); + }); +}); + +describe("values the operation fixes", () => { + it("leaves them to the API rather than re-posting them", () => { + const built = fixedValuesV1({ + ik: "entry-ik", + ledgerIk: "ledger-ik", + amount: "100", + }); + + // `description`, `tags` and the `currency` parameter are fixed in the source + // operation, so they are encoded in the Schema and the API derives them. + expect("description" in built.entry).toBe(false); + expect("tags" in built.entry).toBe(false); + expect(built.entry.parameters).toEqual({ amount: "100" }); + }); + + it("still lets the caller set a common field the operation fixed", () => { + // A fixed value never blocks the caller: every common field is settable. + const built = fixedValuesV1({ + ik: "entry-ik", + ledgerIk: "ledger-ik", + description: "set by the caller", + amount: "100", + }); + + expect(built.entry.description).toEqual("set by the caller"); + }); + +}); diff --git a/tests/fixtures/edge-case-queries.graphql b/tests/fixtures/edge-case-queries.graphql new file mode 100644 index 0000000..b3fe06f --- /dev/null +++ b/tests/fixtures/edge-case-queries.graphql @@ -0,0 +1,104 @@ +# Hand-written operations covering shapes the Fragment CLI does not currently +# generate, but that a caller may write by hand and that the derivation supports. +# Unlike the other fixtures, this file is not generated -- edit it directly. + +# An entry type whose Lines the Schema does not fix, so the caller supplies them. +# No payload is generated for it: `lines` is not a common field, so a payload +# could only ever post an entry with no Lines. Post these with a raw +# `AddLedgerEntryInput` instead. +mutation PostRuntimeLines( + $ik: SafeString! + $ledgerIk: SafeString! + $posted: DateTime + $description: String + $lines: [LedgerLineInput!]! +) { + addLedgerEntry( + ik: $ik + entry: { + ledger: { ik: $ledgerIk } + type: "runtime_lines" + posted: $posted + description: $description + lines: $lines + } + ) { + __typename + } +} + +# `parameters` bound to a variable rather than an inline object, so the payload +# falls back to an untyped map. +mutation PostUntypedParameters( + $ik: SafeString! + $ledgerIk: SafeString! + $parameters: JSON! +) { + addLedgerEntry( + ik: $ik + entry: { ledger: { ik: $ledgerIk }, type: "untyped_parameters", parameters: $parameters } + ) { + __typename + } +} + +# Every parameter optional, so `parameters` itself is omitted when none are set. +mutation PostAllOptional( + $ik: SafeString! + $ledgerIk: SafeString! + $memo: String + $note: String +) { + addLedgerEntry( + ik: $ik + entry: { + ledger: { ik: $ledgerIk } + type: "all_optional" + typeVersion: 2 + parameters: { memo: $memo, note: $note } + } + ) { + __typename + } +} + +# The Ledger matched by either key, so two payload fields write one entry field. +mutation PostEitherLedgerKey( + $ik: SafeString! + $ledgerId: ID + $ledgerIk: SafeString + $amount: String! +) { + addLedgerEntry( + ik: $ik + entry: { + ledger: { id: $ledgerId, ik: $ledgerIk } + type: "either_ledger_key" + parameters: { amount: $amount } + } + ) { + __typename + } +} + +# Values the operation fixes. They are encoded in the Schema the operation was +# generated from, so the API derives them: the payload neither exposes nor +# re-posts them. +mutation PostFixedValues( + $ik: SafeString! + $ledgerIk: SafeString! + $amount: String! +) { + addLedgerEntry( + ik: $ik + entry: { + ledger: { ik: $ledgerIk } + type: "fixed_values" + description: "posted by the nightly sweep" + tags: [{ key: "source", value: "sweep" }] + parameters: { amount: $amount, currency: "USD" } + } + ) { + __typename + } +} diff --git a/tests/fixtures/generated-edge-case-client.ts b/tests/fixtures/generated-edge-case-client.ts new file mode 100644 index 0000000..debca08 --- /dev/null +++ b/tests/fixtures/generated-edge-case-client.ts @@ -0,0 +1,3672 @@ +/* This file is auto-generated by @fragment-dev/node-client. Don't edit it directly. */ +import { GraphQLClient, RequestOptions } from '@fragment-dev/node-client'; +import { gql } from '@fragment-dev/node-client'; +export type Maybe = T | null; +export type InputMaybe = Maybe; +export type Exact = { [K in keyof T]: T[K] }; +export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; +export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; +export type MakeEmpty = { [_ in K]?: never }; +export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; +type GraphQLClientRequestHeaders = RequestOptions['requestHeaders']; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: { input: string; output: string; } + String: { input: string; output: string; } + Boolean: { input: boolean; output: boolean; } + Int: { input: number; output: number; } + Float: { input: number; output: number; } + /** A string that must be alphanumeric */ + AlphaNumericString: { input: string; output: string; } + /** ISO 8601 Date e.g. `1969-07-21` */ + Date: { input: string; output: string; } + /** ISO 8601 DateTime e.g. `1969-07-16T13:32:00.000Z`. You can also provide a date e.g. `1969-01-01` and it will be converted to `1969-01-01T00:00:00.000Z` */ + DateTime: { input: string; output: string; } + /** The first moment of a specific year, month or day or hour e.g. 1969 or 1969-1 or 1969-1-1 or 1969-1-1T00. All of the previous examples are equivalent to `1969-1-1T00:00:00.000`. */ + FirstMoment: { input: any; output: any; } + /** A string representing integers up to 9,223,372,036,854,775,807 (i.e. 2^63-1) */ + Int64: { input: string; output: string; } + /** A string representing integers as big as 2^120-1. The number is signed so the range is from -1,329,227,995,784,915,872,903,807,060,280,344,575 to 1,329,227,995,784,915,872,903,807,060,280,344,575. */ + Int96: { input: string; output: string; } + /** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */ + JSON: { input: Record; output: Record; } + /** The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */ + JSONObject: { input: Record; output: Record; } + /** The last moment of a specific year, month or day or hour e.g. 1969 or 1969-12 or 1969-12-31 or 1969-12-31T23. All of the previous examples are equivalent to `1969-12-31T23:59:59.999`. */ + LastMoment: { input: string; output: string; } + /** A string of non-zero length that can contain parameterized values via handlebars syntax. ex: `"Hello from {{country}}"`. */ + ParameterizedString: { input: string; output: string; } + /** A mapping of parameter keys to values. */ + Parameters: { input: any; output: any; } + /** A specific year ("2021"), quarter ("2021-Q1"), month ("2021-02"), day ("2021-02-03") or hour ("2021-02-03T04") */ + Period: { input: string; output: string; } + /** A specific year ("2026") or month ("2026-05") used to match dates that fall within it. */ + PeriodFilter: { input: any; output: any; } + /** A string with delimiter characters `/`, `#`, and `:` disallowed, as well as parameters in {{handlebar}} syntax. */ + SafeString: { input: string; output: string; } + /** All hour-aligned offsets from -11:00 to +12:00 are supported, e.g. "-08:00" (PT), "-05:00" (ET), "+00:00" (UTC) */ + UTCOffset: { input: string; output: string; } +}; + +/** Error returned when one or more Ledger Entries in the batch could not be added. */ +export type AddLedgerEntriesError = Error & { + __typename?: 'AddLedgerEntriesError'; + /** The status code of error. For example, 'ledger_entry_batch_operation_failed'. */ + code: Scalars['String']['output']; + /** The list of errors for each Ledger Entry that was responsible for the batch's failure. */ + errors: Array; + /** The error message */ + message: Scalars['String']['output']; + /** Whether or not the operation is retryable */ + retryable: Scalars['Boolean']['output']; +}; + +export type AddLedgerEntriesResponse = AddLedgerEntriesError | AddLedgerEntriesResult | BadRequestError | InternalError; + +export type AddLedgerEntriesResult = { + __typename?: 'AddLedgerEntriesResult'; + /** The added Ledger Entries, in the same order as the input */ + results: Array; +}; + +/** Error details for a single Ledger Entry that was responsible for the batch's failure. */ +export type AddLedgerEntryError = Error & { + __typename?: 'AddLedgerEntryError'; + /** The status code of error. For example, 'ledger_entry_too_many_lines'. */ + code: Scalars['String']['output']; + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) of the Ledger Entry */ + ik: Scalars['SafeString']['output']; + /** The error message */ + message: Scalars['String']['output']; + /** Whether or not the operation is retryable */ + retryable: Scalars['Boolean']['output']; +}; + +export type AddLedgerEntryInput = { + /** The [Ledger Entry](https://fragment.dev/api-reference/api-types#input-types-ledgerentryinput) to add */ + entry: LedgerEntryInput; + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry */ + ik: Scalars['SafeString']['input']; +}; + +export type AddLedgerEntryResponse = AddLedgerEntryResult | BadRequestError | InternalError; + +export type AddLedgerEntryResult = { + __typename?: 'AddLedgerEntryResult'; + /** The ledger entry that was posted */ + entry: LedgerEntry; + /** True if this request successfully completed before and the previous response is being returned */ + isIkReplay: Scalars['Boolean']['output']; + /** The ledger lines that were created in that entry */ + lines: Array; +}; + +/** Equivalent to an HTTP 400 - request either has missing or incorrect data */ +export type BadRequestError = Error & { + __typename?: 'BadRequestError'; + /** The status code of error. For example, 'ledger_not_found'. */ + code: Scalars['String']['output']; + /** The error message */ + message: Scalars['String']['output']; + /** Whether or not the operation is retryable */ + retryable: Scalars['Boolean']['output']; +}; + +/** A single amount and the timestamp requested */ +export type BalanceChangeDuring = { + __typename?: 'BalanceChangeDuring'; + /** The balance or balance change */ + amount: CurrencyAmount; + /** The period of the requested balance change */ + period: Scalars['Period']['output']; +}; + +/** A paginated list of amounts and their periods */ +export type BalanceChangeDuringConnection = { + __typename?: 'BalanceChangeDuringConnection'; + /** The end time of the period across which the balance changes are requested */ + endTime: Scalars['LastMoment']['output']; + /** The granularity of the return data */ + granularity: Granularity; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; + /** The start time of the period across which the balance changes are requested */ + startTime: Scalars['FirstMoment']['output']; +}; + +/** Used to configure the write-consistency of a Ledger Account's balance. See [Configure consistency](https://fragment.dev/guides/configure-consistency). */ +export enum BalanceUpdateConsistencyMode { + Eventual = 'eventual', + Strong = 'strong' +} + +/** The input for your Chart of Accounts in a Schema. */ +export type ChartOfAccountsInput = { + /** The Ledger Accounts modeled by your Schema. Ledger Accounts may be nested up to a maximum depth of 10. */ + accounts: Array; + /** + * The default consistency configuration for all Ledger Accounts in this Schema. + * If a Ledger Account does not specify its own consistency configuration, it will use the default values provided here. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + defaultConsistencyConfig?: InputMaybe; + /** + * The default currency of each Ledger Account in the Chart Of Accounts. + * It must be provided if `defaultCurrencyMode` is set to `single`. + * Additionally, `defaultCurrency` must be omitted if `defaultCurrencyMode` is set to `multi`. + */ + defaultCurrency?: InputMaybe; + /** The default currency mode of each Ledger Account in the Chart Of Accounts. */ + defaultCurrencyMode?: InputMaybe; +}; + +export type CreateCustomCurrencyInput = { + /** The currency code for custom currencies. It can be up to 36 characters long. This is used for display purposes. */ + customCode: Scalars['String']['input']; + /** The ID for a custom currency. This is specified when creating the custom currency using the [createCustomCurrency](https://fragment.dev/api-reference/api-mutations#createcustomcurrency) mutation. It can be up to 36 characters long. */ + customCurrencyId: Scalars['SafeString']['input']; + /** A human readable name for the currency (e.g. United States Dollar). This is used for display purposes. */ + name: Scalars['String']['input']; + /** The number of decimal places this currency goes to. For example, United States Dollars have a precision of 2 (i.e. 100 cents in a dollar), whereas the Jordanian Dinar has a precision of 3. This is used for display purposes. */ + precision: Scalars['Int']['input']; +}; + +export type CreateCustomCurrencyResponse = BadRequestError | CreateCustomCurrencyResult | InternalError; + +export type CreateCustomCurrencyResult = { + __typename?: 'CreateCustomCurrencyResult'; + /** The Currency that was created. */ + customCurrency: Currency; +}; + +export type CreateCustomLinkResponse = BadRequestError | CreateCustomLinkResult | InternalError; + +export type CreateCustomLinkResult = { + __typename?: 'CreateCustomLinkResult'; + isIkReplay: Scalars['Boolean']['output']; + /** The custom link that was created. Represents an instance of an external system. */ + link: CustomLink; +}; + +export type CreateLedgerAccountInput = { + /** The consistency configuration for this Ledger Account. This defines how updates to this Ledger Account's balance are handled. */ + consistencyConfig?: InputMaybe; + /** + * The currency of this Ledger Account. If this is not set, and `currencyMode` is + * not set to `multi`, the workspace-level default is used. + */ + currency?: InputMaybe; + /** If set to `multi`, creates a multi-currency Ledger Account. If set to `single`, creates a single-currency Ledger Account. */ + currencyMode?: InputMaybe; + /** The External Account to link to this Ledger Account. */ + linkedAccount?: InputMaybe; + /** The human-readable name of this Ledger Account. */ + name: Scalars['String']['input']; + /** The parent of this Ledger Account. */ + parent?: InputMaybe; + /** The type of ledger account to create. Required if this is a top-level Ledger Account. If not provided, the type will be inferred from the parent. */ + type?: InputMaybe; +}; + +export type CreateLedgerAccountResponse = BadRequestError | CreateLedgerAccountResult | InternalError; + +export type CreateLedgerAccountResult = { + __typename?: 'CreateLedgerAccountResult'; + /** true if a previous request successfully created this ledger account */ + isIkReplay: Scalars['Boolean']['output']; + /** The ledger account that was created */ + ledgerAccount: LedgerAccount; +}; + +export type CreateLedgerAccountsInput = { + /** Ledger Accounts to create as children of this Ledger Account. */ + childLedgerAccounts?: InputMaybe>; + /** The consistency configuration for this ledger account. See [Configure consistency](https://fragment.dev/guides/configure-consistency). */ + consistencyConfig?: InputMaybe; + /** The currency of this Ledger Account. If this is not set, the workspace level default is used. */ + currency?: InputMaybe; + /** The currency mode of this Ledger Account. If this is not set, the workspace level default is used. */ + currencyMode?: InputMaybe; + /** The idempotency key for creating this Ledger Account. */ + ik: Scalars['SafeString']['input']; + /** The External Account to link to this Ledger Account. This can only be specified on leaf Ledger Accounts. See [Reconcile payments](https://fragment.dev/guides/reconcile-payments). */ + linkedAccount?: InputMaybe; + /** The name of the Ledger Account. */ + name: Scalars['String']['input']; + /** The parent of this Ledger Account. This is only valid on the top level Ledger Account in the payload. */ + parent?: InputMaybe; + /** The type of this Ledger Account. This field is only required if this is a root Ledger Account. Otherwise, the type will get inherited from its parent. */ + type?: InputMaybe; +}; + +export type CreateLedgerAccountsResponse = BadRequestError | CreateLedgerAccountsResult | InternalError; + +export type CreateLedgerAccountsResult = { + __typename?: 'CreateLedgerAccountsResult'; + /** Whether the ledger accounts were successfully created by a previous request */ + ikReplays: Array; + /** The ledger accounts that were created */ + ledgerAccounts: Array; +}; + +export type CreateLedgerInput = { + /** + * Use this field to specify a timezone for queries to your Ledger. + * + * When aggregating balances, all transactions within a 24 hour period starting at midnight UTC are included in each day. + * You can specify a different starting hour for balances. For example, use "-08:00" to align balances with Pacific Standard Time. + * Balance queries would then consider the start of each local day to be at 8am UTC the next day in UTC. + * The default timezone is UTC. + */ + balanceUTCOffset?: InputMaybe; + name: Scalars['String']['input']; + type?: InputMaybe; +}; + +export type CreateLedgerResponse = BadRequestError | CreateLedgerResult | InternalError; + +export type CreateLedgerResult = { + __typename?: 'CreateLedgerResult'; + /** true if this request successfully completed before and the previous response is being returned */ + isIkReplay: Scalars['Boolean']['output']; + /** The Ledger that was created */ + ledger: Ledger; +}; + +export type CreatePaymentResponse = BadRequestError | InternalError | Payment; + +export type Currency = { + __typename?: 'Currency'; + /** The currency code. This is an [enum type](https://fragment.dev/api-reference/api-types#scalars-and-enums-currencycode) . */ + code: CurrencyCode; + /** The currency code for custom currencies. This is only set if 'currency' is set to CUSTOM. It can be up to 36 characters long. */ + customCode?: Maybe; + /** The ID for a custom currency. This is specified when creating the custom currency using the [createCustomCurrency](https://fragment.dev/api-reference/api-mutations#createcustomcurrency) mutation. */ + customCurrencyId?: Maybe; + /** A human readable name for the currency (e.g. United States Dollar). This is used for display purposes. */ + name: Scalars['String']['output']; + /** The number of decimal places this currency goes to. For example, United States Dollars have a precision of 2 (i.e. 100 cents in a dollar), whereas the Jordanian Dinar has a precision of 3. This is used for display purposes. */ + precision: Scalars['Int']['output']; +}; + +/** A single amount accompanied by its currency */ +export type CurrencyAmount = { + __typename?: 'CurrencyAmount'; + /** Numerical integer value, serialized as a string */ + amount: Scalars['Int96']['output']; + /** The currency this amount is in */ + currency: Currency; +}; + +/** A paginated list of amounts with their currencies */ +export type CurrencyAmountConnection = { + __typename?: 'CurrencyAmountConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export enum CurrencyCode { + Aave = 'AAVE', + Ada = 'ADA', + Aed = 'AED', + Afn = 'AFN', + All = 'ALL', + Amd = 'AMD', + Ang = 'ANG', + Aoa = 'AOA', + Ars = 'ARS', + Aud = 'AUD', + Awg = 'AWG', + Azn = 'AZN', + Bam = 'BAM', + Bbd = 'BBD', + Bch = 'BCH', + Bdt = 'BDT', + Bgn = 'BGN', + Bhd = 'BHD', + Bif = 'BIF', + Bmd = 'BMD', + Bnd = 'BND', + Bob = 'BOB', + Brl = 'BRL', + Bsd = 'BSD', + Btc = 'BTC', + Btn = 'BTN', + Bwp = 'BWP', + Byr = 'BYR', + Bzd = 'BZD', + Cad = 'CAD', + Cadc = 'CADC', + Cadt = 'CADT', + Cdf = 'CDF', + Chf = 'CHF', + Clp = 'CLP', + Cny = 'CNY', + Cop = 'COP', + Crc = 'CRC', + Cuc = 'CUC', + Cup = 'CUP', + Custom = 'CUSTOM', + Cve = 'CVE', + Czk = 'CZK', + Dai = 'DAI', + Djf = 'DJF', + Dkk = 'DKK', + Dop = 'DOP', + Dzd = 'DZD', + Egp = 'EGP', + Ern = 'ERN', + Etb = 'ETB', + Eth = 'ETH', + Eur = 'EUR', + Eurc = 'EURC', + Fjd = 'FJD', + Fkp = 'FKP', + Gbp = 'GBP', + Gel = 'GEL', + Ggp = 'GGP', + Ghs = 'GHS', + Gip = 'GIP', + Gmd = 'GMD', + Gnf = 'GNF', + Gtq = 'GTQ', + Gyd = 'GYD', + Hkd = 'HKD', + Hnl = 'HNL', + Hrk = 'HRK', + Htg = 'HTG', + Huf = 'HUF', + Idr = 'IDR', + Ils = 'ILS', + Imp = 'IMP', + Inr = 'INR', + Iqd = 'IQD', + Irr = 'IRR', + Isk = 'ISK', + Jmd = 'JMD', + Jod = 'JOD', + Jpy = 'JPY', + Kes = 'KES', + Kgs = 'KGS', + Khr = 'KHR', + Kmf = 'KMF', + Kpw = 'KPW', + Krw = 'KRW', + Kwd = 'KWD', + Kyd = 'KYD', + Kzt = 'KZT', + Lak = 'LAK', + Lbp = 'LBP', + Link = 'LINK', + Lkr = 'LKR', + Logical = 'LOGICAL', + Lrd = 'LRD', + Lsl = 'LSL', + Ltc = 'LTC', + Lyd = 'LYD', + Mad = 'MAD', + Matic = 'MATIC', + Mdl = 'MDL', + Mga = 'MGA', + Mkd = 'MKD', + Mmk = 'MMK', + Mnt = 'MNT', + Mop = 'MOP', + Mur = 'MUR', + Mvr = 'MVR', + Mwk = 'MWK', + Mxn = 'MXN', + Myr = 'MYR', + Mzn = 'MZN', + Nad = 'NAD', + Ngn = 'NGN', + Nio = 'NIO', + Nok = 'NOK', + Npr = 'NPR', + Nzd = 'NZD', + Omr = 'OMR', + Pab = 'PAB', + Pen = 'PEN', + Pgk = 'PGK', + Php = 'PHP', + Pkr = 'PKR', + Pln = 'PLN', + Pts = 'PTS', + Pyg = 'PYG', + Qar = 'QAR', + Ron = 'RON', + Rsd = 'RSD', + Rub = 'RUB', + Rwf = 'RWF', + Sar = 'SAR', + Sbd = 'SBD', + Scr = 'SCR', + Sdg = 'SDG', + Sek = 'SEK', + Sgd = 'SGD', + Shp = 'SHP', + Sll = 'SLL', + Sol = 'SOL', + Sos = 'SOS', + Spl = 'SPL', + Srd = 'SRD', + Stn = 'STN', + Svc = 'SVC', + Syp = 'SYP', + Szl = 'SZL', + Thb = 'THB', + Tjs = 'TJS', + Tmt = 'TMT', + Tnd = 'TND', + Top = 'TOP', + Try = 'TRY', + Ttd = 'TTD', + Tvd = 'TVD', + Twd = 'TWD', + Tzs = 'TZS', + Uah = 'UAH', + Ugx = 'UGX', + Uni = 'UNI', + Usd = 'USD', + Usdc = 'USDC', + Usdg = 'USDG', + Usdt = 'USDT', + Uyu = 'UYU', + Uzs = 'UZS', + Vef = 'VEF', + Vnd = 'VND', + Vuv = 'VUV', + Wst = 'WST', + Xaf = 'XAF', + Xcd = 'XCD', + Xlm = 'XLM', + Xof = 'XOF', + Xpf = 'XPF', + Yer = 'YER', + Zar = 'ZAR', + Zmw = 'ZMW' +} + +export type CurrencyFilter = { + /** Must match the value provided */ + equalTo?: InputMaybe; + /** Must match one of the values provided. Limited to 100 items maximum. */ + in?: InputMaybe>; +}; + +export type CurrencyMatchInput = { + /** The currency code. This is an [enum type](https://fragment.dev/api-reference/api-types#scalars-and-enums-currencycode). */ + code: CurrencyCode; + /** The ID for a custom currency. This is specified when creating the custom currency using the [createCustomCurrency](https://fragment.dev/api-reference/api-mutations#createcustomcurrency) mutation. */ + customCurrencyId?: InputMaybe; +}; + +/** Defines the currency handling of a LedgerAccount, which can either be restricted to a single currency or allow multiple currencies. */ +export enum CurrencyMode { + Multi = 'multi', + Single = 'single' +} + +export type CustomAccountInput = { + /** The currency of this external account. If this is not set, the workspace level default is used. 'currency' cannot be set if 'currencyMode' is 'multi'. */ + currency?: InputMaybe; + /** The currency mode of this external account. If set to multi, creates a multi-currency account. */ + currencyMode?: InputMaybe; + /** The ID of this account at the external system. This is used as the idempotency key, within the scope of its Custom Link. */ + externalId: Scalars['SafeString']['input']; + /** The name of the account at the external system. */ + name: Scalars['String']['input']; +}; + +/** A paginated list of Custom Currencies */ +export type CustomCurrenciesConnection = { + __typename?: 'CustomCurrenciesConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export type CustomLink = Link & { + __typename?: 'CustomLink'; + /** ISO-8601 timestamp when the Link was created. */ + created: Scalars['String']['output']; + /** URL to the Fragment Dashboard for this Link. */ + dashboardUrl: Scalars['String']['output']; + /** A list of External Accounts associated with this Link. */ + externalAccounts: ExternalAccountsConnection; + /** FRAGMENT ID of the Custom Link. */ + id: Scalars['ID']['output']; + /** Name of the Link as it appears in the Fragment Dashboard. */ + name: Scalars['String']['output']; +}; + +export type CustomTxInput = { + account: ExternalAccountMatchInput; + amount: Scalars['Int96']['input']; + /** The currency of this tx. Should be set for multi-currency accounts. */ + currency?: InputMaybe; + description: Scalars['String']['input']; + /** The ID of this tx at the external system. This is used as the idempotency key, within the scope of its Custom Account. */ + externalId: Scalars['SafeString']['input']; + posted: Scalars['DateTime']['input']; +}; + +export type DateFilter = { + equalTo?: InputMaybe; + /** Must match one of the values provided. Limited to 100 items maximum. */ + in?: InputMaybe>; + /** Must fall within the given period. Supports a year (e.g. "2026") or a month (e.g. "2026-05"). To match a specific day, use `equalTo`. Cannot be combined with `withinBalanceUTCOffset`. */ + within?: InputMaybe; + /** Must fall within the given period, taking into account the Ledger's `balanceUTCOffset`. Supports a year (e.g. "2026") or a month (e.g. "2026-05"). Cannot be combined with `within`. */ + withinBalanceUTCOffset?: InputMaybe; +}; + +/** Filters a timestamp field between two moments in time */ +export type DateTimeFilter = { + /** The timestamp value must be after this moment. Specified in ISO 8601 format e.g "1968-01-01T16:45:00Z" */ + after?: InputMaybe; + /** The timestamp value must be before this moment. Specified in ISO 8601 format e.g "1968-01-01T16:45:00Z" */ + before?: InputMaybe; +}; + +export type DeleteCustomTxsResponse = BadRequestError | DeleteCustomTxsResult | InternalError; + +export type DeleteCustomTxsResult = { + __typename?: 'DeleteCustomTxsResult'; + /** List of Txs deleted in this operation */ + txs: Array; +}; + +export type DeleteLedgerResponse = BadRequestError | DeleteLedgerResult | InternalError; + +export type DeleteLedgerResult = { + __typename?: 'DeleteLedgerResult'; + success: Scalars['Boolean']['output']; +}; + +export type DeleteSchemaResponse = BadRequestError | DeleteSchemaResult | InternalError; + +export type DeleteSchemaResult = { + __typename?: 'DeleteSchemaResult'; + success: Scalars['Boolean']['output']; +}; + +export type DeletedCustomTx = { + __typename?: 'DeletedCustomTx'; + /** A deleted Tx */ + tx: Tx; +}; + +export type EntryGroupMatchInput = { + key: Scalars['SafeString']['input']; + value: Scalars['SafeString']['input']; +}; + +/** Base error interface */ +export type Error = { + /** The status code of error. For example, 'ledger_not_found'. */ + code: Scalars['String']['output']; + /** The error message */ + message: Scalars['String']['output']; + /** Whether or not the operation is retryable */ + retryable: Scalars['Boolean']['output']; +}; + +export type ExternalAccount = { + __typename?: 'ExternalAccount'; + /** The currency of this external account. */ + currency?: Maybe; + /** Indicates if the account allows multiple currencies or is restricted to a single currency */ + currencyMode: CurrencyMode; + /** ID used for the external account */ + externalId: Scalars['ID']['output']; + /** FRAGMENT ID of External Account */ + id: Scalars['ID']['output']; + /** Ledger Accounts linked to this External Account. Ledger Accounts are paginated and sorted in reverse-chronological order by created date. */ + ledgerAccounts: LedgerAccountsConnection; + /** The Link that this External Account belongs to. */ + link: Link; + /** FRAGMENT ID of this transaction's external link */ + linkId: Scalars['ID']['output']; + name: Scalars['String']['output']; + /** All Txs in this External Account. */ + txs: TxsConnection; +}; + + +export type ExternalAccountLedgerAccountsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +export type ExternalAccountTxsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +export type ExternalAccountFilter = { + /** Ledger Account must linked to the the specified external account */ + equalTo?: InputMaybe; + /** Ledger Account can be linked to any of the specified external accounts. Limited to 100 items maximum. */ + in?: InputMaybe>; +}; + +/** Specify an External Account by using `id`, or `linkId` and `externalId`. */ +export type ExternalAccountMatchInput = { + /** The external system's ID of the External Account. If this is specified, `linkId` is required. `id` is optional, but will be validated if provided. */ + externalId?: InputMaybe; + /** The FRAGMENT ID of the External Account. If this is specified, both `linkId` and `externalId` are optional, but will be validated if provided. */ + id?: InputMaybe; + /** The FRAGMENT ID of the Link the External Account is in. If this is specified, `externalId` is required. `id` is optional, but will be validated if provided. */ + linkId?: InputMaybe; +}; + +/** A paginated list of External Accounts */ +export type ExternalAccountsConnection = { + __typename?: 'ExternalAccountsConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export enum ExternalTransferType { + Ach = 'ach', + Card = 'card', + Check = 'check', + Internal = 'internal', + Wire = 'wire' +} + +export enum ExternalTxSource { + Increase = 'increase' +} + +export enum Granularity { + Daily = 'daily', + Hourly = 'hourly', + Monthly = 'monthly' +} + +/** A filter to query balances of a specific subset of accounts */ +export type GroupBalanceAccountFilter = { + /** A filter that must match the account ID */ + id?: InputMaybe; + /** A filter that must match the account path. Wildcards ('*') may be used only for template variables, and will only match a single variable each. */ + path?: InputMaybe; +}; + +/** Filter for finding entries by group membership */ +export type GroupFilter = { + /** Find entries that have ALL of the specified groups. Limited to 10 items maximum. */ + all?: InputMaybe>; + /** Find groups that exactly match this group */ + equalTo?: InputMaybe; + /** Find groups that match any of these groups */ + in?: InputMaybe>; + /** Find groups with a specific key */ + keyEqualTo?: InputMaybe; + /** Find groups with any of these keys */ + keyIn?: InputMaybe>; + /** + * Find groups that do not match this predicate + * @deprecated not filter is deprecated. Use notKeyIn or notKeyEqualTo instead. + */ + not?: InputMaybe; + /** Find groups that do not exactly match this group */ + notEqualTo?: InputMaybe; + /** Find groups that do not match any of these groups */ + notIn?: InputMaybe>; + /** Find groups that do not have a specific key */ + notKeyEqualTo?: InputMaybe; + /** Find groups that do not have any of these keys */ + notKeyIn?: InputMaybe>; +}; + +/** A Group in a Schema. Group define sequences of Ledger Entries and can help with reconciliation tasks. */ +export type GroupInput = { + /** Human-readable description of the Group. */ + description?: InputMaybe; + /** The key of this Group. This combined with its value is a stable, unique identifier for this group. */ + key: Scalars['SafeString']['input']; + /** The parameters that are used to enable reconciliation abilities in a group. */ + reconciliation?: InputMaybe; +}; + +/** Input type for matching a specific group by key and value */ +export type GroupMatchInput = { + /** The key of the group to match */ + key: Scalars['SafeString']['input']; + /** The value of the group to match */ + value: Scalars['SafeString']['input']; +}; + +/** DEPRECATED: Use GroupFilter and notKeyIn or notKeyEqualTo instead. Filter for finding entries that do not match this predicate */ +export type GroupNotFilter = { + /** DEPRECATED: Find entries that are not members of all of these groups. This is an AND filter. */ + keyIn?: InputMaybe>; +}; + +/** A set of parameters that are used to enable reconciliation abilities in a group */ +export type GroupReconciliationParametersInput = { + /** The path to the clearing account for this group. A clearing account is an account that is used to indicate funds that are in transit. Also called a suspense account, pending account, or zero balance account. */ + clearingAccountPath: SchemaLedgerAccountMatchInput; +}; + +/** A single amount and the timestamp requested */ +export type HistoricalBalance = { + __typename?: 'HistoricalBalance'; + /** The balance or balance change */ + amount: CurrencyAmount; + /** The timestamp of the requested balance */ + at: Scalars['LastMoment']['output']; +}; + +/** A paginated list of amounts and their periods */ +export type HistoricalBalanceConnection = { + __typename?: 'HistoricalBalanceConnection'; + /** The end time of the period across which the balance changes are requested */ + endTime: Scalars['LastMoment']['output']; + /** The granularity of the return data */ + granularity: Granularity; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; + /** The start time of the period across which the balance changes are requested */ + startTime: Scalars['FirstMoment']['output']; +}; + +export type IkReplay = { + __typename?: 'IkReplay'; + ik: Scalars['SafeString']['output']; + isIkReplay: Scalars['Boolean']['output']; +}; + +export enum IncreaseEnv { + Production = 'production', + Sandbox = 'sandbox' +} + +export type IncreaseLink = Link & { + __typename?: 'IncreaseLink'; + /** ISO-8601 timestamp when the Link was created. */ + created: Scalars['String']['output']; + /** URL to the Fragment Dashboard for this Link. */ + dashboardUrl: Scalars['String']['output']; + /** A list of External Accounts associated with this Link. */ + externalAccounts: ExternalAccountsConnection; + /** FRAGMENT ID of the Increase Link. */ + id: Scalars['ID']['output']; + /** The environment of the Increase Link, either sandbox or production. */ + increaseEnv: IncreaseEnv; + /** Name of the Link as it appears in the Dashboard. */ + name: Scalars['String']['output']; +}; + +/** A condition that must be met on an `Int96` field. */ +export type Int96Condition = { + __typename?: 'Int96Condition'; + /** Amount must exactly match this value. You may not specify this alongside `gte` or `lte`. */ + eq?: Maybe; + /** Amount must be greater than or equal to this value. */ + gte?: Maybe; + /** Amount must be less than or equal to this value. */ + lte?: Maybe; +}; + +/** A condition that must be met on an `Int96` field. */ +export type Int96ConditionInput = { + /** Amount must exactly match this value. You may not specify this alongside `gte` or `lte`. */ + eq?: InputMaybe; + /** Amount must be greater than or equal to this value. */ + gte?: InputMaybe; + /** Amount must be less than or equal to this value. */ + lte?: InputMaybe; +}; + +export type Int96Filter = { + /** Must exactly equal this Int96 value */ + eq?: InputMaybe; + /** Must be greater than or equal to this Int96 value */ + gte?: InputMaybe; + /** Must be less than or equal to this Int96 value */ + lte?: InputMaybe; + /** Must not equal this Int96 value */ + ne?: InputMaybe; +}; + +/** Equivalent to an HTTP 5XX - something went wrong with our API. */ +export type InternalError = Error & { + __typename?: 'InternalError'; + /** The status code of error. For example, 'ledger_not_found'. */ + code: Scalars['String']['output']; + /** The error message */ + message: Scalars['String']['output']; + /** Whether or not the operation is retryable */ + retryable: Scalars['Boolean']['output']; +}; + +/** Ledgers are databases designed for managing money */ +export type Ledger = { + __typename?: 'Ledger'; + /** When aggregating balances, all transactions within a 24 hour period starting at midnight UTC plus this offset are included in each day. */ + balanceUTCOffset: Scalars['UTCOffset']['output']; + created: Scalars['DateTime']['output']; + /** URL to the Fragment Dashboard for this Ledger. */ + dashboardUrl: Scalars['String']['output']; + /** Entry statistics for this Ledger. */ + entryStats: LedgerEntryStatsConnection; + id: Scalars['ID']['output']; + /** The IK passed into the [createLedger](/api-reference/api-mutations#createledger) mutation. This is treated as a unique identifier for this Ledger. */ + ik: Scalars['SafeString']['output']; + /** Ledger Account data migrations affecting this Ledger. */ + ledgerAccountDataMigrations: LedgerAccountDataMigrationConnection; + /** Query LedgerAccounts in Ledger. Ledger Accounts are paginated and returned in reverse-chronological order by their created date. */ + ledgerAccounts: LedgerAccountsConnection; + /** Query Ledger Entries in a Ledger. Ledger Entries are paginated and sorted in reverse-chronological order by posted date. */ + ledgerEntries: LedgerEntriesConnection; + /** Ledger Entry data migrations affecting this Ledger. */ + ledgerEntryDataMigrations: LedgerEntryDataMigrationConnection; + /** Query a Ledger Entry Group for this Ledger given its key and value. */ + ledgerEntryGroup: LedgerEntryGroup; + /** Query LedgerEntryGroups in Ledger. Ledger Entry Groups are paginated and returned in order lexigraphically key then inverse chronologically by created. */ + ledgerEntryGroups: LedgerEntryGroupsConnection; + /** + * List Ledger Lines across accounts in this Ledger, sorted by `posted` in reverse chronological order. + * Specify a single Ledger Account via the `ledgerAccount` field, or query across multiple accounts using the `path` filter or `ledgerAccount.in`. + */ + lines: LedgerLinesConnection; + /** Schema migrations affecting this Ledger. */ + migrations: LedgerMigrationConnection; + /** The name of the Ledger. Can be updated with the [updateLedger](/api-reference/api-mutations#updateledger) mutation. */ + name: Scalars['String']['output']; + /** Schema key associated with this Ledger. */ + schema?: Maybe; + type: LedgerTypes; + /** @deprecated Callers should not need to query or store this value. */ + workspaceId: Scalars['ID']['output']; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerEntryStatsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLedgerAccountDataMigrationsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLedgerAccountsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLedgerEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLedgerEntryDataMigrationsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLedgerEntryGroupArgs = { + ledgerEntryGroup: EntryGroupMatchInput; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLedgerEntryGroupsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLinesArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter: LedgerLinesFilterSet; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerMigrationsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** A ledger account is a container for money */ +export type LedgerAccount = { + __typename?: 'LedgerAccount'; + /** Total of all lines in this ledger account and child ledger accounts of the same currency as this ledger account */ + balance: Scalars['Int96']['output']; + /** How much did the this ledger account's balance change during the specified period. This query will include all child accounts in the same currency as this ledger account. */ + balanceChange: Scalars['Int96']['output']; + /** How much did the this ledger account's balances change during the specified period. This query will include all child accounts of all currencies. */ + balanceChanges: CurrencyAmountConnection; + /** + * How much did the ledger account's balances change over a time period with a specified granularity. + * For example, if the period is 1969-07 and the granularity is daily, the query will return the balance changes for each day in the month. + */ + balanceChangesDuring: BalanceChangeDuringConnection; + /** Total of all lines in this ledger account and child ledger accounts in all currencies */ + balances: CurrencyAmountConnection; + /** + * The ledger account's balances over a time period with a specified granularity. + * For example, if the period is 1969-07 and the granularity is daily, the query will return the balance for each day in the month. + */ + balancesDuring: HistoricalBalanceConnection; + /** Total of all lines in child ledger accounts of the same currency as this ledger account */ + childBalance: Scalars['Int96']['output']; + /** How much did the this ledger account's childBalance change during the specified period. This query will only include child accounts which are in the same currency as this one. See childBalanceChanges to include children of different currencies. */ + childBalanceChange: Scalars['Int96']['output']; + /** How much did the this ledger account's child accounts' balances change during the specified period. This query will include all child accounts of all currencies. */ + childBalanceChanges: CurrencyAmountConnection; + /** + * How much did the ledger account's childBalances change over a time period with a specified granularity. + * For example, if the period is 1969-07 and the granularity is daily, the query will return the balance changes for each day in the month. + */ + childBalanceChangesDuring: BalanceChangeDuringConnection; + /** Total of all lines in child ledger accounts of this ledger in all currencies */ + childBalances: CurrencyAmountConnection; + /** + * The ledger account's childBalances over a time period with a specified granularity. + * For example, if the period is 1969-07 and the granularity is daily, the query will return the balance for each day in the month. + */ + childBalancesDuring: HistoricalBalanceConnection; + /** The child Ledger Accounts of this Ledger Accountw */ + childLedgerAccounts: LedgerAccountsConnection; + /** + * The clearing status of the Ledger Account. + * + * This field is null when the Ledger Account is not configured to be a Clearing account. + */ + clearingStatus?: Maybe; + /** The consistency configuration for this Ledger Account. This defines how updates to this Ledger Account's ownBalance are handled. */ + consistencyConfig: LedgerAccountConsistencyConfig; + created: Scalars['DateTime']['output']; + /** Currency of this ledger account */ + currency?: Maybe; + /** Indicates if the account allows multiple currencies or is restricted to a single currency */ + currencyMode: CurrencyMode; + /** URL to the Fragment Dashboard for this Ledger Account. */ + dashboardUrl: Scalars['String']['output']; + id: Scalars['ID']['output']; + /** The idempotency key used to create this account */ + ik: Scalars['String']['output']; + /** Ledger this account is in */ + ledger: Ledger; + /** All Ledger Entries that have posted to this Ledger Account. Ledger Entries are paginated and sorted in reverse-chronological order by posted date. */ + ledgerEntries: LedgerEntriesConnection; + /** ID of the ledger this account is in */ + ledgerId: Scalars['ID']['output']; + /** List Ledger Lines in this account, sorted by `posted` in reverse chronological order. Does not include Ledger Lines from child Ledger Accounts. */ + lines: LedgerLinesConnection; + /** The Link for the External Account that is linked to this ledger account */ + link?: Maybe; + /** External Account that is linked to this ledger account */ + linkedAccount?: Maybe; + /** The name of your Ledger Account */ + name?: Maybe; + /** Total of all lines in this ledger account, excluding all child ledger accounts */ + ownBalance: Scalars['Int96']['output']; + /** How much did the this ledger account's ownBalance change during the specified period. This query will exclude all child accounts. */ + ownBalanceChange: Scalars['Int96']['output']; + /** How much did the this ledger account's ownBalance change during the specified period. This is the total of all lines in this ledger account, excluding all child ledger accounts */ + ownBalanceChanges: CurrencyAmountConnection; + /** + * How much did the ledger account's ownBalances change over a time period with a specified granularity. + * For example, if the period is 1969-07 and the granularity is daily, the query will return the balance changes for each day in the month. + */ + ownBalanceChangesDuring: BalanceChangeDuringConnection; + /** Total of all lines across all currencies in this ledger account, excluding all child ledger accounts */ + ownBalances: CurrencyAmountConnection; + /** + * The ledger account's ownBalances over a time period with a specified granularity. + * For example, if the period is 1969-07 and the granularity is daily, the query will return the balance for each day in the month. + */ + ownBalancesDuring: HistoricalBalanceConnection; + /** The parent ledger account of this ledger account */ + parentLedgerAccount?: Maybe; + /** ID of the parent ledger account of this ledger account */ + parentLedgerAccountId?: Maybe; + /** + * The unique Path of the ledger account. This is a slash-delimited string containing the location of an account in its chart of accounts. + * For accounts created with a schema, this will be composed of account keys. Else, for accounts created with the createLedgerAccounts API, + * this will be composed of the IKs of an account and its ancestors. + */ + path: Scalars['String']['output']; + /** Payment configuration of this Ledger Account, if it is a payment account. */ + payment?: Maybe; + /** + * The posted timestamp window for this clearing account, representing the earliest and latest + * posted timestamps across all currencies. + * + * This field is null when the Ledger Account is not configured to be a Clearing account + * or when no entries have been posted to this account. + */ + postedWindow?: Maybe; + type: LedgerAccountTypes; + /** A list of external account transactions that haven't been reconciled to this ledger account yet. Only populated for linked ledger accounts. Transactions are sorted in reverse chronological order by posted date. */ + unreconciledTxs: TxsConnection; + /** @deprecated Callers should not need to query or store this value. */ + workspaceId: Scalars['ID']['output']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountBalanceArgs = { + at?: InputMaybe; + consistencyMode?: InputMaybe; + currency?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountBalanceChangeArgs = { + currency?: InputMaybe; + period: Scalars['Period']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountBalanceChangesArgs = { + period: Scalars['Period']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountBalanceChangesDuringArgs = { + currency?: InputMaybe; + duration: Scalars['Int']['input']; + granularity: Granularity; + startTime: Scalars['FirstMoment']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountBalancesArgs = { + after?: InputMaybe; + at?: InputMaybe; + before?: InputMaybe; + consistencyMode?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountBalancesDuringArgs = { + currency?: InputMaybe; + duration: Scalars['Int']['input']; + granularity: Granularity; + startTime: Scalars['FirstMoment']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildBalanceArgs = { + at?: InputMaybe; + consistencyMode?: InputMaybe; + currency?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildBalanceChangeArgs = { + currency?: InputMaybe; + period: Scalars['Period']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildBalanceChangesArgs = { + period: Scalars['Period']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildBalanceChangesDuringArgs = { + currency?: InputMaybe; + duration: Scalars['Int']['input']; + granularity: Granularity; + startTime: Scalars['FirstMoment']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildBalancesArgs = { + after?: InputMaybe; + at?: InputMaybe; + before?: InputMaybe; + consistencyMode?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildBalancesDuringArgs = { + currency?: InputMaybe; + duration: Scalars['Int']['input']; + granularity: Granularity; + startTime: Scalars['FirstMoment']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildLedgerAccountsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountLedgerEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountLinesArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountOwnBalanceArgs = { + at?: InputMaybe; + consistencyMode?: InputMaybe; + currency?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountOwnBalanceChangeArgs = { + currency?: InputMaybe; + period: Scalars['Period']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountOwnBalanceChangesArgs = { + period: Scalars['Period']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountOwnBalanceChangesDuringArgs = { + currency?: InputMaybe; + duration: Scalars['Int']['input']; + granularity: Granularity; + startTime: Scalars['FirstMoment']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountOwnBalancesArgs = { + after?: InputMaybe; + at?: InputMaybe; + before?: InputMaybe; + consistencyMode?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountOwnBalancesDuringArgs = { + currency?: InputMaybe; + duration: Scalars['Int']['input']; + granularity: Granularity; + startTime: Scalars['FirstMoment']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountUnreconciledTxsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** The clearing status of a Ledger Account. */ +export enum LedgerAccountClearingStatus { + /** The account has no outstanding balances. */ + Cleared = 'cleared', + /** The account has outstanding balances that have not been cleared. */ + Pending = 'pending' +} + +export type LedgerAccountClearingStatusFilter = { + /** Results must match the specified clearing account status */ + equalTo?: InputMaybe; +}; + +/** A set of conditions that a Ledger Account must meet for an operation to succeed. */ +export type LedgerAccountCondition = { + __typename?: 'LedgerAccountCondition'; + /** A condition that the `ownBalance` field must satisfy. Note that this condition always applies to the latest balance, not to balances at a specific date or time. See [Read balances](https://fragment.dev/guides/read-balances) for more on the different types of Ledger Account balances. */ + ownBalance?: Maybe; + /** A condition that the `totalBalance` field must satisfy. Note that this condition always applies to the latest balance, not to balances at a specific date or time. See [Read balances](https://fragment.dev/guides/read-balances) for more on the different types of Ledger Account balances. */ + totalBalance?: Maybe; +}; + +/** A set of conditions that a Ledger Account must meet for an operation to succeed. */ +export type LedgerAccountConditionInput = { + /** A condition that the ownBalance field must satisfy. Note that this condition always applies to the latest balance, not to balances at a specific date or time. See [Read balances](https://fragment.dev/read-balances) for more on the different types of Ledger Account balances. */ + ownBalance?: InputMaybe; + /** A condition that the totalBalance field must satisfy. Note that this condition always applies to the latest balance, not to balances at a specific date or time. See [Read balances](https://fragment.dev/read-balances) for more on the different types of Ledger Account balances. */ + totalBalance?: InputMaybe; +}; + +/** + * The consistency configuration of a Ledger Account's balance updates. + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ +export type LedgerAccountConsistencyConfig = { + __typename?: 'LedgerAccountConsistencyConfig'; + lines: LedgerLinesConsistencyMode; + /** + * If set to `strong`, then a Ledger Account's `ownBalance` updates will be strongly consistent with + * the API response. This Ledger Account's balance will be updated and + * available for strongly consistent reads once you receive an API response. + * + * Otherwise if not set or set to `eventual`, `ownBalance` updates are applied + * asynchronously and may not be immediately reflected in queries. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + ownBalanceUpdates: BalanceUpdateConsistencyMode; + /** + * If set to `strong`, then a Ledger Account's `ownBalance`, `childBalance`, and `balance` fields' updates will be strongly consistent with + * the API response. This Ledger Account's balance will be updated and + * available for strongly consistent reads once you receive an API response. + * + * Otherwise if not set or set to `eventual`, updates are applied + * asynchronously and may not be immediately reflected in queries. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + totalBalanceUpdates?: Maybe; +}; + +/** + * The payload configuring the consistency for this Ledger Account. + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ +export type LedgerAccountConsistencyConfigInput = { + /** + * The consistency configuration for Ledger Entry Groups affecting this account. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + groups?: InputMaybe>; + /** + * If set to `strong`, then a Ledger Account's `lines` updates will be strongly consistent with the API response. + * This Ledger Account's balance will be updated and available for strongly consistent reads before you receive an API response. + * + * Otherwise if unset or set to `eventual`, `lines` updates are applied asynchronously and may not be immediately reflected in queries. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + lines?: InputMaybe; + /** + * If set to `strong`, then a Ledger Account's `ownBalance` updates will be strongly consistent with the API response. + * This Ledger Account's balance will be updated and available for strongly consistent reads before you receive an API response. + * + * Otherwise if unset or set to `eventual`, `ownBalance` updates are applied asynchronously and may not be immediately reflected in queries. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + ownBalanceUpdates?: InputMaybe; + /** + * EXPERIMENTAL: If set to `strong`, then a Ledger Account's `totalBalance` updates will be strongly consistent with the API response. + * This Ledger Account's balance will be updated and available for strongly consistent reads before you receive an API response. + * + * Otherwise if unset or set to `eventual`, `totalBalance` updates are applied asynchronously and may not be immediately reflected in queries. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + totalBalanceUpdates?: InputMaybe; +}; + +/** Represents a data migration for a specific Ledger Account in a Ledger. */ +export type LedgerAccountDataMigration = LedgerDataMigration & { + __typename?: 'LedgerAccountDataMigration'; + /** The path of the Ledger Account being migrated. */ + accountPath: Scalars['String']['output']; + /** Current active migration info (null if migration is inactive). */ + currentMigration?: Maybe; + /** The historical transitions of this migration. */ + history: LedgerDataMigrationHistoryConnection; + /** The ledger entries to be migrated. */ + ledgerEntries: LedgerEntriesConnection; + /** The status of the data migration. */ + status: LedgerDataMigrationStatus; +}; + + +/** Represents a data migration for a specific Ledger Account in a Ledger. */ +export type LedgerAccountDataMigrationHistoryArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Represents a data migration for a specific Ledger Account in a Ledger. */ +export type LedgerAccountDataMigrationLedgerEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +export type LedgerAccountDataMigrationConnection = { + __typename?: 'LedgerAccountDataMigrationConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +export type LedgerAccountDataMigrationsFilterSet = { + /** Filter by Ledger Account path. */ + accountPath?: InputMaybe; + /** Filter by the status of the data migration. */ + status?: InputMaybe; +}; + +export type LedgerAccountFilter = { + /** Result must match the specified Ledger Account */ + equalTo?: InputMaybe; + /** Results can match any of specified Ledger Accounts */ + in?: InputMaybe>; +}; + +/** The consistency configuration for a specific Ledger Entry Group in this account. */ +export type LedgerAccountGroupConsistencyConfigInput = { + /** The group key for this configuration. */ + key: Scalars['SafeString']['input']; + /** + * If set to `strong`, then Ledger Entry Group `ownBalance`s updates for this account will be strongly consistent with the API response. + * This Ledger Account's Ledger Entry Group balances will be updated and available for strongly consistent reads before you receive an API response. + * + * Otherwise if unset or set to `eventual`, Ledger Entry Group `ownBalance` updates are applied asynchronously and may not be immediately reflected in queries. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + ownBalanceUpdates: BalanceUpdateConsistencyMode; +}; + +/** + * Specify a Ledger Account by using `id` or `path`. + * + * When specifying a Ledger Account by `path`, you must provide `ledger`. + */ +export type LedgerAccountMatchInput = { + /** The FRAGMENT ID of the Ledger Account */ + id?: InputMaybe; + /** The Ledger to which this Ledger Account belongs. This is required if you are specifying the Ledger Account by `path`. */ + ledger?: InputMaybe; + /** + * The unique path of the Ledger Account. + * This is a slash-delimited string containing the keys of an account and all its direct ancestors. + */ + path?: InputMaybe; +}; + +/** Payment configuration of a Ledger Account. */ +export type LedgerAccountPayment = { + __typename?: 'LedgerAccountPayment'; + penguin: Scalars['Boolean']['output']; +}; + +export type LedgerAccountTypeFilter = { + /** Results must be of the specified Ledger Account type */ + equalTo?: InputMaybe; + /** Results can have any of the specified Ledger Account types */ + in?: InputMaybe>; +}; + +export enum LedgerAccountTypes { + Asset = 'asset', + Expense = 'expense', + Income = 'income', + Liability = 'liability' +} + +/** A paginated list of Ledger Accounts */ +export type LedgerAccountsConnection = { + __typename?: 'LedgerAccountsConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export type LedgerAccountsFilterSet = { + /** Use this to filter Ledger Accounts by their clearing account status */ + clearingStatus?: InputMaybe; + /** + * Filter by the earliest posted timestamp across all currencies for clearing accounts. You must also provide clearingStatus in the same filter. + * Only clearing accounts where the minimum posted timestamp (across all currencies) matches this filter will be included. + */ + earliestPosted?: InputMaybe; + /** Use this to filter Ledger Accounts by their parent status */ + hasParentLedgerAccount?: InputMaybe; + /** Use this to filter Ledger Accounts by their linked status */ + isLinkedAccount?: InputMaybe; + /** + * Filter by the latest posted timestamp across all currencies for clearing accounts. You must also provide clearingStatus in the same filter. + * Only clearing accounts where the maximum posted timestamp (across all currencies) matches this filter will be included. + */ + latestPosted?: InputMaybe; + /** Use this to filter Ledger Accounts by their ID or path */ + ledgerAccount?: InputMaybe; + /** Use this to filter Ledger Accounts by their external linked account ID */ + linkedAccount?: InputMaybe; + /** Use this to filter Ledger Accounts by their parent account IDs */ + parentLedgerAccount?: InputMaybe; + /** + * A filter that must match the account path. Wildcards ('*') may be used only for template variables, and will only match a single variable each. + * For example: 'assets-root/accounts-receivable/merchant:*' would match: 'assets-root/accounts-receivable/merchant:1' and 'assets-root/accounts-receivable/merchant:1/child'. + * Wildcards may not be used outside of template variables. For example, passing in 'assets-root/*' as a filter is invalid and would raise a GraphQL error. + */ + path?: InputMaybe; + /** Use this to filter Ledger Accounts by their type */ + type?: InputMaybe; +}; + +/** Represents a data migration for a Ledger. */ +export type LedgerDataMigration = { + /** Current active migration info (null if migration is inactive). */ + currentMigration?: Maybe; + /** The historical transitions of this migration. */ + history: LedgerDataMigrationHistoryConnection; + /** The ledger entries to be migrated. */ + ledgerEntries: LedgerEntriesConnection; + /** The status of the data migration. */ + status: LedgerDataMigrationStatus; +}; + + +/** Represents a data migration for a Ledger. */ +export type LedgerDataMigrationHistoryArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Represents a data migration for a Ledger. */ +export type LedgerDataMigrationLedgerEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** A paginated list of migration history entries. */ +export type LedgerDataMigrationHistoryConnection = { + __typename?: 'LedgerDataMigrationHistoryConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +/** A single schema version in the migration history. */ +export type LedgerDataMigrationHistoryEntry = { + __typename?: 'LedgerDataMigrationHistoryEntry'; + /** The schema version. */ + schemaVersion: Scalars['Int']['output']; + /** The current status of this schema version (active if it's the latest and migration is active, otherwise inactive). */ + status: LedgerDataMigrationStatus; +}; + +/** The status of a ledger data migration. */ +export enum LedgerDataMigrationStatus { + /** The migration is active. */ + Active = 'active', + /** The migration is inactive. */ + Inactive = 'inactive' +} + +/** A paginated list of Ledger Entries */ +export type LedgerEntriesConnection = { + __typename?: 'LedgerEntriesConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export type LedgerEntriesFilterSet = { + /** Use this filter to filter Ledger Entries by their `posted` date. */ + date?: InputMaybe; + /** Use this to filter Ledger Entries by groups. The response will include entries that contain or do not contain specific groups. */ + group?: InputMaybe; + /** Use this to filter Ledger Entries that were posted using `reverseLedgerEntry`. */ + isReversal?: InputMaybe; + /** Use this to filter Ledger Entries that have been reversed. */ + isReversed?: InputMaybe; + /** Use to filter Ledger Entries by their IDs or IKs. */ + ledgerEntry?: InputMaybe; + /** Use this filter to filter Ledger Entries by their `posted` timestamp. */ + posted?: InputMaybe; + /** Use this filter to show hidden Ledger Entries. */ + showHidden?: InputMaybe; + /** Use this to filter Ledger Entries by tags. The response will include entries that contain tags matching the filter. */ + tag?: InputMaybe; + /** Use this to filter Ledger Entries by type. Ledger Entry types are defined in Schemas. */ + type?: InputMaybe; + /** Use this to filter Ledger Entries by their type version. */ + typeVersion?: InputMaybe; +}; + +export type LedgerEntry = { + __typename?: 'LedgerEntry'; + /** The conditions that were satisfied by this Ledger Entry when it was posted. */ + conditions: Array; + /** ISO-8601 timestamp this LedgerEntry was created in Fragment. */ + created: Scalars['DateTime']['output']; + /** URL to the Fragment Dashboard for this Ledger Entry. */ + dashboardUrl: Scalars['String']['output']; + /** Date this LedgerEntry posted to its Ledger e.g. "2021-01-01". */ + date: Scalars['Date']['output']; + /** Description posted for this Ledger Entry. */ + description?: Maybe; + /** The Ledger Entry Groups this Ledger Entry is in. */ + groups: Array; + /** + * Indicates whether this Ledger Entry is hidden when listing Ledger Entries. + * Reversed and Reversal Ledger Entries are hidden by default because taken together they have no impact on a Ledger's balances. + */ + hidden: Scalars['Boolean']['output']; + /** The ID of this LedgerEntry. */ + id: Scalars['ID']['output']; + /** The idempotency key used to post this ledger entry */ + ik: Scalars['String']['output']; + /** + * Indicates whether this Ledger Entry is a reversal of another Ledger Entry. + * If so, reverses will point to that Ledger Entry. + */ + isReversal: Scalars['Boolean']['output']; + /** + * Indicates whether this Ledger Entry has been reversed by another Ledger Entry. + * If so, reversedBy will point to that Ledger Entry. + */ + isReversed: Scalars['Boolean']['output']; + /** The Ledger that this Ledger Entry is posted to. */ + ledger: Ledger; + /** The ID of the Ledger this Ledger Entry is posted to. */ + ledgerId: Scalars['ID']['output']; + /** Lines posted in this Ledger Entry. */ + lines: LedgerLinesConnection; + /** The parameters used to post this Ledger Entry. */ + parameters?: Maybe; + /** ISO-8601 timestamp this LedgerEntry posted to its Ledger. */ + posted: Scalars['DateTime']['output']; + /** The reversal history of this Ledger Entry. Each entry in this connection shares the same IK. */ + reversalHistory: LedgerEntriesConnection; + /** The position of this Ledger Entry in its reversalHistory. This is a one-indexed value, so the initial entry will have reversalPosition 1. */ + reversalPosition: Scalars['Int']['output']; + /** ISO-8601 timestamp of when this Ledger Entry was reversed. */ + reversedAt?: Maybe; + /** The Ledger Entry that reversed this Ledger Entry. */ + reversedBy?: Maybe; + /** The Ledger Entry that was reversed by this Ledger Entry. */ + reverses?: Maybe; + /** The set of tags attached to this Ledger Entry. */ + tags: Array; + /** The type of the Ledger Entry. */ + type?: Maybe; + /** The version of the Ledger Entry type used when it was posted. */ + typeVersion?: Maybe; + /** @deprecated Callers should not need to query or store this value. */ + workspaceId: Scalars['ID']['output']; +}; + +/** A set of pre-conditions and post-conditions that a Ledger Account must have satisfied. Each `LedgerEntryCondition` has at least one of `precondition` or `postcondition`. */ +export type LedgerEntryCondition = { + __typename?: 'LedgerEntryCondition'; + /** The Ledger Account that must satisfied the provided conditions. */ + account: LedgerAccount; + /** The currency of the balance associated with this `LedgerEntryCondition`. */ + currency: Currency; + /** The conditions that must be satisfied after the operation. */ + postcondition?: Maybe; + /** The conditions that must be satisfied prior to the operation. */ + precondition?: Maybe; +}; + +/** A set of pre-conditions and post-conditions that a Ledger Account balance must meet for an operation to succeed. You must specify at least one of `precondition` or `postcondition` for each condition. */ +export type LedgerEntryConditionInput = { + /** The Ledger Account that must satisfy the provided conditions. */ + account: LedgerAccountMatchInput; + /** For Ledger Accounts in the `multi` currency mode, you must specify the currency of the balance affected by the condition. You only need to specify this field for multi-currency accounts. */ + currency?: InputMaybe; + /** The conditions that must hold after the operation. */ + postcondition?: InputMaybe; + /** The conditions that must hold prior to the operation. */ + precondition?: InputMaybe; +}; + +/** Represents a data migration for a specific entry type in a Ledger. */ +export type LedgerEntryDataMigration = LedgerDataMigration & { + __typename?: 'LedgerEntryDataMigration'; + /** Current active migration info (null if migration is inactive). */ + currentMigration?: Maybe; + /** The entry type being migrated. */ + entryType: Scalars['SafeString']['output']; + /** The historical transitions of this migration. */ + history: LedgerDataMigrationHistoryConnection; + /** The ledger entries to be migrated. */ + ledgerEntries: LedgerEntriesConnection; + /** The status of the data migration. */ + status: LedgerDataMigrationStatus; + /** The version of the entry type being migrated. */ + typeVersion: Scalars['Int']['output']; +}; + + +/** Represents a data migration for a specific entry type in a Ledger. */ +export type LedgerEntryDataMigrationHistoryArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Represents a data migration for a specific entry type in a Ledger. */ +export type LedgerEntryDataMigrationLedgerEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +export type LedgerEntryDataMigrationConnection = { + __typename?: 'LedgerEntryDataMigrationConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +export type LedgerEntryDataMigrationsFilterSet = { + /** Filter by Ledger Entry type. */ + entryType?: InputMaybe; + /** Filter by the status of the data migration. */ + status?: InputMaybe; + /** Filter by Ledger Entry type version. */ + typeVersion?: InputMaybe; +}; + +export type LedgerEntryFilter = { + /** Result must be the specified Ledger Entry. */ + equalTo?: InputMaybe; + /** Result can be any of the specified Ledger Entries. Limited to 100 items maximum. */ + in?: InputMaybe>; +}; + +/** A group of Ledger Entries */ +export type LedgerEntryGroup = { + __typename?: 'LedgerEntryGroup'; + balances: LedgerEntryGroupBalanceConnection; + /** ISO-8601 timestamp this LedgerEntryGroup was created in Fragment. */ + created?: Maybe; + /** URL to the Fragment Dashboard for this Ledger Entry Group. */ + dashboardUrl: Scalars['String']['output']; + /** The key of this Ledger Entry Group. */ + key: Scalars['SafeString']['output']; + /** The Ledger that this Ledger Entry Group is within. */ + ledger: Ledger; + ledgerEntries: LedgerEntriesConnection; + /** The ID of the Ledger this Ledger Entry Group is within. */ + ledgerId: Scalars['ID']['output']; + /** The value associated with Ledger Entry Group. */ + value: Scalars['SafeString']['output']; +}; + + +/** A group of Ledger Entries */ +export type LedgerEntryGroupBalancesArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A group of Ledger Entries */ +export type LedgerEntryGroupLedgerEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** Represents the total effect of a Ledger Entry Group on a Ledger Account balance for a single currency. */ +export type LedgerEntryGroupBalance = { + __typename?: 'LedgerEntryGroupBalance'; + /** The Ledger Account whose balance is affected. */ + account: LedgerAccount; + /** The currency of the affected balance. */ + currency: Currency; + /** The total balance change for this Ledger Account and currency. */ + ownBalance: Scalars['Int96']['output']; +}; + + +/** Represents the total effect of a Ledger Entry Group on a Ledger Account balance for a single currency. */ +export type LedgerEntryGroupBalanceOwnBalanceArgs = { + consistencyMode?: InputMaybe; +}; + +/** A set of balance changes for a specific Ledger Entry Group. */ +export type LedgerEntryGroupBalanceConnection = { + __typename?: 'LedgerEntryGroupBalanceConnection'; + nodes: Array; + pageInfo: PageInfo; +}; + +/** Filter Ledger Entry Groups by their balance impact on a Ledger Account. If a group has a matching balance for the specified account, the group will be included in the results. */ +export type LedgerEntryGroupBalanceFilter = { + /** A Ledger Entry Group will be included in the result if it has a balance for the specified account. If 'account' is the only filter specified, then any non-null balance in any currency will match. */ + account: GroupBalanceAccountFilter; + /** A Ledger Entry Group will be included in the result if it has a balance for the specified account in the specified currency. If the 'ownBalance' filter is omitted then any non-null balance will match. */ + currency?: InputMaybe; + /** A Ledger Entry Group will be included in the result if it has a balance for the specified account that passes the specified value predicate. If the 'currency' filter is omitted then any balance in any currency that passes the predicate will match. If the 'currency' filter is included, the value predicate will only be evaluated against the specified currency. */ + ownBalance?: InputMaybe; +}; + +/** Optional filters for querying balances on a Ledger Entry Group. */ +export type LedgerEntryGroupBalanceFilterSet = { + /** Filter to a subset of accounts */ + account?: InputMaybe; + /** Filter to one or more currencies */ + currency?: InputMaybe; + /** Filter to only balances in a certain range */ + ownBalance?: InputMaybe; +}; + +export type LedgerEntryGroupInput = { + /** The key of this group. Can be up to 128 characters long. */ + key: Scalars['SafeString']['input']; + /** The value associated with this group's key. Can be up to 128 characters long. */ + value: Scalars['SafeString']['input']; +}; + +export type LedgerEntryGroupMatchInput = { + key: Scalars['SafeString']['input']; + ledger: LedgerMatchInput; + value: Scalars['SafeString']['input']; +}; + +/** A paginated list of Ledger Entry Groups */ +export type LedgerEntryGroupsConnection = { + __typename?: 'LedgerEntryGroupsConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +export type LedgerEntryGroupsFilterSet = { + /** Filter Ledger Entry Groups by their balance impact on a Ledger Account. If a group has a matching balance for the specified account, the group will be included in the results. */ + balance?: InputMaybe; + /** Use to filter Ledger Entry Groups by their created timestamp */ + created?: InputMaybe; + /** Use to filter Ledger Entry Groups by their key */ + key?: InputMaybe; + /** Use to filter Ledger Entry Groups by their value */ + value?: InputMaybe; +}; + +/** Ledger Entries are limited to 30 Ledger Lines. */ +export type LedgerEntryInput = { + /** Conditions that must be satisfied to post this Ledger Entry. The Ledger Entry will reject with a BadRequestError if any condition is not met. You can only add a condition on a Ledger Account containing a Line in this Ledger Entry. */ + conditions?: InputMaybe>; + /** If specified, will also be used as the description for LedgerLines unless they specify their own description. */ + description?: InputMaybe; + /** Adds this Ledger Entry to this set of Ledger Entry Groups */ + groups?: InputMaybe>; + /** The Ledger to which to post this Ledger Entry. Must be linked to a Schema that defines the provided Ledger Entry type. */ + ledger?: InputMaybe; + /** The Ledger Lines to create as part of this Ledger Entry. This cannot be used with Ledger Entries that have a 'type' i.e. Ledger Entries defined in the Schema. This can be useful during non-routine operations such as an incident. It is not recommended to use 'lines' during routine operations. */ + lines?: InputMaybe>; + /** Parameters to be included in a templated Ledger Entry. All provided parameters must be present in the typed Ledger Entry within the Schema linked to the provided Ledger. */ + parameters?: InputMaybe; + /** ISO 8601 timestamp to post this Ledger Entry e.g. "2021-01-01" or "2021-01-01T16:45:00Z". Will error out if supplied to reconcileTx or createOrder since the transaction timestamp will be used instead */ + posted?: InputMaybe; + /** A set of tags attached to this Ledger Entry. */ + tags?: InputMaybe>; + /** The type of the Ledger Entry. Must be defined in the Schema linked to the Ledger specified below. */ + type?: InputMaybe; + /** Experimental: This field is reserved for an upcoming feature and is not yet supported. */ + typeVersion?: InputMaybe; +}; + +/** Specify a Ledger Entry by using `id`. */ +export type LedgerEntryMatchInput = { + /** The FRAGMENT ID of the Ledger Entry */ + id?: InputMaybe; + /** The IK provided to the `addLedgerEntry` mutation or the `ik` field returned from a `reconcileTx` mutation. This is required if you have not provided `id`. */ + ik?: InputMaybe; + /** The FRAGMENT ID of the Ledger to which this Ledger Entry belongs. This is required if you have not provided `id`. */ + ledger?: InputMaybe; +}; + +/** Posting count statistics for a specific type and typeVersion of entry in a Ledger. */ +export type LedgerEntryStats = { + __typename?: 'LedgerEntryStats'; + /** The total number of entries of this type. */ + count: Scalars['Int96']['output']; + /** The ledger ID these stats are for. */ + ledgerId: Scalars['SafeString']['output']; + /** The net number of entries (count - reversalsCount). */ + netCount: Scalars['Int96']['output']; + /** The number of entries that are reversals. */ + reversalsCount: Scalars['Int96']['output']; + /** The schema key associated with these stats. */ + schemaKey: Scalars['SafeString']['output']; + /** The type of entry these stats are for. */ + type: Scalars['SafeString']['output']; + /** The version of the entry type these stats are for. */ + typeVersion: Scalars['Int']['output']; +}; + +/** A paginated list of Ledger Entry Stats */ +export type LedgerEntryStatsConnection = { + __typename?: 'LedgerEntryStatsConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +/** A tag attached to a Ledger Entry. */ +export type LedgerEntryTag = { + __typename?: 'LedgerEntryTag'; + /** The key of this tag. */ + key: Scalars['SafeString']['output']; + /** The value associated with this tag's key. */ + value: Scalars['SafeString']['output']; +}; + +export type LedgerEntryTagInput = { + /** The key of this tag. Can be up to 128 characters long. */ + key: Scalars['SafeString']['input']; + /** The value associated with this tag's key. Can be up to 128 characters long. */ + value: Scalars['SafeString']['input']; +}; + +export type LedgerLine = { + __typename?: 'LedgerLine'; + /** LedgerAccount that contains this line */ + account: LedgerAccount; + accountId: Scalars['ID']['output']; + /** How much this line's LedgerAccount's balance changed in integer cents (i.e. in USD 100 is 1 dollar, 100 cents) */ + amount: Scalars['Int96']['output']; + /** ISO-8601 timestamp this LedgerLine was created in Fragment */ + created?: Maybe; + /** Currency of this LedgerLine */ + currency?: Maybe; + /** Date this LedgerLine posted to its LedgerAccount e.g. "2021-01-01" */ + date?: Maybe; + /** Description of this LedgerLine */ + description?: Maybe; + /** ID in the external system of the payment or transfer that created the transaction linked to this LedgerLine */ + externalTransferId?: Maybe; + /** Whether the transaction linked to this LedgerLine was a payment or transfer */ + externalTransferType?: Maybe; + /** ID in the external system of the transaction linked to this LedgerLine */ + externalTxId?: Maybe; + /** + * Indicates whether this Ledger Line is hidden when listing Ledger Lines. + * Reversed and Reversal Ledger Lines are hidden by default because taken together they have no impact on a Ledger Account's balance + */ + hidden: Scalars['Boolean']['output']; + id: Scalars['ID']['output']; + /** + * Indicates whether this Ledger Line is a reversal of another Ledger Line. + * If so, reverses will point to that Ledger Line. + */ + isReversal: Scalars['Boolean']['output']; + /** + * Indicates whether this Ledger Line has been reversed by another Ledger Line. + * If so, reversedBy will point to that Ledger Line. + */ + isReversed: Scalars['Boolean']['output']; + key?: Maybe; + ledger: Ledger; + /** LedgerEntry that contains this line */ + ledgerEntry: LedgerEntry; + /** ID of the LedgerEntry that contains this line */ + ledgerEntryId: Scalars['ID']['output']; + /** Ledger that contains this line */ + ledgerId: Scalars['ID']['output']; + /** ID in the external system of destination or source bank account for an internal bank transfer. Only for internal bank transfers - see otherTxId */ + otherTxExternalAccountExternalId?: Maybe; + /** FRAGMENT ID of destination or source bank account. Only for internal bank transfers - see otherTxId */ + otherTxExternalAccountId?: Maybe; + /** ID in the external system of transaction in the destination or source bank account. Only for internal bank transfers - see otherTxId */ + otherTxExternalId?: Maybe; + /** FRAGMENT ID of the transaction in the destination account (if sending money from this account) or source account (if pulling money into this account). Only applicable if this line is linked to a transaction created through an internal transfer */ + otherTxId?: Maybe; + /** ISO-8601 timestamp this LedgerLine posted to its LedgerAccount */ + posted?: Maybe; + /** ISO-8601 timestamp of when this Ledger Line was reversed. */ + reversedAt?: Maybe; + /** The Ledger Line that reverses the balance changes of this Ledger Line. */ + reversedBy?: Maybe; + /** The Ledger Line whose balance changes are reversed by this Ledger Line. */ + reverses?: Maybe; + /** Tags attached to this Ledger Line. */ + tags: Array; + /** The transaction linked to this LedgerLine */ + tx?: Maybe; + /** Fragment ID of the transaction linked to this LedgerLine */ + txId?: Maybe; + /** credit or debit */ + type: TxType; + /** @deprecated Callers should not need to query or store this value. */ + workspaceId: Scalars['ID']['output']; +}; + + +export type LedgerLineAmountArgs = { + absolute?: InputMaybe; +}; + +export type LedgerLineInput = { + /** The LedgerAccount this line is being added to */ + account: LedgerAccountMatchInput; + /** A positive amount increases the balance of its LedgerAccount, a negative amount reduces the balance of its LedgerAccount */ + amount?: InputMaybe; + /** The currency the ledger line is in */ + currency?: InputMaybe; + /** If not specified the description from the parent LedgerEntryInput will be used */ + description?: InputMaybe; + /** Optional identifier for Ledger Line. You can filter lines by key using [LedgerLinesFilterSet](https://fragment.dev/api-reference/api-types#filter-types-ledgerlinesfilterset). */ + key?: InputMaybe; + /** A set of tags attached to this Ledger Line. */ + tags?: InputMaybe>; + /** Required for reconcileTx to specify the transaction being reconciled, you can specify either the FRAGMENT ID or external ID of the transaction */ + tx?: InputMaybe; +}; + +/** Specify a Ledger Line by using `id`. */ +export type LedgerLineMatchInput = { + /** The FRAGMENT ID of the ledger line */ + id: Scalars['ID']['input']; +}; + +/** A tag attached to a Ledger Line. */ +export type LedgerLineTag = { + __typename?: 'LedgerLineTag'; + /** The key of this tag. */ + key: Scalars['SafeString']['output']; + /** The value associated with this tag's key. */ + value: Scalars['SafeString']['output']; +}; + +/** A paginated list of Ledger Lines */ +export type LedgerLinesConnection = { + __typename?: 'LedgerLinesConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export enum LedgerLinesConsistencyMode { + Eventual = 'eventual', + Strong = 'strong' +} + +export type LedgerLinesFilterSet = { + /** Filter by the created timestamp of the Ledger Line. This is the wall-clock time when the Ledger Line was created. */ + created?: InputMaybe; + /** Filter by the currency of the Ledger Line. */ + currency?: InputMaybe; + /** Use this filter to filter Ledger Lines by their `posted` date. */ + date?: InputMaybe; + /** Use this to filter Ledger Lines that were posted to this Ledger Account, using `reverseLedgerEntry`. */ + isReversal?: InputMaybe; + /** Use this to filter Ledger Lines that have been reversed. */ + isReversed?: InputMaybe; + /** Use this to filter Ledger Lines by key. Ledger Line keys are defined in Schemas. */ + key?: InputMaybe; + /** Specify which Ledger Account to read lines from. Required when querying lines via `Ledger.lines` without a `path` filter. Not allowed when querying via `LedgerAccount.lines`. */ + ledgerAccount?: InputMaybe; + /** + * A filter that string matches the account path. Wildcards ('*') can be used to return lines across multiple accounts. + * To search for all instances of a a Ledger Account template, use the `matches` filter with an wildcard character in place of the template value e.g. `assets/user:*`. This returns lines from all instances of this template, interleaved by `posted` timestamp. + * To search for all descendant Ledger Accounts under a given path, use a trailing `/*` in the `matches` filter e.g. `assets/user:user-1>/*`. This returns lines from all descendants at any depth, but not lines from the parent account at `assets/user:user-1>`. + * To OR multiple `matches` patterns and get a single paginated list, use `matchesAny` — e.g. `matchesAny: ["assets/user:user-1/*", "assets/user:user-2/*"]` returns descendants of both prefixes interleaved by `posted` timestamp. + * Cannot be combined with `ledgerAccount` filter. Not allowed when querying via `LedgerAccount.lines`. You cannot use wildcards for both descendant and template instance matching in the same query. + */ + path?: InputMaybe; + /** Use this filter to filter Ledger Lines by their `posted` timestamp. */ + posted?: InputMaybe; + /** Use this filter to find hidden Ledger Lines. */ + showHidden?: InputMaybe; + /** Filter Ledger Lines by tag. Only matches lines that have the specified tags attached directly to them. */ + tag?: InputMaybe; + type?: InputMaybe; +}; + +/** Specify a Ledger by using `id` or `ik`. */ +export type LedgerMatchInput = { + /** The FRAGMENT ID of the Ledger */ + id?: InputMaybe; + /** The IK passed into the [createLedger](/api-reference/api-mutations#createledger) mutation. This is treated as a second unique identifier for this Ledger. */ + ik?: InputMaybe; +}; + +/** + * Represents a Schema being applied to a Ledger. + * It contains metadata about the Ledger, the Schema, and the status of the migration. + */ +export type LedgerMigration = { + __typename?: 'LedgerMigration'; + /** The Ledger that the migration is run on. */ + ledger: Ledger; + schemaVersion: SchemaVersion; + /** The status of the Ledger Migration. */ + status: LedgerMigrationStatus; +}; + +/** A paginated list of Ledger Migrations */ +export type LedgerMigrationConnection = { + __typename?: 'LedgerMigrationConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +/** The status of a ledger migration. */ +export enum LedgerMigrationStatus { + /** + * The Ledger Migration has been successfully completed. + * This is a terminal state. + */ + Completed = 'completed', + /** + * The Ledger Migration has failed. + * This can happen either due to an invalid schema or an internal error. + * This is a terminal state. + */ + Failed = 'failed', + /** The Ledger Migration has been queued. */ + Queued = 'queued', + /** + * The Ledger Migration has been skipped because a newer version is available. + * This is a terminal state. + */ + Skipped = 'skipped', + /** The Ledger Migration has been started. */ + Started = 'started' +} + +export type LedgerTypeFilter = { + equalTo?: InputMaybe; + /** Must match one of the values provided. Limited to 100 items maximum. */ + in?: InputMaybe>; +}; + +export enum LedgerTypes { + Double = 'double' +} + +/** A paginated list of Ledgers */ +export type LedgersConnection = { + __typename?: 'LedgersConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export type LedgersFilterSet = { + hasSchema?: InputMaybe; + type?: InputMaybe; +}; + +export type Link = { + /** ISO-8601 timestamp when the Link was created. */ + created: Scalars['String']['output']; + /** URL to the Fragment Dashboard for this Link. */ + dashboardUrl: Scalars['String']['output']; + /** A list of External Accounts associated with this Link. */ + externalAccounts: ExternalAccountsConnection; + /** FRAGMENT ID of the Link. */ + id: Scalars['ID']['output']; + /** Name of the Link as it appears in the Dashboard. */ + name: Scalars['String']['output']; +}; + +export type LinkMatchInput = { + id: Scalars['ID']['input']; +}; + +/** The type of Link an external account belongs to. */ +export enum LinkType { + /** A Custom Link */ + CustomLink = 'CustomLink', + /** An Increase Link */ + IncreaseLink = 'IncreaseLink', + /** A Stripe Link */ + StripeLink = 'StripeLink', + /** A Unit Link */ + UnitLink = 'UnitLink' +} + +/** A paginated list of Links */ +export type LinksConnection = { + __typename?: 'LinksConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +/** An object defining the input for migrating a Ledger Entry. */ +export type MigrateLedgerEntryInput = { + /** The Ledger Entry to migrate */ + id: Scalars['ID']['input']; + /** The Ledger Entry you want to migrate it to */ + newLedgerEntry: LedgerEntryInput; +}; + +export type MigrateLedgerEntryResponse = BadRequestError | InternalError | MigrateLedgerEntryResult; + +export type MigrateLedgerEntryResult = { + __typename?: 'MigrateLedgerEntryResult'; + /** Whether this migration was an IK replay or not */ + isIkReplay: Scalars['Boolean']['output']; + /** The new Ledger Entry posted as a result of the migration */ + newLedgerEntry: LedgerEntry; + /** The Ledger Entry that was migrated */ + reversedLedgerEntry: LedgerEntry; + /** The reversal Ledger Entry that was posted to reverse the Ledger Entry being migrated */ + reversingLedgerEntry: LedgerEntry; +}; + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type Mutation = { + __typename?: 'Mutation'; + _empty?: Maybe; + /** + * Batch version of [addLedgerEntry](http://localhost:3001/api-reference/ledger-mutations#addledgerentry). + * + * Adds a batch of Ledger Entries in one synchronous and atomic transaction. Either every entry is added or none are. + */ + addLedgerEntries: AddLedgerEntriesResponse; + /** Adds a Ledger Entry to a Ledger. This Ledger Entry cannot be into a Linked Ledger Account. For that, use [reconcileTx](https://fragment.dev/api-reference/api-mutations#reconciletx) */ + addLedgerEntry: AddLedgerEntryResponse; + /** Creates a custom currency. */ + createCustomCurrency: CreateCustomCurrencyResponse; + /** Custom Links let you integrate external systems that don't have native support. See [Custom Links](https://fragment.dev/guides/sync-payments#custom-link) */ + createCustomLink: CreateCustomLinkResponse; + /** Creates a Ledger. */ + createLedger: CreateLedgerResponse; + createLedgerAccount: CreateLedgerAccountResponse; + /** This API call is used to create Ledger Accounts. It is only used if you are not using a Schema. Unlike other mutations that take a single IK, 'createLedgerAccount' accepts an IK for each of the ledger accounts in the request payload. This is so you can recover in the case of a partial failure. One API call can create up to 200 Ledger Accounts, up to 10 levels deep. */ + createLedgerAccounts: CreateLedgerAccountsResponse; + /** + * EXPERIMENTAL — subject to change. + * + * Create a Payment. + */ + createPayment: CreatePaymentResponse; + /** Delete Txs on a Custom Link. Once deleted, a Tx will not show up in listing queries, but can be resolved by if you lookup by its Fragment ID. */ + deleteCustomTxs: DeleteCustomTxsResponse; + /** + * Delete a Ledger. + * + * After using the deleteLedger mutation you can re-use the ik in a new ledger after a 30 second wait. + */ + deleteLedger: DeleteLedgerResponse; + /** Delete a Schema */ + deleteSchema: DeleteSchemaResponse; + /** + * Migrate an existing Ledger Entry to a new type and typeVersion. + * + * Migrating a Ledger Entry will do the following: + * 1. Reverse the existing Ledger Entry + * 2. Post a new Ledger Entry with the new type, typeVersion, and parameters provided + */ + migrateLedgerEntry: MigrateLedgerEntryResponse; + /** This mutation is used to [reconcile](https://fragment.dev/guides/reconcile-payments#reconcile-a-tx) transactions from an external system into a Ledger Entry. This mutation does not require an idempotency key since a transaction can only be reconciled once per Linked Ledger Account. If you are reconciling a transfer between two Link Accounts which are both linked to the same Ledger, use a transit account in between to split the transfer into two `reconcileTx` calls. */ + reconcileTx: ReconcileTxResponse; + /** Reverses a Ledger Entry */ + reverseLedgerEntry: ReverseLedgerEntryResponse; + /** + * Stores a Schema in your workspace. If no Schema with the same key exists in your worksapce, a new Schema is created. + * Else, the Schema is updated, and every Ledger associated with it is migrated to the latest version. + */ + storeSchema: StoreSchemaResponse; + /** Once you've created a [Custom Link](https://fragment.dev/guides/sync-payments#custom-link), create accounts under it using this mutation. Each Custom Account is an immutable, single-entry view of all the transactions in the external account. You can sync up to 100 Custom Accounts in one API call. */ + syncCustomAccounts: SyncCustomAccountsResponse; + /** You can create transactions under a Custom Account in a [Custom Link](https://fragment.dev/guides/sync-payments#custom-link) using this mutation. Once you've imported transactions, you can use the reconcileTx mutation to add them to a Ledger via the Linked Ledger Account. You can sync up to 100 Custom Transactions in one API call. */ + syncCustomTxs: SyncCustomTxsResponse; + /** Updates a Ledger. Currently, you can change only the Ledger 'name'. */ + updateLedger: UpdateLedgerResponse; + /** Updates a ledger account. Only supports name right now. */ + updateLedgerAccount: UpdateLedgerAccountResponse; + /** Update a ledger entry */ + updateLedgerEntry: UpdateLedgerEntryResponse; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationAddLedgerEntriesArgs = { + entries: Array; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationAddLedgerEntryArgs = { + entry: LedgerEntryInput; + ik: Scalars['SafeString']['input']; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationCreateCustomCurrencyArgs = { + customCurrency: CreateCustomCurrencyInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationCreateCustomLinkArgs = { + ik: Scalars['SafeString']['input']; + name: Scalars['String']['input']; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationCreateLedgerArgs = { + ik: Scalars['SafeString']['input']; + ledger: CreateLedgerInput; + schema?: InputMaybe; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationCreateLedgerAccountArgs = { + ik: Scalars['SafeString']['input']; + ledger: LedgerMatchInput; + ledgerAccount: CreateLedgerAccountInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationCreateLedgerAccountsArgs = { + ledger: LedgerMatchInput; + ledgerAccounts: Array; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationCreatePaymentArgs = { + amount: Scalars['Int96']['input']; + ik: Scalars['SafeString']['input']; + ledger: LedgerMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationDeleteCustomTxsArgs = { + txs: Array; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationDeleteLedgerArgs = { + ledger: LedgerMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationDeleteSchemaArgs = { + schema: SchemaMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationMigrateLedgerEntryArgs = { + input: MigrateLedgerEntryInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationReconcileTxArgs = { + entry: LedgerEntryInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationReverseLedgerEntryArgs = { + id: Scalars['ID']['input']; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationStoreSchemaArgs = { + schema: SchemaInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationSyncCustomAccountsArgs = { + accounts: Array; + link: LinkMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationSyncCustomTxsArgs = { + link: LinkMatchInput; + txs: Array; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationUpdateLedgerArgs = { + ledger: LedgerMatchInput; + update: UpdateLedgerInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationUpdateLedgerAccountArgs = { + ledgerAccount: LedgerAccountMatchInput; + update: UpdateLedgerAccountInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationUpdateLedgerEntryArgs = { + ledgerEntry: LedgerEntryMatchInput; + update: UpdateLedgerEntryInput; +}; + +/** Equivalent to an HTTP 404 */ +export type NotFoundError = Error & { + __typename?: 'NotFoundError'; + /** The status code of error. For example, 'ledger_not_found'. */ + code: Scalars['String']['output']; + /** The error message */ + message: Scalars['String']['output']; + /** Whether or not the operation is retryable */ + retryable: Scalars['Boolean']['output']; +}; + +/** An object containing [pagination](https://fragment.dev/guides/query-data#basics-pagination) details. */ +export type PageInfo = { + __typename?: 'PageInfo'; + endCursor?: Maybe; + hasNextPage: Scalars['Boolean']['output']; + hasPreviousPage: Scalars['Boolean']['output']; + startCursor?: Maybe; +}; + +export type Payment = { + __typename?: 'Payment'; + /** The secret handed to the payments SDK to render the payment method capture. */ + clientSecret: Scalars['String']['output']; + /** The status of this Payment. */ + status: PaymentStatus; +}; + +/** + * EXPERIMENTAL — subject to change. + * + * Status of a Payment. + */ +export enum PaymentStatus { + Processing = 'processing', + RequiresConfirmation = 'requires_confirmation', + Settled = 'settled' +} + +/** + * Controls how lines are posted for a Ledger Entry. + * New entries created via the dashboard default to `net_amounts`. + * Existing entries without this field set are treated as `raw_lines`. + */ +export enum PostLinesAs { + /** Lines targeting the same account, currency, and tx are aggregated into a single line with the net amount. Lines that sum to zero are skipped. If all lines sum to zero, no lines are skipped. */ + NetAmounts = 'net_amounts', + /** Lines are posted as-is without aggregation. */ + RawLines = 'raw_lines', + /** Lines with a zero amount are skipped, but lines are not aggregated. If all lines have a zero amount, no lines are skipped. */ + SkipZeroLines = 'skip_zero_lines' +} + +/** + * The posted timestamp window for a clearing account, representing the earliest and latest + * posted timestamps across all currencies. + */ +export type PostedWindow = { + __typename?: 'PostedWindow'; + /** The earliest posted timestamp across all currencies for this clearing account. */ + earliest: Scalars['DateTime']['output']; + /** The latest posted timestamp across all currencies for this clearing account. */ + latest: Scalars['DateTime']['output']; +}; + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type Query = { + __typename?: 'Query'; + _empty?: Maybe; + /** Query Custom Currencies in the workspace */ + customCurrencies: CustomCurrenciesConnection; + /** Get External Account by Link and External ID or FRAGMENT ID. */ + externalAccount?: Maybe; + /** Get a Ledger by ID */ + ledger?: Maybe; + /** Get a Ledger Account by ID */ + ledgerAccount?: Maybe; + /** Get Ledger Entry by ID. */ + ledgerEntry?: Maybe; + /** Query a Ledger Entry Group given its Ledger, key, and value. */ + ledgerEntryGroup?: Maybe; + /** Get the reversal history of a Ledger Entry. */ + ledgerEntryHistory: LedgerEntriesConnection; + /** Get LedgerLine by ID */ + ledgerLine?: Maybe; + /** Query Ledgers in workspace. Ledgers are paginated and returned in reverse-chronological order by their created date. */ + ledgers: LedgersConnection; + /** Get a Link by ID. Returns a BadRequestError if the Link is not found. */ + link?: Maybe; + /** Get all links in a workspace */ + links: LinksConnection; + /** Get a Schema by key. */ + schema?: Maybe; + /** Retrieve all of the Schemas in the workspace. */ + schemas: SchemaConnection; + /** Get a Tx by ID */ + tx?: Maybe; + /** Get the current Workspace */ + workspace: Workspace; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryCustomCurrenciesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryExternalAccountArgs = { + externalAccount: ExternalAccountMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgerArgs = { + ledger: LedgerMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgerAccountArgs = { + ledgerAccount: LedgerAccountMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgerEntryArgs = { + ledgerEntry: LedgerEntryMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgerEntryGroupArgs = { + ledgerEntryGroup: LedgerEntryGroupMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgerEntryHistoryArgs = { + ledgerEntry: LedgerEntryMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgerLineArgs = { + ledgerLine: LedgerLineMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgersArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLinkArgs = { + link: LinkMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QuerySchemaArgs = { + schema: SchemaMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QuerySchemasArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryTxArgs = { + tx: TxMatchInput; +}; + +/** The consistency configuration of a Ledger Account's balance queries. If not provided as an argument to a balance query, the default behavior is to read eventually consistent balances. See [Configure consistency](https://fragment.dev/guides/configure-consistency). */ +export enum ReadBalanceConsistencyMode { + /** Balance queries will read eventually consistent balances. This is the default behavior if `ReadBalanceConsistencyMode` is not provided as an argument to the balance field. Both Ledger Accounts configured with strongly and eventually consistent balance updates support this enum. */ + Eventual = 'eventual', + /** Balance queries will read strongly consistent balances. This is only allowed if the Ledger Account's `ownBalanceUpdates` in its `consistencyConfig` is `strong`. */ + Strong = 'strong', + /** Balance queries will use the value from the Ledger Account's `ownBalanceUpdates` in its `consistencyConfig`. */ + UseAccount = 'use_account' +} + +export type ReconcileTxResponse = BadRequestError | InternalError | ReconcileTxResult; + +export type ReconcileTxResult = { + __typename?: 'ReconcileTxResult'; + /** The ledger entry that was posted */ + entry: LedgerEntry; + /** True if this request successfully completed before and the previous response is being returned */ + isIkReplay: Scalars['Boolean']['output']; + /** The ledger lines that were created in that entry */ + lines: Array; +}; + +export type ReverseLedgerEntryResponse = BadRequestError | InternalError | ReverseLedgerEntryResult; + +export type ReverseLedgerEntryResult = { + __typename?: 'ReverseLedgerEntryResult'; + /** Whether the reversal was an IK replay */ + isIkReplay: Scalars['Boolean']['output']; + /** The Ledger Entry that was reversed */ + reversedLedgerEntry: LedgerEntry; + /** The reversal Ledger Entry that was created */ + reversingLedgerEntry: LedgerEntry; +}; + +/** A simulated Ledger Entry posted as a part of a Scene. */ +export type SceneEntryInput = { + /** Any parameters to be used as inputs to this simulated Ledger Entry. */ + parameters?: InputMaybe; + /** The type of the simulated Ledger Entry. Must match one of the types provided in schema.ledgerEntries.types. */ + type: Scalars['SafeString']['input']; + /** The version of the Ledger Entry type. */ + typeVersion?: InputMaybe; +}; + +export type SceneEventInput = { + /** The simulated Ledger Entry. */ + entry: SceneEntryInput; + /** The type of the Scene Event. Currently, only entries are supported. */ + eventType: SceneEventType; +}; + +export enum SceneEventType { + Entry = 'entry' +} + +export type SceneInput = { + /** A list of simulated ledger entries that make up the Scene. */ + events: Array; + /** The human-readable name of the Scene. */ + name: Scalars['String']['input']; +}; + +export type Schema = { + __typename?: 'Schema'; + /** + * The identifier for a Schema. + * `key` is unique to a Workspace. + */ + key: Scalars['SafeString']['output']; + /** The paginated list of ledgers the Schema has been applied to. */ + ledgers: LedgersConnection; + /** The name of a Schema. It defaults to the `key` if not provided in your SchemaInput. */ + name: Scalars['String']['output']; + /** The metadata for a specific SchemaVersion. */ + version: SchemaVersion; + /** A paginated list of SchemaVersions. */ + versions: SchemaVersionConnection; +}; + + +export type SchemaLedgersArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +export type SchemaVersionArgs = { + version?: InputMaybe; +}; + + +export type SchemaVersionsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** + * A condition that must be met on a Ledger Account balance. The condition can be + * either a `precondition` or `postcondition`. + */ +export type SchemaConditionInput = { + /** A condition on the `ownBalance` of the Ledger Account. */ + ownBalance?: InputMaybe; + /** A condition on the `totalBalance` of the Ledger Account. */ + totalBalance?: InputMaybe; +}; + +/** A paginated list of Schemas in a Workspace. */ +export type SchemaConnection = { + __typename?: 'SchemaConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +/** + * The consistency configuration for entities created within Ledgers created by this Schema. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ +export type SchemaConsistencyConfigInput = { + /** + * The consistency mode for the Ledger Entries list query within Ledgers created by this Schema. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + entries?: InputMaybe; +}; + +/** + * The consistency modes available for entities created within this Schema. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ +export enum SchemaConsistencyMode { + /** Eventually consistent entity updates */ + Eventual = 'eventual', + /** Strongly consistent entity updates */ + Strong = 'strong' +} + +/** + * Matches a Currency. Can be a built-in [CurrencyCode](https://fragment.dev/api-reference/api-types#scalars-and-enums-currencycode), custom Currency, or a parameterized string. + * If you supply a parameterized string, you must pass in a valid CurrencyCode as a parameter when posting a Ledger Entry. + */ +export type SchemaCurrencyMatchInput = { + /** The currency code. This must either be a [CurrencyCode](https://fragment.dev/api-reference/api-types#scalars-and-enums-currencycode) or a parameterized string that resolves to a CurrencyCode . */ + code: Scalars['ParameterizedString']['input']; + /** The ID for a custom currency. This is specified when creating the custom currency using the [createCustomCurrency](https://fragment.dev/api-reference/api-mutations#createcustomcurrency) mutation. */ + customCurrencyId?: InputMaybe; +}; + +export type SchemaExternalAccountMatchInput = { + /** The External systems's ID of the account */ + externalId?: InputMaybe; + /** The FRAGMENT ID of the external account */ + id?: InputMaybe; + /** The FRAGMENT ID of the link */ + linkId?: InputMaybe; + /** The type of Link this external account belongs to. Must be one of: IncreaseLink, UnitLink, CustomLink, or StripeLink. */ + linkType?: InputMaybe; +}; + +/** Input to the API for creating a Schema. */ +export type SchemaInput = { + /** The Chart of Accounts for the Schema. */ + chartOfAccounts: ChartOfAccountsInput; + /** The consistency configuration for this Schema. */ + consistencyConfig?: InputMaybe; + /** Any groups associated with this Schema. */ + groups?: InputMaybe>; + /** The key of the Schema. This is a stable, unique identifier for the Schema. Uniqueness is enforced at the Workspace level. */ + key: Scalars['SafeString']['input']; + /** The Ledger Entries to add to the Schema. */ + ledgerEntries?: InputMaybe; + /** The human-readable name of the Schema. */ + name?: InputMaybe; + /** Any scenes associated with this Schema. */ + scenes?: InputMaybe>; +}; + +/** A condition that must be met on a field. */ +export type SchemaInt96ConditionInput = { + /** Amount must be exactly equal to this value. You may not specify this alongside `gte` or `lte`. */ + eq?: InputMaybe; + /** Amount must be greater than or equal to this value. */ + gte?: InputMaybe; + /** Amount must be less than or equal to this value. */ + lte?: InputMaybe; +}; + +/** + * Models a Ledger Account in a Schema. + * Upon successfully storing a [Schema](https://fragment.dev/api-reference/api-types#core-types-schema), a [LedgerAccount](https://fragment.dev/api-reference/api-types#core-types-ledgeraccount) will be created for + * each corresponding non-templated `SchemaLedgerAccountInput` in your Chart of Accounts. + */ +export type SchemaLedgerAccountInput = { + /** Ledger Accounts to create as children of this Ledger Account. Ledger Accounts may be nested up to a maximum depth of 10. */ + children?: InputMaybe>; + /** + * EXPERIMENTAL: Whether or not this Ledger Account is a Clearing Account. + * Clearing Accounts have balances that should tend to zero. They are used to track in-progress workflows and payments. + */ + clearing?: InputMaybe; + /** The consistency configuration for this ledger account. See [Configure consistency](https://fragment.dev/guides/configure-consistency). */ + consistencyConfig?: InputMaybe; + /** + * The currency of this Ledger Account. If this is not set, and `currencyMode` is + * not set to `multi`, it is derived from the Chart of Accounts' default. + */ + currency?: InputMaybe; + /** If set to `multi`, creates a multi-currency Ledger Account. If set to `single`, creates a single-currency Ledger Account. */ + currencyMode?: InputMaybe; + /** + * The key of this Ledger Account. Keys are used to formulate the unique path of the Ledger Account in your Chart of Accounts. + * Siblings must have unique keys. + */ + key: Scalars['SafeString']['input']; + /** + * The External Account to link to this Ledger Account. + * It must be provided of `linked` is true. + */ + linkedAccount?: InputMaybe; + /** The human-readable name of this Ledger Account. */ + name?: InputMaybe; + /** EXPERIMENTAL: Marks this as a Payment Account. */ + payment?: InputMaybe; + /** The status of this Ledger Account. Defaults to active. */ + status?: InputMaybe; + /** Whether or not this Ledger Account should be templated. */ + template?: InputMaybe; + /** The type of ledger account to create. Required if this is a top-level Ledger Account. If not provided, the type will be inferred from the parent. */ + type?: InputMaybe; +}; + +/** Matches a Ledger Account in a Schema. */ +export type SchemaLedgerAccountMatchInput = { + /** + * The unique path of the Ledger Account in the Schema. + * This is a slash-delimited string containing the keys of a Ledger Account and all its direct ancestors. + * ex: expense-root/subscriptions/netflix + * For Templated Ledger Accounts, you must supply a parameter in the path that will be used to name an instance of the template. + * ex: `"expense-root/subscriptions/vendor:{{vendor_name}}"` + */ + path: Scalars['ParameterizedString']['input']; +}; + +/** The status of a Ledger Account. */ +export enum SchemaLedgerAccountStatus { + /** The Ledger Account is active. */ + Active = 'active', + /** The Ledger Account is archived. */ + Archived = 'archived', + /** The Ledger Account is disabled. */ + Disabled = 'disabled' +} + +/** The Ledger Entries in your Schema. */ +export type SchemaLedgerEntriesInput = { + /** A list of Ledger Entry definitions. */ + types: Array; +}; + +/** A condition that must be met on a Ledger Account when a Ledger Entry is posted. */ +export type SchemaLedgerEntryConditionInput = { + /** The Ledger Account to apply the condition to. */ + account: SchemaLedgerAccountMatchInput; + /** + * The currency of the balance to apply the condition to. Required if the Ledger Account matched is a multi-currency Ledger Account. + * Otherwise, this field is defaults to the Ledger Account's currency. + */ + currency?: InputMaybe; + /** A `postcondition` must be met after the Ledger Entry updates are applied. */ + postcondition?: InputMaybe; + /** A `precondition` must be met before any Ledger Entry updates are applied. */ + precondition?: InputMaybe; + /** + * Repeated expansion configuration. When set, this condition is expanded at runtime for each element + * in the array parameter named by the key. + */ + repeated?: InputMaybe; +}; + +/** A Ledger Entry Group associated with a Ledger Entry type. */ +export type SchemaLedgerEntryGroupInput = { + /** The key for this Ledger Entry Group. */ + key: Scalars['SafeString']['input']; + /** The value associated with this Ledger Entry Group. */ + value: Scalars['ParameterizedString']['input']; +}; + +/** A Ledger Entry in a Schema. All Ledger Entries defined in a Schema must have a unique `type`. */ +export type SchemaLedgerEntryInput = { + /** Conditions that must be satisfied to post this Ledger Entry. The Ledger Entry will reject with a BadRequestError if any condition is not met. You can only add a condition on a Ledger Account containing a Line in this Ledger Entry. */ + conditions?: InputMaybe>; + /** Human-readable description of the Ledger Entry. */ + description?: InputMaybe; + /** Ledger Entries posted with this type will be in these Ledger Entry Groups. */ + groups?: InputMaybe>; + /** + * The Ledger Lines in the Ledger Entry. + * If provided, when posting a Typed Entry, a [LedgerEntry](https://fragment.dev/api-reference/api-types#core-types-ledgerline) will be posted containing [LedgerLines](https://fragment.dev/api-reference/api-types#core-types-ledgerline) corresponding + * to the values you provide here. If your lines contain parameters, you must supply values for those parameters that balance out the Ledger Entry. If not provided, lines will be required when posting a Typed Entry. + */ + lines?: InputMaybe>; + /** Fixed partial set of parameters to be included in a templated Ledger Entry. */ + parameters?: InputMaybe; + /** Controls how lines are posted. When set to `net_amounts`, all lines targeting the same account, currency, and tx are aggregated into a single line with the net amount, and lines that sum to zero are skipped. When set to `skip_zero_lines`, lines with a zero amount are skipped but not aggregated. In both modes, if all lines are zero, no lines are skipped. When set to `raw_lines`, lines are posted as-is without aggregation. New entries created via the dashboard default to `net_amounts`. Existing entries without this field set are treated as `raw_lines`. */ + postLinesAs?: InputMaybe; + /** The status of this Ledger Entry. Defaults to active. */ + status?: InputMaybe; + /** Ledger Entries posted with this type will be associated with these tags. */ + tags?: InputMaybe>; + /** + * The type of this Ledger Entry. This is a stable, unique identifier for this entry. Uniqueness is enforced at the Schema level. + * You can filter on this field when querying for Ledger Entries. See the docs on [LedgerEntryFilterSet](https://fragment.dev/api-reference/api-types#filter-types-ledgerentriesfilterset) + */ + type: Scalars['SafeString']['input']; + /** The version of the Ledger Entry type. */ + typeVersion?: InputMaybe; +}; + +/** The status of a Ledger Entry. */ +export enum SchemaLedgerEntryStatus { + /** The Ledger Entry is active. */ + Active = 'active', + /** The Ledger Entry is archived. */ + Archived = 'archived', + /** The Ledger Entry is disabled. */ + Disabled = 'disabled' +} + +/** A tag associated with a Ledger Entry type. */ +export type SchemaLedgerEntryTagInput = { + /** The key for this tag. */ + key: Scalars['SafeString']['input']; + /** The value associated with the given key for this tag. */ + value: Scalars['ParameterizedString']['input']; +}; + +/** A Ledger Line in a Ledger Entry. */ +export type SchemaLedgerLineInput = { + /** + * The Ledger Account this Ledger Line will be posted to. + * It supports parameters in its attributes via handlebars syntax. + */ + account: SchemaLedgerAccountMatchInput; + /** The amount of the Ledger Line. It supports parameters via the handlebars syntax and addition (+) and subtraction (-). */ + amount?: InputMaybe; + /** + * The currency of the Ledger Line. This is required if the Ledger Account has currencyMode multi. + * It supports parameters in its attributes via handlebars syntax. + */ + currency?: InputMaybe; + /** Human-readable description of the line. */ + description?: InputMaybe; + /** The key for the Ledger Line. Ledger Line keys must be unique within a Ledger Entry. Key can be filtered on as part of the LedgerLinesFilterSet. */ + key: Scalars['SafeString']['input']; + /** + * Repeated expansion configuration. When set, this line is expanded at runtime for each element + * in the array parameter named by the key. + */ + repeated?: InputMaybe; + /** Tags to attach to this Ledger Line. Supports parameterized values via handlebars syntax. */ + tags?: InputMaybe>; + /** + * The external transaction to reconcile. + * This field is required if the Ledger Account being posted to is a Linked Ledger Account. Otherwise, this field is disallowed. + * It supports parameters in its attributes via handlebars syntax. + * + * See the docs on [reconciling payments](https://fragment.dev/guides/reconcile-payments). + */ + tx?: InputMaybe; +}; + +/** An object used to retrieve a Schema. */ +export type SchemaMatchInput = { + /** + * The key to retrieve a Schema by. + * `key` is unique to a Workspace. + */ + key: Scalars['SafeString']['input']; + /** Optional parameter to specify version of requested Schema. If not provided, it defaults to 0, representing the latest available version for the provided Schema key. */ + version?: InputMaybe; +}; + +/** EXPERIMENTAL: Marks a Ledger Account as a Payment Account. */ +export type SchemaPaymentInput = { + penguin: Scalars['Boolean']['input']; +}; + +/** + * Configuration for repeated expansion of a line or condition. The key names a client-supplied + * array parameter whose elements each generate one copy of the line or condition at runtime. + */ +export type SchemaRepeatedConfigInput = { + /** The key of the array parameter whose elements expand this line or condition. */ + key: Scalars['SafeString']['input']; +}; + +/** + * Matches a transaction at an external system. + * This is used to specify the transaction being reconciled into a Linked Ledger Account + */ +export type SchemaTxMatchInput = { + /** The external system's ID for the transaction. */ + externalId?: InputMaybe; + /** The FRAGMENT ID for the transaction. */ + id?: InputMaybe; +}; + +/** + * An instance of a Schema stored in a Workspace. + * A new SchemaVersion is created each time a Schema is stored. + * It stores the Chart of Accounts and list of Ledger Entries as well as a history of its Ledger migrations. + */ +export type SchemaVersion = { + __typename?: 'SchemaVersion'; + created: Scalars['DateTime']['output']; + json: Scalars['JSON']['output']; + migrations: LedgerMigrationConnection; + /** The version of the schema. */ + version: Scalars['Int']['output']; +}; + +/** A paginated list of SchemaVersions for a given Schema. */ +export type SchemaVersionConnection = { + __typename?: 'SchemaVersionConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +/** Returned by the [storeSchema](https://fragment.dev/api-reference/api-mutations#storeschema) mutation. */ +export type StoreSchemaResponse = BadRequestError | InternalError | StoreSchemaResult; + +/** `StoreSchemaResult` represents a successful execution of `storeSchema`. */ +export type StoreSchemaResult = { + __typename?: 'StoreSchemaResult'; + /** The Schema that was stored as a result of calling `storeSchema`. */ + schema: Schema; +}; + +export type StringFilter = { + equalTo?: InputMaybe; + /** Must match one of the values provided. Limited to 100 items maximum. */ + in?: InputMaybe>; + /** Must not equal this string value */ + notEqualTo?: InputMaybe; + /** Must not match any of the values provided. Limited to 100 items maximum. */ + notIn?: InputMaybe>; +}; + +export type StringMatchFilter = { + /** Must contain the provided pattern somewhere within the string. For example, 'contains: hat' will match 'hat', 'chat', and 'hate'. */ + contains?: InputMaybe; + /** Must exactly equal the provided value */ + equalTo?: InputMaybe; + /** Must exactly equal one of the provided values. Limited to 100 items maximum. */ + in?: InputMaybe>; + /** Must match the provided pattern. Wildcards ("*") will match any substring */ + matches?: InputMaybe; + /** Must match any one of the provided `matches` patterns, OR-ed together — results are returned as a single paginated list. Each entry uses the same wildcard grammar as `matches`. Cannot be combined with `matches`. Limited to 100 entries. */ + matchesAny?: InputMaybe>; +}; + +export enum StripeEnv { + Livemode = 'livemode', + Testmode = 'testmode' +} + +export type StripeLink = Link & { + __typename?: 'StripeLink'; + /** ISO-8601 timestamp when the Link was created. */ + created: Scalars['String']['output']; + /** URL to the Fragment Dashboard for this Link. */ + dashboardUrl: Scalars['String']['output']; + /** A list of External Accounts associated with this Link. */ + externalAccounts: ExternalAccountsConnection; + /** FRAGMENT ID of the Custom Link. */ + id: Scalars['ID']['output']; + /** Name of the Link as it appears in the Fragment Dashboard. */ + name: Scalars['String']['output']; + /** The environment of the Stripe Link, either testmode or livemode. */ + stripeEnv: StripeEnv; +}; + +export type SyncCustomAccountsResponse = BadRequestError | InternalError | SyncCustomAccountsResult; + +export type SyncCustomAccountsResult = { + __typename?: 'SyncCustomAccountsResult'; + /** The external accounts that were synced. */ + accounts: Array; +}; + +export type SyncCustomTxsResponse = BadRequestError | InternalError | SyncCustomTxsResult; + +export type SyncCustomTxsResult = { + __typename?: 'SyncCustomTxsResult'; + txs: Array; +}; + +/** Filters a result set based on the tags it contains. */ +export type TagFilter = { + /** Matches entries that have ALL of the specified tags. The key and value are both matched exactly. Limited to 10 items maximum. */ + all?: InputMaybe>; + /** Matches tag values based on the existence of the provided string within the tag value. The key is matched exactly. */ + contains?: InputMaybe; + /** Matches tags based on the exact value provided. The key and value are both matched exactly. */ + equalTo?: InputMaybe; + /** Matches tags based on a list of possible tag matches. The key and value are both matched exactly. Limited to 100 items maximum. */ + in?: InputMaybe>; + /** Matches tags where the key exactly equals the provided value. */ + keyEqualTo?: InputMaybe; + /** Matches tags where the key matches any of the provided values. Limited to 100 items maximum. */ + keyIn?: InputMaybe>; + /** Matches tags that do not equal the provided value. The key and value are both matched exactly. */ + notEqualTo?: InputMaybe; + /** Matches tags that do not match any of the provided values. The key and value are both matched exactly. Limited to 100 items maximum. */ + notIn?: InputMaybe>; + /** Matches tags where the key does not equal the provided value. */ + notKeyEqualTo?: InputMaybe; + /** Matches tags where the key does not match any of the provided values. Limited to 100 items maximum. */ + notKeyIn?: InputMaybe>; +}; + +/** Specifies a single tag that an entity is expected to have. You must specify both the key and the value. */ +export type TagMatchInput = { + /** The key of this tag. */ + key: Scalars['SafeString']['input']; + /** The value associated with this tag's key. */ + value: Scalars['SafeString']['input']; +}; + +export type Tx = { + __typename?: 'Tx'; + /** FRAGMENT ID of this transaction's external account */ + accountId: Scalars['ID']['output']; + /** Integer amount in cents. Positive indicates money entering the external account, negative indicates money leaving */ + amount: Scalars['Int96']['output']; + /** Currency of this Tx */ + currency?: Maybe; + /** Date this Tx posted to the external account */ + date: Scalars['Date']['output']; + /** ISO-8601 timestamp when this Tx was deleted */ + deletedAt?: Maybe; + /** Description at the external account */ + description: Scalars['String']['output']; + /** The External Account that this transaction belongs to. */ + externalAccount: ExternalAccount; + /** ID in the external system of this transaction's external account */ + externalAccountId: Scalars['ID']['output']; + /** ID of this transaction in the external system */ + externalId: Scalars['ID']['output']; + /** FRAGMENT ID of this Tx. If you delete a Tx via deleteCustomTxs, it will not show up in listing queries, but can be resolved by if you lookup by its Fragment ID. If you resync a Tx with the same externalId, its Fragment ID will be different than the previous Tx. */ + id: Scalars['ID']['output']; + /** Whether this Tx has been deleted via deleteCustomTxs */ + isDeleted: Scalars['Boolean']['output']; + /** Returns ledger entries that are linked to this transaction. You can link the same external account to multiple ledgers, so there could be multiple entries associated with one transaction - one for each linked ledger account this transaction has been reconciled with */ + ledgerEntries: LedgerEntriesConnection; + /** Same as ledgerEntries, but returns an array of IDs instead */ + ledgerEntryIds?: Maybe>; + /** Same as ledgerLines, but returns an array of IDs instead */ + ledgerLineIds?: Maybe>; + /** Returns ledger lines that are linked to this transaction. You can link the same external account to multiple ledgers, so there could be multipe lines associated with one transaction - one for each linked ledger account this transaction has been reconciled with */ + ledgerLines: LedgerLinesConnection; + /** This transaction's Link. */ + link: Link; + /** FRAGMENT ID of this transaction's Link */ + linkId: Scalars['ID']['output']; + /** ISO-8601 timestamp when this Tx posted to the external account */ + posted: Scalars['DateTime']['output']; + /** When a Tx is deleted and a new Tx is synced with the same externalId, its sequence will be higher than the previous Tx. You can use this to distinguish different instances of Txs that have the same externalId. */ + sequence: Scalars['Int']['output']; + /** @deprecated Callers should not need to query or store this value. */ + workspaceId: Scalars['ID']['output']; +}; + +/** Specify a Tx by using `id` or `externalId`, the Link it belongs to by `linkId`, and the External Account it is a part of by `accountId` or `externalAccountId`. */ +export type TxMatchInput = { + /** The FRAGMENT ID of the external account */ + accountId?: InputMaybe; + /** The external system's ID for the account */ + externalAccountId?: InputMaybe; + /** The external system's ID for the transaction */ + externalId?: InputMaybe; + /** The FRAGMENT ID of the transaction */ + id?: InputMaybe; + /** The FRAGMENT ID of the link */ + linkId?: InputMaybe; +}; + +export enum TxType { + Credit = 'credit', + Debit = 'debit' +} + +export type TxTypeFilter = { + equalTo?: InputMaybe; + /** Must match one of the values provided. Limited to 100 items maximum. */ + in?: InputMaybe>; +}; + +/** A paginated list of Txs */ +export type TxsConnection = { + __typename?: 'TxsConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export enum UnitEnv { + Production = 'production', + Sandbox = 'sandbox' +} + +export type UnitLink = Link & { + __typename?: 'UnitLink'; + /** ISO-8601 timestamp when the Link was created. */ + created: Scalars['String']['output']; + /** URL to the Fragment Dashboard for this Link. */ + dashboardUrl: Scalars['String']['output']; + /** A list of External Accounts associated with this Link. */ + externalAccounts: ExternalAccountsConnection; + /** FRAGMENT ID of the Unit Link. */ + id: Scalars['ID']['output']; + /** Name of the Link as it appears in the Dashboard. */ + name: Scalars['String']['output']; + /** The environment of the Unit Link, either sandbox or production. */ + unitEnv: UnitEnv; +}; + +export type UpdateLedgerAccountInput = { + /** The consistency configuration for this ledger account. This defines how updates to this ledger account's balance are handled. */ + consistencyConfig?: InputMaybe; + /** The name to update the ledger account to */ + name?: InputMaybe; +}; + +export type UpdateLedgerAccountResponse = BadRequestError | InternalError | UpdateLedgerAccountResult; + +export type UpdateLedgerAccountResult = { + __typename?: 'UpdateLedgerAccountResult'; + /** The ledger account that was updated */ + ledgerAccount: LedgerAccount; +}; + +export type UpdateLedgerEntryInput = { + /** The list of Groups to add to this Ledger Entry. */ + groups?: InputMaybe>; + /** The list of Tags to add and/or update on this Ledger Entry. */ + tags?: InputMaybe>; + /** The list of Tags to remove from this Ledger Entry. */ + tagsToRemove?: InputMaybe>; +}; + +export type UpdateLedgerEntryResponse = BadRequestError | InternalError | UpdateLedgerEntryResult; + +export type UpdateLedgerEntryResult = { + __typename?: 'UpdateLedgerEntryResult'; + /** The Ledger Entry that was updated. */ + entry: LedgerEntry; +}; + +export type UpdateLedgerInput = { + /** The new Ledger name. */ + name?: InputMaybe; +}; + +export type UpdateLedgerResponse = BadRequestError | InternalError | UpdateLedgerResult; + +export type UpdateLedgerResult = { + __typename?: 'UpdateLedgerResult'; + /** The updated Ledger. */ + ledger: Ledger; +}; + +export type Workspace = { + __typename?: 'Workspace'; + /** The ID of the Workspace */ + id: Scalars['String']['output']; + /** The name of the Workspace */ + name: Scalars['String']['output']; +}; + +export type PostRuntimeLinesMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + ledgerIk: Scalars['SafeString']['input']; + posted?: InputMaybe; + description?: InputMaybe; + lines: Array | LedgerLineInput; +}>; + + +export type PostRuntimeLinesMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult' } | { __typename: 'BadRequestError' } | { __typename: 'InternalError' } }; + +export type PostUntypedParametersMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + ledgerIk: Scalars['SafeString']['input']; + parameters: Scalars['JSON']['input']; +}>; + + +export type PostUntypedParametersMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult' } | { __typename: 'BadRequestError' } | { __typename: 'InternalError' } }; + +export type PostAllOptionalMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + ledgerIk: Scalars['SafeString']['input']; + memo?: InputMaybe; + note?: InputMaybe; +}>; + + +export type PostAllOptionalMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult' } | { __typename: 'BadRequestError' } | { __typename: 'InternalError' } }; + +export type PostEitherLedgerKeyMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + ledgerId?: InputMaybe; + ledgerIk?: InputMaybe; + amount: Scalars['String']['input']; +}>; + + +export type PostEitherLedgerKeyMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult' } | { __typename: 'BadRequestError' } | { __typename: 'InternalError' } }; + +export type PostFixedValuesMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + ledgerIk: Scalars['SafeString']['input']; + amount: Scalars['String']['input']; +}>; + + +export type PostFixedValuesMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult' } | { __typename: 'BadRequestError' } | { __typename: 'InternalError' } }; + + +export const PostRuntimeLinesDocument = gql` + mutation PostRuntimeLines($ik: SafeString!, $ledgerIk: SafeString!, $posted: DateTime, $description: String, $lines: [LedgerLineInput!]!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "runtime_lines", posted: $posted, description: $description, lines: $lines} + ) { + __typename + } +} + `; +export const PostUntypedParametersDocument = gql` + mutation PostUntypedParameters($ik: SafeString!, $ledgerIk: SafeString!, $parameters: JSON!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "untyped_parameters", parameters: $parameters} + ) { + __typename + } +} + `; +export const PostAllOptionalDocument = gql` + mutation PostAllOptional($ik: SafeString!, $ledgerIk: SafeString!, $memo: String, $note: String) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "all_optional", typeVersion: 2, parameters: {memo: $memo, note: $note}} + ) { + __typename + } +} + `; +export const PostEitherLedgerKeyDocument = gql` + mutation PostEitherLedgerKey($ik: SafeString!, $ledgerId: ID, $ledgerIk: SafeString, $amount: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {id: $ledgerId, ik: $ledgerIk}, type: "either_ledger_key", parameters: {amount: $amount}} + ) { + __typename + } +} + `; +export const PostFixedValuesDocument = gql` + mutation PostFixedValues($ik: SafeString!, $ledgerIk: SafeString!, $amount: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "fixed_values", description: "posted by the nightly sweep", tags: [{key: "source", value: "sweep"}], parameters: {amount: $amount, currency: "USD"}} + ) { + __typename + } +} + `; + +export type SdkFunctionWrapper = (action: (requestHeaders?:Record) => Promise, operationName: string, operationType?: string, variables?: any) => Promise; + + +const defaultWrapper: SdkFunctionWrapper = (action, _operationName, _operationType, _variables) => action(); + +export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) { + return { + PostRuntimeLines(variables: PostRuntimeLinesMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostRuntimeLinesDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostRuntimeLines', 'mutation', variables); + }, + PostUntypedParameters(variables: PostUntypedParametersMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostUntypedParametersDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostUntypedParameters', 'mutation', variables); + }, + PostAllOptional(variables: PostAllOptionalMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostAllOptionalDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostAllOptional', 'mutation', variables); + }, + PostEitherLedgerKey(variables: PostEitherLedgerKeyMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostEitherLedgerKeyDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostEitherLedgerKey', 'mutation', variables); + }, + PostFixedValues(variables: PostFixedValuesMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostFixedValuesDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostFixedValues', 'mutation', variables); + } + }; +} +export type Sdk = ReturnType; +/** + * Typed payloads for batching Ledger Entries with `addLedgerEntries`. + * + * Each payload is derived from the single-entry `addLedgerEntry` operation for + * one `(type, typeVersion)` pair, and builds an `AddLedgerEntryInput` you can + * mix freely with raw, untyped entry inputs in the same batch: + * + * ```ts + * await client.addLedgerEntries({ + * entries: [untypedParametersV1({ ik, ledgerIk, parameters: { ... } })], + * }); + * ``` + * + * A payload takes exactly what its source operation binds, so what you can set + * here is what that entry type accepts — no more. A field you do not set is + * left out of the request rather than sent as `null`. + */ + +/** + * Payload for the `untyped_parameters` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostUntypedParameters` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type UntypedParametersV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + /** + * This entry type's operation does not bind its parameters to typed + * variables, so they cannot be typed individually. + */ + parameters: Scalars['JSON']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `untyped_parameters` (typeVersion 1). */ +export const untypedParametersV1 = ( + input: UntypedParametersV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: input.parameters, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'untyped_parameters', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Payload for the `all_optional` (typeVersion 2) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostAllOptional` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type AllOptionalV2 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + memo?: Scalars['String']['input'] | undefined; + note?: Scalars['String']['input'] | undefined; +}; + +/** Builds an `addLedgerEntries` entry for `all_optional` (typeVersion 2). */ +export const allOptionalV2 = ( + input: AllOptionalV2, +): AddLedgerEntryInput => { + const parameters = { + ...(input.memo !== undefined && { memo: input.memo }), + ...(input.note !== undefined && { note: input.note }), + }; + return { + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + ...(Object.keys(parameters).length > 0 && { parameters }), + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'all_optional', + typeVersion: 2, + }, + ik: input.ik, + }; +}; + +/** + * Payload for the `either_ledger_key` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostEitherLedgerKey` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type EitherLedgerKeyV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + ledgerId?: Scalars['ID']['input'] | undefined; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk?: Scalars['SafeString']['input'] | undefined; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + amount: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `either_ledger_key` (typeVersion 1). */ +export const eitherLedgerKeyV1 = ( + input: EitherLedgerKeyV1, +): AddLedgerEntryInput => { + const ledger = { + ...(input.ledgerId !== undefined && { id: input.ledgerId }), + ...(input.ledgerIk !== undefined && { ik: input.ledgerIk }), + }; + return { + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ...(Object.keys(ledger).length > 0 && { ledger }), + parameters: { + amount: input.amount, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'either_ledger_key', + typeVersion: 1, + }, + ik: input.ik, + }; +}; + +/** + * Payload for the `fixed_values` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostFixedValues` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type FixedValuesV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + amount: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `fixed_values` (typeVersion 1). */ +export const fixedValuesV1 = ( + input: FixedValuesV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + amount: input.amount, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'fixed_values', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Every typed payload builder, keyed by `"@"`. Useful + * when the entry type is only known at runtime. + */ +export const typedLedgerEntryBuilders = { + 'untyped_parameters@1': untypedParametersV1, + 'all_optional@2': allOptionalV2, + 'either_ledger_key@1': eitherLedgerKeyV1, + 'fixed_values@1': fixedValuesV1, +} as const; diff --git a/tests/fixtures/generated-template-client.ts b/tests/fixtures/generated-template-client.ts new file mode 100644 index 0000000..157a11b --- /dev/null +++ b/tests/fixtures/generated-template-client.ts @@ -0,0 +1,4466 @@ +/* This file is auto-generated by @fragment-dev/node-client. Don't edit it directly. */ +import { GraphQLClient, RequestOptions } from '@fragment-dev/node-client'; +import { gql } from '@fragment-dev/node-client'; +export type Maybe = T | null; +export type InputMaybe = Maybe; +export type Exact = { [K in keyof T]: T[K] }; +export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; +export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; +export type MakeEmpty = { [_ in K]?: never }; +export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; +type GraphQLClientRequestHeaders = RequestOptions['requestHeaders']; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: { input: string; output: string; } + String: { input: string; output: string; } + Boolean: { input: boolean; output: boolean; } + Int: { input: number; output: number; } + Float: { input: number; output: number; } + /** A string that must be alphanumeric */ + AlphaNumericString: { input: string; output: string; } + /** ISO 8601 Date e.g. `1969-07-21` */ + Date: { input: string; output: string; } + /** ISO 8601 DateTime e.g. `1969-07-16T13:32:00.000Z`. You can also provide a date e.g. `1969-01-01` and it will be converted to `1969-01-01T00:00:00.000Z` */ + DateTime: { input: string; output: string; } + /** The first moment of a specific year, month or day or hour e.g. 1969 or 1969-1 or 1969-1-1 or 1969-1-1T00. All of the previous examples are equivalent to `1969-1-1T00:00:00.000`. */ + FirstMoment: { input: any; output: any; } + /** A string representing integers up to 9,223,372,036,854,775,807 (i.e. 2^63-1) */ + Int64: { input: string; output: string; } + /** A string representing integers as big as 2^120-1. The number is signed so the range is from -1,329,227,995,784,915,872,903,807,060,280,344,575 to 1,329,227,995,784,915,872,903,807,060,280,344,575. */ + Int96: { input: string; output: string; } + /** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */ + JSON: { input: Record; output: Record; } + /** The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */ + JSONObject: { input: Record; output: Record; } + /** The last moment of a specific year, month or day or hour e.g. 1969 or 1969-12 or 1969-12-31 or 1969-12-31T23. All of the previous examples are equivalent to `1969-12-31T23:59:59.999`. */ + LastMoment: { input: string; output: string; } + /** A string of non-zero length that can contain parameterized values via handlebars syntax. ex: `"Hello from {{country}}"`. */ + ParameterizedString: { input: string; output: string; } + /** A mapping of parameter keys to values. */ + Parameters: { input: any; output: any; } + /** A specific year ("2021"), quarter ("2021-Q1"), month ("2021-02"), day ("2021-02-03") or hour ("2021-02-03T04") */ + Period: { input: string; output: string; } + /** A specific year ("2026") or month ("2026-05") used to match dates that fall within it. */ + PeriodFilter: { input: any; output: any; } + /** A string with delimiter characters `/`, `#`, and `:` disallowed, as well as parameters in {{handlebar}} syntax. */ + SafeString: { input: string; output: string; } + /** All hour-aligned offsets from -11:00 to +12:00 are supported, e.g. "-08:00" (PT), "-05:00" (ET), "+00:00" (UTC) */ + UTCOffset: { input: string; output: string; } +}; + +/** Error returned when one or more Ledger Entries in the batch could not be added. */ +export type AddLedgerEntriesError = Error & { + __typename?: 'AddLedgerEntriesError'; + /** The status code of error. For example, 'ledger_entry_batch_operation_failed'. */ + code: Scalars['String']['output']; + /** The list of errors for each Ledger Entry that was responsible for the batch's failure. */ + errors: Array; + /** The error message */ + message: Scalars['String']['output']; + /** Whether or not the operation is retryable */ + retryable: Scalars['Boolean']['output']; +}; + +export type AddLedgerEntriesResponse = AddLedgerEntriesError | AddLedgerEntriesResult | BadRequestError | InternalError; + +export type AddLedgerEntriesResult = { + __typename?: 'AddLedgerEntriesResult'; + /** The added Ledger Entries, in the same order as the input */ + results: Array; +}; + +/** Error details for a single Ledger Entry that was responsible for the batch's failure. */ +export type AddLedgerEntryError = Error & { + __typename?: 'AddLedgerEntryError'; + /** The status code of error. For example, 'ledger_entry_too_many_lines'. */ + code: Scalars['String']['output']; + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) of the Ledger Entry */ + ik: Scalars['SafeString']['output']; + /** The error message */ + message: Scalars['String']['output']; + /** Whether or not the operation is retryable */ + retryable: Scalars['Boolean']['output']; +}; + +export type AddLedgerEntryInput = { + /** The [Ledger Entry](https://fragment.dev/api-reference/api-types#input-types-ledgerentryinput) to add */ + entry: LedgerEntryInput; + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry */ + ik: Scalars['SafeString']['input']; +}; + +export type AddLedgerEntryResponse = AddLedgerEntryResult | BadRequestError | InternalError; + +export type AddLedgerEntryResult = { + __typename?: 'AddLedgerEntryResult'; + /** The ledger entry that was posted */ + entry: LedgerEntry; + /** True if this request successfully completed before and the previous response is being returned */ + isIkReplay: Scalars['Boolean']['output']; + /** The ledger lines that were created in that entry */ + lines: Array; +}; + +/** Equivalent to an HTTP 400 - request either has missing or incorrect data */ +export type BadRequestError = Error & { + __typename?: 'BadRequestError'; + /** The status code of error. For example, 'ledger_not_found'. */ + code: Scalars['String']['output']; + /** The error message */ + message: Scalars['String']['output']; + /** Whether or not the operation is retryable */ + retryable: Scalars['Boolean']['output']; +}; + +/** A single amount and the timestamp requested */ +export type BalanceChangeDuring = { + __typename?: 'BalanceChangeDuring'; + /** The balance or balance change */ + amount: CurrencyAmount; + /** The period of the requested balance change */ + period: Scalars['Period']['output']; +}; + +/** A paginated list of amounts and their periods */ +export type BalanceChangeDuringConnection = { + __typename?: 'BalanceChangeDuringConnection'; + /** The end time of the period across which the balance changes are requested */ + endTime: Scalars['LastMoment']['output']; + /** The granularity of the return data */ + granularity: Granularity; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; + /** The start time of the period across which the balance changes are requested */ + startTime: Scalars['FirstMoment']['output']; +}; + +/** Used to configure the write-consistency of a Ledger Account's balance. See [Configure consistency](https://fragment.dev/guides/configure-consistency). */ +export enum BalanceUpdateConsistencyMode { + Eventual = 'eventual', + Strong = 'strong' +} + +/** The input for your Chart of Accounts in a Schema. */ +export type ChartOfAccountsInput = { + /** The Ledger Accounts modeled by your Schema. Ledger Accounts may be nested up to a maximum depth of 10. */ + accounts: Array; + /** + * The default consistency configuration for all Ledger Accounts in this Schema. + * If a Ledger Account does not specify its own consistency configuration, it will use the default values provided here. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + defaultConsistencyConfig?: InputMaybe; + /** + * The default currency of each Ledger Account in the Chart Of Accounts. + * It must be provided if `defaultCurrencyMode` is set to `single`. + * Additionally, `defaultCurrency` must be omitted if `defaultCurrencyMode` is set to `multi`. + */ + defaultCurrency?: InputMaybe; + /** The default currency mode of each Ledger Account in the Chart Of Accounts. */ + defaultCurrencyMode?: InputMaybe; +}; + +export type CreateCustomCurrencyInput = { + /** The currency code for custom currencies. It can be up to 36 characters long. This is used for display purposes. */ + customCode: Scalars['String']['input']; + /** The ID for a custom currency. This is specified when creating the custom currency using the [createCustomCurrency](https://fragment.dev/api-reference/api-mutations#createcustomcurrency) mutation. It can be up to 36 characters long. */ + customCurrencyId: Scalars['SafeString']['input']; + /** A human readable name for the currency (e.g. United States Dollar). This is used for display purposes. */ + name: Scalars['String']['input']; + /** The number of decimal places this currency goes to. For example, United States Dollars have a precision of 2 (i.e. 100 cents in a dollar), whereas the Jordanian Dinar has a precision of 3. This is used for display purposes. */ + precision: Scalars['Int']['input']; +}; + +export type CreateCustomCurrencyResponse = BadRequestError | CreateCustomCurrencyResult | InternalError; + +export type CreateCustomCurrencyResult = { + __typename?: 'CreateCustomCurrencyResult'; + /** The Currency that was created. */ + customCurrency: Currency; +}; + +export type CreateCustomLinkResponse = BadRequestError | CreateCustomLinkResult | InternalError; + +export type CreateCustomLinkResult = { + __typename?: 'CreateCustomLinkResult'; + isIkReplay: Scalars['Boolean']['output']; + /** The custom link that was created. Represents an instance of an external system. */ + link: CustomLink; +}; + +export type CreateLedgerAccountInput = { + /** The consistency configuration for this Ledger Account. This defines how updates to this Ledger Account's balance are handled. */ + consistencyConfig?: InputMaybe; + /** + * The currency of this Ledger Account. If this is not set, and `currencyMode` is + * not set to `multi`, the workspace-level default is used. + */ + currency?: InputMaybe; + /** If set to `multi`, creates a multi-currency Ledger Account. If set to `single`, creates a single-currency Ledger Account. */ + currencyMode?: InputMaybe; + /** The External Account to link to this Ledger Account. */ + linkedAccount?: InputMaybe; + /** The human-readable name of this Ledger Account. */ + name: Scalars['String']['input']; + /** The parent of this Ledger Account. */ + parent?: InputMaybe; + /** The type of ledger account to create. Required if this is a top-level Ledger Account. If not provided, the type will be inferred from the parent. */ + type?: InputMaybe; +}; + +export type CreateLedgerAccountResponse = BadRequestError | CreateLedgerAccountResult | InternalError; + +export type CreateLedgerAccountResult = { + __typename?: 'CreateLedgerAccountResult'; + /** true if a previous request successfully created this ledger account */ + isIkReplay: Scalars['Boolean']['output']; + /** The ledger account that was created */ + ledgerAccount: LedgerAccount; +}; + +export type CreateLedgerAccountsInput = { + /** Ledger Accounts to create as children of this Ledger Account. */ + childLedgerAccounts?: InputMaybe>; + /** The consistency configuration for this ledger account. See [Configure consistency](https://fragment.dev/guides/configure-consistency). */ + consistencyConfig?: InputMaybe; + /** The currency of this Ledger Account. If this is not set, the workspace level default is used. */ + currency?: InputMaybe; + /** The currency mode of this Ledger Account. If this is not set, the workspace level default is used. */ + currencyMode?: InputMaybe; + /** The idempotency key for creating this Ledger Account. */ + ik: Scalars['SafeString']['input']; + /** The External Account to link to this Ledger Account. This can only be specified on leaf Ledger Accounts. See [Reconcile payments](https://fragment.dev/guides/reconcile-payments). */ + linkedAccount?: InputMaybe; + /** The name of the Ledger Account. */ + name: Scalars['String']['input']; + /** The parent of this Ledger Account. This is only valid on the top level Ledger Account in the payload. */ + parent?: InputMaybe; + /** The type of this Ledger Account. This field is only required if this is a root Ledger Account. Otherwise, the type will get inherited from its parent. */ + type?: InputMaybe; +}; + +export type CreateLedgerAccountsResponse = BadRequestError | CreateLedgerAccountsResult | InternalError; + +export type CreateLedgerAccountsResult = { + __typename?: 'CreateLedgerAccountsResult'; + /** Whether the ledger accounts were successfully created by a previous request */ + ikReplays: Array; + /** The ledger accounts that were created */ + ledgerAccounts: Array; +}; + +export type CreateLedgerInput = { + /** + * Use this field to specify a timezone for queries to your Ledger. + * + * When aggregating balances, all transactions within a 24 hour period starting at midnight UTC are included in each day. + * You can specify a different starting hour for balances. For example, use "-08:00" to align balances with Pacific Standard Time. + * Balance queries would then consider the start of each local day to be at 8am UTC the next day in UTC. + * The default timezone is UTC. + */ + balanceUTCOffset?: InputMaybe; + name: Scalars['String']['input']; + type?: InputMaybe; +}; + +export type CreateLedgerResponse = BadRequestError | CreateLedgerResult | InternalError; + +export type CreateLedgerResult = { + __typename?: 'CreateLedgerResult'; + /** true if this request successfully completed before and the previous response is being returned */ + isIkReplay: Scalars['Boolean']['output']; + /** The Ledger that was created */ + ledger: Ledger; +}; + +export type CreatePaymentResponse = BadRequestError | InternalError | Payment; + +export type Currency = { + __typename?: 'Currency'; + /** The currency code. This is an [enum type](https://fragment.dev/api-reference/api-types#scalars-and-enums-currencycode) . */ + code: CurrencyCode; + /** The currency code for custom currencies. This is only set if 'currency' is set to CUSTOM. It can be up to 36 characters long. */ + customCode?: Maybe; + /** The ID for a custom currency. This is specified when creating the custom currency using the [createCustomCurrency](https://fragment.dev/api-reference/api-mutations#createcustomcurrency) mutation. */ + customCurrencyId?: Maybe; + /** A human readable name for the currency (e.g. United States Dollar). This is used for display purposes. */ + name: Scalars['String']['output']; + /** The number of decimal places this currency goes to. For example, United States Dollars have a precision of 2 (i.e. 100 cents in a dollar), whereas the Jordanian Dinar has a precision of 3. This is used for display purposes. */ + precision: Scalars['Int']['output']; +}; + +/** A single amount accompanied by its currency */ +export type CurrencyAmount = { + __typename?: 'CurrencyAmount'; + /** Numerical integer value, serialized as a string */ + amount: Scalars['Int96']['output']; + /** The currency this amount is in */ + currency: Currency; +}; + +/** A paginated list of amounts with their currencies */ +export type CurrencyAmountConnection = { + __typename?: 'CurrencyAmountConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export enum CurrencyCode { + Aave = 'AAVE', + Ada = 'ADA', + Aed = 'AED', + Afn = 'AFN', + All = 'ALL', + Amd = 'AMD', + Ang = 'ANG', + Aoa = 'AOA', + Ars = 'ARS', + Aud = 'AUD', + Awg = 'AWG', + Azn = 'AZN', + Bam = 'BAM', + Bbd = 'BBD', + Bch = 'BCH', + Bdt = 'BDT', + Bgn = 'BGN', + Bhd = 'BHD', + Bif = 'BIF', + Bmd = 'BMD', + Bnd = 'BND', + Bob = 'BOB', + Brl = 'BRL', + Bsd = 'BSD', + Btc = 'BTC', + Btn = 'BTN', + Bwp = 'BWP', + Byr = 'BYR', + Bzd = 'BZD', + Cad = 'CAD', + Cadc = 'CADC', + Cadt = 'CADT', + Cdf = 'CDF', + Chf = 'CHF', + Clp = 'CLP', + Cny = 'CNY', + Cop = 'COP', + Crc = 'CRC', + Cuc = 'CUC', + Cup = 'CUP', + Custom = 'CUSTOM', + Cve = 'CVE', + Czk = 'CZK', + Dai = 'DAI', + Djf = 'DJF', + Dkk = 'DKK', + Dop = 'DOP', + Dzd = 'DZD', + Egp = 'EGP', + Ern = 'ERN', + Etb = 'ETB', + Eth = 'ETH', + Eur = 'EUR', + Eurc = 'EURC', + Fjd = 'FJD', + Fkp = 'FKP', + Gbp = 'GBP', + Gel = 'GEL', + Ggp = 'GGP', + Ghs = 'GHS', + Gip = 'GIP', + Gmd = 'GMD', + Gnf = 'GNF', + Gtq = 'GTQ', + Gyd = 'GYD', + Hkd = 'HKD', + Hnl = 'HNL', + Hrk = 'HRK', + Htg = 'HTG', + Huf = 'HUF', + Idr = 'IDR', + Ils = 'ILS', + Imp = 'IMP', + Inr = 'INR', + Iqd = 'IQD', + Irr = 'IRR', + Isk = 'ISK', + Jmd = 'JMD', + Jod = 'JOD', + Jpy = 'JPY', + Kes = 'KES', + Kgs = 'KGS', + Khr = 'KHR', + Kmf = 'KMF', + Kpw = 'KPW', + Krw = 'KRW', + Kwd = 'KWD', + Kyd = 'KYD', + Kzt = 'KZT', + Lak = 'LAK', + Lbp = 'LBP', + Link = 'LINK', + Lkr = 'LKR', + Logical = 'LOGICAL', + Lrd = 'LRD', + Lsl = 'LSL', + Ltc = 'LTC', + Lyd = 'LYD', + Mad = 'MAD', + Matic = 'MATIC', + Mdl = 'MDL', + Mga = 'MGA', + Mkd = 'MKD', + Mmk = 'MMK', + Mnt = 'MNT', + Mop = 'MOP', + Mur = 'MUR', + Mvr = 'MVR', + Mwk = 'MWK', + Mxn = 'MXN', + Myr = 'MYR', + Mzn = 'MZN', + Nad = 'NAD', + Ngn = 'NGN', + Nio = 'NIO', + Nok = 'NOK', + Npr = 'NPR', + Nzd = 'NZD', + Omr = 'OMR', + Pab = 'PAB', + Pen = 'PEN', + Pgk = 'PGK', + Php = 'PHP', + Pkr = 'PKR', + Pln = 'PLN', + Pts = 'PTS', + Pyg = 'PYG', + Qar = 'QAR', + Ron = 'RON', + Rsd = 'RSD', + Rub = 'RUB', + Rwf = 'RWF', + Sar = 'SAR', + Sbd = 'SBD', + Scr = 'SCR', + Sdg = 'SDG', + Sek = 'SEK', + Sgd = 'SGD', + Shp = 'SHP', + Sll = 'SLL', + Sol = 'SOL', + Sos = 'SOS', + Spl = 'SPL', + Srd = 'SRD', + Stn = 'STN', + Svc = 'SVC', + Syp = 'SYP', + Szl = 'SZL', + Thb = 'THB', + Tjs = 'TJS', + Tmt = 'TMT', + Tnd = 'TND', + Top = 'TOP', + Try = 'TRY', + Ttd = 'TTD', + Tvd = 'TVD', + Twd = 'TWD', + Tzs = 'TZS', + Uah = 'UAH', + Ugx = 'UGX', + Uni = 'UNI', + Usd = 'USD', + Usdc = 'USDC', + Usdg = 'USDG', + Usdt = 'USDT', + Uyu = 'UYU', + Uzs = 'UZS', + Vef = 'VEF', + Vnd = 'VND', + Vuv = 'VUV', + Wst = 'WST', + Xaf = 'XAF', + Xcd = 'XCD', + Xlm = 'XLM', + Xof = 'XOF', + Xpf = 'XPF', + Yer = 'YER', + Zar = 'ZAR', + Zmw = 'ZMW' +} + +export type CurrencyFilter = { + /** Must match the value provided */ + equalTo?: InputMaybe; + /** Must match one of the values provided. Limited to 100 items maximum. */ + in?: InputMaybe>; +}; + +export type CurrencyMatchInput = { + /** The currency code. This is an [enum type](https://fragment.dev/api-reference/api-types#scalars-and-enums-currencycode). */ + code: CurrencyCode; + /** The ID for a custom currency. This is specified when creating the custom currency using the [createCustomCurrency](https://fragment.dev/api-reference/api-mutations#createcustomcurrency) mutation. */ + customCurrencyId?: InputMaybe; +}; + +/** Defines the currency handling of a LedgerAccount, which can either be restricted to a single currency or allow multiple currencies. */ +export enum CurrencyMode { + Multi = 'multi', + Single = 'single' +} + +export type CustomAccountInput = { + /** The currency of this external account. If this is not set, the workspace level default is used. 'currency' cannot be set if 'currencyMode' is 'multi'. */ + currency?: InputMaybe; + /** The currency mode of this external account. If set to multi, creates a multi-currency account. */ + currencyMode?: InputMaybe; + /** The ID of this account at the external system. This is used as the idempotency key, within the scope of its Custom Link. */ + externalId: Scalars['SafeString']['input']; + /** The name of the account at the external system. */ + name: Scalars['String']['input']; +}; + +/** A paginated list of Custom Currencies */ +export type CustomCurrenciesConnection = { + __typename?: 'CustomCurrenciesConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export type CustomLink = Link & { + __typename?: 'CustomLink'; + /** ISO-8601 timestamp when the Link was created. */ + created: Scalars['String']['output']; + /** URL to the Fragment Dashboard for this Link. */ + dashboardUrl: Scalars['String']['output']; + /** A list of External Accounts associated with this Link. */ + externalAccounts: ExternalAccountsConnection; + /** FRAGMENT ID of the Custom Link. */ + id: Scalars['ID']['output']; + /** Name of the Link as it appears in the Fragment Dashboard. */ + name: Scalars['String']['output']; +}; + +export type CustomTxInput = { + account: ExternalAccountMatchInput; + amount: Scalars['Int96']['input']; + /** The currency of this tx. Should be set for multi-currency accounts. */ + currency?: InputMaybe; + description: Scalars['String']['input']; + /** The ID of this tx at the external system. This is used as the idempotency key, within the scope of its Custom Account. */ + externalId: Scalars['SafeString']['input']; + posted: Scalars['DateTime']['input']; +}; + +export type DateFilter = { + equalTo?: InputMaybe; + /** Must match one of the values provided. Limited to 100 items maximum. */ + in?: InputMaybe>; + /** Must fall within the given period. Supports a year (e.g. "2026") or a month (e.g. "2026-05"). To match a specific day, use `equalTo`. Cannot be combined with `withinBalanceUTCOffset`. */ + within?: InputMaybe; + /** Must fall within the given period, taking into account the Ledger's `balanceUTCOffset`. Supports a year (e.g. "2026") or a month (e.g. "2026-05"). Cannot be combined with `within`. */ + withinBalanceUTCOffset?: InputMaybe; +}; + +/** Filters a timestamp field between two moments in time */ +export type DateTimeFilter = { + /** The timestamp value must be after this moment. Specified in ISO 8601 format e.g "1968-01-01T16:45:00Z" */ + after?: InputMaybe; + /** The timestamp value must be before this moment. Specified in ISO 8601 format e.g "1968-01-01T16:45:00Z" */ + before?: InputMaybe; +}; + +export type DeleteCustomTxsResponse = BadRequestError | DeleteCustomTxsResult | InternalError; + +export type DeleteCustomTxsResult = { + __typename?: 'DeleteCustomTxsResult'; + /** List of Txs deleted in this operation */ + txs: Array; +}; + +export type DeleteLedgerResponse = BadRequestError | DeleteLedgerResult | InternalError; + +export type DeleteLedgerResult = { + __typename?: 'DeleteLedgerResult'; + success: Scalars['Boolean']['output']; +}; + +export type DeleteSchemaResponse = BadRequestError | DeleteSchemaResult | InternalError; + +export type DeleteSchemaResult = { + __typename?: 'DeleteSchemaResult'; + success: Scalars['Boolean']['output']; +}; + +export type DeletedCustomTx = { + __typename?: 'DeletedCustomTx'; + /** A deleted Tx */ + tx: Tx; +}; + +export type EntryGroupMatchInput = { + key: Scalars['SafeString']['input']; + value: Scalars['SafeString']['input']; +}; + +/** Base error interface */ +export type Error = { + /** The status code of error. For example, 'ledger_not_found'. */ + code: Scalars['String']['output']; + /** The error message */ + message: Scalars['String']['output']; + /** Whether or not the operation is retryable */ + retryable: Scalars['Boolean']['output']; +}; + +export type ExternalAccount = { + __typename?: 'ExternalAccount'; + /** The currency of this external account. */ + currency?: Maybe; + /** Indicates if the account allows multiple currencies or is restricted to a single currency */ + currencyMode: CurrencyMode; + /** ID used for the external account */ + externalId: Scalars['ID']['output']; + /** FRAGMENT ID of External Account */ + id: Scalars['ID']['output']; + /** Ledger Accounts linked to this External Account. Ledger Accounts are paginated and sorted in reverse-chronological order by created date. */ + ledgerAccounts: LedgerAccountsConnection; + /** The Link that this External Account belongs to. */ + link: Link; + /** FRAGMENT ID of this transaction's external link */ + linkId: Scalars['ID']['output']; + name: Scalars['String']['output']; + /** All Txs in this External Account. */ + txs: TxsConnection; +}; + + +export type ExternalAccountLedgerAccountsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +export type ExternalAccountTxsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +export type ExternalAccountFilter = { + /** Ledger Account must linked to the the specified external account */ + equalTo?: InputMaybe; + /** Ledger Account can be linked to any of the specified external accounts. Limited to 100 items maximum. */ + in?: InputMaybe>; +}; + +/** Specify an External Account by using `id`, or `linkId` and `externalId`. */ +export type ExternalAccountMatchInput = { + /** The external system's ID of the External Account. If this is specified, `linkId` is required. `id` is optional, but will be validated if provided. */ + externalId?: InputMaybe; + /** The FRAGMENT ID of the External Account. If this is specified, both `linkId` and `externalId` are optional, but will be validated if provided. */ + id?: InputMaybe; + /** The FRAGMENT ID of the Link the External Account is in. If this is specified, `externalId` is required. `id` is optional, but will be validated if provided. */ + linkId?: InputMaybe; +}; + +/** A paginated list of External Accounts */ +export type ExternalAccountsConnection = { + __typename?: 'ExternalAccountsConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export enum ExternalTransferType { + Ach = 'ach', + Card = 'card', + Check = 'check', + Internal = 'internal', + Wire = 'wire' +} + +export enum ExternalTxSource { + Increase = 'increase' +} + +export enum Granularity { + Daily = 'daily', + Hourly = 'hourly', + Monthly = 'monthly' +} + +/** A filter to query balances of a specific subset of accounts */ +export type GroupBalanceAccountFilter = { + /** A filter that must match the account ID */ + id?: InputMaybe; + /** A filter that must match the account path. Wildcards ('*') may be used only for template variables, and will only match a single variable each. */ + path?: InputMaybe; +}; + +/** Filter for finding entries by group membership */ +export type GroupFilter = { + /** Find entries that have ALL of the specified groups. Limited to 10 items maximum. */ + all?: InputMaybe>; + /** Find groups that exactly match this group */ + equalTo?: InputMaybe; + /** Find groups that match any of these groups */ + in?: InputMaybe>; + /** Find groups with a specific key */ + keyEqualTo?: InputMaybe; + /** Find groups with any of these keys */ + keyIn?: InputMaybe>; + /** + * Find groups that do not match this predicate + * @deprecated not filter is deprecated. Use notKeyIn or notKeyEqualTo instead. + */ + not?: InputMaybe; + /** Find groups that do not exactly match this group */ + notEqualTo?: InputMaybe; + /** Find groups that do not match any of these groups */ + notIn?: InputMaybe>; + /** Find groups that do not have a specific key */ + notKeyEqualTo?: InputMaybe; + /** Find groups that do not have any of these keys */ + notKeyIn?: InputMaybe>; +}; + +/** A Group in a Schema. Group define sequences of Ledger Entries and can help with reconciliation tasks. */ +export type GroupInput = { + /** Human-readable description of the Group. */ + description?: InputMaybe; + /** The key of this Group. This combined with its value is a stable, unique identifier for this group. */ + key: Scalars['SafeString']['input']; + /** The parameters that are used to enable reconciliation abilities in a group. */ + reconciliation?: InputMaybe; +}; + +/** Input type for matching a specific group by key and value */ +export type GroupMatchInput = { + /** The key of the group to match */ + key: Scalars['SafeString']['input']; + /** The value of the group to match */ + value: Scalars['SafeString']['input']; +}; + +/** DEPRECATED: Use GroupFilter and notKeyIn or notKeyEqualTo instead. Filter for finding entries that do not match this predicate */ +export type GroupNotFilter = { + /** DEPRECATED: Find entries that are not members of all of these groups. This is an AND filter. */ + keyIn?: InputMaybe>; +}; + +/** A set of parameters that are used to enable reconciliation abilities in a group */ +export type GroupReconciliationParametersInput = { + /** The path to the clearing account for this group. A clearing account is an account that is used to indicate funds that are in transit. Also called a suspense account, pending account, or zero balance account. */ + clearingAccountPath: SchemaLedgerAccountMatchInput; +}; + +/** A single amount and the timestamp requested */ +export type HistoricalBalance = { + __typename?: 'HistoricalBalance'; + /** The balance or balance change */ + amount: CurrencyAmount; + /** The timestamp of the requested balance */ + at: Scalars['LastMoment']['output']; +}; + +/** A paginated list of amounts and their periods */ +export type HistoricalBalanceConnection = { + __typename?: 'HistoricalBalanceConnection'; + /** The end time of the period across which the balance changes are requested */ + endTime: Scalars['LastMoment']['output']; + /** The granularity of the return data */ + granularity: Granularity; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; + /** The start time of the period across which the balance changes are requested */ + startTime: Scalars['FirstMoment']['output']; +}; + +export type IkReplay = { + __typename?: 'IkReplay'; + ik: Scalars['SafeString']['output']; + isIkReplay: Scalars['Boolean']['output']; +}; + +export enum IncreaseEnv { + Production = 'production', + Sandbox = 'sandbox' +} + +export type IncreaseLink = Link & { + __typename?: 'IncreaseLink'; + /** ISO-8601 timestamp when the Link was created. */ + created: Scalars['String']['output']; + /** URL to the Fragment Dashboard for this Link. */ + dashboardUrl: Scalars['String']['output']; + /** A list of External Accounts associated with this Link. */ + externalAccounts: ExternalAccountsConnection; + /** FRAGMENT ID of the Increase Link. */ + id: Scalars['ID']['output']; + /** The environment of the Increase Link, either sandbox or production. */ + increaseEnv: IncreaseEnv; + /** Name of the Link as it appears in the Dashboard. */ + name: Scalars['String']['output']; +}; + +/** A condition that must be met on an `Int96` field. */ +export type Int96Condition = { + __typename?: 'Int96Condition'; + /** Amount must exactly match this value. You may not specify this alongside `gte` or `lte`. */ + eq?: Maybe; + /** Amount must be greater than or equal to this value. */ + gte?: Maybe; + /** Amount must be less than or equal to this value. */ + lte?: Maybe; +}; + +/** A condition that must be met on an `Int96` field. */ +export type Int96ConditionInput = { + /** Amount must exactly match this value. You may not specify this alongside `gte` or `lte`. */ + eq?: InputMaybe; + /** Amount must be greater than or equal to this value. */ + gte?: InputMaybe; + /** Amount must be less than or equal to this value. */ + lte?: InputMaybe; +}; + +export type Int96Filter = { + /** Must exactly equal this Int96 value */ + eq?: InputMaybe; + /** Must be greater than or equal to this Int96 value */ + gte?: InputMaybe; + /** Must be less than or equal to this Int96 value */ + lte?: InputMaybe; + /** Must not equal this Int96 value */ + ne?: InputMaybe; +}; + +/** Equivalent to an HTTP 5XX - something went wrong with our API. */ +export type InternalError = Error & { + __typename?: 'InternalError'; + /** The status code of error. For example, 'ledger_not_found'. */ + code: Scalars['String']['output']; + /** The error message */ + message: Scalars['String']['output']; + /** Whether or not the operation is retryable */ + retryable: Scalars['Boolean']['output']; +}; + +/** Ledgers are databases designed for managing money */ +export type Ledger = { + __typename?: 'Ledger'; + /** When aggregating balances, all transactions within a 24 hour period starting at midnight UTC plus this offset are included in each day. */ + balanceUTCOffset: Scalars['UTCOffset']['output']; + created: Scalars['DateTime']['output']; + /** URL to the Fragment Dashboard for this Ledger. */ + dashboardUrl: Scalars['String']['output']; + /** Entry statistics for this Ledger. */ + entryStats: LedgerEntryStatsConnection; + id: Scalars['ID']['output']; + /** The IK passed into the [createLedger](/api-reference/api-mutations#createledger) mutation. This is treated as a unique identifier for this Ledger. */ + ik: Scalars['SafeString']['output']; + /** Ledger Account data migrations affecting this Ledger. */ + ledgerAccountDataMigrations: LedgerAccountDataMigrationConnection; + /** Query LedgerAccounts in Ledger. Ledger Accounts are paginated and returned in reverse-chronological order by their created date. */ + ledgerAccounts: LedgerAccountsConnection; + /** Query Ledger Entries in a Ledger. Ledger Entries are paginated and sorted in reverse-chronological order by posted date. */ + ledgerEntries: LedgerEntriesConnection; + /** Ledger Entry data migrations affecting this Ledger. */ + ledgerEntryDataMigrations: LedgerEntryDataMigrationConnection; + /** Query a Ledger Entry Group for this Ledger given its key and value. */ + ledgerEntryGroup: LedgerEntryGroup; + /** Query LedgerEntryGroups in Ledger. Ledger Entry Groups are paginated and returned in order lexigraphically key then inverse chronologically by created. */ + ledgerEntryGroups: LedgerEntryGroupsConnection; + /** + * List Ledger Lines across accounts in this Ledger, sorted by `posted` in reverse chronological order. + * Specify a single Ledger Account via the `ledgerAccount` field, or query across multiple accounts using the `path` filter or `ledgerAccount.in`. + */ + lines: LedgerLinesConnection; + /** Schema migrations affecting this Ledger. */ + migrations: LedgerMigrationConnection; + /** The name of the Ledger. Can be updated with the [updateLedger](/api-reference/api-mutations#updateledger) mutation. */ + name: Scalars['String']['output']; + /** Schema key associated with this Ledger. */ + schema?: Maybe; + type: LedgerTypes; + /** @deprecated Callers should not need to query or store this value. */ + workspaceId: Scalars['ID']['output']; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerEntryStatsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLedgerAccountDataMigrationsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLedgerAccountsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLedgerEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLedgerEntryDataMigrationsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLedgerEntryGroupArgs = { + ledgerEntryGroup: EntryGroupMatchInput; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLedgerEntryGroupsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLinesArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter: LedgerLinesFilterSet; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerMigrationsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** A ledger account is a container for money */ +export type LedgerAccount = { + __typename?: 'LedgerAccount'; + /** Total of all lines in this ledger account and child ledger accounts of the same currency as this ledger account */ + balance: Scalars['Int96']['output']; + /** How much did the this ledger account's balance change during the specified period. This query will include all child accounts in the same currency as this ledger account. */ + balanceChange: Scalars['Int96']['output']; + /** How much did the this ledger account's balances change during the specified period. This query will include all child accounts of all currencies. */ + balanceChanges: CurrencyAmountConnection; + /** + * How much did the ledger account's balances change over a time period with a specified granularity. + * For example, if the period is 1969-07 and the granularity is daily, the query will return the balance changes for each day in the month. + */ + balanceChangesDuring: BalanceChangeDuringConnection; + /** Total of all lines in this ledger account and child ledger accounts in all currencies */ + balances: CurrencyAmountConnection; + /** + * The ledger account's balances over a time period with a specified granularity. + * For example, if the period is 1969-07 and the granularity is daily, the query will return the balance for each day in the month. + */ + balancesDuring: HistoricalBalanceConnection; + /** Total of all lines in child ledger accounts of the same currency as this ledger account */ + childBalance: Scalars['Int96']['output']; + /** How much did the this ledger account's childBalance change during the specified period. This query will only include child accounts which are in the same currency as this one. See childBalanceChanges to include children of different currencies. */ + childBalanceChange: Scalars['Int96']['output']; + /** How much did the this ledger account's child accounts' balances change during the specified period. This query will include all child accounts of all currencies. */ + childBalanceChanges: CurrencyAmountConnection; + /** + * How much did the ledger account's childBalances change over a time period with a specified granularity. + * For example, if the period is 1969-07 and the granularity is daily, the query will return the balance changes for each day in the month. + */ + childBalanceChangesDuring: BalanceChangeDuringConnection; + /** Total of all lines in child ledger accounts of this ledger in all currencies */ + childBalances: CurrencyAmountConnection; + /** + * The ledger account's childBalances over a time period with a specified granularity. + * For example, if the period is 1969-07 and the granularity is daily, the query will return the balance for each day in the month. + */ + childBalancesDuring: HistoricalBalanceConnection; + /** The child Ledger Accounts of this Ledger Accountw */ + childLedgerAccounts: LedgerAccountsConnection; + /** + * The clearing status of the Ledger Account. + * + * This field is null when the Ledger Account is not configured to be a Clearing account. + */ + clearingStatus?: Maybe; + /** The consistency configuration for this Ledger Account. This defines how updates to this Ledger Account's ownBalance are handled. */ + consistencyConfig: LedgerAccountConsistencyConfig; + created: Scalars['DateTime']['output']; + /** Currency of this ledger account */ + currency?: Maybe; + /** Indicates if the account allows multiple currencies or is restricted to a single currency */ + currencyMode: CurrencyMode; + /** URL to the Fragment Dashboard for this Ledger Account. */ + dashboardUrl: Scalars['String']['output']; + id: Scalars['ID']['output']; + /** The idempotency key used to create this account */ + ik: Scalars['String']['output']; + /** Ledger this account is in */ + ledger: Ledger; + /** All Ledger Entries that have posted to this Ledger Account. Ledger Entries are paginated and sorted in reverse-chronological order by posted date. */ + ledgerEntries: LedgerEntriesConnection; + /** ID of the ledger this account is in */ + ledgerId: Scalars['ID']['output']; + /** List Ledger Lines in this account, sorted by `posted` in reverse chronological order. Does not include Ledger Lines from child Ledger Accounts. */ + lines: LedgerLinesConnection; + /** The Link for the External Account that is linked to this ledger account */ + link?: Maybe; + /** External Account that is linked to this ledger account */ + linkedAccount?: Maybe; + /** The name of your Ledger Account */ + name?: Maybe; + /** Total of all lines in this ledger account, excluding all child ledger accounts */ + ownBalance: Scalars['Int96']['output']; + /** How much did the this ledger account's ownBalance change during the specified period. This query will exclude all child accounts. */ + ownBalanceChange: Scalars['Int96']['output']; + /** How much did the this ledger account's ownBalance change during the specified period. This is the total of all lines in this ledger account, excluding all child ledger accounts */ + ownBalanceChanges: CurrencyAmountConnection; + /** + * How much did the ledger account's ownBalances change over a time period with a specified granularity. + * For example, if the period is 1969-07 and the granularity is daily, the query will return the balance changes for each day in the month. + */ + ownBalanceChangesDuring: BalanceChangeDuringConnection; + /** Total of all lines across all currencies in this ledger account, excluding all child ledger accounts */ + ownBalances: CurrencyAmountConnection; + /** + * The ledger account's ownBalances over a time period with a specified granularity. + * For example, if the period is 1969-07 and the granularity is daily, the query will return the balance for each day in the month. + */ + ownBalancesDuring: HistoricalBalanceConnection; + /** The parent ledger account of this ledger account */ + parentLedgerAccount?: Maybe; + /** ID of the parent ledger account of this ledger account */ + parentLedgerAccountId?: Maybe; + /** + * The unique Path of the ledger account. This is a slash-delimited string containing the location of an account in its chart of accounts. + * For accounts created with a schema, this will be composed of account keys. Else, for accounts created with the createLedgerAccounts API, + * this will be composed of the IKs of an account and its ancestors. + */ + path: Scalars['String']['output']; + /** Payment configuration of this Ledger Account, if it is a payment account. */ + payment?: Maybe; + /** + * The posted timestamp window for this clearing account, representing the earliest and latest + * posted timestamps across all currencies. + * + * This field is null when the Ledger Account is not configured to be a Clearing account + * or when no entries have been posted to this account. + */ + postedWindow?: Maybe; + type: LedgerAccountTypes; + /** A list of external account transactions that haven't been reconciled to this ledger account yet. Only populated for linked ledger accounts. Transactions are sorted in reverse chronological order by posted date. */ + unreconciledTxs: TxsConnection; + /** @deprecated Callers should not need to query or store this value. */ + workspaceId: Scalars['ID']['output']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountBalanceArgs = { + at?: InputMaybe; + consistencyMode?: InputMaybe; + currency?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountBalanceChangeArgs = { + currency?: InputMaybe; + period: Scalars['Period']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountBalanceChangesArgs = { + period: Scalars['Period']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountBalanceChangesDuringArgs = { + currency?: InputMaybe; + duration: Scalars['Int']['input']; + granularity: Granularity; + startTime: Scalars['FirstMoment']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountBalancesArgs = { + after?: InputMaybe; + at?: InputMaybe; + before?: InputMaybe; + consistencyMode?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountBalancesDuringArgs = { + currency?: InputMaybe; + duration: Scalars['Int']['input']; + granularity: Granularity; + startTime: Scalars['FirstMoment']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildBalanceArgs = { + at?: InputMaybe; + consistencyMode?: InputMaybe; + currency?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildBalanceChangeArgs = { + currency?: InputMaybe; + period: Scalars['Period']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildBalanceChangesArgs = { + period: Scalars['Period']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildBalanceChangesDuringArgs = { + currency?: InputMaybe; + duration: Scalars['Int']['input']; + granularity: Granularity; + startTime: Scalars['FirstMoment']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildBalancesArgs = { + after?: InputMaybe; + at?: InputMaybe; + before?: InputMaybe; + consistencyMode?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildBalancesDuringArgs = { + currency?: InputMaybe; + duration: Scalars['Int']['input']; + granularity: Granularity; + startTime: Scalars['FirstMoment']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildLedgerAccountsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountLedgerEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountLinesArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountOwnBalanceArgs = { + at?: InputMaybe; + consistencyMode?: InputMaybe; + currency?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountOwnBalanceChangeArgs = { + currency?: InputMaybe; + period: Scalars['Period']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountOwnBalanceChangesArgs = { + period: Scalars['Period']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountOwnBalanceChangesDuringArgs = { + currency?: InputMaybe; + duration: Scalars['Int']['input']; + granularity: Granularity; + startTime: Scalars['FirstMoment']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountOwnBalancesArgs = { + after?: InputMaybe; + at?: InputMaybe; + before?: InputMaybe; + consistencyMode?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountOwnBalancesDuringArgs = { + currency?: InputMaybe; + duration: Scalars['Int']['input']; + granularity: Granularity; + startTime: Scalars['FirstMoment']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountUnreconciledTxsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** The clearing status of a Ledger Account. */ +export enum LedgerAccountClearingStatus { + /** The account has no outstanding balances. */ + Cleared = 'cleared', + /** The account has outstanding balances that have not been cleared. */ + Pending = 'pending' +} + +export type LedgerAccountClearingStatusFilter = { + /** Results must match the specified clearing account status */ + equalTo?: InputMaybe; +}; + +/** A set of conditions that a Ledger Account must meet for an operation to succeed. */ +export type LedgerAccountCondition = { + __typename?: 'LedgerAccountCondition'; + /** A condition that the `ownBalance` field must satisfy. Note that this condition always applies to the latest balance, not to balances at a specific date or time. See [Read balances](https://fragment.dev/guides/read-balances) for more on the different types of Ledger Account balances. */ + ownBalance?: Maybe; + /** A condition that the `totalBalance` field must satisfy. Note that this condition always applies to the latest balance, not to balances at a specific date or time. See [Read balances](https://fragment.dev/guides/read-balances) for more on the different types of Ledger Account balances. */ + totalBalance?: Maybe; +}; + +/** A set of conditions that a Ledger Account must meet for an operation to succeed. */ +export type LedgerAccountConditionInput = { + /** A condition that the ownBalance field must satisfy. Note that this condition always applies to the latest balance, not to balances at a specific date or time. See [Read balances](https://fragment.dev/read-balances) for more on the different types of Ledger Account balances. */ + ownBalance?: InputMaybe; + /** A condition that the totalBalance field must satisfy. Note that this condition always applies to the latest balance, not to balances at a specific date or time. See [Read balances](https://fragment.dev/read-balances) for more on the different types of Ledger Account balances. */ + totalBalance?: InputMaybe; +}; + +/** + * The consistency configuration of a Ledger Account's balance updates. + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ +export type LedgerAccountConsistencyConfig = { + __typename?: 'LedgerAccountConsistencyConfig'; + lines: LedgerLinesConsistencyMode; + /** + * If set to `strong`, then a Ledger Account's `ownBalance` updates will be strongly consistent with + * the API response. This Ledger Account's balance will be updated and + * available for strongly consistent reads once you receive an API response. + * + * Otherwise if not set or set to `eventual`, `ownBalance` updates are applied + * asynchronously and may not be immediately reflected in queries. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + ownBalanceUpdates: BalanceUpdateConsistencyMode; + /** + * If set to `strong`, then a Ledger Account's `ownBalance`, `childBalance`, and `balance` fields' updates will be strongly consistent with + * the API response. This Ledger Account's balance will be updated and + * available for strongly consistent reads once you receive an API response. + * + * Otherwise if not set or set to `eventual`, updates are applied + * asynchronously and may not be immediately reflected in queries. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + totalBalanceUpdates?: Maybe; +}; + +/** + * The payload configuring the consistency for this Ledger Account. + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ +export type LedgerAccountConsistencyConfigInput = { + /** + * The consistency configuration for Ledger Entry Groups affecting this account. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + groups?: InputMaybe>; + /** + * If set to `strong`, then a Ledger Account's `lines` updates will be strongly consistent with the API response. + * This Ledger Account's balance will be updated and available for strongly consistent reads before you receive an API response. + * + * Otherwise if unset or set to `eventual`, `lines` updates are applied asynchronously and may not be immediately reflected in queries. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + lines?: InputMaybe; + /** + * If set to `strong`, then a Ledger Account's `ownBalance` updates will be strongly consistent with the API response. + * This Ledger Account's balance will be updated and available for strongly consistent reads before you receive an API response. + * + * Otherwise if unset or set to `eventual`, `ownBalance` updates are applied asynchronously and may not be immediately reflected in queries. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + ownBalanceUpdates?: InputMaybe; + /** + * EXPERIMENTAL: If set to `strong`, then a Ledger Account's `totalBalance` updates will be strongly consistent with the API response. + * This Ledger Account's balance will be updated and available for strongly consistent reads before you receive an API response. + * + * Otherwise if unset or set to `eventual`, `totalBalance` updates are applied asynchronously and may not be immediately reflected in queries. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + totalBalanceUpdates?: InputMaybe; +}; + +/** Represents a data migration for a specific Ledger Account in a Ledger. */ +export type LedgerAccountDataMigration = LedgerDataMigration & { + __typename?: 'LedgerAccountDataMigration'; + /** The path of the Ledger Account being migrated. */ + accountPath: Scalars['String']['output']; + /** Current active migration info (null if migration is inactive). */ + currentMigration?: Maybe; + /** The historical transitions of this migration. */ + history: LedgerDataMigrationHistoryConnection; + /** The ledger entries to be migrated. */ + ledgerEntries: LedgerEntriesConnection; + /** The status of the data migration. */ + status: LedgerDataMigrationStatus; +}; + + +/** Represents a data migration for a specific Ledger Account in a Ledger. */ +export type LedgerAccountDataMigrationHistoryArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Represents a data migration for a specific Ledger Account in a Ledger. */ +export type LedgerAccountDataMigrationLedgerEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +export type LedgerAccountDataMigrationConnection = { + __typename?: 'LedgerAccountDataMigrationConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +export type LedgerAccountDataMigrationsFilterSet = { + /** Filter by Ledger Account path. */ + accountPath?: InputMaybe; + /** Filter by the status of the data migration. */ + status?: InputMaybe; +}; + +export type LedgerAccountFilter = { + /** Result must match the specified Ledger Account */ + equalTo?: InputMaybe; + /** Results can match any of specified Ledger Accounts */ + in?: InputMaybe>; +}; + +/** The consistency configuration for a specific Ledger Entry Group in this account. */ +export type LedgerAccountGroupConsistencyConfigInput = { + /** The group key for this configuration. */ + key: Scalars['SafeString']['input']; + /** + * If set to `strong`, then Ledger Entry Group `ownBalance`s updates for this account will be strongly consistent with the API response. + * This Ledger Account's Ledger Entry Group balances will be updated and available for strongly consistent reads before you receive an API response. + * + * Otherwise if unset or set to `eventual`, Ledger Entry Group `ownBalance` updates are applied asynchronously and may not be immediately reflected in queries. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + ownBalanceUpdates: BalanceUpdateConsistencyMode; +}; + +/** + * Specify a Ledger Account by using `id` or `path`. + * + * When specifying a Ledger Account by `path`, you must provide `ledger`. + */ +export type LedgerAccountMatchInput = { + /** The FRAGMENT ID of the Ledger Account */ + id?: InputMaybe; + /** The Ledger to which this Ledger Account belongs. This is required if you are specifying the Ledger Account by `path`. */ + ledger?: InputMaybe; + /** + * The unique path of the Ledger Account. + * This is a slash-delimited string containing the keys of an account and all its direct ancestors. + */ + path?: InputMaybe; +}; + +/** Payment configuration of a Ledger Account. */ +export type LedgerAccountPayment = { + __typename?: 'LedgerAccountPayment'; + penguin: Scalars['Boolean']['output']; +}; + +export type LedgerAccountTypeFilter = { + /** Results must be of the specified Ledger Account type */ + equalTo?: InputMaybe; + /** Results can have any of the specified Ledger Account types */ + in?: InputMaybe>; +}; + +export enum LedgerAccountTypes { + Asset = 'asset', + Expense = 'expense', + Income = 'income', + Liability = 'liability' +} + +/** A paginated list of Ledger Accounts */ +export type LedgerAccountsConnection = { + __typename?: 'LedgerAccountsConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export type LedgerAccountsFilterSet = { + /** Use this to filter Ledger Accounts by their clearing account status */ + clearingStatus?: InputMaybe; + /** + * Filter by the earliest posted timestamp across all currencies for clearing accounts. You must also provide clearingStatus in the same filter. + * Only clearing accounts where the minimum posted timestamp (across all currencies) matches this filter will be included. + */ + earliestPosted?: InputMaybe; + /** Use this to filter Ledger Accounts by their parent status */ + hasParentLedgerAccount?: InputMaybe; + /** Use this to filter Ledger Accounts by their linked status */ + isLinkedAccount?: InputMaybe; + /** + * Filter by the latest posted timestamp across all currencies for clearing accounts. You must also provide clearingStatus in the same filter. + * Only clearing accounts where the maximum posted timestamp (across all currencies) matches this filter will be included. + */ + latestPosted?: InputMaybe; + /** Use this to filter Ledger Accounts by their ID or path */ + ledgerAccount?: InputMaybe; + /** Use this to filter Ledger Accounts by their external linked account ID */ + linkedAccount?: InputMaybe; + /** Use this to filter Ledger Accounts by their parent account IDs */ + parentLedgerAccount?: InputMaybe; + /** + * A filter that must match the account path. Wildcards ('*') may be used only for template variables, and will only match a single variable each. + * For example: 'assets-root/accounts-receivable/merchant:*' would match: 'assets-root/accounts-receivable/merchant:1' and 'assets-root/accounts-receivable/merchant:1/child'. + * Wildcards may not be used outside of template variables. For example, passing in 'assets-root/*' as a filter is invalid and would raise a GraphQL error. + */ + path?: InputMaybe; + /** Use this to filter Ledger Accounts by their type */ + type?: InputMaybe; +}; + +/** Represents a data migration for a Ledger. */ +export type LedgerDataMigration = { + /** Current active migration info (null if migration is inactive). */ + currentMigration?: Maybe; + /** The historical transitions of this migration. */ + history: LedgerDataMigrationHistoryConnection; + /** The ledger entries to be migrated. */ + ledgerEntries: LedgerEntriesConnection; + /** The status of the data migration. */ + status: LedgerDataMigrationStatus; +}; + + +/** Represents a data migration for a Ledger. */ +export type LedgerDataMigrationHistoryArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Represents a data migration for a Ledger. */ +export type LedgerDataMigrationLedgerEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** A paginated list of migration history entries. */ +export type LedgerDataMigrationHistoryConnection = { + __typename?: 'LedgerDataMigrationHistoryConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +/** A single schema version in the migration history. */ +export type LedgerDataMigrationHistoryEntry = { + __typename?: 'LedgerDataMigrationHistoryEntry'; + /** The schema version. */ + schemaVersion: Scalars['Int']['output']; + /** The current status of this schema version (active if it's the latest and migration is active, otherwise inactive). */ + status: LedgerDataMigrationStatus; +}; + +/** The status of a ledger data migration. */ +export enum LedgerDataMigrationStatus { + /** The migration is active. */ + Active = 'active', + /** The migration is inactive. */ + Inactive = 'inactive' +} + +/** A paginated list of Ledger Entries */ +export type LedgerEntriesConnection = { + __typename?: 'LedgerEntriesConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export type LedgerEntriesFilterSet = { + /** Use this filter to filter Ledger Entries by their `posted` date. */ + date?: InputMaybe; + /** Use this to filter Ledger Entries by groups. The response will include entries that contain or do not contain specific groups. */ + group?: InputMaybe; + /** Use this to filter Ledger Entries that were posted using `reverseLedgerEntry`. */ + isReversal?: InputMaybe; + /** Use this to filter Ledger Entries that have been reversed. */ + isReversed?: InputMaybe; + /** Use to filter Ledger Entries by their IDs or IKs. */ + ledgerEntry?: InputMaybe; + /** Use this filter to filter Ledger Entries by their `posted` timestamp. */ + posted?: InputMaybe; + /** Use this filter to show hidden Ledger Entries. */ + showHidden?: InputMaybe; + /** Use this to filter Ledger Entries by tags. The response will include entries that contain tags matching the filter. */ + tag?: InputMaybe; + /** Use this to filter Ledger Entries by type. Ledger Entry types are defined in Schemas. */ + type?: InputMaybe; + /** Use this to filter Ledger Entries by their type version. */ + typeVersion?: InputMaybe; +}; + +export type LedgerEntry = { + __typename?: 'LedgerEntry'; + /** The conditions that were satisfied by this Ledger Entry when it was posted. */ + conditions: Array; + /** ISO-8601 timestamp this LedgerEntry was created in Fragment. */ + created: Scalars['DateTime']['output']; + /** URL to the Fragment Dashboard for this Ledger Entry. */ + dashboardUrl: Scalars['String']['output']; + /** Date this LedgerEntry posted to its Ledger e.g. "2021-01-01". */ + date: Scalars['Date']['output']; + /** Description posted for this Ledger Entry. */ + description?: Maybe; + /** The Ledger Entry Groups this Ledger Entry is in. */ + groups: Array; + /** + * Indicates whether this Ledger Entry is hidden when listing Ledger Entries. + * Reversed and Reversal Ledger Entries are hidden by default because taken together they have no impact on a Ledger's balances. + */ + hidden: Scalars['Boolean']['output']; + /** The ID of this LedgerEntry. */ + id: Scalars['ID']['output']; + /** The idempotency key used to post this ledger entry */ + ik: Scalars['String']['output']; + /** + * Indicates whether this Ledger Entry is a reversal of another Ledger Entry. + * If so, reverses will point to that Ledger Entry. + */ + isReversal: Scalars['Boolean']['output']; + /** + * Indicates whether this Ledger Entry has been reversed by another Ledger Entry. + * If so, reversedBy will point to that Ledger Entry. + */ + isReversed: Scalars['Boolean']['output']; + /** The Ledger that this Ledger Entry is posted to. */ + ledger: Ledger; + /** The ID of the Ledger this Ledger Entry is posted to. */ + ledgerId: Scalars['ID']['output']; + /** Lines posted in this Ledger Entry. */ + lines: LedgerLinesConnection; + /** The parameters used to post this Ledger Entry. */ + parameters?: Maybe; + /** ISO-8601 timestamp this LedgerEntry posted to its Ledger. */ + posted: Scalars['DateTime']['output']; + /** The reversal history of this Ledger Entry. Each entry in this connection shares the same IK. */ + reversalHistory: LedgerEntriesConnection; + /** The position of this Ledger Entry in its reversalHistory. This is a one-indexed value, so the initial entry will have reversalPosition 1. */ + reversalPosition: Scalars['Int']['output']; + /** ISO-8601 timestamp of when this Ledger Entry was reversed. */ + reversedAt?: Maybe; + /** The Ledger Entry that reversed this Ledger Entry. */ + reversedBy?: Maybe; + /** The Ledger Entry that was reversed by this Ledger Entry. */ + reverses?: Maybe; + /** The set of tags attached to this Ledger Entry. */ + tags: Array; + /** The type of the Ledger Entry. */ + type?: Maybe; + /** The version of the Ledger Entry type used when it was posted. */ + typeVersion?: Maybe; + /** @deprecated Callers should not need to query or store this value. */ + workspaceId: Scalars['ID']['output']; +}; + +/** A set of pre-conditions and post-conditions that a Ledger Account must have satisfied. Each `LedgerEntryCondition` has at least one of `precondition` or `postcondition`. */ +export type LedgerEntryCondition = { + __typename?: 'LedgerEntryCondition'; + /** The Ledger Account that must satisfied the provided conditions. */ + account: LedgerAccount; + /** The currency of the balance associated with this `LedgerEntryCondition`. */ + currency: Currency; + /** The conditions that must be satisfied after the operation. */ + postcondition?: Maybe; + /** The conditions that must be satisfied prior to the operation. */ + precondition?: Maybe; +}; + +/** A set of pre-conditions and post-conditions that a Ledger Account balance must meet for an operation to succeed. You must specify at least one of `precondition` or `postcondition` for each condition. */ +export type LedgerEntryConditionInput = { + /** The Ledger Account that must satisfy the provided conditions. */ + account: LedgerAccountMatchInput; + /** For Ledger Accounts in the `multi` currency mode, you must specify the currency of the balance affected by the condition. You only need to specify this field for multi-currency accounts. */ + currency?: InputMaybe; + /** The conditions that must hold after the operation. */ + postcondition?: InputMaybe; + /** The conditions that must hold prior to the operation. */ + precondition?: InputMaybe; +}; + +/** Represents a data migration for a specific entry type in a Ledger. */ +export type LedgerEntryDataMigration = LedgerDataMigration & { + __typename?: 'LedgerEntryDataMigration'; + /** Current active migration info (null if migration is inactive). */ + currentMigration?: Maybe; + /** The entry type being migrated. */ + entryType: Scalars['SafeString']['output']; + /** The historical transitions of this migration. */ + history: LedgerDataMigrationHistoryConnection; + /** The ledger entries to be migrated. */ + ledgerEntries: LedgerEntriesConnection; + /** The status of the data migration. */ + status: LedgerDataMigrationStatus; + /** The version of the entry type being migrated. */ + typeVersion: Scalars['Int']['output']; +}; + + +/** Represents a data migration for a specific entry type in a Ledger. */ +export type LedgerEntryDataMigrationHistoryArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Represents a data migration for a specific entry type in a Ledger. */ +export type LedgerEntryDataMigrationLedgerEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +export type LedgerEntryDataMigrationConnection = { + __typename?: 'LedgerEntryDataMigrationConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +export type LedgerEntryDataMigrationsFilterSet = { + /** Filter by Ledger Entry type. */ + entryType?: InputMaybe; + /** Filter by the status of the data migration. */ + status?: InputMaybe; + /** Filter by Ledger Entry type version. */ + typeVersion?: InputMaybe; +}; + +export type LedgerEntryFilter = { + /** Result must be the specified Ledger Entry. */ + equalTo?: InputMaybe; + /** Result can be any of the specified Ledger Entries. Limited to 100 items maximum. */ + in?: InputMaybe>; +}; + +/** A group of Ledger Entries */ +export type LedgerEntryGroup = { + __typename?: 'LedgerEntryGroup'; + balances: LedgerEntryGroupBalanceConnection; + /** ISO-8601 timestamp this LedgerEntryGroup was created in Fragment. */ + created?: Maybe; + /** URL to the Fragment Dashboard for this Ledger Entry Group. */ + dashboardUrl: Scalars['String']['output']; + /** The key of this Ledger Entry Group. */ + key: Scalars['SafeString']['output']; + /** The Ledger that this Ledger Entry Group is within. */ + ledger: Ledger; + ledgerEntries: LedgerEntriesConnection; + /** The ID of the Ledger this Ledger Entry Group is within. */ + ledgerId: Scalars['ID']['output']; + /** The value associated with Ledger Entry Group. */ + value: Scalars['SafeString']['output']; +}; + + +/** A group of Ledger Entries */ +export type LedgerEntryGroupBalancesArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A group of Ledger Entries */ +export type LedgerEntryGroupLedgerEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** Represents the total effect of a Ledger Entry Group on a Ledger Account balance for a single currency. */ +export type LedgerEntryGroupBalance = { + __typename?: 'LedgerEntryGroupBalance'; + /** The Ledger Account whose balance is affected. */ + account: LedgerAccount; + /** The currency of the affected balance. */ + currency: Currency; + /** The total balance change for this Ledger Account and currency. */ + ownBalance: Scalars['Int96']['output']; +}; + + +/** Represents the total effect of a Ledger Entry Group on a Ledger Account balance for a single currency. */ +export type LedgerEntryGroupBalanceOwnBalanceArgs = { + consistencyMode?: InputMaybe; +}; + +/** A set of balance changes for a specific Ledger Entry Group. */ +export type LedgerEntryGroupBalanceConnection = { + __typename?: 'LedgerEntryGroupBalanceConnection'; + nodes: Array; + pageInfo: PageInfo; +}; + +/** Filter Ledger Entry Groups by their balance impact on a Ledger Account. If a group has a matching balance for the specified account, the group will be included in the results. */ +export type LedgerEntryGroupBalanceFilter = { + /** A Ledger Entry Group will be included in the result if it has a balance for the specified account. If 'account' is the only filter specified, then any non-null balance in any currency will match. */ + account: GroupBalanceAccountFilter; + /** A Ledger Entry Group will be included in the result if it has a balance for the specified account in the specified currency. If the 'ownBalance' filter is omitted then any non-null balance will match. */ + currency?: InputMaybe; + /** A Ledger Entry Group will be included in the result if it has a balance for the specified account that passes the specified value predicate. If the 'currency' filter is omitted then any balance in any currency that passes the predicate will match. If the 'currency' filter is included, the value predicate will only be evaluated against the specified currency. */ + ownBalance?: InputMaybe; +}; + +/** Optional filters for querying balances on a Ledger Entry Group. */ +export type LedgerEntryGroupBalanceFilterSet = { + /** Filter to a subset of accounts */ + account?: InputMaybe; + /** Filter to one or more currencies */ + currency?: InputMaybe; + /** Filter to only balances in a certain range */ + ownBalance?: InputMaybe; +}; + +export type LedgerEntryGroupInput = { + /** The key of this group. Can be up to 128 characters long. */ + key: Scalars['SafeString']['input']; + /** The value associated with this group's key. Can be up to 128 characters long. */ + value: Scalars['SafeString']['input']; +}; + +export type LedgerEntryGroupMatchInput = { + key: Scalars['SafeString']['input']; + ledger: LedgerMatchInput; + value: Scalars['SafeString']['input']; +}; + +/** A paginated list of Ledger Entry Groups */ +export type LedgerEntryGroupsConnection = { + __typename?: 'LedgerEntryGroupsConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +export type LedgerEntryGroupsFilterSet = { + /** Filter Ledger Entry Groups by their balance impact on a Ledger Account. If a group has a matching balance for the specified account, the group will be included in the results. */ + balance?: InputMaybe; + /** Use to filter Ledger Entry Groups by their created timestamp */ + created?: InputMaybe; + /** Use to filter Ledger Entry Groups by their key */ + key?: InputMaybe; + /** Use to filter Ledger Entry Groups by their value */ + value?: InputMaybe; +}; + +/** Ledger Entries are limited to 30 Ledger Lines. */ +export type LedgerEntryInput = { + /** Conditions that must be satisfied to post this Ledger Entry. The Ledger Entry will reject with a BadRequestError if any condition is not met. You can only add a condition on a Ledger Account containing a Line in this Ledger Entry. */ + conditions?: InputMaybe>; + /** If specified, will also be used as the description for LedgerLines unless they specify their own description. */ + description?: InputMaybe; + /** Adds this Ledger Entry to this set of Ledger Entry Groups */ + groups?: InputMaybe>; + /** The Ledger to which to post this Ledger Entry. Must be linked to a Schema that defines the provided Ledger Entry type. */ + ledger?: InputMaybe; + /** The Ledger Lines to create as part of this Ledger Entry. This cannot be used with Ledger Entries that have a 'type' i.e. Ledger Entries defined in the Schema. This can be useful during non-routine operations such as an incident. It is not recommended to use 'lines' during routine operations. */ + lines?: InputMaybe>; + /** Parameters to be included in a templated Ledger Entry. All provided parameters must be present in the typed Ledger Entry within the Schema linked to the provided Ledger. */ + parameters?: InputMaybe; + /** ISO 8601 timestamp to post this Ledger Entry e.g. "2021-01-01" or "2021-01-01T16:45:00Z". Will error out if supplied to reconcileTx or createOrder since the transaction timestamp will be used instead */ + posted?: InputMaybe; + /** A set of tags attached to this Ledger Entry. */ + tags?: InputMaybe>; + /** The type of the Ledger Entry. Must be defined in the Schema linked to the Ledger specified below. */ + type?: InputMaybe; + /** Experimental: This field is reserved for an upcoming feature and is not yet supported. */ + typeVersion?: InputMaybe; +}; + +/** Specify a Ledger Entry by using `id`. */ +export type LedgerEntryMatchInput = { + /** The FRAGMENT ID of the Ledger Entry */ + id?: InputMaybe; + /** The IK provided to the `addLedgerEntry` mutation or the `ik` field returned from a `reconcileTx` mutation. This is required if you have not provided `id`. */ + ik?: InputMaybe; + /** The FRAGMENT ID of the Ledger to which this Ledger Entry belongs. This is required if you have not provided `id`. */ + ledger?: InputMaybe; +}; + +/** Posting count statistics for a specific type and typeVersion of entry in a Ledger. */ +export type LedgerEntryStats = { + __typename?: 'LedgerEntryStats'; + /** The total number of entries of this type. */ + count: Scalars['Int96']['output']; + /** The ledger ID these stats are for. */ + ledgerId: Scalars['SafeString']['output']; + /** The net number of entries (count - reversalsCount). */ + netCount: Scalars['Int96']['output']; + /** The number of entries that are reversals. */ + reversalsCount: Scalars['Int96']['output']; + /** The schema key associated with these stats. */ + schemaKey: Scalars['SafeString']['output']; + /** The type of entry these stats are for. */ + type: Scalars['SafeString']['output']; + /** The version of the entry type these stats are for. */ + typeVersion: Scalars['Int']['output']; +}; + +/** A paginated list of Ledger Entry Stats */ +export type LedgerEntryStatsConnection = { + __typename?: 'LedgerEntryStatsConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +/** A tag attached to a Ledger Entry. */ +export type LedgerEntryTag = { + __typename?: 'LedgerEntryTag'; + /** The key of this tag. */ + key: Scalars['SafeString']['output']; + /** The value associated with this tag's key. */ + value: Scalars['SafeString']['output']; +}; + +export type LedgerEntryTagInput = { + /** The key of this tag. Can be up to 128 characters long. */ + key: Scalars['SafeString']['input']; + /** The value associated with this tag's key. Can be up to 128 characters long. */ + value: Scalars['SafeString']['input']; +}; + +export type LedgerLine = { + __typename?: 'LedgerLine'; + /** LedgerAccount that contains this line */ + account: LedgerAccount; + accountId: Scalars['ID']['output']; + /** How much this line's LedgerAccount's balance changed in integer cents (i.e. in USD 100 is 1 dollar, 100 cents) */ + amount: Scalars['Int96']['output']; + /** ISO-8601 timestamp this LedgerLine was created in Fragment */ + created?: Maybe; + /** Currency of this LedgerLine */ + currency?: Maybe; + /** Date this LedgerLine posted to its LedgerAccount e.g. "2021-01-01" */ + date?: Maybe; + /** Description of this LedgerLine */ + description?: Maybe; + /** ID in the external system of the payment or transfer that created the transaction linked to this LedgerLine */ + externalTransferId?: Maybe; + /** Whether the transaction linked to this LedgerLine was a payment or transfer */ + externalTransferType?: Maybe; + /** ID in the external system of the transaction linked to this LedgerLine */ + externalTxId?: Maybe; + /** + * Indicates whether this Ledger Line is hidden when listing Ledger Lines. + * Reversed and Reversal Ledger Lines are hidden by default because taken together they have no impact on a Ledger Account's balance + */ + hidden: Scalars['Boolean']['output']; + id: Scalars['ID']['output']; + /** + * Indicates whether this Ledger Line is a reversal of another Ledger Line. + * If so, reverses will point to that Ledger Line. + */ + isReversal: Scalars['Boolean']['output']; + /** + * Indicates whether this Ledger Line has been reversed by another Ledger Line. + * If so, reversedBy will point to that Ledger Line. + */ + isReversed: Scalars['Boolean']['output']; + key?: Maybe; + ledger: Ledger; + /** LedgerEntry that contains this line */ + ledgerEntry: LedgerEntry; + /** ID of the LedgerEntry that contains this line */ + ledgerEntryId: Scalars['ID']['output']; + /** Ledger that contains this line */ + ledgerId: Scalars['ID']['output']; + /** ID in the external system of destination or source bank account for an internal bank transfer. Only for internal bank transfers - see otherTxId */ + otherTxExternalAccountExternalId?: Maybe; + /** FRAGMENT ID of destination or source bank account. Only for internal bank transfers - see otherTxId */ + otherTxExternalAccountId?: Maybe; + /** ID in the external system of transaction in the destination or source bank account. Only for internal bank transfers - see otherTxId */ + otherTxExternalId?: Maybe; + /** FRAGMENT ID of the transaction in the destination account (if sending money from this account) or source account (if pulling money into this account). Only applicable if this line is linked to a transaction created through an internal transfer */ + otherTxId?: Maybe; + /** ISO-8601 timestamp this LedgerLine posted to its LedgerAccount */ + posted?: Maybe; + /** ISO-8601 timestamp of when this Ledger Line was reversed. */ + reversedAt?: Maybe; + /** The Ledger Line that reverses the balance changes of this Ledger Line. */ + reversedBy?: Maybe; + /** The Ledger Line whose balance changes are reversed by this Ledger Line. */ + reverses?: Maybe; + /** Tags attached to this Ledger Line. */ + tags: Array; + /** The transaction linked to this LedgerLine */ + tx?: Maybe; + /** Fragment ID of the transaction linked to this LedgerLine */ + txId?: Maybe; + /** credit or debit */ + type: TxType; + /** @deprecated Callers should not need to query or store this value. */ + workspaceId: Scalars['ID']['output']; +}; + + +export type LedgerLineAmountArgs = { + absolute?: InputMaybe; +}; + +export type LedgerLineInput = { + /** The LedgerAccount this line is being added to */ + account: LedgerAccountMatchInput; + /** A positive amount increases the balance of its LedgerAccount, a negative amount reduces the balance of its LedgerAccount */ + amount?: InputMaybe; + /** The currency the ledger line is in */ + currency?: InputMaybe; + /** If not specified the description from the parent LedgerEntryInput will be used */ + description?: InputMaybe; + /** Optional identifier for Ledger Line. You can filter lines by key using [LedgerLinesFilterSet](https://fragment.dev/api-reference/api-types#filter-types-ledgerlinesfilterset). */ + key?: InputMaybe; + /** A set of tags attached to this Ledger Line. */ + tags?: InputMaybe>; + /** Required for reconcileTx to specify the transaction being reconciled, you can specify either the FRAGMENT ID or external ID of the transaction */ + tx?: InputMaybe; +}; + +/** Specify a Ledger Line by using `id`. */ +export type LedgerLineMatchInput = { + /** The FRAGMENT ID of the ledger line */ + id: Scalars['ID']['input']; +}; + +/** A tag attached to a Ledger Line. */ +export type LedgerLineTag = { + __typename?: 'LedgerLineTag'; + /** The key of this tag. */ + key: Scalars['SafeString']['output']; + /** The value associated with this tag's key. */ + value: Scalars['SafeString']['output']; +}; + +/** A paginated list of Ledger Lines */ +export type LedgerLinesConnection = { + __typename?: 'LedgerLinesConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export enum LedgerLinesConsistencyMode { + Eventual = 'eventual', + Strong = 'strong' +} + +export type LedgerLinesFilterSet = { + /** Filter by the created timestamp of the Ledger Line. This is the wall-clock time when the Ledger Line was created. */ + created?: InputMaybe; + /** Filter by the currency of the Ledger Line. */ + currency?: InputMaybe; + /** Use this filter to filter Ledger Lines by their `posted` date. */ + date?: InputMaybe; + /** Use this to filter Ledger Lines that were posted to this Ledger Account, using `reverseLedgerEntry`. */ + isReversal?: InputMaybe; + /** Use this to filter Ledger Lines that have been reversed. */ + isReversed?: InputMaybe; + /** Use this to filter Ledger Lines by key. Ledger Line keys are defined in Schemas. */ + key?: InputMaybe; + /** Specify which Ledger Account to read lines from. Required when querying lines via `Ledger.lines` without a `path` filter. Not allowed when querying via `LedgerAccount.lines`. */ + ledgerAccount?: InputMaybe; + /** + * A filter that string matches the account path. Wildcards ('*') can be used to return lines across multiple accounts. + * To search for all instances of a a Ledger Account template, use the `matches` filter with an wildcard character in place of the template value e.g. `assets/user:*`. This returns lines from all instances of this template, interleaved by `posted` timestamp. + * To search for all descendant Ledger Accounts under a given path, use a trailing `/*` in the `matches` filter e.g. `assets/user:user-1>/*`. This returns lines from all descendants at any depth, but not lines from the parent account at `assets/user:user-1>`. + * To OR multiple `matches` patterns and get a single paginated list, use `matchesAny` — e.g. `matchesAny: ["assets/user:user-1/*", "assets/user:user-2/*"]` returns descendants of both prefixes interleaved by `posted` timestamp. + * Cannot be combined with `ledgerAccount` filter. Not allowed when querying via `LedgerAccount.lines`. You cannot use wildcards for both descendant and template instance matching in the same query. + */ + path?: InputMaybe; + /** Use this filter to filter Ledger Lines by their `posted` timestamp. */ + posted?: InputMaybe; + /** Use this filter to find hidden Ledger Lines. */ + showHidden?: InputMaybe; + /** Filter Ledger Lines by tag. Only matches lines that have the specified tags attached directly to them. */ + tag?: InputMaybe; + type?: InputMaybe; +}; + +/** Specify a Ledger by using `id` or `ik`. */ +export type LedgerMatchInput = { + /** The FRAGMENT ID of the Ledger */ + id?: InputMaybe; + /** The IK passed into the [createLedger](/api-reference/api-mutations#createledger) mutation. This is treated as a second unique identifier for this Ledger. */ + ik?: InputMaybe; +}; + +/** + * Represents a Schema being applied to a Ledger. + * It contains metadata about the Ledger, the Schema, and the status of the migration. + */ +export type LedgerMigration = { + __typename?: 'LedgerMigration'; + /** The Ledger that the migration is run on. */ + ledger: Ledger; + schemaVersion: SchemaVersion; + /** The status of the Ledger Migration. */ + status: LedgerMigrationStatus; +}; + +/** A paginated list of Ledger Migrations */ +export type LedgerMigrationConnection = { + __typename?: 'LedgerMigrationConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +/** The status of a ledger migration. */ +export enum LedgerMigrationStatus { + /** + * The Ledger Migration has been successfully completed. + * This is a terminal state. + */ + Completed = 'completed', + /** + * The Ledger Migration has failed. + * This can happen either due to an invalid schema or an internal error. + * This is a terminal state. + */ + Failed = 'failed', + /** The Ledger Migration has been queued. */ + Queued = 'queued', + /** + * The Ledger Migration has been skipped because a newer version is available. + * This is a terminal state. + */ + Skipped = 'skipped', + /** The Ledger Migration has been started. */ + Started = 'started' +} + +export type LedgerTypeFilter = { + equalTo?: InputMaybe; + /** Must match one of the values provided. Limited to 100 items maximum. */ + in?: InputMaybe>; +}; + +export enum LedgerTypes { + Double = 'double' +} + +/** A paginated list of Ledgers */ +export type LedgersConnection = { + __typename?: 'LedgersConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export type LedgersFilterSet = { + hasSchema?: InputMaybe; + type?: InputMaybe; +}; + +export type Link = { + /** ISO-8601 timestamp when the Link was created. */ + created: Scalars['String']['output']; + /** URL to the Fragment Dashboard for this Link. */ + dashboardUrl: Scalars['String']['output']; + /** A list of External Accounts associated with this Link. */ + externalAccounts: ExternalAccountsConnection; + /** FRAGMENT ID of the Link. */ + id: Scalars['ID']['output']; + /** Name of the Link as it appears in the Dashboard. */ + name: Scalars['String']['output']; +}; + +export type LinkMatchInput = { + id: Scalars['ID']['input']; +}; + +/** The type of Link an external account belongs to. */ +export enum LinkType { + /** A Custom Link */ + CustomLink = 'CustomLink', + /** An Increase Link */ + IncreaseLink = 'IncreaseLink', + /** A Stripe Link */ + StripeLink = 'StripeLink', + /** A Unit Link */ + UnitLink = 'UnitLink' +} + +/** A paginated list of Links */ +export type LinksConnection = { + __typename?: 'LinksConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +/** An object defining the input for migrating a Ledger Entry. */ +export type MigrateLedgerEntryInput = { + /** The Ledger Entry to migrate */ + id: Scalars['ID']['input']; + /** The Ledger Entry you want to migrate it to */ + newLedgerEntry: LedgerEntryInput; +}; + +export type MigrateLedgerEntryResponse = BadRequestError | InternalError | MigrateLedgerEntryResult; + +export type MigrateLedgerEntryResult = { + __typename?: 'MigrateLedgerEntryResult'; + /** Whether this migration was an IK replay or not */ + isIkReplay: Scalars['Boolean']['output']; + /** The new Ledger Entry posted as a result of the migration */ + newLedgerEntry: LedgerEntry; + /** The Ledger Entry that was migrated */ + reversedLedgerEntry: LedgerEntry; + /** The reversal Ledger Entry that was posted to reverse the Ledger Entry being migrated */ + reversingLedgerEntry: LedgerEntry; +}; + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type Mutation = { + __typename?: 'Mutation'; + _empty?: Maybe; + /** + * Batch version of [addLedgerEntry](http://localhost:3001/api-reference/ledger-mutations#addledgerentry). + * + * Adds a batch of Ledger Entries in one synchronous and atomic transaction. Either every entry is added or none are. + */ + addLedgerEntries: AddLedgerEntriesResponse; + /** Adds a Ledger Entry to a Ledger. This Ledger Entry cannot be into a Linked Ledger Account. For that, use [reconcileTx](https://fragment.dev/api-reference/api-mutations#reconciletx) */ + addLedgerEntry: AddLedgerEntryResponse; + /** Creates a custom currency. */ + createCustomCurrency: CreateCustomCurrencyResponse; + /** Custom Links let you integrate external systems that don't have native support. See [Custom Links](https://fragment.dev/guides/sync-payments#custom-link) */ + createCustomLink: CreateCustomLinkResponse; + /** Creates a Ledger. */ + createLedger: CreateLedgerResponse; + createLedgerAccount: CreateLedgerAccountResponse; + /** This API call is used to create Ledger Accounts. It is only used if you are not using a Schema. Unlike other mutations that take a single IK, 'createLedgerAccount' accepts an IK for each of the ledger accounts in the request payload. This is so you can recover in the case of a partial failure. One API call can create up to 200 Ledger Accounts, up to 10 levels deep. */ + createLedgerAccounts: CreateLedgerAccountsResponse; + /** + * EXPERIMENTAL — subject to change. + * + * Create a Payment. + */ + createPayment: CreatePaymentResponse; + /** Delete Txs on a Custom Link. Once deleted, a Tx will not show up in listing queries, but can be resolved by if you lookup by its Fragment ID. */ + deleteCustomTxs: DeleteCustomTxsResponse; + /** + * Delete a Ledger. + * + * After using the deleteLedger mutation you can re-use the ik in a new ledger after a 30 second wait. + */ + deleteLedger: DeleteLedgerResponse; + /** Delete a Schema */ + deleteSchema: DeleteSchemaResponse; + /** + * Migrate an existing Ledger Entry to a new type and typeVersion. + * + * Migrating a Ledger Entry will do the following: + * 1. Reverse the existing Ledger Entry + * 2. Post a new Ledger Entry with the new type, typeVersion, and parameters provided + */ + migrateLedgerEntry: MigrateLedgerEntryResponse; + /** This mutation is used to [reconcile](https://fragment.dev/guides/reconcile-payments#reconcile-a-tx) transactions from an external system into a Ledger Entry. This mutation does not require an idempotency key since a transaction can only be reconciled once per Linked Ledger Account. If you are reconciling a transfer between two Link Accounts which are both linked to the same Ledger, use a transit account in between to split the transfer into two `reconcileTx` calls. */ + reconcileTx: ReconcileTxResponse; + /** Reverses a Ledger Entry */ + reverseLedgerEntry: ReverseLedgerEntryResponse; + /** + * Stores a Schema in your workspace. If no Schema with the same key exists in your worksapce, a new Schema is created. + * Else, the Schema is updated, and every Ledger associated with it is migrated to the latest version. + */ + storeSchema: StoreSchemaResponse; + /** Once you've created a [Custom Link](https://fragment.dev/guides/sync-payments#custom-link), create accounts under it using this mutation. Each Custom Account is an immutable, single-entry view of all the transactions in the external account. You can sync up to 100 Custom Accounts in one API call. */ + syncCustomAccounts: SyncCustomAccountsResponse; + /** You can create transactions under a Custom Account in a [Custom Link](https://fragment.dev/guides/sync-payments#custom-link) using this mutation. Once you've imported transactions, you can use the reconcileTx mutation to add them to a Ledger via the Linked Ledger Account. You can sync up to 100 Custom Transactions in one API call. */ + syncCustomTxs: SyncCustomTxsResponse; + /** Updates a Ledger. Currently, you can change only the Ledger 'name'. */ + updateLedger: UpdateLedgerResponse; + /** Updates a ledger account. Only supports name right now. */ + updateLedgerAccount: UpdateLedgerAccountResponse; + /** Update a ledger entry */ + updateLedgerEntry: UpdateLedgerEntryResponse; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationAddLedgerEntriesArgs = { + entries: Array; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationAddLedgerEntryArgs = { + entry: LedgerEntryInput; + ik: Scalars['SafeString']['input']; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationCreateCustomCurrencyArgs = { + customCurrency: CreateCustomCurrencyInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationCreateCustomLinkArgs = { + ik: Scalars['SafeString']['input']; + name: Scalars['String']['input']; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationCreateLedgerArgs = { + ik: Scalars['SafeString']['input']; + ledger: CreateLedgerInput; + schema?: InputMaybe; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationCreateLedgerAccountArgs = { + ik: Scalars['SafeString']['input']; + ledger: LedgerMatchInput; + ledgerAccount: CreateLedgerAccountInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationCreateLedgerAccountsArgs = { + ledger: LedgerMatchInput; + ledgerAccounts: Array; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationCreatePaymentArgs = { + amount: Scalars['Int96']['input']; + ik: Scalars['SafeString']['input']; + ledger: LedgerMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationDeleteCustomTxsArgs = { + txs: Array; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationDeleteLedgerArgs = { + ledger: LedgerMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationDeleteSchemaArgs = { + schema: SchemaMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationMigrateLedgerEntryArgs = { + input: MigrateLedgerEntryInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationReconcileTxArgs = { + entry: LedgerEntryInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationReverseLedgerEntryArgs = { + id: Scalars['ID']['input']; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationStoreSchemaArgs = { + schema: SchemaInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationSyncCustomAccountsArgs = { + accounts: Array; + link: LinkMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationSyncCustomTxsArgs = { + link: LinkMatchInput; + txs: Array; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationUpdateLedgerArgs = { + ledger: LedgerMatchInput; + update: UpdateLedgerInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationUpdateLedgerAccountArgs = { + ledgerAccount: LedgerAccountMatchInput; + update: UpdateLedgerAccountInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationUpdateLedgerEntryArgs = { + ledgerEntry: LedgerEntryMatchInput; + update: UpdateLedgerEntryInput; +}; + +/** Equivalent to an HTTP 404 */ +export type NotFoundError = Error & { + __typename?: 'NotFoundError'; + /** The status code of error. For example, 'ledger_not_found'. */ + code: Scalars['String']['output']; + /** The error message */ + message: Scalars['String']['output']; + /** Whether or not the operation is retryable */ + retryable: Scalars['Boolean']['output']; +}; + +/** An object containing [pagination](https://fragment.dev/guides/query-data#basics-pagination) details. */ +export type PageInfo = { + __typename?: 'PageInfo'; + endCursor?: Maybe; + hasNextPage: Scalars['Boolean']['output']; + hasPreviousPage: Scalars['Boolean']['output']; + startCursor?: Maybe; +}; + +export type Payment = { + __typename?: 'Payment'; + /** The secret handed to the payments SDK to render the payment method capture. */ + clientSecret: Scalars['String']['output']; + /** The status of this Payment. */ + status: PaymentStatus; +}; + +/** + * EXPERIMENTAL — subject to change. + * + * Status of a Payment. + */ +export enum PaymentStatus { + Processing = 'processing', + RequiresConfirmation = 'requires_confirmation', + Settled = 'settled' +} + +/** + * Controls how lines are posted for a Ledger Entry. + * New entries created via the dashboard default to `net_amounts`. + * Existing entries without this field set are treated as `raw_lines`. + */ +export enum PostLinesAs { + /** Lines targeting the same account, currency, and tx are aggregated into a single line with the net amount. Lines that sum to zero are skipped. If all lines sum to zero, no lines are skipped. */ + NetAmounts = 'net_amounts', + /** Lines are posted as-is without aggregation. */ + RawLines = 'raw_lines', + /** Lines with a zero amount are skipped, but lines are not aggregated. If all lines have a zero amount, no lines are skipped. */ + SkipZeroLines = 'skip_zero_lines' +} + +/** + * The posted timestamp window for a clearing account, representing the earliest and latest + * posted timestamps across all currencies. + */ +export type PostedWindow = { + __typename?: 'PostedWindow'; + /** The earliest posted timestamp across all currencies for this clearing account. */ + earliest: Scalars['DateTime']['output']; + /** The latest posted timestamp across all currencies for this clearing account. */ + latest: Scalars['DateTime']['output']; +}; + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type Query = { + __typename?: 'Query'; + _empty?: Maybe; + /** Query Custom Currencies in the workspace */ + customCurrencies: CustomCurrenciesConnection; + /** Get External Account by Link and External ID or FRAGMENT ID. */ + externalAccount?: Maybe; + /** Get a Ledger by ID */ + ledger?: Maybe; + /** Get a Ledger Account by ID */ + ledgerAccount?: Maybe; + /** Get Ledger Entry by ID. */ + ledgerEntry?: Maybe; + /** Query a Ledger Entry Group given its Ledger, key, and value. */ + ledgerEntryGroup?: Maybe; + /** Get the reversal history of a Ledger Entry. */ + ledgerEntryHistory: LedgerEntriesConnection; + /** Get LedgerLine by ID */ + ledgerLine?: Maybe; + /** Query Ledgers in workspace. Ledgers are paginated and returned in reverse-chronological order by their created date. */ + ledgers: LedgersConnection; + /** Get a Link by ID. Returns a BadRequestError if the Link is not found. */ + link?: Maybe; + /** Get all links in a workspace */ + links: LinksConnection; + /** Get a Schema by key. */ + schema?: Maybe; + /** Retrieve all of the Schemas in the workspace. */ + schemas: SchemaConnection; + /** Get a Tx by ID */ + tx?: Maybe; + /** Get the current Workspace */ + workspace: Workspace; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryCustomCurrenciesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryExternalAccountArgs = { + externalAccount: ExternalAccountMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgerArgs = { + ledger: LedgerMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgerAccountArgs = { + ledgerAccount: LedgerAccountMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgerEntryArgs = { + ledgerEntry: LedgerEntryMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgerEntryGroupArgs = { + ledgerEntryGroup: LedgerEntryGroupMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgerEntryHistoryArgs = { + ledgerEntry: LedgerEntryMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgerLineArgs = { + ledgerLine: LedgerLineMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgersArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLinkArgs = { + link: LinkMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QuerySchemaArgs = { + schema: SchemaMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QuerySchemasArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryTxArgs = { + tx: TxMatchInput; +}; + +/** The consistency configuration of a Ledger Account's balance queries. If not provided as an argument to a balance query, the default behavior is to read eventually consistent balances. See [Configure consistency](https://fragment.dev/guides/configure-consistency). */ +export enum ReadBalanceConsistencyMode { + /** Balance queries will read eventually consistent balances. This is the default behavior if `ReadBalanceConsistencyMode` is not provided as an argument to the balance field. Both Ledger Accounts configured with strongly and eventually consistent balance updates support this enum. */ + Eventual = 'eventual', + /** Balance queries will read strongly consistent balances. This is only allowed if the Ledger Account's `ownBalanceUpdates` in its `consistencyConfig` is `strong`. */ + Strong = 'strong', + /** Balance queries will use the value from the Ledger Account's `ownBalanceUpdates` in its `consistencyConfig`. */ + UseAccount = 'use_account' +} + +export type ReconcileTxResponse = BadRequestError | InternalError | ReconcileTxResult; + +export type ReconcileTxResult = { + __typename?: 'ReconcileTxResult'; + /** The ledger entry that was posted */ + entry: LedgerEntry; + /** True if this request successfully completed before and the previous response is being returned */ + isIkReplay: Scalars['Boolean']['output']; + /** The ledger lines that were created in that entry */ + lines: Array; +}; + +export type ReverseLedgerEntryResponse = BadRequestError | InternalError | ReverseLedgerEntryResult; + +export type ReverseLedgerEntryResult = { + __typename?: 'ReverseLedgerEntryResult'; + /** Whether the reversal was an IK replay */ + isIkReplay: Scalars['Boolean']['output']; + /** The Ledger Entry that was reversed */ + reversedLedgerEntry: LedgerEntry; + /** The reversal Ledger Entry that was created */ + reversingLedgerEntry: LedgerEntry; +}; + +/** A simulated Ledger Entry posted as a part of a Scene. */ +export type SceneEntryInput = { + /** Any parameters to be used as inputs to this simulated Ledger Entry. */ + parameters?: InputMaybe; + /** The type of the simulated Ledger Entry. Must match one of the types provided in schema.ledgerEntries.types. */ + type: Scalars['SafeString']['input']; + /** The version of the Ledger Entry type. */ + typeVersion?: InputMaybe; +}; + +export type SceneEventInput = { + /** The simulated Ledger Entry. */ + entry: SceneEntryInput; + /** The type of the Scene Event. Currently, only entries are supported. */ + eventType: SceneEventType; +}; + +export enum SceneEventType { + Entry = 'entry' +} + +export type SceneInput = { + /** A list of simulated ledger entries that make up the Scene. */ + events: Array; + /** The human-readable name of the Scene. */ + name: Scalars['String']['input']; +}; + +export type Schema = { + __typename?: 'Schema'; + /** + * The identifier for a Schema. + * `key` is unique to a Workspace. + */ + key: Scalars['SafeString']['output']; + /** The paginated list of ledgers the Schema has been applied to. */ + ledgers: LedgersConnection; + /** The name of a Schema. It defaults to the `key` if not provided in your SchemaInput. */ + name: Scalars['String']['output']; + /** The metadata for a specific SchemaVersion. */ + version: SchemaVersion; + /** A paginated list of SchemaVersions. */ + versions: SchemaVersionConnection; +}; + + +export type SchemaLedgersArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +export type SchemaVersionArgs = { + version?: InputMaybe; +}; + + +export type SchemaVersionsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** + * A condition that must be met on a Ledger Account balance. The condition can be + * either a `precondition` or `postcondition`. + */ +export type SchemaConditionInput = { + /** A condition on the `ownBalance` of the Ledger Account. */ + ownBalance?: InputMaybe; + /** A condition on the `totalBalance` of the Ledger Account. */ + totalBalance?: InputMaybe; +}; + +/** A paginated list of Schemas in a Workspace. */ +export type SchemaConnection = { + __typename?: 'SchemaConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +/** + * The consistency configuration for entities created within Ledgers created by this Schema. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ +export type SchemaConsistencyConfigInput = { + /** + * The consistency mode for the Ledger Entries list query within Ledgers created by this Schema. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + entries?: InputMaybe; +}; + +/** + * The consistency modes available for entities created within this Schema. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ +export enum SchemaConsistencyMode { + /** Eventually consistent entity updates */ + Eventual = 'eventual', + /** Strongly consistent entity updates */ + Strong = 'strong' +} + +/** + * Matches a Currency. Can be a built-in [CurrencyCode](https://fragment.dev/api-reference/api-types#scalars-and-enums-currencycode), custom Currency, or a parameterized string. + * If you supply a parameterized string, you must pass in a valid CurrencyCode as a parameter when posting a Ledger Entry. + */ +export type SchemaCurrencyMatchInput = { + /** The currency code. This must either be a [CurrencyCode](https://fragment.dev/api-reference/api-types#scalars-and-enums-currencycode) or a parameterized string that resolves to a CurrencyCode . */ + code: Scalars['ParameterizedString']['input']; + /** The ID for a custom currency. This is specified when creating the custom currency using the [createCustomCurrency](https://fragment.dev/api-reference/api-mutations#createcustomcurrency) mutation. */ + customCurrencyId?: InputMaybe; +}; + +export type SchemaExternalAccountMatchInput = { + /** The External systems's ID of the account */ + externalId?: InputMaybe; + /** The FRAGMENT ID of the external account */ + id?: InputMaybe; + /** The FRAGMENT ID of the link */ + linkId?: InputMaybe; + /** The type of Link this external account belongs to. Must be one of: IncreaseLink, UnitLink, CustomLink, or StripeLink. */ + linkType?: InputMaybe; +}; + +/** Input to the API for creating a Schema. */ +export type SchemaInput = { + /** The Chart of Accounts for the Schema. */ + chartOfAccounts: ChartOfAccountsInput; + /** The consistency configuration for this Schema. */ + consistencyConfig?: InputMaybe; + /** Any groups associated with this Schema. */ + groups?: InputMaybe>; + /** The key of the Schema. This is a stable, unique identifier for the Schema. Uniqueness is enforced at the Workspace level. */ + key: Scalars['SafeString']['input']; + /** The Ledger Entries to add to the Schema. */ + ledgerEntries?: InputMaybe; + /** The human-readable name of the Schema. */ + name?: InputMaybe; + /** Any scenes associated with this Schema. */ + scenes?: InputMaybe>; +}; + +/** A condition that must be met on a field. */ +export type SchemaInt96ConditionInput = { + /** Amount must be exactly equal to this value. You may not specify this alongside `gte` or `lte`. */ + eq?: InputMaybe; + /** Amount must be greater than or equal to this value. */ + gte?: InputMaybe; + /** Amount must be less than or equal to this value. */ + lte?: InputMaybe; +}; + +/** + * Models a Ledger Account in a Schema. + * Upon successfully storing a [Schema](https://fragment.dev/api-reference/api-types#core-types-schema), a [LedgerAccount](https://fragment.dev/api-reference/api-types#core-types-ledgeraccount) will be created for + * each corresponding non-templated `SchemaLedgerAccountInput` in your Chart of Accounts. + */ +export type SchemaLedgerAccountInput = { + /** Ledger Accounts to create as children of this Ledger Account. Ledger Accounts may be nested up to a maximum depth of 10. */ + children?: InputMaybe>; + /** + * EXPERIMENTAL: Whether or not this Ledger Account is a Clearing Account. + * Clearing Accounts have balances that should tend to zero. They are used to track in-progress workflows and payments. + */ + clearing?: InputMaybe; + /** The consistency configuration for this ledger account. See [Configure consistency](https://fragment.dev/guides/configure-consistency). */ + consistencyConfig?: InputMaybe; + /** + * The currency of this Ledger Account. If this is not set, and `currencyMode` is + * not set to `multi`, it is derived from the Chart of Accounts' default. + */ + currency?: InputMaybe; + /** If set to `multi`, creates a multi-currency Ledger Account. If set to `single`, creates a single-currency Ledger Account. */ + currencyMode?: InputMaybe; + /** + * The key of this Ledger Account. Keys are used to formulate the unique path of the Ledger Account in your Chart of Accounts. + * Siblings must have unique keys. + */ + key: Scalars['SafeString']['input']; + /** + * The External Account to link to this Ledger Account. + * It must be provided of `linked` is true. + */ + linkedAccount?: InputMaybe; + /** The human-readable name of this Ledger Account. */ + name?: InputMaybe; + /** EXPERIMENTAL: Marks this as a Payment Account. */ + payment?: InputMaybe; + /** The status of this Ledger Account. Defaults to active. */ + status?: InputMaybe; + /** Whether or not this Ledger Account should be templated. */ + template?: InputMaybe; + /** The type of ledger account to create. Required if this is a top-level Ledger Account. If not provided, the type will be inferred from the parent. */ + type?: InputMaybe; +}; + +/** Matches a Ledger Account in a Schema. */ +export type SchemaLedgerAccountMatchInput = { + /** + * The unique path of the Ledger Account in the Schema. + * This is a slash-delimited string containing the keys of a Ledger Account and all its direct ancestors. + * ex: expense-root/subscriptions/netflix + * For Templated Ledger Accounts, you must supply a parameter in the path that will be used to name an instance of the template. + * ex: `"expense-root/subscriptions/vendor:{{vendor_name}}"` + */ + path: Scalars['ParameterizedString']['input']; +}; + +/** The status of a Ledger Account. */ +export enum SchemaLedgerAccountStatus { + /** The Ledger Account is active. */ + Active = 'active', + /** The Ledger Account is archived. */ + Archived = 'archived', + /** The Ledger Account is disabled. */ + Disabled = 'disabled' +} + +/** The Ledger Entries in your Schema. */ +export type SchemaLedgerEntriesInput = { + /** A list of Ledger Entry definitions. */ + types: Array; +}; + +/** A condition that must be met on a Ledger Account when a Ledger Entry is posted. */ +export type SchemaLedgerEntryConditionInput = { + /** The Ledger Account to apply the condition to. */ + account: SchemaLedgerAccountMatchInput; + /** + * The currency of the balance to apply the condition to. Required if the Ledger Account matched is a multi-currency Ledger Account. + * Otherwise, this field is defaults to the Ledger Account's currency. + */ + currency?: InputMaybe; + /** A `postcondition` must be met after the Ledger Entry updates are applied. */ + postcondition?: InputMaybe; + /** A `precondition` must be met before any Ledger Entry updates are applied. */ + precondition?: InputMaybe; + /** + * Repeated expansion configuration. When set, this condition is expanded at runtime for each element + * in the array parameter named by the key. + */ + repeated?: InputMaybe; +}; + +/** A Ledger Entry Group associated with a Ledger Entry type. */ +export type SchemaLedgerEntryGroupInput = { + /** The key for this Ledger Entry Group. */ + key: Scalars['SafeString']['input']; + /** The value associated with this Ledger Entry Group. */ + value: Scalars['ParameterizedString']['input']; +}; + +/** A Ledger Entry in a Schema. All Ledger Entries defined in a Schema must have a unique `type`. */ +export type SchemaLedgerEntryInput = { + /** Conditions that must be satisfied to post this Ledger Entry. The Ledger Entry will reject with a BadRequestError if any condition is not met. You can only add a condition on a Ledger Account containing a Line in this Ledger Entry. */ + conditions?: InputMaybe>; + /** Human-readable description of the Ledger Entry. */ + description?: InputMaybe; + /** Ledger Entries posted with this type will be in these Ledger Entry Groups. */ + groups?: InputMaybe>; + /** + * The Ledger Lines in the Ledger Entry. + * If provided, when posting a Typed Entry, a [LedgerEntry](https://fragment.dev/api-reference/api-types#core-types-ledgerline) will be posted containing [LedgerLines](https://fragment.dev/api-reference/api-types#core-types-ledgerline) corresponding + * to the values you provide here. If your lines contain parameters, you must supply values for those parameters that balance out the Ledger Entry. If not provided, lines will be required when posting a Typed Entry. + */ + lines?: InputMaybe>; + /** Fixed partial set of parameters to be included in a templated Ledger Entry. */ + parameters?: InputMaybe; + /** Controls how lines are posted. When set to `net_amounts`, all lines targeting the same account, currency, and tx are aggregated into a single line with the net amount, and lines that sum to zero are skipped. When set to `skip_zero_lines`, lines with a zero amount are skipped but not aggregated. In both modes, if all lines are zero, no lines are skipped. When set to `raw_lines`, lines are posted as-is without aggregation. New entries created via the dashboard default to `net_amounts`. Existing entries without this field set are treated as `raw_lines`. */ + postLinesAs?: InputMaybe; + /** The status of this Ledger Entry. Defaults to active. */ + status?: InputMaybe; + /** Ledger Entries posted with this type will be associated with these tags. */ + tags?: InputMaybe>; + /** + * The type of this Ledger Entry. This is a stable, unique identifier for this entry. Uniqueness is enforced at the Schema level. + * You can filter on this field when querying for Ledger Entries. See the docs on [LedgerEntryFilterSet](https://fragment.dev/api-reference/api-types#filter-types-ledgerentriesfilterset) + */ + type: Scalars['SafeString']['input']; + /** The version of the Ledger Entry type. */ + typeVersion?: InputMaybe; +}; + +/** The status of a Ledger Entry. */ +export enum SchemaLedgerEntryStatus { + /** The Ledger Entry is active. */ + Active = 'active', + /** The Ledger Entry is archived. */ + Archived = 'archived', + /** The Ledger Entry is disabled. */ + Disabled = 'disabled' +} + +/** A tag associated with a Ledger Entry type. */ +export type SchemaLedgerEntryTagInput = { + /** The key for this tag. */ + key: Scalars['SafeString']['input']; + /** The value associated with the given key for this tag. */ + value: Scalars['ParameterizedString']['input']; +}; + +/** A Ledger Line in a Ledger Entry. */ +export type SchemaLedgerLineInput = { + /** + * The Ledger Account this Ledger Line will be posted to. + * It supports parameters in its attributes via handlebars syntax. + */ + account: SchemaLedgerAccountMatchInput; + /** The amount of the Ledger Line. It supports parameters via the handlebars syntax and addition (+) and subtraction (-). */ + amount?: InputMaybe; + /** + * The currency of the Ledger Line. This is required if the Ledger Account has currencyMode multi. + * It supports parameters in its attributes via handlebars syntax. + */ + currency?: InputMaybe; + /** Human-readable description of the line. */ + description?: InputMaybe; + /** The key for the Ledger Line. Ledger Line keys must be unique within a Ledger Entry. Key can be filtered on as part of the LedgerLinesFilterSet. */ + key: Scalars['SafeString']['input']; + /** + * Repeated expansion configuration. When set, this line is expanded at runtime for each element + * in the array parameter named by the key. + */ + repeated?: InputMaybe; + /** Tags to attach to this Ledger Line. Supports parameterized values via handlebars syntax. */ + tags?: InputMaybe>; + /** + * The external transaction to reconcile. + * This field is required if the Ledger Account being posted to is a Linked Ledger Account. Otherwise, this field is disallowed. + * It supports parameters in its attributes via handlebars syntax. + * + * See the docs on [reconciling payments](https://fragment.dev/guides/reconcile-payments). + */ + tx?: InputMaybe; +}; + +/** An object used to retrieve a Schema. */ +export type SchemaMatchInput = { + /** + * The key to retrieve a Schema by. + * `key` is unique to a Workspace. + */ + key: Scalars['SafeString']['input']; + /** Optional parameter to specify version of requested Schema. If not provided, it defaults to 0, representing the latest available version for the provided Schema key. */ + version?: InputMaybe; +}; + +/** EXPERIMENTAL: Marks a Ledger Account as a Payment Account. */ +export type SchemaPaymentInput = { + penguin: Scalars['Boolean']['input']; +}; + +/** + * Configuration for repeated expansion of a line or condition. The key names a client-supplied + * array parameter whose elements each generate one copy of the line or condition at runtime. + */ +export type SchemaRepeatedConfigInput = { + /** The key of the array parameter whose elements expand this line or condition. */ + key: Scalars['SafeString']['input']; +}; + +/** + * Matches a transaction at an external system. + * This is used to specify the transaction being reconciled into a Linked Ledger Account + */ +export type SchemaTxMatchInput = { + /** The external system's ID for the transaction. */ + externalId?: InputMaybe; + /** The FRAGMENT ID for the transaction. */ + id?: InputMaybe; +}; + +/** + * An instance of a Schema stored in a Workspace. + * A new SchemaVersion is created each time a Schema is stored. + * It stores the Chart of Accounts and list of Ledger Entries as well as a history of its Ledger migrations. + */ +export type SchemaVersion = { + __typename?: 'SchemaVersion'; + created: Scalars['DateTime']['output']; + json: Scalars['JSON']['output']; + migrations: LedgerMigrationConnection; + /** The version of the schema. */ + version: Scalars['Int']['output']; +}; + +/** A paginated list of SchemaVersions for a given Schema. */ +export type SchemaVersionConnection = { + __typename?: 'SchemaVersionConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +/** Returned by the [storeSchema](https://fragment.dev/api-reference/api-mutations#storeschema) mutation. */ +export type StoreSchemaResponse = BadRequestError | InternalError | StoreSchemaResult; + +/** `StoreSchemaResult` represents a successful execution of `storeSchema`. */ +export type StoreSchemaResult = { + __typename?: 'StoreSchemaResult'; + /** The Schema that was stored as a result of calling `storeSchema`. */ + schema: Schema; +}; + +export type StringFilter = { + equalTo?: InputMaybe; + /** Must match one of the values provided. Limited to 100 items maximum. */ + in?: InputMaybe>; + /** Must not equal this string value */ + notEqualTo?: InputMaybe; + /** Must not match any of the values provided. Limited to 100 items maximum. */ + notIn?: InputMaybe>; +}; + +export type StringMatchFilter = { + /** Must contain the provided pattern somewhere within the string. For example, 'contains: hat' will match 'hat', 'chat', and 'hate'. */ + contains?: InputMaybe; + /** Must exactly equal the provided value */ + equalTo?: InputMaybe; + /** Must exactly equal one of the provided values. Limited to 100 items maximum. */ + in?: InputMaybe>; + /** Must match the provided pattern. Wildcards ("*") will match any substring */ + matches?: InputMaybe; + /** Must match any one of the provided `matches` patterns, OR-ed together — results are returned as a single paginated list. Each entry uses the same wildcard grammar as `matches`. Cannot be combined with `matches`. Limited to 100 entries. */ + matchesAny?: InputMaybe>; +}; + +export enum StripeEnv { + Livemode = 'livemode', + Testmode = 'testmode' +} + +export type StripeLink = Link & { + __typename?: 'StripeLink'; + /** ISO-8601 timestamp when the Link was created. */ + created: Scalars['String']['output']; + /** URL to the Fragment Dashboard for this Link. */ + dashboardUrl: Scalars['String']['output']; + /** A list of External Accounts associated with this Link. */ + externalAccounts: ExternalAccountsConnection; + /** FRAGMENT ID of the Custom Link. */ + id: Scalars['ID']['output']; + /** Name of the Link as it appears in the Fragment Dashboard. */ + name: Scalars['String']['output']; + /** The environment of the Stripe Link, either testmode or livemode. */ + stripeEnv: StripeEnv; +}; + +export type SyncCustomAccountsResponse = BadRequestError | InternalError | SyncCustomAccountsResult; + +export type SyncCustomAccountsResult = { + __typename?: 'SyncCustomAccountsResult'; + /** The external accounts that were synced. */ + accounts: Array; +}; + +export type SyncCustomTxsResponse = BadRequestError | InternalError | SyncCustomTxsResult; + +export type SyncCustomTxsResult = { + __typename?: 'SyncCustomTxsResult'; + txs: Array; +}; + +/** Filters a result set based on the tags it contains. */ +export type TagFilter = { + /** Matches entries that have ALL of the specified tags. The key and value are both matched exactly. Limited to 10 items maximum. */ + all?: InputMaybe>; + /** Matches tag values based on the existence of the provided string within the tag value. The key is matched exactly. */ + contains?: InputMaybe; + /** Matches tags based on the exact value provided. The key and value are both matched exactly. */ + equalTo?: InputMaybe; + /** Matches tags based on a list of possible tag matches. The key and value are both matched exactly. Limited to 100 items maximum. */ + in?: InputMaybe>; + /** Matches tags where the key exactly equals the provided value. */ + keyEqualTo?: InputMaybe; + /** Matches tags where the key matches any of the provided values. Limited to 100 items maximum. */ + keyIn?: InputMaybe>; + /** Matches tags that do not equal the provided value. The key and value are both matched exactly. */ + notEqualTo?: InputMaybe; + /** Matches tags that do not match any of the provided values. The key and value are both matched exactly. Limited to 100 items maximum. */ + notIn?: InputMaybe>; + /** Matches tags where the key does not equal the provided value. */ + notKeyEqualTo?: InputMaybe; + /** Matches tags where the key does not match any of the provided values. Limited to 100 items maximum. */ + notKeyIn?: InputMaybe>; +}; + +/** Specifies a single tag that an entity is expected to have. You must specify both the key and the value. */ +export type TagMatchInput = { + /** The key of this tag. */ + key: Scalars['SafeString']['input']; + /** The value associated with this tag's key. */ + value: Scalars['SafeString']['input']; +}; + +export type Tx = { + __typename?: 'Tx'; + /** FRAGMENT ID of this transaction's external account */ + accountId: Scalars['ID']['output']; + /** Integer amount in cents. Positive indicates money entering the external account, negative indicates money leaving */ + amount: Scalars['Int96']['output']; + /** Currency of this Tx */ + currency?: Maybe; + /** Date this Tx posted to the external account */ + date: Scalars['Date']['output']; + /** ISO-8601 timestamp when this Tx was deleted */ + deletedAt?: Maybe; + /** Description at the external account */ + description: Scalars['String']['output']; + /** The External Account that this transaction belongs to. */ + externalAccount: ExternalAccount; + /** ID in the external system of this transaction's external account */ + externalAccountId: Scalars['ID']['output']; + /** ID of this transaction in the external system */ + externalId: Scalars['ID']['output']; + /** FRAGMENT ID of this Tx. If you delete a Tx via deleteCustomTxs, it will not show up in listing queries, but can be resolved by if you lookup by its Fragment ID. If you resync a Tx with the same externalId, its Fragment ID will be different than the previous Tx. */ + id: Scalars['ID']['output']; + /** Whether this Tx has been deleted via deleteCustomTxs */ + isDeleted: Scalars['Boolean']['output']; + /** Returns ledger entries that are linked to this transaction. You can link the same external account to multiple ledgers, so there could be multiple entries associated with one transaction - one for each linked ledger account this transaction has been reconciled with */ + ledgerEntries: LedgerEntriesConnection; + /** Same as ledgerEntries, but returns an array of IDs instead */ + ledgerEntryIds?: Maybe>; + /** Same as ledgerLines, but returns an array of IDs instead */ + ledgerLineIds?: Maybe>; + /** Returns ledger lines that are linked to this transaction. You can link the same external account to multiple ledgers, so there could be multipe lines associated with one transaction - one for each linked ledger account this transaction has been reconciled with */ + ledgerLines: LedgerLinesConnection; + /** This transaction's Link. */ + link: Link; + /** FRAGMENT ID of this transaction's Link */ + linkId: Scalars['ID']['output']; + /** ISO-8601 timestamp when this Tx posted to the external account */ + posted: Scalars['DateTime']['output']; + /** When a Tx is deleted and a new Tx is synced with the same externalId, its sequence will be higher than the previous Tx. You can use this to distinguish different instances of Txs that have the same externalId. */ + sequence: Scalars['Int']['output']; + /** @deprecated Callers should not need to query or store this value. */ + workspaceId: Scalars['ID']['output']; +}; + +/** Specify a Tx by using `id` or `externalId`, the Link it belongs to by `linkId`, and the External Account it is a part of by `accountId` or `externalAccountId`. */ +export type TxMatchInput = { + /** The FRAGMENT ID of the external account */ + accountId?: InputMaybe; + /** The external system's ID for the account */ + externalAccountId?: InputMaybe; + /** The external system's ID for the transaction */ + externalId?: InputMaybe; + /** The FRAGMENT ID of the transaction */ + id?: InputMaybe; + /** The FRAGMENT ID of the link */ + linkId?: InputMaybe; +}; + +export enum TxType { + Credit = 'credit', + Debit = 'debit' +} + +export type TxTypeFilter = { + equalTo?: InputMaybe; + /** Must match one of the values provided. Limited to 100 items maximum. */ + in?: InputMaybe>; +}; + +/** A paginated list of Txs */ +export type TxsConnection = { + __typename?: 'TxsConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export enum UnitEnv { + Production = 'production', + Sandbox = 'sandbox' +} + +export type UnitLink = Link & { + __typename?: 'UnitLink'; + /** ISO-8601 timestamp when the Link was created. */ + created: Scalars['String']['output']; + /** URL to the Fragment Dashboard for this Link. */ + dashboardUrl: Scalars['String']['output']; + /** A list of External Accounts associated with this Link. */ + externalAccounts: ExternalAccountsConnection; + /** FRAGMENT ID of the Unit Link. */ + id: Scalars['ID']['output']; + /** Name of the Link as it appears in the Dashboard. */ + name: Scalars['String']['output']; + /** The environment of the Unit Link, either sandbox or production. */ + unitEnv: UnitEnv; +}; + +export type UpdateLedgerAccountInput = { + /** The consistency configuration for this ledger account. This defines how updates to this ledger account's balance are handled. */ + consistencyConfig?: InputMaybe; + /** The name to update the ledger account to */ + name?: InputMaybe; +}; + +export type UpdateLedgerAccountResponse = BadRequestError | InternalError | UpdateLedgerAccountResult; + +export type UpdateLedgerAccountResult = { + __typename?: 'UpdateLedgerAccountResult'; + /** The ledger account that was updated */ + ledgerAccount: LedgerAccount; +}; + +export type UpdateLedgerEntryInput = { + /** The list of Groups to add to this Ledger Entry. */ + groups?: InputMaybe>; + /** The list of Tags to add and/or update on this Ledger Entry. */ + tags?: InputMaybe>; + /** The list of Tags to remove from this Ledger Entry. */ + tagsToRemove?: InputMaybe>; +}; + +export type UpdateLedgerEntryResponse = BadRequestError | InternalError | UpdateLedgerEntryResult; + +export type UpdateLedgerEntryResult = { + __typename?: 'UpdateLedgerEntryResult'; + /** The Ledger Entry that was updated. */ + entry: LedgerEntry; +}; + +export type UpdateLedgerInput = { + /** The new Ledger name. */ + name?: InputMaybe; +}; + +export type UpdateLedgerResponse = BadRequestError | InternalError | UpdateLedgerResult; + +export type UpdateLedgerResult = { + __typename?: 'UpdateLedgerResult'; + /** The updated Ledger. */ + ledger: Ledger; +}; + +export type Workspace = { + __typename?: 'Workspace'; + /** The ID of the Workspace */ + id: Scalars['String']['output']; + /** The name of the Workspace */ + name: Scalars['String']['output']; +}; + +export type PostOrderPlacedMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + posted?: InputMaybe; + ledgerIk: Scalars['SafeString']['input']; + user_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + order_cost: Scalars['String']['input']; + currency: Scalars['String']['input']; + platform_fee: Scalars['String']['input']; + driver_fee: Scalars['String']['input']; + restaurant_id: Scalars['String']['input']; + driver_id: Scalars['String']['input']; +}>; + + +export type PostOrderPlacedMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult', isIkReplay: boolean, entry: { __typename?: 'LedgerEntry', ik: string, id: string, created: string, type?: string | null, posted: string, description?: string | null, ledger: { __typename?: 'Ledger', ik: string }, tags: Array<{ __typename?: 'LedgerEntryTag', key: string, value: string }>, groups: Array<{ __typename?: 'LedgerEntryGroup', key: string, value: string }> }, lines: Array<{ __typename?: 'LedgerLine', amount: string, key?: string | null, account: { __typename?: 'LedgerAccount', path: string }, currency?: { __typename?: 'Currency', code: CurrencyCode, customCurrencyId?: string | null } | null }> } | { __typename: 'BadRequestError', code: string, message: string, retryable: boolean } | { __typename: 'InternalError', code: string, message: string, retryable: boolean } }; + +export type PostOrderPlaced_V2MutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + posted?: InputMaybe; + ledgerIk: Scalars['SafeString']['input']; + user_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + order_cost: Scalars['String']['input']; + currency: Scalars['String']['input']; + platform_fee: Scalars['String']['input']; + service_fee: Scalars['String']['input']; + driver_fee: Scalars['String']['input']; + restaurant_id: Scalars['String']['input']; + driver_id: Scalars['String']['input']; +}>; + + +export type PostOrderPlaced_V2Mutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult', isIkReplay: boolean, entry: { __typename?: 'LedgerEntry', ik: string, id: string, created: string, type?: string | null, posted: string, description?: string | null, ledger: { __typename?: 'Ledger', ik: string }, tags: Array<{ __typename?: 'LedgerEntryTag', key: string, value: string }>, groups: Array<{ __typename?: 'LedgerEntryGroup', key: string, value: string }> }, lines: Array<{ __typename?: 'LedgerLine', amount: string, key?: string | null, account: { __typename?: 'LedgerAccount', path: string }, currency?: { __typename?: 'Currency', code: CurrencyCode, customCurrencyId?: string | null } | null }> } | { __typename: 'BadRequestError', code: string, message: string, retryable: boolean } | { __typename: 'InternalError', code: string, message: string, retryable: boolean } }; + +export type PostCardSettleMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + posted?: InputMaybe; + ledgerIk: Scalars['SafeString']['input']; + user_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; +}>; + + +export type PostCardSettleMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult', isIkReplay: boolean, entry: { __typename?: 'LedgerEntry', ik: string, id: string, created: string, type?: string | null, posted: string, description?: string | null, ledger: { __typename?: 'Ledger', ik: string }, tags: Array<{ __typename?: 'LedgerEntryTag', key: string, value: string }>, groups: Array<{ __typename?: 'LedgerEntryGroup', key: string, value: string }> }, lines: Array<{ __typename?: 'LedgerLine', amount: string, key?: string | null, account: { __typename?: 'LedgerAccount', path: string }, currency?: { __typename?: 'Currency', code: CurrencyCode, customCurrencyId?: string | null } | null }> } | { __typename: 'BadRequestError', code: string, message: string, retryable: boolean } | { __typename: 'InternalError', code: string, message: string, retryable: boolean } }; + +export type PostRestaurantPayoutInitiateMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + posted?: InputMaybe; + ledgerIk: Scalars['SafeString']['input']; + restaurant_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; + payout_id: Scalars['String']['input']; +}>; + + +export type PostRestaurantPayoutInitiateMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult', isIkReplay: boolean, entry: { __typename?: 'LedgerEntry', ik: string, id: string, created: string, type?: string | null, posted: string, description?: string | null, ledger: { __typename?: 'Ledger', ik: string }, tags: Array<{ __typename?: 'LedgerEntryTag', key: string, value: string }>, groups: Array<{ __typename?: 'LedgerEntryGroup', key: string, value: string }> }, lines: Array<{ __typename?: 'LedgerLine', amount: string, key?: string | null, account: { __typename?: 'LedgerAccount', path: string }, currency?: { __typename?: 'Currency', code: CurrencyCode, customCurrencyId?: string | null } | null }> } | { __typename: 'BadRequestError', code: string, message: string, retryable: boolean } | { __typename: 'InternalError', code: string, message: string, retryable: boolean } }; + +export type PostRestaurantPayoutSettleMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + posted?: InputMaybe; + ledgerIk: Scalars['SafeString']['input']; + restaurant_id: Scalars['String']['input']; + payout_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; +}>; + + +export type PostRestaurantPayoutSettleMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult', isIkReplay: boolean, entry: { __typename?: 'LedgerEntry', ik: string, id: string, created: string, type?: string | null, posted: string, description?: string | null, ledger: { __typename?: 'Ledger', ik: string }, tags: Array<{ __typename?: 'LedgerEntryTag', key: string, value: string }>, groups: Array<{ __typename?: 'LedgerEntryGroup', key: string, value: string }> }, lines: Array<{ __typename?: 'LedgerLine', amount: string, key?: string | null, account: { __typename?: 'LedgerAccount', path: string }, currency?: { __typename?: 'Currency', code: CurrencyCode, customCurrencyId?: string | null } | null }> } | { __typename: 'BadRequestError', code: string, message: string, retryable: boolean } | { __typename: 'InternalError', code: string, message: string, retryable: boolean } }; + +export type PostDriverPayoutInitiateMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + posted?: InputMaybe; + ledgerIk: Scalars['SafeString']['input']; + driver_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; + payout_id: Scalars['String']['input']; +}>; + + +export type PostDriverPayoutInitiateMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult', isIkReplay: boolean, entry: { __typename?: 'LedgerEntry', ik: string, id: string, created: string, type?: string | null, posted: string, description?: string | null, ledger: { __typename?: 'Ledger', ik: string }, tags: Array<{ __typename?: 'LedgerEntryTag', key: string, value: string }>, groups: Array<{ __typename?: 'LedgerEntryGroup', key: string, value: string }> }, lines: Array<{ __typename?: 'LedgerLine', amount: string, key?: string | null, account: { __typename?: 'LedgerAccount', path: string }, currency?: { __typename?: 'Currency', code: CurrencyCode, customCurrencyId?: string | null } | null }> } | { __typename: 'BadRequestError', code: string, message: string, retryable: boolean } | { __typename: 'InternalError', code: string, message: string, retryable: boolean } }; + +export type PostDriverPayoutSettleMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + posted?: InputMaybe; + ledgerIk: Scalars['SafeString']['input']; + driver_id: Scalars['String']['input']; + payout_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; +}>; + + +export type PostDriverPayoutSettleMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult', isIkReplay: boolean, entry: { __typename?: 'LedgerEntry', ik: string, id: string, created: string, type?: string | null, posted: string, description?: string | null, ledger: { __typename?: 'Ledger', ik: string }, tags: Array<{ __typename?: 'LedgerEntryTag', key: string, value: string }>, groups: Array<{ __typename?: 'LedgerEntryGroup', key: string, value: string }> }, lines: Array<{ __typename?: 'LedgerLine', amount: string, key?: string | null, account: { __typename?: 'LedgerAccount', path: string }, currency?: { __typename?: 'Currency', code: CurrencyCode, customCurrencyId?: string | null } | null }> } | { __typename: 'BadRequestError', code: string, message: string, retryable: boolean } | { __typename: 'InternalError', code: string, message: string, retryable: boolean } }; + +export type PostDisputePayoutInitiateMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + posted?: InputMaybe; + ledgerIk: Scalars['SafeString']['input']; + user_id: Scalars['String']['input']; + disputes_id: Scalars['String']['input']; + amount: Scalars['String']['input']; + currency: Scalars['String']['input']; + payout_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; +}>; + + +export type PostDisputePayoutInitiateMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult', isIkReplay: boolean, entry: { __typename?: 'LedgerEntry', ik: string, id: string, created: string, type?: string | null, posted: string, description?: string | null, ledger: { __typename?: 'Ledger', ik: string }, tags: Array<{ __typename?: 'LedgerEntryTag', key: string, value: string }>, groups: Array<{ __typename?: 'LedgerEntryGroup', key: string, value: string }> }, lines: Array<{ __typename?: 'LedgerLine', amount: string, key?: string | null, account: { __typename?: 'LedgerAccount', path: string }, currency?: { __typename?: 'Currency', code: CurrencyCode, customCurrencyId?: string | null } | null }> } | { __typename: 'BadRequestError', code: string, message: string, retryable: boolean } | { __typename: 'InternalError', code: string, message: string, retryable: boolean } }; + +export type PostDisputePayoutSettleMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + posted?: InputMaybe; + ledgerIk: Scalars['SafeString']['input']; + user_id: Scalars['String']['input']; + disputes_id: Scalars['String']['input']; + amount: Scalars['String']['input']; + currency: Scalars['String']['input']; + order_id: Scalars['String']['input']; +}>; + + +export type PostDisputePayoutSettleMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult', isIkReplay: boolean, entry: { __typename?: 'LedgerEntry', ik: string, id: string, created: string, type?: string | null, posted: string, description?: string | null, ledger: { __typename?: 'Ledger', ik: string }, tags: Array<{ __typename?: 'LedgerEntryTag', key: string, value: string }>, groups: Array<{ __typename?: 'LedgerEntryGroup', key: string, value: string }> }, lines: Array<{ __typename?: 'LedgerLine', amount: string, key?: string | null, account: { __typename?: 'LedgerAccount', path: string }, currency?: { __typename?: 'Currency', code: CurrencyCode, customCurrencyId?: string | null } | null }> } | { __typename: 'BadRequestError', code: string, message: string, retryable: boolean } | { __typename: 'InternalError', code: string, message: string, retryable: boolean } }; + + +export const PostOrderPlacedDocument = gql` + mutation PostOrderPlaced($ik: SafeString!, $posted: DateTime, $ledgerIk: SafeString!, $user_id: String!, $order_id: String!, $order_cost: String!, $currency: String!, $platform_fee: String!, $driver_fee: String!, $restaurant_id: String!, $driver_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "order_placed", typeVersion: 1, posted: $posted, parameters: {user_id: $user_id, order_id: $order_id, order_cost: $order_cost, currency: $currency, platform_fee: $platform_fee, driver_fee: $driver_fee, restaurant_id: $restaurant_id, driver_id: $driver_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} + `; +export const PostOrderPlaced_V2Document = gql` + mutation PostOrderPlaced_v2($ik: SafeString!, $posted: DateTime, $ledgerIk: SafeString!, $user_id: String!, $order_id: String!, $order_cost: String!, $currency: String!, $platform_fee: String!, $service_fee: String!, $driver_fee: String!, $restaurant_id: String!, $driver_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "order_placed", typeVersion: 2, posted: $posted, parameters: {user_id: $user_id, order_id: $order_id, order_cost: $order_cost, currency: $currency, platform_fee: $platform_fee, service_fee: $service_fee, driver_fee: $driver_fee, restaurant_id: $restaurant_id, driver_id: $driver_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} + `; +export const PostCardSettleDocument = gql` + mutation PostCardSettle($ik: SafeString!, $posted: DateTime, $ledgerIk: SafeString!, $user_id: String!, $order_id: String!, $currency: String!, $amount: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "card_settle", typeVersion: 1, posted: $posted, parameters: {user_id: $user_id, order_id: $order_id, currency: $currency, amount: $amount}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} + `; +export const PostRestaurantPayoutInitiateDocument = gql` + mutation PostRestaurantPayoutInitiate($ik: SafeString!, $posted: DateTime, $ledgerIk: SafeString!, $restaurant_id: String!, $order_id: String!, $currency: String!, $amount: String!, $payout_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "restaurant_payout_initiate", typeVersion: 1, posted: $posted, parameters: {restaurant_id: $restaurant_id, order_id: $order_id, currency: $currency, amount: $amount, payout_id: $payout_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} + `; +export const PostRestaurantPayoutSettleDocument = gql` + mutation PostRestaurantPayoutSettle($ik: SafeString!, $posted: DateTime, $ledgerIk: SafeString!, $restaurant_id: String!, $payout_id: String!, $currency: String!, $amount: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "restaurant_payout_settle", typeVersion: 1, posted: $posted, parameters: {restaurant_id: $restaurant_id, payout_id: $payout_id, currency: $currency, amount: $amount}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} + `; +export const PostDriverPayoutInitiateDocument = gql` + mutation PostDriverPayoutInitiate($ik: SafeString!, $posted: DateTime, $ledgerIk: SafeString!, $driver_id: String!, $order_id: String!, $currency: String!, $amount: String!, $payout_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "driver_payout_initiate", typeVersion: 1, posted: $posted, parameters: {driver_id: $driver_id, order_id: $order_id, currency: $currency, amount: $amount, payout_id: $payout_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} + `; +export const PostDriverPayoutSettleDocument = gql` + mutation PostDriverPayoutSettle($ik: SafeString!, $posted: DateTime, $ledgerIk: SafeString!, $driver_id: String!, $payout_id: String!, $currency: String!, $amount: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "driver_payout_settle", typeVersion: 1, posted: $posted, parameters: {driver_id: $driver_id, payout_id: $payout_id, currency: $currency, amount: $amount}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} + `; +export const PostDisputePayoutInitiateDocument = gql` + mutation PostDisputePayoutInitiate($ik: SafeString!, $posted: DateTime, $ledgerIk: SafeString!, $user_id: String!, $disputes_id: String!, $amount: String!, $currency: String!, $payout_id: String!, $order_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "dispute_payout_initiate", typeVersion: 1, posted: $posted, parameters: {user_id: $user_id, disputes_id: $disputes_id, amount: $amount, currency: $currency, payout_id: $payout_id, order_id: $order_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} + `; +export const PostDisputePayoutSettleDocument = gql` + mutation PostDisputePayoutSettle($ik: SafeString!, $posted: DateTime, $ledgerIk: SafeString!, $user_id: String!, $disputes_id: String!, $amount: String!, $currency: String!, $order_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "dispute_payout_settle", typeVersion: 1, posted: $posted, parameters: {user_id: $user_id, disputes_id: $disputes_id, amount: $amount, currency: $currency, order_id: $order_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} + `; + +export type SdkFunctionWrapper = (action: (requestHeaders?:Record) => Promise, operationName: string, operationType?: string, variables?: any) => Promise; + + +const defaultWrapper: SdkFunctionWrapper = (action, _operationName, _operationType, _variables) => action(); + +export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) { + return { + PostOrderPlaced(variables: PostOrderPlacedMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostOrderPlacedDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostOrderPlaced', 'mutation', variables); + }, + PostOrderPlaced_v2(variables: PostOrderPlaced_V2MutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostOrderPlaced_V2Document, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostOrderPlaced_v2', 'mutation', variables); + }, + PostCardSettle(variables: PostCardSettleMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostCardSettleDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostCardSettle', 'mutation', variables); + }, + PostRestaurantPayoutInitiate(variables: PostRestaurantPayoutInitiateMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostRestaurantPayoutInitiateDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostRestaurantPayoutInitiate', 'mutation', variables); + }, + PostRestaurantPayoutSettle(variables: PostRestaurantPayoutSettleMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostRestaurantPayoutSettleDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostRestaurantPayoutSettle', 'mutation', variables); + }, + PostDriverPayoutInitiate(variables: PostDriverPayoutInitiateMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostDriverPayoutInitiateDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostDriverPayoutInitiate', 'mutation', variables); + }, + PostDriverPayoutSettle(variables: PostDriverPayoutSettleMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostDriverPayoutSettleDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostDriverPayoutSettle', 'mutation', variables); + }, + PostDisputePayoutInitiate(variables: PostDisputePayoutInitiateMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostDisputePayoutInitiateDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostDisputePayoutInitiate', 'mutation', variables); + }, + PostDisputePayoutSettle(variables: PostDisputePayoutSettleMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostDisputePayoutSettleDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostDisputePayoutSettle', 'mutation', variables); + } + }; +} +export type Sdk = ReturnType; +/** + * Typed payloads for batching Ledger Entries with `addLedgerEntries`. + * + * Each payload is derived from the single-entry `addLedgerEntry` operation for + * one `(type, typeVersion)` pair, and builds an `AddLedgerEntryInput` you can + * mix freely with raw, untyped entry inputs in the same batch: + * + * ```ts + * await client.addLedgerEntries({ + * entries: [orderPlacedV1({ ik, ledgerIk, parameters: { ... } })], + * }); + * ``` + * + * A payload takes exactly what its source operation binds, so what you can set + * here is what that entry type accepts — no more. A field you do not set is + * left out of the request rather than sent as `null`. + */ + +/** + * Payload for the `order_placed` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostOrderPlaced` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type OrderPlacedV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + user_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + order_cost: Scalars['String']['input']; + currency: Scalars['String']['input']; + platform_fee: Scalars['String']['input']; + driver_fee: Scalars['String']['input']; + restaurant_id: Scalars['String']['input']; + driver_id: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `order_placed` (typeVersion 1). */ +export const orderPlacedV1 = ( + input: OrderPlacedV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + user_id: input.user_id, + order_id: input.order_id, + order_cost: input.order_cost, + currency: input.currency, + platform_fee: input.platform_fee, + driver_fee: input.driver_fee, + restaurant_id: input.restaurant_id, + driver_id: input.driver_id, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'order_placed', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Payload for the `order_placed` (typeVersion 2) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostOrderPlaced_v2` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type OrderPlacedV2 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + user_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + order_cost: Scalars['String']['input']; + currency: Scalars['String']['input']; + platform_fee: Scalars['String']['input']; + service_fee: Scalars['String']['input']; + driver_fee: Scalars['String']['input']; + restaurant_id: Scalars['String']['input']; + driver_id: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `order_placed` (typeVersion 2). */ +export const orderPlacedV2 = ( + input: OrderPlacedV2, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + user_id: input.user_id, + order_id: input.order_id, + order_cost: input.order_cost, + currency: input.currency, + platform_fee: input.platform_fee, + service_fee: input.service_fee, + driver_fee: input.driver_fee, + restaurant_id: input.restaurant_id, + driver_id: input.driver_id, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'order_placed', + typeVersion: 2, + }, + ik: input.ik, +}); + +/** + * Payload for the `card_settle` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostCardSettle` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type CardSettleV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + user_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `card_settle` (typeVersion 1). */ +export const cardSettleV1 = ( + input: CardSettleV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + user_id: input.user_id, + order_id: input.order_id, + currency: input.currency, + amount: input.amount, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'card_settle', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Payload for the `restaurant_payout_initiate` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostRestaurantPayoutInitiate` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type RestaurantPayoutInitiateV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + restaurant_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; + payout_id: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `restaurant_payout_initiate` (typeVersion 1). */ +export const restaurantPayoutInitiateV1 = ( + input: RestaurantPayoutInitiateV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + restaurant_id: input.restaurant_id, + order_id: input.order_id, + currency: input.currency, + amount: input.amount, + payout_id: input.payout_id, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'restaurant_payout_initiate', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Payload for the `restaurant_payout_settle` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostRestaurantPayoutSettle` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type RestaurantPayoutSettleV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + restaurant_id: Scalars['String']['input']; + payout_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `restaurant_payout_settle` (typeVersion 1). */ +export const restaurantPayoutSettleV1 = ( + input: RestaurantPayoutSettleV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + restaurant_id: input.restaurant_id, + payout_id: input.payout_id, + currency: input.currency, + amount: input.amount, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'restaurant_payout_settle', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Payload for the `driver_payout_initiate` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostDriverPayoutInitiate` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type DriverPayoutInitiateV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + driver_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; + payout_id: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `driver_payout_initiate` (typeVersion 1). */ +export const driverPayoutInitiateV1 = ( + input: DriverPayoutInitiateV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + driver_id: input.driver_id, + order_id: input.order_id, + currency: input.currency, + amount: input.amount, + payout_id: input.payout_id, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'driver_payout_initiate', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Payload for the `driver_payout_settle` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostDriverPayoutSettle` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type DriverPayoutSettleV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + driver_id: Scalars['String']['input']; + payout_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `driver_payout_settle` (typeVersion 1). */ +export const driverPayoutSettleV1 = ( + input: DriverPayoutSettleV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + driver_id: input.driver_id, + payout_id: input.payout_id, + currency: input.currency, + amount: input.amount, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'driver_payout_settle', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Payload for the `dispute_payout_initiate` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostDisputePayoutInitiate` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type DisputePayoutInitiateV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + user_id: Scalars['String']['input']; + disputes_id: Scalars['String']['input']; + amount: Scalars['String']['input']; + currency: Scalars['String']['input']; + payout_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `dispute_payout_initiate` (typeVersion 1). */ +export const disputePayoutInitiateV1 = ( + input: DisputePayoutInitiateV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + user_id: input.user_id, + disputes_id: input.disputes_id, + amount: input.amount, + currency: input.currency, + payout_id: input.payout_id, + order_id: input.order_id, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'dispute_payout_initiate', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Payload for the `dispute_payout_settle` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostDisputePayoutSettle` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type DisputePayoutSettleV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + user_id: Scalars['String']['input']; + disputes_id: Scalars['String']['input']; + amount: Scalars['String']['input']; + currency: Scalars['String']['input']; + order_id: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `dispute_payout_settle` (typeVersion 1). */ +export const disputePayoutSettleV1 = ( + input: DisputePayoutSettleV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + user_id: input.user_id, + disputes_id: input.disputes_id, + amount: input.amount, + currency: input.currency, + order_id: input.order_id, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'dispute_payout_settle', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Every typed payload builder, keyed by `"@"`. Useful + * when the entry type is only known at runtime. + */ +export const typedLedgerEntryBuilders = { + 'order_placed@1': orderPlacedV1, + 'order_placed@2': orderPlacedV2, + 'card_settle@1': cardSettleV1, + 'restaurant_payout_initiate@1': restaurantPayoutInitiateV1, + 'restaurant_payout_settle@1': restaurantPayoutSettleV1, + 'driver_payout_initiate@1': driverPayoutInitiateV1, + 'driver_payout_settle@1': driverPayoutSettleV1, + 'dispute_payout_initiate@1': disputePayoutInitiateV1, + 'dispute_payout_settle@1': disputePayoutSettleV1, +} as const; diff --git a/tests/fixtures/generated-template-runtime-args-client.ts b/tests/fixtures/generated-template-runtime-args-client.ts new file mode 100644 index 0000000..676cc0b --- /dev/null +++ b/tests/fixtures/generated-template-runtime-args-client.ts @@ -0,0 +1,4488 @@ +/* This file is auto-generated by @fragment-dev/node-client. Don't edit it directly. */ +import { GraphQLClient, RequestOptions } from '@fragment-dev/node-client'; +import { gql } from '@fragment-dev/node-client'; +export type Maybe = T | null; +export type InputMaybe = Maybe; +export type Exact = { [K in keyof T]: T[K] }; +export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; +export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; +export type MakeEmpty = { [_ in K]?: never }; +export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; +type GraphQLClientRequestHeaders = RequestOptions['requestHeaders']; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: { input: string; output: string; } + String: { input: string; output: string; } + Boolean: { input: boolean; output: boolean; } + Int: { input: number; output: number; } + Float: { input: number; output: number; } + /** A string that must be alphanumeric */ + AlphaNumericString: { input: string; output: string; } + /** ISO 8601 Date e.g. `1969-07-21` */ + Date: { input: string; output: string; } + /** ISO 8601 DateTime e.g. `1969-07-16T13:32:00.000Z`. You can also provide a date e.g. `1969-01-01` and it will be converted to `1969-01-01T00:00:00.000Z` */ + DateTime: { input: string; output: string; } + /** The first moment of a specific year, month or day or hour e.g. 1969 or 1969-1 or 1969-1-1 or 1969-1-1T00. All of the previous examples are equivalent to `1969-1-1T00:00:00.000`. */ + FirstMoment: { input: any; output: any; } + /** A string representing integers up to 9,223,372,036,854,775,807 (i.e. 2^63-1) */ + Int64: { input: string; output: string; } + /** A string representing integers as big as 2^120-1. The number is signed so the range is from -1,329,227,995,784,915,872,903,807,060,280,344,575 to 1,329,227,995,784,915,872,903,807,060,280,344,575. */ + Int96: { input: string; output: string; } + /** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */ + JSON: { input: Record; output: Record; } + /** The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */ + JSONObject: { input: Record; output: Record; } + /** The last moment of a specific year, month or day or hour e.g. 1969 or 1969-12 or 1969-12-31 or 1969-12-31T23. All of the previous examples are equivalent to `1969-12-31T23:59:59.999`. */ + LastMoment: { input: string; output: string; } + /** A string of non-zero length that can contain parameterized values via handlebars syntax. ex: `"Hello from {{country}}"`. */ + ParameterizedString: { input: string; output: string; } + /** A mapping of parameter keys to values. */ + Parameters: { input: any; output: any; } + /** A specific year ("2021"), quarter ("2021-Q1"), month ("2021-02"), day ("2021-02-03") or hour ("2021-02-03T04") */ + Period: { input: string; output: string; } + /** A specific year ("2026") or month ("2026-05") used to match dates that fall within it. */ + PeriodFilter: { input: any; output: any; } + /** A string with delimiter characters `/`, `#`, and `:` disallowed, as well as parameters in {{handlebar}} syntax. */ + SafeString: { input: string; output: string; } + /** All hour-aligned offsets from -11:00 to +12:00 are supported, e.g. "-08:00" (PT), "-05:00" (ET), "+00:00" (UTC) */ + UTCOffset: { input: string; output: string; } +}; + +/** Error returned when one or more Ledger Entries in the batch could not be added. */ +export type AddLedgerEntriesError = Error & { + __typename?: 'AddLedgerEntriesError'; + /** The status code of error. For example, 'ledger_entry_batch_operation_failed'. */ + code: Scalars['String']['output']; + /** The list of errors for each Ledger Entry that was responsible for the batch's failure. */ + errors: Array; + /** The error message */ + message: Scalars['String']['output']; + /** Whether or not the operation is retryable */ + retryable: Scalars['Boolean']['output']; +}; + +export type AddLedgerEntriesResponse = AddLedgerEntriesError | AddLedgerEntriesResult | BadRequestError | InternalError; + +export type AddLedgerEntriesResult = { + __typename?: 'AddLedgerEntriesResult'; + /** The added Ledger Entries, in the same order as the input */ + results: Array; +}; + +/** Error details for a single Ledger Entry that was responsible for the batch's failure. */ +export type AddLedgerEntryError = Error & { + __typename?: 'AddLedgerEntryError'; + /** The status code of error. For example, 'ledger_entry_too_many_lines'. */ + code: Scalars['String']['output']; + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) of the Ledger Entry */ + ik: Scalars['SafeString']['output']; + /** The error message */ + message: Scalars['String']['output']; + /** Whether or not the operation is retryable */ + retryable: Scalars['Boolean']['output']; +}; + +export type AddLedgerEntryInput = { + /** The [Ledger Entry](https://fragment.dev/api-reference/api-types#input-types-ledgerentryinput) to add */ + entry: LedgerEntryInput; + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry */ + ik: Scalars['SafeString']['input']; +}; + +export type AddLedgerEntryResponse = AddLedgerEntryResult | BadRequestError | InternalError; + +export type AddLedgerEntryResult = { + __typename?: 'AddLedgerEntryResult'; + /** The ledger entry that was posted */ + entry: LedgerEntry; + /** True if this request successfully completed before and the previous response is being returned */ + isIkReplay: Scalars['Boolean']['output']; + /** The ledger lines that were created in that entry */ + lines: Array; +}; + +/** Equivalent to an HTTP 400 - request either has missing or incorrect data */ +export type BadRequestError = Error & { + __typename?: 'BadRequestError'; + /** The status code of error. For example, 'ledger_not_found'. */ + code: Scalars['String']['output']; + /** The error message */ + message: Scalars['String']['output']; + /** Whether or not the operation is retryable */ + retryable: Scalars['Boolean']['output']; +}; + +/** A single amount and the timestamp requested */ +export type BalanceChangeDuring = { + __typename?: 'BalanceChangeDuring'; + /** The balance or balance change */ + amount: CurrencyAmount; + /** The period of the requested balance change */ + period: Scalars['Period']['output']; +}; + +/** A paginated list of amounts and their periods */ +export type BalanceChangeDuringConnection = { + __typename?: 'BalanceChangeDuringConnection'; + /** The end time of the period across which the balance changes are requested */ + endTime: Scalars['LastMoment']['output']; + /** The granularity of the return data */ + granularity: Granularity; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; + /** The start time of the period across which the balance changes are requested */ + startTime: Scalars['FirstMoment']['output']; +}; + +/** Used to configure the write-consistency of a Ledger Account's balance. See [Configure consistency](https://fragment.dev/guides/configure-consistency). */ +export enum BalanceUpdateConsistencyMode { + Eventual = 'eventual', + Strong = 'strong' +} + +/** The input for your Chart of Accounts in a Schema. */ +export type ChartOfAccountsInput = { + /** The Ledger Accounts modeled by your Schema. Ledger Accounts may be nested up to a maximum depth of 10. */ + accounts: Array; + /** + * The default consistency configuration for all Ledger Accounts in this Schema. + * If a Ledger Account does not specify its own consistency configuration, it will use the default values provided here. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + defaultConsistencyConfig?: InputMaybe; + /** + * The default currency of each Ledger Account in the Chart Of Accounts. + * It must be provided if `defaultCurrencyMode` is set to `single`. + * Additionally, `defaultCurrency` must be omitted if `defaultCurrencyMode` is set to `multi`. + */ + defaultCurrency?: InputMaybe; + /** The default currency mode of each Ledger Account in the Chart Of Accounts. */ + defaultCurrencyMode?: InputMaybe; +}; + +export type CreateCustomCurrencyInput = { + /** The currency code for custom currencies. It can be up to 36 characters long. This is used for display purposes. */ + customCode: Scalars['String']['input']; + /** The ID for a custom currency. This is specified when creating the custom currency using the [createCustomCurrency](https://fragment.dev/api-reference/api-mutations#createcustomcurrency) mutation. It can be up to 36 characters long. */ + customCurrencyId: Scalars['SafeString']['input']; + /** A human readable name for the currency (e.g. United States Dollar). This is used for display purposes. */ + name: Scalars['String']['input']; + /** The number of decimal places this currency goes to. For example, United States Dollars have a precision of 2 (i.e. 100 cents in a dollar), whereas the Jordanian Dinar has a precision of 3. This is used for display purposes. */ + precision: Scalars['Int']['input']; +}; + +export type CreateCustomCurrencyResponse = BadRequestError | CreateCustomCurrencyResult | InternalError; + +export type CreateCustomCurrencyResult = { + __typename?: 'CreateCustomCurrencyResult'; + /** The Currency that was created. */ + customCurrency: Currency; +}; + +export type CreateCustomLinkResponse = BadRequestError | CreateCustomLinkResult | InternalError; + +export type CreateCustomLinkResult = { + __typename?: 'CreateCustomLinkResult'; + isIkReplay: Scalars['Boolean']['output']; + /** The custom link that was created. Represents an instance of an external system. */ + link: CustomLink; +}; + +export type CreateLedgerAccountInput = { + /** The consistency configuration for this Ledger Account. This defines how updates to this Ledger Account's balance are handled. */ + consistencyConfig?: InputMaybe; + /** + * The currency of this Ledger Account. If this is not set, and `currencyMode` is + * not set to `multi`, the workspace-level default is used. + */ + currency?: InputMaybe; + /** If set to `multi`, creates a multi-currency Ledger Account. If set to `single`, creates a single-currency Ledger Account. */ + currencyMode?: InputMaybe; + /** The External Account to link to this Ledger Account. */ + linkedAccount?: InputMaybe; + /** The human-readable name of this Ledger Account. */ + name: Scalars['String']['input']; + /** The parent of this Ledger Account. */ + parent?: InputMaybe; + /** The type of ledger account to create. Required if this is a top-level Ledger Account. If not provided, the type will be inferred from the parent. */ + type?: InputMaybe; +}; + +export type CreateLedgerAccountResponse = BadRequestError | CreateLedgerAccountResult | InternalError; + +export type CreateLedgerAccountResult = { + __typename?: 'CreateLedgerAccountResult'; + /** true if a previous request successfully created this ledger account */ + isIkReplay: Scalars['Boolean']['output']; + /** The ledger account that was created */ + ledgerAccount: LedgerAccount; +}; + +export type CreateLedgerAccountsInput = { + /** Ledger Accounts to create as children of this Ledger Account. */ + childLedgerAccounts?: InputMaybe>; + /** The consistency configuration for this ledger account. See [Configure consistency](https://fragment.dev/guides/configure-consistency). */ + consistencyConfig?: InputMaybe; + /** The currency of this Ledger Account. If this is not set, the workspace level default is used. */ + currency?: InputMaybe; + /** The currency mode of this Ledger Account. If this is not set, the workspace level default is used. */ + currencyMode?: InputMaybe; + /** The idempotency key for creating this Ledger Account. */ + ik: Scalars['SafeString']['input']; + /** The External Account to link to this Ledger Account. This can only be specified on leaf Ledger Accounts. See [Reconcile payments](https://fragment.dev/guides/reconcile-payments). */ + linkedAccount?: InputMaybe; + /** The name of the Ledger Account. */ + name: Scalars['String']['input']; + /** The parent of this Ledger Account. This is only valid on the top level Ledger Account in the payload. */ + parent?: InputMaybe; + /** The type of this Ledger Account. This field is only required if this is a root Ledger Account. Otherwise, the type will get inherited from its parent. */ + type?: InputMaybe; +}; + +export type CreateLedgerAccountsResponse = BadRequestError | CreateLedgerAccountsResult | InternalError; + +export type CreateLedgerAccountsResult = { + __typename?: 'CreateLedgerAccountsResult'; + /** Whether the ledger accounts were successfully created by a previous request */ + ikReplays: Array; + /** The ledger accounts that were created */ + ledgerAccounts: Array; +}; + +export type CreateLedgerInput = { + /** + * Use this field to specify a timezone for queries to your Ledger. + * + * When aggregating balances, all transactions within a 24 hour period starting at midnight UTC are included in each day. + * You can specify a different starting hour for balances. For example, use "-08:00" to align balances with Pacific Standard Time. + * Balance queries would then consider the start of each local day to be at 8am UTC the next day in UTC. + * The default timezone is UTC. + */ + balanceUTCOffset?: InputMaybe; + name: Scalars['String']['input']; + type?: InputMaybe; +}; + +export type CreateLedgerResponse = BadRequestError | CreateLedgerResult | InternalError; + +export type CreateLedgerResult = { + __typename?: 'CreateLedgerResult'; + /** true if this request successfully completed before and the previous response is being returned */ + isIkReplay: Scalars['Boolean']['output']; + /** The Ledger that was created */ + ledger: Ledger; +}; + +export type CreatePaymentResponse = BadRequestError | InternalError | Payment; + +export type Currency = { + __typename?: 'Currency'; + /** The currency code. This is an [enum type](https://fragment.dev/api-reference/api-types#scalars-and-enums-currencycode) . */ + code: CurrencyCode; + /** The currency code for custom currencies. This is only set if 'currency' is set to CUSTOM. It can be up to 36 characters long. */ + customCode?: Maybe; + /** The ID for a custom currency. This is specified when creating the custom currency using the [createCustomCurrency](https://fragment.dev/api-reference/api-mutations#createcustomcurrency) mutation. */ + customCurrencyId?: Maybe; + /** A human readable name for the currency (e.g. United States Dollar). This is used for display purposes. */ + name: Scalars['String']['output']; + /** The number of decimal places this currency goes to. For example, United States Dollars have a precision of 2 (i.e. 100 cents in a dollar), whereas the Jordanian Dinar has a precision of 3. This is used for display purposes. */ + precision: Scalars['Int']['output']; +}; + +/** A single amount accompanied by its currency */ +export type CurrencyAmount = { + __typename?: 'CurrencyAmount'; + /** Numerical integer value, serialized as a string */ + amount: Scalars['Int96']['output']; + /** The currency this amount is in */ + currency: Currency; +}; + +/** A paginated list of amounts with their currencies */ +export type CurrencyAmountConnection = { + __typename?: 'CurrencyAmountConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export enum CurrencyCode { + Aave = 'AAVE', + Ada = 'ADA', + Aed = 'AED', + Afn = 'AFN', + All = 'ALL', + Amd = 'AMD', + Ang = 'ANG', + Aoa = 'AOA', + Ars = 'ARS', + Aud = 'AUD', + Awg = 'AWG', + Azn = 'AZN', + Bam = 'BAM', + Bbd = 'BBD', + Bch = 'BCH', + Bdt = 'BDT', + Bgn = 'BGN', + Bhd = 'BHD', + Bif = 'BIF', + Bmd = 'BMD', + Bnd = 'BND', + Bob = 'BOB', + Brl = 'BRL', + Bsd = 'BSD', + Btc = 'BTC', + Btn = 'BTN', + Bwp = 'BWP', + Byr = 'BYR', + Bzd = 'BZD', + Cad = 'CAD', + Cadc = 'CADC', + Cadt = 'CADT', + Cdf = 'CDF', + Chf = 'CHF', + Clp = 'CLP', + Cny = 'CNY', + Cop = 'COP', + Crc = 'CRC', + Cuc = 'CUC', + Cup = 'CUP', + Custom = 'CUSTOM', + Cve = 'CVE', + Czk = 'CZK', + Dai = 'DAI', + Djf = 'DJF', + Dkk = 'DKK', + Dop = 'DOP', + Dzd = 'DZD', + Egp = 'EGP', + Ern = 'ERN', + Etb = 'ETB', + Eth = 'ETH', + Eur = 'EUR', + Eurc = 'EURC', + Fjd = 'FJD', + Fkp = 'FKP', + Gbp = 'GBP', + Gel = 'GEL', + Ggp = 'GGP', + Ghs = 'GHS', + Gip = 'GIP', + Gmd = 'GMD', + Gnf = 'GNF', + Gtq = 'GTQ', + Gyd = 'GYD', + Hkd = 'HKD', + Hnl = 'HNL', + Hrk = 'HRK', + Htg = 'HTG', + Huf = 'HUF', + Idr = 'IDR', + Ils = 'ILS', + Imp = 'IMP', + Inr = 'INR', + Iqd = 'IQD', + Irr = 'IRR', + Isk = 'ISK', + Jmd = 'JMD', + Jod = 'JOD', + Jpy = 'JPY', + Kes = 'KES', + Kgs = 'KGS', + Khr = 'KHR', + Kmf = 'KMF', + Kpw = 'KPW', + Krw = 'KRW', + Kwd = 'KWD', + Kyd = 'KYD', + Kzt = 'KZT', + Lak = 'LAK', + Lbp = 'LBP', + Link = 'LINK', + Lkr = 'LKR', + Logical = 'LOGICAL', + Lrd = 'LRD', + Lsl = 'LSL', + Ltc = 'LTC', + Lyd = 'LYD', + Mad = 'MAD', + Matic = 'MATIC', + Mdl = 'MDL', + Mga = 'MGA', + Mkd = 'MKD', + Mmk = 'MMK', + Mnt = 'MNT', + Mop = 'MOP', + Mur = 'MUR', + Mvr = 'MVR', + Mwk = 'MWK', + Mxn = 'MXN', + Myr = 'MYR', + Mzn = 'MZN', + Nad = 'NAD', + Ngn = 'NGN', + Nio = 'NIO', + Nok = 'NOK', + Npr = 'NPR', + Nzd = 'NZD', + Omr = 'OMR', + Pab = 'PAB', + Pen = 'PEN', + Pgk = 'PGK', + Php = 'PHP', + Pkr = 'PKR', + Pln = 'PLN', + Pts = 'PTS', + Pyg = 'PYG', + Qar = 'QAR', + Ron = 'RON', + Rsd = 'RSD', + Rub = 'RUB', + Rwf = 'RWF', + Sar = 'SAR', + Sbd = 'SBD', + Scr = 'SCR', + Sdg = 'SDG', + Sek = 'SEK', + Sgd = 'SGD', + Shp = 'SHP', + Sll = 'SLL', + Sol = 'SOL', + Sos = 'SOS', + Spl = 'SPL', + Srd = 'SRD', + Stn = 'STN', + Svc = 'SVC', + Syp = 'SYP', + Szl = 'SZL', + Thb = 'THB', + Tjs = 'TJS', + Tmt = 'TMT', + Tnd = 'TND', + Top = 'TOP', + Try = 'TRY', + Ttd = 'TTD', + Tvd = 'TVD', + Twd = 'TWD', + Tzs = 'TZS', + Uah = 'UAH', + Ugx = 'UGX', + Uni = 'UNI', + Usd = 'USD', + Usdc = 'USDC', + Usdg = 'USDG', + Usdt = 'USDT', + Uyu = 'UYU', + Uzs = 'UZS', + Vef = 'VEF', + Vnd = 'VND', + Vuv = 'VUV', + Wst = 'WST', + Xaf = 'XAF', + Xcd = 'XCD', + Xlm = 'XLM', + Xof = 'XOF', + Xpf = 'XPF', + Yer = 'YER', + Zar = 'ZAR', + Zmw = 'ZMW' +} + +export type CurrencyFilter = { + /** Must match the value provided */ + equalTo?: InputMaybe; + /** Must match one of the values provided. Limited to 100 items maximum. */ + in?: InputMaybe>; +}; + +export type CurrencyMatchInput = { + /** The currency code. This is an [enum type](https://fragment.dev/api-reference/api-types#scalars-and-enums-currencycode). */ + code: CurrencyCode; + /** The ID for a custom currency. This is specified when creating the custom currency using the [createCustomCurrency](https://fragment.dev/api-reference/api-mutations#createcustomcurrency) mutation. */ + customCurrencyId?: InputMaybe; +}; + +/** Defines the currency handling of a LedgerAccount, which can either be restricted to a single currency or allow multiple currencies. */ +export enum CurrencyMode { + Multi = 'multi', + Single = 'single' +} + +export type CustomAccountInput = { + /** The currency of this external account. If this is not set, the workspace level default is used. 'currency' cannot be set if 'currencyMode' is 'multi'. */ + currency?: InputMaybe; + /** The currency mode of this external account. If set to multi, creates a multi-currency account. */ + currencyMode?: InputMaybe; + /** The ID of this account at the external system. This is used as the idempotency key, within the scope of its Custom Link. */ + externalId: Scalars['SafeString']['input']; + /** The name of the account at the external system. */ + name: Scalars['String']['input']; +}; + +/** A paginated list of Custom Currencies */ +export type CustomCurrenciesConnection = { + __typename?: 'CustomCurrenciesConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export type CustomLink = Link & { + __typename?: 'CustomLink'; + /** ISO-8601 timestamp when the Link was created. */ + created: Scalars['String']['output']; + /** URL to the Fragment Dashboard for this Link. */ + dashboardUrl: Scalars['String']['output']; + /** A list of External Accounts associated with this Link. */ + externalAccounts: ExternalAccountsConnection; + /** FRAGMENT ID of the Custom Link. */ + id: Scalars['ID']['output']; + /** Name of the Link as it appears in the Fragment Dashboard. */ + name: Scalars['String']['output']; +}; + +export type CustomTxInput = { + account: ExternalAccountMatchInput; + amount: Scalars['Int96']['input']; + /** The currency of this tx. Should be set for multi-currency accounts. */ + currency?: InputMaybe; + description: Scalars['String']['input']; + /** The ID of this tx at the external system. This is used as the idempotency key, within the scope of its Custom Account. */ + externalId: Scalars['SafeString']['input']; + posted: Scalars['DateTime']['input']; +}; + +export type DateFilter = { + equalTo?: InputMaybe; + /** Must match one of the values provided. Limited to 100 items maximum. */ + in?: InputMaybe>; + /** Must fall within the given period. Supports a year (e.g. "2026") or a month (e.g. "2026-05"). To match a specific day, use `equalTo`. Cannot be combined with `withinBalanceUTCOffset`. */ + within?: InputMaybe; + /** Must fall within the given period, taking into account the Ledger's `balanceUTCOffset`. Supports a year (e.g. "2026") or a month (e.g. "2026-05"). Cannot be combined with `within`. */ + withinBalanceUTCOffset?: InputMaybe; +}; + +/** Filters a timestamp field between two moments in time */ +export type DateTimeFilter = { + /** The timestamp value must be after this moment. Specified in ISO 8601 format e.g "1968-01-01T16:45:00Z" */ + after?: InputMaybe; + /** The timestamp value must be before this moment. Specified in ISO 8601 format e.g "1968-01-01T16:45:00Z" */ + before?: InputMaybe; +}; + +export type DeleteCustomTxsResponse = BadRequestError | DeleteCustomTxsResult | InternalError; + +export type DeleteCustomTxsResult = { + __typename?: 'DeleteCustomTxsResult'; + /** List of Txs deleted in this operation */ + txs: Array; +}; + +export type DeleteLedgerResponse = BadRequestError | DeleteLedgerResult | InternalError; + +export type DeleteLedgerResult = { + __typename?: 'DeleteLedgerResult'; + success: Scalars['Boolean']['output']; +}; + +export type DeleteSchemaResponse = BadRequestError | DeleteSchemaResult | InternalError; + +export type DeleteSchemaResult = { + __typename?: 'DeleteSchemaResult'; + success: Scalars['Boolean']['output']; +}; + +export type DeletedCustomTx = { + __typename?: 'DeletedCustomTx'; + /** A deleted Tx */ + tx: Tx; +}; + +export type EntryGroupMatchInput = { + key: Scalars['SafeString']['input']; + value: Scalars['SafeString']['input']; +}; + +/** Base error interface */ +export type Error = { + /** The status code of error. For example, 'ledger_not_found'. */ + code: Scalars['String']['output']; + /** The error message */ + message: Scalars['String']['output']; + /** Whether or not the operation is retryable */ + retryable: Scalars['Boolean']['output']; +}; + +export type ExternalAccount = { + __typename?: 'ExternalAccount'; + /** The currency of this external account. */ + currency?: Maybe; + /** Indicates if the account allows multiple currencies or is restricted to a single currency */ + currencyMode: CurrencyMode; + /** ID used for the external account */ + externalId: Scalars['ID']['output']; + /** FRAGMENT ID of External Account */ + id: Scalars['ID']['output']; + /** Ledger Accounts linked to this External Account. Ledger Accounts are paginated and sorted in reverse-chronological order by created date. */ + ledgerAccounts: LedgerAccountsConnection; + /** The Link that this External Account belongs to. */ + link: Link; + /** FRAGMENT ID of this transaction's external link */ + linkId: Scalars['ID']['output']; + name: Scalars['String']['output']; + /** All Txs in this External Account. */ + txs: TxsConnection; +}; + + +export type ExternalAccountLedgerAccountsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +export type ExternalAccountTxsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +export type ExternalAccountFilter = { + /** Ledger Account must linked to the the specified external account */ + equalTo?: InputMaybe; + /** Ledger Account can be linked to any of the specified external accounts. Limited to 100 items maximum. */ + in?: InputMaybe>; +}; + +/** Specify an External Account by using `id`, or `linkId` and `externalId`. */ +export type ExternalAccountMatchInput = { + /** The external system's ID of the External Account. If this is specified, `linkId` is required. `id` is optional, but will be validated if provided. */ + externalId?: InputMaybe; + /** The FRAGMENT ID of the External Account. If this is specified, both `linkId` and `externalId` are optional, but will be validated if provided. */ + id?: InputMaybe; + /** The FRAGMENT ID of the Link the External Account is in. If this is specified, `externalId` is required. `id` is optional, but will be validated if provided. */ + linkId?: InputMaybe; +}; + +/** A paginated list of External Accounts */ +export type ExternalAccountsConnection = { + __typename?: 'ExternalAccountsConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export enum ExternalTransferType { + Ach = 'ach', + Card = 'card', + Check = 'check', + Internal = 'internal', + Wire = 'wire' +} + +export enum ExternalTxSource { + Increase = 'increase' +} + +export enum Granularity { + Daily = 'daily', + Hourly = 'hourly', + Monthly = 'monthly' +} + +/** A filter to query balances of a specific subset of accounts */ +export type GroupBalanceAccountFilter = { + /** A filter that must match the account ID */ + id?: InputMaybe; + /** A filter that must match the account path. Wildcards ('*') may be used only for template variables, and will only match a single variable each. */ + path?: InputMaybe; +}; + +/** Filter for finding entries by group membership */ +export type GroupFilter = { + /** Find entries that have ALL of the specified groups. Limited to 10 items maximum. */ + all?: InputMaybe>; + /** Find groups that exactly match this group */ + equalTo?: InputMaybe; + /** Find groups that match any of these groups */ + in?: InputMaybe>; + /** Find groups with a specific key */ + keyEqualTo?: InputMaybe; + /** Find groups with any of these keys */ + keyIn?: InputMaybe>; + /** + * Find groups that do not match this predicate + * @deprecated not filter is deprecated. Use notKeyIn or notKeyEqualTo instead. + */ + not?: InputMaybe; + /** Find groups that do not exactly match this group */ + notEqualTo?: InputMaybe; + /** Find groups that do not match any of these groups */ + notIn?: InputMaybe>; + /** Find groups that do not have a specific key */ + notKeyEqualTo?: InputMaybe; + /** Find groups that do not have any of these keys */ + notKeyIn?: InputMaybe>; +}; + +/** A Group in a Schema. Group define sequences of Ledger Entries and can help with reconciliation tasks. */ +export type GroupInput = { + /** Human-readable description of the Group. */ + description?: InputMaybe; + /** The key of this Group. This combined with its value is a stable, unique identifier for this group. */ + key: Scalars['SafeString']['input']; + /** The parameters that are used to enable reconciliation abilities in a group. */ + reconciliation?: InputMaybe; +}; + +/** Input type for matching a specific group by key and value */ +export type GroupMatchInput = { + /** The key of the group to match */ + key: Scalars['SafeString']['input']; + /** The value of the group to match */ + value: Scalars['SafeString']['input']; +}; + +/** DEPRECATED: Use GroupFilter and notKeyIn or notKeyEqualTo instead. Filter for finding entries that do not match this predicate */ +export type GroupNotFilter = { + /** DEPRECATED: Find entries that are not members of all of these groups. This is an AND filter. */ + keyIn?: InputMaybe>; +}; + +/** A set of parameters that are used to enable reconciliation abilities in a group */ +export type GroupReconciliationParametersInput = { + /** The path to the clearing account for this group. A clearing account is an account that is used to indicate funds that are in transit. Also called a suspense account, pending account, or zero balance account. */ + clearingAccountPath: SchemaLedgerAccountMatchInput; +}; + +/** A single amount and the timestamp requested */ +export type HistoricalBalance = { + __typename?: 'HistoricalBalance'; + /** The balance or balance change */ + amount: CurrencyAmount; + /** The timestamp of the requested balance */ + at: Scalars['LastMoment']['output']; +}; + +/** A paginated list of amounts and their periods */ +export type HistoricalBalanceConnection = { + __typename?: 'HistoricalBalanceConnection'; + /** The end time of the period across which the balance changes are requested */ + endTime: Scalars['LastMoment']['output']; + /** The granularity of the return data */ + granularity: Granularity; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; + /** The start time of the period across which the balance changes are requested */ + startTime: Scalars['FirstMoment']['output']; +}; + +export type IkReplay = { + __typename?: 'IkReplay'; + ik: Scalars['SafeString']['output']; + isIkReplay: Scalars['Boolean']['output']; +}; + +export enum IncreaseEnv { + Production = 'production', + Sandbox = 'sandbox' +} + +export type IncreaseLink = Link & { + __typename?: 'IncreaseLink'; + /** ISO-8601 timestamp when the Link was created. */ + created: Scalars['String']['output']; + /** URL to the Fragment Dashboard for this Link. */ + dashboardUrl: Scalars['String']['output']; + /** A list of External Accounts associated with this Link. */ + externalAccounts: ExternalAccountsConnection; + /** FRAGMENT ID of the Increase Link. */ + id: Scalars['ID']['output']; + /** The environment of the Increase Link, either sandbox or production. */ + increaseEnv: IncreaseEnv; + /** Name of the Link as it appears in the Dashboard. */ + name: Scalars['String']['output']; +}; + +/** A condition that must be met on an `Int96` field. */ +export type Int96Condition = { + __typename?: 'Int96Condition'; + /** Amount must exactly match this value. You may not specify this alongside `gte` or `lte`. */ + eq?: Maybe; + /** Amount must be greater than or equal to this value. */ + gte?: Maybe; + /** Amount must be less than or equal to this value. */ + lte?: Maybe; +}; + +/** A condition that must be met on an `Int96` field. */ +export type Int96ConditionInput = { + /** Amount must exactly match this value. You may not specify this alongside `gte` or `lte`. */ + eq?: InputMaybe; + /** Amount must be greater than or equal to this value. */ + gte?: InputMaybe; + /** Amount must be less than or equal to this value. */ + lte?: InputMaybe; +}; + +export type Int96Filter = { + /** Must exactly equal this Int96 value */ + eq?: InputMaybe; + /** Must be greater than or equal to this Int96 value */ + gte?: InputMaybe; + /** Must be less than or equal to this Int96 value */ + lte?: InputMaybe; + /** Must not equal this Int96 value */ + ne?: InputMaybe; +}; + +/** Equivalent to an HTTP 5XX - something went wrong with our API. */ +export type InternalError = Error & { + __typename?: 'InternalError'; + /** The status code of error. For example, 'ledger_not_found'. */ + code: Scalars['String']['output']; + /** The error message */ + message: Scalars['String']['output']; + /** Whether or not the operation is retryable */ + retryable: Scalars['Boolean']['output']; +}; + +/** Ledgers are databases designed for managing money */ +export type Ledger = { + __typename?: 'Ledger'; + /** When aggregating balances, all transactions within a 24 hour period starting at midnight UTC plus this offset are included in each day. */ + balanceUTCOffset: Scalars['UTCOffset']['output']; + created: Scalars['DateTime']['output']; + /** URL to the Fragment Dashboard for this Ledger. */ + dashboardUrl: Scalars['String']['output']; + /** Entry statistics for this Ledger. */ + entryStats: LedgerEntryStatsConnection; + id: Scalars['ID']['output']; + /** The IK passed into the [createLedger](/api-reference/api-mutations#createledger) mutation. This is treated as a unique identifier for this Ledger. */ + ik: Scalars['SafeString']['output']; + /** Ledger Account data migrations affecting this Ledger. */ + ledgerAccountDataMigrations: LedgerAccountDataMigrationConnection; + /** Query LedgerAccounts in Ledger. Ledger Accounts are paginated and returned in reverse-chronological order by their created date. */ + ledgerAccounts: LedgerAccountsConnection; + /** Query Ledger Entries in a Ledger. Ledger Entries are paginated and sorted in reverse-chronological order by posted date. */ + ledgerEntries: LedgerEntriesConnection; + /** Ledger Entry data migrations affecting this Ledger. */ + ledgerEntryDataMigrations: LedgerEntryDataMigrationConnection; + /** Query a Ledger Entry Group for this Ledger given its key and value. */ + ledgerEntryGroup: LedgerEntryGroup; + /** Query LedgerEntryGroups in Ledger. Ledger Entry Groups are paginated and returned in order lexigraphically key then inverse chronologically by created. */ + ledgerEntryGroups: LedgerEntryGroupsConnection; + /** + * List Ledger Lines across accounts in this Ledger, sorted by `posted` in reverse chronological order. + * Specify a single Ledger Account via the `ledgerAccount` field, or query across multiple accounts using the `path` filter or `ledgerAccount.in`. + */ + lines: LedgerLinesConnection; + /** Schema migrations affecting this Ledger. */ + migrations: LedgerMigrationConnection; + /** The name of the Ledger. Can be updated with the [updateLedger](/api-reference/api-mutations#updateledger) mutation. */ + name: Scalars['String']['output']; + /** Schema key associated with this Ledger. */ + schema?: Maybe; + type: LedgerTypes; + /** @deprecated Callers should not need to query or store this value. */ + workspaceId: Scalars['ID']['output']; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerEntryStatsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLedgerAccountDataMigrationsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLedgerAccountsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLedgerEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLedgerEntryDataMigrationsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLedgerEntryGroupArgs = { + ledgerEntryGroup: EntryGroupMatchInput; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLedgerEntryGroupsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerLinesArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter: LedgerLinesFilterSet; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Ledgers are databases designed for managing money */ +export type LedgerMigrationsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** A ledger account is a container for money */ +export type LedgerAccount = { + __typename?: 'LedgerAccount'; + /** Total of all lines in this ledger account and child ledger accounts of the same currency as this ledger account */ + balance: Scalars['Int96']['output']; + /** How much did the this ledger account's balance change during the specified period. This query will include all child accounts in the same currency as this ledger account. */ + balanceChange: Scalars['Int96']['output']; + /** How much did the this ledger account's balances change during the specified period. This query will include all child accounts of all currencies. */ + balanceChanges: CurrencyAmountConnection; + /** + * How much did the ledger account's balances change over a time period with a specified granularity. + * For example, if the period is 1969-07 and the granularity is daily, the query will return the balance changes for each day in the month. + */ + balanceChangesDuring: BalanceChangeDuringConnection; + /** Total of all lines in this ledger account and child ledger accounts in all currencies */ + balances: CurrencyAmountConnection; + /** + * The ledger account's balances over a time period with a specified granularity. + * For example, if the period is 1969-07 and the granularity is daily, the query will return the balance for each day in the month. + */ + balancesDuring: HistoricalBalanceConnection; + /** Total of all lines in child ledger accounts of the same currency as this ledger account */ + childBalance: Scalars['Int96']['output']; + /** How much did the this ledger account's childBalance change during the specified period. This query will only include child accounts which are in the same currency as this one. See childBalanceChanges to include children of different currencies. */ + childBalanceChange: Scalars['Int96']['output']; + /** How much did the this ledger account's child accounts' balances change during the specified period. This query will include all child accounts of all currencies. */ + childBalanceChanges: CurrencyAmountConnection; + /** + * How much did the ledger account's childBalances change over a time period with a specified granularity. + * For example, if the period is 1969-07 and the granularity is daily, the query will return the balance changes for each day in the month. + */ + childBalanceChangesDuring: BalanceChangeDuringConnection; + /** Total of all lines in child ledger accounts of this ledger in all currencies */ + childBalances: CurrencyAmountConnection; + /** + * The ledger account's childBalances over a time period with a specified granularity. + * For example, if the period is 1969-07 and the granularity is daily, the query will return the balance for each day in the month. + */ + childBalancesDuring: HistoricalBalanceConnection; + /** The child Ledger Accounts of this Ledger Accountw */ + childLedgerAccounts: LedgerAccountsConnection; + /** + * The clearing status of the Ledger Account. + * + * This field is null when the Ledger Account is not configured to be a Clearing account. + */ + clearingStatus?: Maybe; + /** The consistency configuration for this Ledger Account. This defines how updates to this Ledger Account's ownBalance are handled. */ + consistencyConfig: LedgerAccountConsistencyConfig; + created: Scalars['DateTime']['output']; + /** Currency of this ledger account */ + currency?: Maybe; + /** Indicates if the account allows multiple currencies or is restricted to a single currency */ + currencyMode: CurrencyMode; + /** URL to the Fragment Dashboard for this Ledger Account. */ + dashboardUrl: Scalars['String']['output']; + id: Scalars['ID']['output']; + /** The idempotency key used to create this account */ + ik: Scalars['String']['output']; + /** Ledger this account is in */ + ledger: Ledger; + /** All Ledger Entries that have posted to this Ledger Account. Ledger Entries are paginated and sorted in reverse-chronological order by posted date. */ + ledgerEntries: LedgerEntriesConnection; + /** ID of the ledger this account is in */ + ledgerId: Scalars['ID']['output']; + /** List Ledger Lines in this account, sorted by `posted` in reverse chronological order. Does not include Ledger Lines from child Ledger Accounts. */ + lines: LedgerLinesConnection; + /** The Link for the External Account that is linked to this ledger account */ + link?: Maybe; + /** External Account that is linked to this ledger account */ + linkedAccount?: Maybe; + /** The name of your Ledger Account */ + name?: Maybe; + /** Total of all lines in this ledger account, excluding all child ledger accounts */ + ownBalance: Scalars['Int96']['output']; + /** How much did the this ledger account's ownBalance change during the specified period. This query will exclude all child accounts. */ + ownBalanceChange: Scalars['Int96']['output']; + /** How much did the this ledger account's ownBalance change during the specified period. This is the total of all lines in this ledger account, excluding all child ledger accounts */ + ownBalanceChanges: CurrencyAmountConnection; + /** + * How much did the ledger account's ownBalances change over a time period with a specified granularity. + * For example, if the period is 1969-07 and the granularity is daily, the query will return the balance changes for each day in the month. + */ + ownBalanceChangesDuring: BalanceChangeDuringConnection; + /** Total of all lines across all currencies in this ledger account, excluding all child ledger accounts */ + ownBalances: CurrencyAmountConnection; + /** + * The ledger account's ownBalances over a time period with a specified granularity. + * For example, if the period is 1969-07 and the granularity is daily, the query will return the balance for each day in the month. + */ + ownBalancesDuring: HistoricalBalanceConnection; + /** The parent ledger account of this ledger account */ + parentLedgerAccount?: Maybe; + /** ID of the parent ledger account of this ledger account */ + parentLedgerAccountId?: Maybe; + /** + * The unique Path of the ledger account. This is a slash-delimited string containing the location of an account in its chart of accounts. + * For accounts created with a schema, this will be composed of account keys. Else, for accounts created with the createLedgerAccounts API, + * this will be composed of the IKs of an account and its ancestors. + */ + path: Scalars['String']['output']; + /** Payment configuration of this Ledger Account, if it is a payment account. */ + payment?: Maybe; + /** + * The posted timestamp window for this clearing account, representing the earliest and latest + * posted timestamps across all currencies. + * + * This field is null when the Ledger Account is not configured to be a Clearing account + * or when no entries have been posted to this account. + */ + postedWindow?: Maybe; + type: LedgerAccountTypes; + /** A list of external account transactions that haven't been reconciled to this ledger account yet. Only populated for linked ledger accounts. Transactions are sorted in reverse chronological order by posted date. */ + unreconciledTxs: TxsConnection; + /** @deprecated Callers should not need to query or store this value. */ + workspaceId: Scalars['ID']['output']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountBalanceArgs = { + at?: InputMaybe; + consistencyMode?: InputMaybe; + currency?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountBalanceChangeArgs = { + currency?: InputMaybe; + period: Scalars['Period']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountBalanceChangesArgs = { + period: Scalars['Period']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountBalanceChangesDuringArgs = { + currency?: InputMaybe; + duration: Scalars['Int']['input']; + granularity: Granularity; + startTime: Scalars['FirstMoment']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountBalancesArgs = { + after?: InputMaybe; + at?: InputMaybe; + before?: InputMaybe; + consistencyMode?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountBalancesDuringArgs = { + currency?: InputMaybe; + duration: Scalars['Int']['input']; + granularity: Granularity; + startTime: Scalars['FirstMoment']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildBalanceArgs = { + at?: InputMaybe; + consistencyMode?: InputMaybe; + currency?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildBalanceChangeArgs = { + currency?: InputMaybe; + period: Scalars['Period']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildBalanceChangesArgs = { + period: Scalars['Period']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildBalanceChangesDuringArgs = { + currency?: InputMaybe; + duration: Scalars['Int']['input']; + granularity: Granularity; + startTime: Scalars['FirstMoment']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildBalancesArgs = { + after?: InputMaybe; + at?: InputMaybe; + before?: InputMaybe; + consistencyMode?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildBalancesDuringArgs = { + currency?: InputMaybe; + duration: Scalars['Int']['input']; + granularity: Granularity; + startTime: Scalars['FirstMoment']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountChildLedgerAccountsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountLedgerEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountLinesArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountOwnBalanceArgs = { + at?: InputMaybe; + consistencyMode?: InputMaybe; + currency?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountOwnBalanceChangeArgs = { + currency?: InputMaybe; + period: Scalars['Period']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountOwnBalanceChangesArgs = { + period: Scalars['Period']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountOwnBalanceChangesDuringArgs = { + currency?: InputMaybe; + duration: Scalars['Int']['input']; + granularity: Granularity; + startTime: Scalars['FirstMoment']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountOwnBalancesArgs = { + after?: InputMaybe; + at?: InputMaybe; + before?: InputMaybe; + consistencyMode?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountOwnBalancesDuringArgs = { + currency?: InputMaybe; + duration: Scalars['Int']['input']; + granularity: Granularity; + startTime: Scalars['FirstMoment']['input']; +}; + + +/** A ledger account is a container for money */ +export type LedgerAccountUnreconciledTxsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** The clearing status of a Ledger Account. */ +export enum LedgerAccountClearingStatus { + /** The account has no outstanding balances. */ + Cleared = 'cleared', + /** The account has outstanding balances that have not been cleared. */ + Pending = 'pending' +} + +export type LedgerAccountClearingStatusFilter = { + /** Results must match the specified clearing account status */ + equalTo?: InputMaybe; +}; + +/** A set of conditions that a Ledger Account must meet for an operation to succeed. */ +export type LedgerAccountCondition = { + __typename?: 'LedgerAccountCondition'; + /** A condition that the `ownBalance` field must satisfy. Note that this condition always applies to the latest balance, not to balances at a specific date or time. See [Read balances](https://fragment.dev/guides/read-balances) for more on the different types of Ledger Account balances. */ + ownBalance?: Maybe; + /** A condition that the `totalBalance` field must satisfy. Note that this condition always applies to the latest balance, not to balances at a specific date or time. See [Read balances](https://fragment.dev/guides/read-balances) for more on the different types of Ledger Account balances. */ + totalBalance?: Maybe; +}; + +/** A set of conditions that a Ledger Account must meet for an operation to succeed. */ +export type LedgerAccountConditionInput = { + /** A condition that the ownBalance field must satisfy. Note that this condition always applies to the latest balance, not to balances at a specific date or time. See [Read balances](https://fragment.dev/read-balances) for more on the different types of Ledger Account balances. */ + ownBalance?: InputMaybe; + /** A condition that the totalBalance field must satisfy. Note that this condition always applies to the latest balance, not to balances at a specific date or time. See [Read balances](https://fragment.dev/read-balances) for more on the different types of Ledger Account balances. */ + totalBalance?: InputMaybe; +}; + +/** + * The consistency configuration of a Ledger Account's balance updates. + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ +export type LedgerAccountConsistencyConfig = { + __typename?: 'LedgerAccountConsistencyConfig'; + lines: LedgerLinesConsistencyMode; + /** + * If set to `strong`, then a Ledger Account's `ownBalance` updates will be strongly consistent with + * the API response. This Ledger Account's balance will be updated and + * available for strongly consistent reads once you receive an API response. + * + * Otherwise if not set or set to `eventual`, `ownBalance` updates are applied + * asynchronously and may not be immediately reflected in queries. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + ownBalanceUpdates: BalanceUpdateConsistencyMode; + /** + * If set to `strong`, then a Ledger Account's `ownBalance`, `childBalance`, and `balance` fields' updates will be strongly consistent with + * the API response. This Ledger Account's balance will be updated and + * available for strongly consistent reads once you receive an API response. + * + * Otherwise if not set or set to `eventual`, updates are applied + * asynchronously and may not be immediately reflected in queries. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + totalBalanceUpdates?: Maybe; +}; + +/** + * The payload configuring the consistency for this Ledger Account. + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ +export type LedgerAccountConsistencyConfigInput = { + /** + * The consistency configuration for Ledger Entry Groups affecting this account. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + groups?: InputMaybe>; + /** + * If set to `strong`, then a Ledger Account's `lines` updates will be strongly consistent with the API response. + * This Ledger Account's balance will be updated and available for strongly consistent reads before you receive an API response. + * + * Otherwise if unset or set to `eventual`, `lines` updates are applied asynchronously and may not be immediately reflected in queries. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + lines?: InputMaybe; + /** + * If set to `strong`, then a Ledger Account's `ownBalance` updates will be strongly consistent with the API response. + * This Ledger Account's balance will be updated and available for strongly consistent reads before you receive an API response. + * + * Otherwise if unset or set to `eventual`, `ownBalance` updates are applied asynchronously and may not be immediately reflected in queries. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + ownBalanceUpdates?: InputMaybe; + /** + * EXPERIMENTAL: If set to `strong`, then a Ledger Account's `totalBalance` updates will be strongly consistent with the API response. + * This Ledger Account's balance will be updated and available for strongly consistent reads before you receive an API response. + * + * Otherwise if unset or set to `eventual`, `totalBalance` updates are applied asynchronously and may not be immediately reflected in queries. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + totalBalanceUpdates?: InputMaybe; +}; + +/** Represents a data migration for a specific Ledger Account in a Ledger. */ +export type LedgerAccountDataMigration = LedgerDataMigration & { + __typename?: 'LedgerAccountDataMigration'; + /** The path of the Ledger Account being migrated. */ + accountPath: Scalars['String']['output']; + /** Current active migration info (null if migration is inactive). */ + currentMigration?: Maybe; + /** The historical transitions of this migration. */ + history: LedgerDataMigrationHistoryConnection; + /** The ledger entries to be migrated. */ + ledgerEntries: LedgerEntriesConnection; + /** The status of the data migration. */ + status: LedgerDataMigrationStatus; +}; + + +/** Represents a data migration for a specific Ledger Account in a Ledger. */ +export type LedgerAccountDataMigrationHistoryArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Represents a data migration for a specific Ledger Account in a Ledger. */ +export type LedgerAccountDataMigrationLedgerEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +export type LedgerAccountDataMigrationConnection = { + __typename?: 'LedgerAccountDataMigrationConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +export type LedgerAccountDataMigrationsFilterSet = { + /** Filter by Ledger Account path. */ + accountPath?: InputMaybe; + /** Filter by the status of the data migration. */ + status?: InputMaybe; +}; + +export type LedgerAccountFilter = { + /** Result must match the specified Ledger Account */ + equalTo?: InputMaybe; + /** Results can match any of specified Ledger Accounts */ + in?: InputMaybe>; +}; + +/** The consistency configuration for a specific Ledger Entry Group in this account. */ +export type LedgerAccountGroupConsistencyConfigInput = { + /** The group key for this configuration. */ + key: Scalars['SafeString']['input']; + /** + * If set to `strong`, then Ledger Entry Group `ownBalance`s updates for this account will be strongly consistent with the API response. + * This Ledger Account's Ledger Entry Group balances will be updated and available for strongly consistent reads before you receive an API response. + * + * Otherwise if unset or set to `eventual`, Ledger Entry Group `ownBalance` updates are applied asynchronously and may not be immediately reflected in queries. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + ownBalanceUpdates: BalanceUpdateConsistencyMode; +}; + +/** + * Specify a Ledger Account by using `id` or `path`. + * + * When specifying a Ledger Account by `path`, you must provide `ledger`. + */ +export type LedgerAccountMatchInput = { + /** The FRAGMENT ID of the Ledger Account */ + id?: InputMaybe; + /** The Ledger to which this Ledger Account belongs. This is required if you are specifying the Ledger Account by `path`. */ + ledger?: InputMaybe; + /** + * The unique path of the Ledger Account. + * This is a slash-delimited string containing the keys of an account and all its direct ancestors. + */ + path?: InputMaybe; +}; + +/** Payment configuration of a Ledger Account. */ +export type LedgerAccountPayment = { + __typename?: 'LedgerAccountPayment'; + penguin: Scalars['Boolean']['output']; +}; + +export type LedgerAccountTypeFilter = { + /** Results must be of the specified Ledger Account type */ + equalTo?: InputMaybe; + /** Results can have any of the specified Ledger Account types */ + in?: InputMaybe>; +}; + +export enum LedgerAccountTypes { + Asset = 'asset', + Expense = 'expense', + Income = 'income', + Liability = 'liability' +} + +/** A paginated list of Ledger Accounts */ +export type LedgerAccountsConnection = { + __typename?: 'LedgerAccountsConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export type LedgerAccountsFilterSet = { + /** Use this to filter Ledger Accounts by their clearing account status */ + clearingStatus?: InputMaybe; + /** + * Filter by the earliest posted timestamp across all currencies for clearing accounts. You must also provide clearingStatus in the same filter. + * Only clearing accounts where the minimum posted timestamp (across all currencies) matches this filter will be included. + */ + earliestPosted?: InputMaybe; + /** Use this to filter Ledger Accounts by their parent status */ + hasParentLedgerAccount?: InputMaybe; + /** Use this to filter Ledger Accounts by their linked status */ + isLinkedAccount?: InputMaybe; + /** + * Filter by the latest posted timestamp across all currencies for clearing accounts. You must also provide clearingStatus in the same filter. + * Only clearing accounts where the maximum posted timestamp (across all currencies) matches this filter will be included. + */ + latestPosted?: InputMaybe; + /** Use this to filter Ledger Accounts by their ID or path */ + ledgerAccount?: InputMaybe; + /** Use this to filter Ledger Accounts by their external linked account ID */ + linkedAccount?: InputMaybe; + /** Use this to filter Ledger Accounts by their parent account IDs */ + parentLedgerAccount?: InputMaybe; + /** + * A filter that must match the account path. Wildcards ('*') may be used only for template variables, and will only match a single variable each. + * For example: 'assets-root/accounts-receivable/merchant:*' would match: 'assets-root/accounts-receivable/merchant:1' and 'assets-root/accounts-receivable/merchant:1/child'. + * Wildcards may not be used outside of template variables. For example, passing in 'assets-root/*' as a filter is invalid and would raise a GraphQL error. + */ + path?: InputMaybe; + /** Use this to filter Ledger Accounts by their type */ + type?: InputMaybe; +}; + +/** Represents a data migration for a Ledger. */ +export type LedgerDataMigration = { + /** Current active migration info (null if migration is inactive). */ + currentMigration?: Maybe; + /** The historical transitions of this migration. */ + history: LedgerDataMigrationHistoryConnection; + /** The ledger entries to be migrated. */ + ledgerEntries: LedgerEntriesConnection; + /** The status of the data migration. */ + status: LedgerDataMigrationStatus; +}; + + +/** Represents a data migration for a Ledger. */ +export type LedgerDataMigrationHistoryArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Represents a data migration for a Ledger. */ +export type LedgerDataMigrationLedgerEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** A paginated list of migration history entries. */ +export type LedgerDataMigrationHistoryConnection = { + __typename?: 'LedgerDataMigrationHistoryConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +/** A single schema version in the migration history. */ +export type LedgerDataMigrationHistoryEntry = { + __typename?: 'LedgerDataMigrationHistoryEntry'; + /** The schema version. */ + schemaVersion: Scalars['Int']['output']; + /** The current status of this schema version (active if it's the latest and migration is active, otherwise inactive). */ + status: LedgerDataMigrationStatus; +}; + +/** The status of a ledger data migration. */ +export enum LedgerDataMigrationStatus { + /** The migration is active. */ + Active = 'active', + /** The migration is inactive. */ + Inactive = 'inactive' +} + +/** A paginated list of Ledger Entries */ +export type LedgerEntriesConnection = { + __typename?: 'LedgerEntriesConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export type LedgerEntriesFilterSet = { + /** Use this filter to filter Ledger Entries by their `posted` date. */ + date?: InputMaybe; + /** Use this to filter Ledger Entries by groups. The response will include entries that contain or do not contain specific groups. */ + group?: InputMaybe; + /** Use this to filter Ledger Entries that were posted using `reverseLedgerEntry`. */ + isReversal?: InputMaybe; + /** Use this to filter Ledger Entries that have been reversed. */ + isReversed?: InputMaybe; + /** Use to filter Ledger Entries by their IDs or IKs. */ + ledgerEntry?: InputMaybe; + /** Use this filter to filter Ledger Entries by their `posted` timestamp. */ + posted?: InputMaybe; + /** Use this filter to show hidden Ledger Entries. */ + showHidden?: InputMaybe; + /** Use this to filter Ledger Entries by tags. The response will include entries that contain tags matching the filter. */ + tag?: InputMaybe; + /** Use this to filter Ledger Entries by type. Ledger Entry types are defined in Schemas. */ + type?: InputMaybe; + /** Use this to filter Ledger Entries by their type version. */ + typeVersion?: InputMaybe; +}; + +export type LedgerEntry = { + __typename?: 'LedgerEntry'; + /** The conditions that were satisfied by this Ledger Entry when it was posted. */ + conditions: Array; + /** ISO-8601 timestamp this LedgerEntry was created in Fragment. */ + created: Scalars['DateTime']['output']; + /** URL to the Fragment Dashboard for this Ledger Entry. */ + dashboardUrl: Scalars['String']['output']; + /** Date this LedgerEntry posted to its Ledger e.g. "2021-01-01". */ + date: Scalars['Date']['output']; + /** Description posted for this Ledger Entry. */ + description?: Maybe; + /** The Ledger Entry Groups this Ledger Entry is in. */ + groups: Array; + /** + * Indicates whether this Ledger Entry is hidden when listing Ledger Entries. + * Reversed and Reversal Ledger Entries are hidden by default because taken together they have no impact on a Ledger's balances. + */ + hidden: Scalars['Boolean']['output']; + /** The ID of this LedgerEntry. */ + id: Scalars['ID']['output']; + /** The idempotency key used to post this ledger entry */ + ik: Scalars['String']['output']; + /** + * Indicates whether this Ledger Entry is a reversal of another Ledger Entry. + * If so, reverses will point to that Ledger Entry. + */ + isReversal: Scalars['Boolean']['output']; + /** + * Indicates whether this Ledger Entry has been reversed by another Ledger Entry. + * If so, reversedBy will point to that Ledger Entry. + */ + isReversed: Scalars['Boolean']['output']; + /** The Ledger that this Ledger Entry is posted to. */ + ledger: Ledger; + /** The ID of the Ledger this Ledger Entry is posted to. */ + ledgerId: Scalars['ID']['output']; + /** Lines posted in this Ledger Entry. */ + lines: LedgerLinesConnection; + /** The parameters used to post this Ledger Entry. */ + parameters?: Maybe; + /** ISO-8601 timestamp this LedgerEntry posted to its Ledger. */ + posted: Scalars['DateTime']['output']; + /** The reversal history of this Ledger Entry. Each entry in this connection shares the same IK. */ + reversalHistory: LedgerEntriesConnection; + /** The position of this Ledger Entry in its reversalHistory. This is a one-indexed value, so the initial entry will have reversalPosition 1. */ + reversalPosition: Scalars['Int']['output']; + /** ISO-8601 timestamp of when this Ledger Entry was reversed. */ + reversedAt?: Maybe; + /** The Ledger Entry that reversed this Ledger Entry. */ + reversedBy?: Maybe; + /** The Ledger Entry that was reversed by this Ledger Entry. */ + reverses?: Maybe; + /** The set of tags attached to this Ledger Entry. */ + tags: Array; + /** The type of the Ledger Entry. */ + type?: Maybe; + /** The version of the Ledger Entry type used when it was posted. */ + typeVersion?: Maybe; + /** @deprecated Callers should not need to query or store this value. */ + workspaceId: Scalars['ID']['output']; +}; + +/** A set of pre-conditions and post-conditions that a Ledger Account must have satisfied. Each `LedgerEntryCondition` has at least one of `precondition` or `postcondition`. */ +export type LedgerEntryCondition = { + __typename?: 'LedgerEntryCondition'; + /** The Ledger Account that must satisfied the provided conditions. */ + account: LedgerAccount; + /** The currency of the balance associated with this `LedgerEntryCondition`. */ + currency: Currency; + /** The conditions that must be satisfied after the operation. */ + postcondition?: Maybe; + /** The conditions that must be satisfied prior to the operation. */ + precondition?: Maybe; +}; + +/** A set of pre-conditions and post-conditions that a Ledger Account balance must meet for an operation to succeed. You must specify at least one of `precondition` or `postcondition` for each condition. */ +export type LedgerEntryConditionInput = { + /** The Ledger Account that must satisfy the provided conditions. */ + account: LedgerAccountMatchInput; + /** For Ledger Accounts in the `multi` currency mode, you must specify the currency of the balance affected by the condition. You only need to specify this field for multi-currency accounts. */ + currency?: InputMaybe; + /** The conditions that must hold after the operation. */ + postcondition?: InputMaybe; + /** The conditions that must hold prior to the operation. */ + precondition?: InputMaybe; +}; + +/** Represents a data migration for a specific entry type in a Ledger. */ +export type LedgerEntryDataMigration = LedgerDataMigration & { + __typename?: 'LedgerEntryDataMigration'; + /** Current active migration info (null if migration is inactive). */ + currentMigration?: Maybe; + /** The entry type being migrated. */ + entryType: Scalars['SafeString']['output']; + /** The historical transitions of this migration. */ + history: LedgerDataMigrationHistoryConnection; + /** The ledger entries to be migrated. */ + ledgerEntries: LedgerEntriesConnection; + /** The status of the data migration. */ + status: LedgerDataMigrationStatus; + /** The version of the entry type being migrated. */ + typeVersion: Scalars['Int']['output']; +}; + + +/** Represents a data migration for a specific entry type in a Ledger. */ +export type LedgerEntryDataMigrationHistoryArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** Represents a data migration for a specific entry type in a Ledger. */ +export type LedgerEntryDataMigrationLedgerEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +export type LedgerEntryDataMigrationConnection = { + __typename?: 'LedgerEntryDataMigrationConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +export type LedgerEntryDataMigrationsFilterSet = { + /** Filter by Ledger Entry type. */ + entryType?: InputMaybe; + /** Filter by the status of the data migration. */ + status?: InputMaybe; + /** Filter by Ledger Entry type version. */ + typeVersion?: InputMaybe; +}; + +export type LedgerEntryFilter = { + /** Result must be the specified Ledger Entry. */ + equalTo?: InputMaybe; + /** Result can be any of the specified Ledger Entries. Limited to 100 items maximum. */ + in?: InputMaybe>; +}; + +/** A group of Ledger Entries */ +export type LedgerEntryGroup = { + __typename?: 'LedgerEntryGroup'; + balances: LedgerEntryGroupBalanceConnection; + /** ISO-8601 timestamp this LedgerEntryGroup was created in Fragment. */ + created?: Maybe; + /** URL to the Fragment Dashboard for this Ledger Entry Group. */ + dashboardUrl: Scalars['String']['output']; + /** The key of this Ledger Entry Group. */ + key: Scalars['SafeString']['output']; + /** The Ledger that this Ledger Entry Group is within. */ + ledger: Ledger; + ledgerEntries: LedgerEntriesConnection; + /** The ID of the Ledger this Ledger Entry Group is within. */ + ledgerId: Scalars['ID']['output']; + /** The value associated with Ledger Entry Group. */ + value: Scalars['SafeString']['output']; +}; + + +/** A group of Ledger Entries */ +export type LedgerEntryGroupBalancesArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A group of Ledger Entries */ +export type LedgerEntryGroupLedgerEntriesArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** Represents the total effect of a Ledger Entry Group on a Ledger Account balance for a single currency. */ +export type LedgerEntryGroupBalance = { + __typename?: 'LedgerEntryGroupBalance'; + /** The Ledger Account whose balance is affected. */ + account: LedgerAccount; + /** The currency of the affected balance. */ + currency: Currency; + /** The total balance change for this Ledger Account and currency. */ + ownBalance: Scalars['Int96']['output']; +}; + + +/** Represents the total effect of a Ledger Entry Group on a Ledger Account balance for a single currency. */ +export type LedgerEntryGroupBalanceOwnBalanceArgs = { + consistencyMode?: InputMaybe; +}; + +/** A set of balance changes for a specific Ledger Entry Group. */ +export type LedgerEntryGroupBalanceConnection = { + __typename?: 'LedgerEntryGroupBalanceConnection'; + nodes: Array; + pageInfo: PageInfo; +}; + +/** Filter Ledger Entry Groups by their balance impact on a Ledger Account. If a group has a matching balance for the specified account, the group will be included in the results. */ +export type LedgerEntryGroupBalanceFilter = { + /** A Ledger Entry Group will be included in the result if it has a balance for the specified account. If 'account' is the only filter specified, then any non-null balance in any currency will match. */ + account: GroupBalanceAccountFilter; + /** A Ledger Entry Group will be included in the result if it has a balance for the specified account in the specified currency. If the 'ownBalance' filter is omitted then any non-null balance will match. */ + currency?: InputMaybe; + /** A Ledger Entry Group will be included in the result if it has a balance for the specified account that passes the specified value predicate. If the 'currency' filter is omitted then any balance in any currency that passes the predicate will match. If the 'currency' filter is included, the value predicate will only be evaluated against the specified currency. */ + ownBalance?: InputMaybe; +}; + +/** Optional filters for querying balances on a Ledger Entry Group. */ +export type LedgerEntryGroupBalanceFilterSet = { + /** Filter to a subset of accounts */ + account?: InputMaybe; + /** Filter to one or more currencies */ + currency?: InputMaybe; + /** Filter to only balances in a certain range */ + ownBalance?: InputMaybe; +}; + +export type LedgerEntryGroupInput = { + /** The key of this group. Can be up to 128 characters long. */ + key: Scalars['SafeString']['input']; + /** The value associated with this group's key. Can be up to 128 characters long. */ + value: Scalars['SafeString']['input']; +}; + +export type LedgerEntryGroupMatchInput = { + key: Scalars['SafeString']['input']; + ledger: LedgerMatchInput; + value: Scalars['SafeString']['input']; +}; + +/** A paginated list of Ledger Entry Groups */ +export type LedgerEntryGroupsConnection = { + __typename?: 'LedgerEntryGroupsConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +export type LedgerEntryGroupsFilterSet = { + /** Filter Ledger Entry Groups by their balance impact on a Ledger Account. If a group has a matching balance for the specified account, the group will be included in the results. */ + balance?: InputMaybe; + /** Use to filter Ledger Entry Groups by their created timestamp */ + created?: InputMaybe; + /** Use to filter Ledger Entry Groups by their key */ + key?: InputMaybe; + /** Use to filter Ledger Entry Groups by their value */ + value?: InputMaybe; +}; + +/** Ledger Entries are limited to 30 Ledger Lines. */ +export type LedgerEntryInput = { + /** Conditions that must be satisfied to post this Ledger Entry. The Ledger Entry will reject with a BadRequestError if any condition is not met. You can only add a condition on a Ledger Account containing a Line in this Ledger Entry. */ + conditions?: InputMaybe>; + /** If specified, will also be used as the description for LedgerLines unless they specify their own description. */ + description?: InputMaybe; + /** Adds this Ledger Entry to this set of Ledger Entry Groups */ + groups?: InputMaybe>; + /** The Ledger to which to post this Ledger Entry. Must be linked to a Schema that defines the provided Ledger Entry type. */ + ledger?: InputMaybe; + /** The Ledger Lines to create as part of this Ledger Entry. This cannot be used with Ledger Entries that have a 'type' i.e. Ledger Entries defined in the Schema. This can be useful during non-routine operations such as an incident. It is not recommended to use 'lines' during routine operations. */ + lines?: InputMaybe>; + /** Parameters to be included in a templated Ledger Entry. All provided parameters must be present in the typed Ledger Entry within the Schema linked to the provided Ledger. */ + parameters?: InputMaybe; + /** ISO 8601 timestamp to post this Ledger Entry e.g. "2021-01-01" or "2021-01-01T16:45:00Z". Will error out if supplied to reconcileTx or createOrder since the transaction timestamp will be used instead */ + posted?: InputMaybe; + /** A set of tags attached to this Ledger Entry. */ + tags?: InputMaybe>; + /** The type of the Ledger Entry. Must be defined in the Schema linked to the Ledger specified below. */ + type?: InputMaybe; + /** Experimental: This field is reserved for an upcoming feature and is not yet supported. */ + typeVersion?: InputMaybe; +}; + +/** Specify a Ledger Entry by using `id`. */ +export type LedgerEntryMatchInput = { + /** The FRAGMENT ID of the Ledger Entry */ + id?: InputMaybe; + /** The IK provided to the `addLedgerEntry` mutation or the `ik` field returned from a `reconcileTx` mutation. This is required if you have not provided `id`. */ + ik?: InputMaybe; + /** The FRAGMENT ID of the Ledger to which this Ledger Entry belongs. This is required if you have not provided `id`. */ + ledger?: InputMaybe; +}; + +/** Posting count statistics for a specific type and typeVersion of entry in a Ledger. */ +export type LedgerEntryStats = { + __typename?: 'LedgerEntryStats'; + /** The total number of entries of this type. */ + count: Scalars['Int96']['output']; + /** The ledger ID these stats are for. */ + ledgerId: Scalars['SafeString']['output']; + /** The net number of entries (count - reversalsCount). */ + netCount: Scalars['Int96']['output']; + /** The number of entries that are reversals. */ + reversalsCount: Scalars['Int96']['output']; + /** The schema key associated with these stats. */ + schemaKey: Scalars['SafeString']['output']; + /** The type of entry these stats are for. */ + type: Scalars['SafeString']['output']; + /** The version of the entry type these stats are for. */ + typeVersion: Scalars['Int']['output']; +}; + +/** A paginated list of Ledger Entry Stats */ +export type LedgerEntryStatsConnection = { + __typename?: 'LedgerEntryStatsConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +/** A tag attached to a Ledger Entry. */ +export type LedgerEntryTag = { + __typename?: 'LedgerEntryTag'; + /** The key of this tag. */ + key: Scalars['SafeString']['output']; + /** The value associated with this tag's key. */ + value: Scalars['SafeString']['output']; +}; + +export type LedgerEntryTagInput = { + /** The key of this tag. Can be up to 128 characters long. */ + key: Scalars['SafeString']['input']; + /** The value associated with this tag's key. Can be up to 128 characters long. */ + value: Scalars['SafeString']['input']; +}; + +export type LedgerLine = { + __typename?: 'LedgerLine'; + /** LedgerAccount that contains this line */ + account: LedgerAccount; + accountId: Scalars['ID']['output']; + /** How much this line's LedgerAccount's balance changed in integer cents (i.e. in USD 100 is 1 dollar, 100 cents) */ + amount: Scalars['Int96']['output']; + /** ISO-8601 timestamp this LedgerLine was created in Fragment */ + created?: Maybe; + /** Currency of this LedgerLine */ + currency?: Maybe; + /** Date this LedgerLine posted to its LedgerAccount e.g. "2021-01-01" */ + date?: Maybe; + /** Description of this LedgerLine */ + description?: Maybe; + /** ID in the external system of the payment or transfer that created the transaction linked to this LedgerLine */ + externalTransferId?: Maybe; + /** Whether the transaction linked to this LedgerLine was a payment or transfer */ + externalTransferType?: Maybe; + /** ID in the external system of the transaction linked to this LedgerLine */ + externalTxId?: Maybe; + /** + * Indicates whether this Ledger Line is hidden when listing Ledger Lines. + * Reversed and Reversal Ledger Lines are hidden by default because taken together they have no impact on a Ledger Account's balance + */ + hidden: Scalars['Boolean']['output']; + id: Scalars['ID']['output']; + /** + * Indicates whether this Ledger Line is a reversal of another Ledger Line. + * If so, reverses will point to that Ledger Line. + */ + isReversal: Scalars['Boolean']['output']; + /** + * Indicates whether this Ledger Line has been reversed by another Ledger Line. + * If so, reversedBy will point to that Ledger Line. + */ + isReversed: Scalars['Boolean']['output']; + key?: Maybe; + ledger: Ledger; + /** LedgerEntry that contains this line */ + ledgerEntry: LedgerEntry; + /** ID of the LedgerEntry that contains this line */ + ledgerEntryId: Scalars['ID']['output']; + /** Ledger that contains this line */ + ledgerId: Scalars['ID']['output']; + /** ID in the external system of destination or source bank account for an internal bank transfer. Only for internal bank transfers - see otherTxId */ + otherTxExternalAccountExternalId?: Maybe; + /** FRAGMENT ID of destination or source bank account. Only for internal bank transfers - see otherTxId */ + otherTxExternalAccountId?: Maybe; + /** ID in the external system of transaction in the destination or source bank account. Only for internal bank transfers - see otherTxId */ + otherTxExternalId?: Maybe; + /** FRAGMENT ID of the transaction in the destination account (if sending money from this account) or source account (if pulling money into this account). Only applicable if this line is linked to a transaction created through an internal transfer */ + otherTxId?: Maybe; + /** ISO-8601 timestamp this LedgerLine posted to its LedgerAccount */ + posted?: Maybe; + /** ISO-8601 timestamp of when this Ledger Line was reversed. */ + reversedAt?: Maybe; + /** The Ledger Line that reverses the balance changes of this Ledger Line. */ + reversedBy?: Maybe; + /** The Ledger Line whose balance changes are reversed by this Ledger Line. */ + reverses?: Maybe; + /** Tags attached to this Ledger Line. */ + tags: Array; + /** The transaction linked to this LedgerLine */ + tx?: Maybe; + /** Fragment ID of the transaction linked to this LedgerLine */ + txId?: Maybe; + /** credit or debit */ + type: TxType; + /** @deprecated Callers should not need to query or store this value. */ + workspaceId: Scalars['ID']['output']; +}; + + +export type LedgerLineAmountArgs = { + absolute?: InputMaybe; +}; + +export type LedgerLineInput = { + /** The LedgerAccount this line is being added to */ + account: LedgerAccountMatchInput; + /** A positive amount increases the balance of its LedgerAccount, a negative amount reduces the balance of its LedgerAccount */ + amount?: InputMaybe; + /** The currency the ledger line is in */ + currency?: InputMaybe; + /** If not specified the description from the parent LedgerEntryInput will be used */ + description?: InputMaybe; + /** Optional identifier for Ledger Line. You can filter lines by key using [LedgerLinesFilterSet](https://fragment.dev/api-reference/api-types#filter-types-ledgerlinesfilterset). */ + key?: InputMaybe; + /** A set of tags attached to this Ledger Line. */ + tags?: InputMaybe>; + /** Required for reconcileTx to specify the transaction being reconciled, you can specify either the FRAGMENT ID or external ID of the transaction */ + tx?: InputMaybe; +}; + +/** Specify a Ledger Line by using `id`. */ +export type LedgerLineMatchInput = { + /** The FRAGMENT ID of the ledger line */ + id: Scalars['ID']['input']; +}; + +/** A tag attached to a Ledger Line. */ +export type LedgerLineTag = { + __typename?: 'LedgerLineTag'; + /** The key of this tag. */ + key: Scalars['SafeString']['output']; + /** The value associated with this tag's key. */ + value: Scalars['SafeString']['output']; +}; + +/** A paginated list of Ledger Lines */ +export type LedgerLinesConnection = { + __typename?: 'LedgerLinesConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export enum LedgerLinesConsistencyMode { + Eventual = 'eventual', + Strong = 'strong' +} + +export type LedgerLinesFilterSet = { + /** Filter by the created timestamp of the Ledger Line. This is the wall-clock time when the Ledger Line was created. */ + created?: InputMaybe; + /** Filter by the currency of the Ledger Line. */ + currency?: InputMaybe; + /** Use this filter to filter Ledger Lines by their `posted` date. */ + date?: InputMaybe; + /** Use this to filter Ledger Lines that were posted to this Ledger Account, using `reverseLedgerEntry`. */ + isReversal?: InputMaybe; + /** Use this to filter Ledger Lines that have been reversed. */ + isReversed?: InputMaybe; + /** Use this to filter Ledger Lines by key. Ledger Line keys are defined in Schemas. */ + key?: InputMaybe; + /** Specify which Ledger Account to read lines from. Required when querying lines via `Ledger.lines` without a `path` filter. Not allowed when querying via `LedgerAccount.lines`. */ + ledgerAccount?: InputMaybe; + /** + * A filter that string matches the account path. Wildcards ('*') can be used to return lines across multiple accounts. + * To search for all instances of a a Ledger Account template, use the `matches` filter with an wildcard character in place of the template value e.g. `assets/user:*`. This returns lines from all instances of this template, interleaved by `posted` timestamp. + * To search for all descendant Ledger Accounts under a given path, use a trailing `/*` in the `matches` filter e.g. `assets/user:user-1>/*`. This returns lines from all descendants at any depth, but not lines from the parent account at `assets/user:user-1>`. + * To OR multiple `matches` patterns and get a single paginated list, use `matchesAny` — e.g. `matchesAny: ["assets/user:user-1/*", "assets/user:user-2/*"]` returns descendants of both prefixes interleaved by `posted` timestamp. + * Cannot be combined with `ledgerAccount` filter. Not allowed when querying via `LedgerAccount.lines`. You cannot use wildcards for both descendant and template instance matching in the same query. + */ + path?: InputMaybe; + /** Use this filter to filter Ledger Lines by their `posted` timestamp. */ + posted?: InputMaybe; + /** Use this filter to find hidden Ledger Lines. */ + showHidden?: InputMaybe; + /** Filter Ledger Lines by tag. Only matches lines that have the specified tags attached directly to them. */ + tag?: InputMaybe; + type?: InputMaybe; +}; + +/** Specify a Ledger by using `id` or `ik`. */ +export type LedgerMatchInput = { + /** The FRAGMENT ID of the Ledger */ + id?: InputMaybe; + /** The IK passed into the [createLedger](/api-reference/api-mutations#createledger) mutation. This is treated as a second unique identifier for this Ledger. */ + ik?: InputMaybe; +}; + +/** + * Represents a Schema being applied to a Ledger. + * It contains metadata about the Ledger, the Schema, and the status of the migration. + */ +export type LedgerMigration = { + __typename?: 'LedgerMigration'; + /** The Ledger that the migration is run on. */ + ledger: Ledger; + schemaVersion: SchemaVersion; + /** The status of the Ledger Migration. */ + status: LedgerMigrationStatus; +}; + +/** A paginated list of Ledger Migrations */ +export type LedgerMigrationConnection = { + __typename?: 'LedgerMigrationConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +/** The status of a ledger migration. */ +export enum LedgerMigrationStatus { + /** + * The Ledger Migration has been successfully completed. + * This is a terminal state. + */ + Completed = 'completed', + /** + * The Ledger Migration has failed. + * This can happen either due to an invalid schema or an internal error. + * This is a terminal state. + */ + Failed = 'failed', + /** The Ledger Migration has been queued. */ + Queued = 'queued', + /** + * The Ledger Migration has been skipped because a newer version is available. + * This is a terminal state. + */ + Skipped = 'skipped', + /** The Ledger Migration has been started. */ + Started = 'started' +} + +export type LedgerTypeFilter = { + equalTo?: InputMaybe; + /** Must match one of the values provided. Limited to 100 items maximum. */ + in?: InputMaybe>; +}; + +export enum LedgerTypes { + Double = 'double' +} + +/** A paginated list of Ledgers */ +export type LedgersConnection = { + __typename?: 'LedgersConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export type LedgersFilterSet = { + hasSchema?: InputMaybe; + type?: InputMaybe; +}; + +export type Link = { + /** ISO-8601 timestamp when the Link was created. */ + created: Scalars['String']['output']; + /** URL to the Fragment Dashboard for this Link. */ + dashboardUrl: Scalars['String']['output']; + /** A list of External Accounts associated with this Link. */ + externalAccounts: ExternalAccountsConnection; + /** FRAGMENT ID of the Link. */ + id: Scalars['ID']['output']; + /** Name of the Link as it appears in the Dashboard. */ + name: Scalars['String']['output']; +}; + +export type LinkMatchInput = { + id: Scalars['ID']['input']; +}; + +/** The type of Link an external account belongs to. */ +export enum LinkType { + /** A Custom Link */ + CustomLink = 'CustomLink', + /** An Increase Link */ + IncreaseLink = 'IncreaseLink', + /** A Stripe Link */ + StripeLink = 'StripeLink', + /** A Unit Link */ + UnitLink = 'UnitLink' +} + +/** A paginated list of Links */ +export type LinksConnection = { + __typename?: 'LinksConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +/** An object defining the input for migrating a Ledger Entry. */ +export type MigrateLedgerEntryInput = { + /** The Ledger Entry to migrate */ + id: Scalars['ID']['input']; + /** The Ledger Entry you want to migrate it to */ + newLedgerEntry: LedgerEntryInput; +}; + +export type MigrateLedgerEntryResponse = BadRequestError | InternalError | MigrateLedgerEntryResult; + +export type MigrateLedgerEntryResult = { + __typename?: 'MigrateLedgerEntryResult'; + /** Whether this migration was an IK replay or not */ + isIkReplay: Scalars['Boolean']['output']; + /** The new Ledger Entry posted as a result of the migration */ + newLedgerEntry: LedgerEntry; + /** The Ledger Entry that was migrated */ + reversedLedgerEntry: LedgerEntry; + /** The reversal Ledger Entry that was posted to reverse the Ledger Entry being migrated */ + reversingLedgerEntry: LedgerEntry; +}; + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type Mutation = { + __typename?: 'Mutation'; + _empty?: Maybe; + /** + * Batch version of [addLedgerEntry](http://localhost:3001/api-reference/ledger-mutations#addledgerentry). + * + * Adds a batch of Ledger Entries in one synchronous and atomic transaction. Either every entry is added or none are. + */ + addLedgerEntries: AddLedgerEntriesResponse; + /** Adds a Ledger Entry to a Ledger. This Ledger Entry cannot be into a Linked Ledger Account. For that, use [reconcileTx](https://fragment.dev/api-reference/api-mutations#reconciletx) */ + addLedgerEntry: AddLedgerEntryResponse; + /** Creates a custom currency. */ + createCustomCurrency: CreateCustomCurrencyResponse; + /** Custom Links let you integrate external systems that don't have native support. See [Custom Links](https://fragment.dev/guides/sync-payments#custom-link) */ + createCustomLink: CreateCustomLinkResponse; + /** Creates a Ledger. */ + createLedger: CreateLedgerResponse; + createLedgerAccount: CreateLedgerAccountResponse; + /** This API call is used to create Ledger Accounts. It is only used if you are not using a Schema. Unlike other mutations that take a single IK, 'createLedgerAccount' accepts an IK for each of the ledger accounts in the request payload. This is so you can recover in the case of a partial failure. One API call can create up to 200 Ledger Accounts, up to 10 levels deep. */ + createLedgerAccounts: CreateLedgerAccountsResponse; + /** + * EXPERIMENTAL — subject to change. + * + * Create a Payment. + */ + createPayment: CreatePaymentResponse; + /** Delete Txs on a Custom Link. Once deleted, a Tx will not show up in listing queries, but can be resolved by if you lookup by its Fragment ID. */ + deleteCustomTxs: DeleteCustomTxsResponse; + /** + * Delete a Ledger. + * + * After using the deleteLedger mutation you can re-use the ik in a new ledger after a 30 second wait. + */ + deleteLedger: DeleteLedgerResponse; + /** Delete a Schema */ + deleteSchema: DeleteSchemaResponse; + /** + * Migrate an existing Ledger Entry to a new type and typeVersion. + * + * Migrating a Ledger Entry will do the following: + * 1. Reverse the existing Ledger Entry + * 2. Post a new Ledger Entry with the new type, typeVersion, and parameters provided + */ + migrateLedgerEntry: MigrateLedgerEntryResponse; + /** This mutation is used to [reconcile](https://fragment.dev/guides/reconcile-payments#reconcile-a-tx) transactions from an external system into a Ledger Entry. This mutation does not require an idempotency key since a transaction can only be reconciled once per Linked Ledger Account. If you are reconciling a transfer between two Link Accounts which are both linked to the same Ledger, use a transit account in between to split the transfer into two `reconcileTx` calls. */ + reconcileTx: ReconcileTxResponse; + /** Reverses a Ledger Entry */ + reverseLedgerEntry: ReverseLedgerEntryResponse; + /** + * Stores a Schema in your workspace. If no Schema with the same key exists in your worksapce, a new Schema is created. + * Else, the Schema is updated, and every Ledger associated with it is migrated to the latest version. + */ + storeSchema: StoreSchemaResponse; + /** Once you've created a [Custom Link](https://fragment.dev/guides/sync-payments#custom-link), create accounts under it using this mutation. Each Custom Account is an immutable, single-entry view of all the transactions in the external account. You can sync up to 100 Custom Accounts in one API call. */ + syncCustomAccounts: SyncCustomAccountsResponse; + /** You can create transactions under a Custom Account in a [Custom Link](https://fragment.dev/guides/sync-payments#custom-link) using this mutation. Once you've imported transactions, you can use the reconcileTx mutation to add them to a Ledger via the Linked Ledger Account. You can sync up to 100 Custom Transactions in one API call. */ + syncCustomTxs: SyncCustomTxsResponse; + /** Updates a Ledger. Currently, you can change only the Ledger 'name'. */ + updateLedger: UpdateLedgerResponse; + /** Updates a ledger account. Only supports name right now. */ + updateLedgerAccount: UpdateLedgerAccountResponse; + /** Update a ledger entry */ + updateLedgerEntry: UpdateLedgerEntryResponse; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationAddLedgerEntriesArgs = { + entries: Array; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationAddLedgerEntryArgs = { + entry: LedgerEntryInput; + ik: Scalars['SafeString']['input']; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationCreateCustomCurrencyArgs = { + customCurrency: CreateCustomCurrencyInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationCreateCustomLinkArgs = { + ik: Scalars['SafeString']['input']; + name: Scalars['String']['input']; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationCreateLedgerArgs = { + ik: Scalars['SafeString']['input']; + ledger: CreateLedgerInput; + schema?: InputMaybe; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationCreateLedgerAccountArgs = { + ik: Scalars['SafeString']['input']; + ledger: LedgerMatchInput; + ledgerAccount: CreateLedgerAccountInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationCreateLedgerAccountsArgs = { + ledger: LedgerMatchInput; + ledgerAccounts: Array; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationCreatePaymentArgs = { + amount: Scalars['Int96']['input']; + ik: Scalars['SafeString']['input']; + ledger: LedgerMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationDeleteCustomTxsArgs = { + txs: Array; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationDeleteLedgerArgs = { + ledger: LedgerMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationDeleteSchemaArgs = { + schema: SchemaMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationMigrateLedgerEntryArgs = { + input: MigrateLedgerEntryInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationReconcileTxArgs = { + entry: LedgerEntryInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationReverseLedgerEntryArgs = { + id: Scalars['ID']['input']; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationStoreSchemaArgs = { + schema: SchemaInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationSyncCustomAccountsArgs = { + accounts: Array; + link: LinkMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationSyncCustomTxsArgs = { + link: LinkMatchInput; + txs: Array; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationUpdateLedgerArgs = { + ledger: LedgerMatchInput; + update: UpdateLedgerInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationUpdateLedgerAccountArgs = { + ledgerAccount: LedgerAccountMatchInput; + update: UpdateLedgerAccountInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-mutations) */ +export type MutationUpdateLedgerEntryArgs = { + ledgerEntry: LedgerEntryMatchInput; + update: UpdateLedgerEntryInput; +}; + +/** Equivalent to an HTTP 404 */ +export type NotFoundError = Error & { + __typename?: 'NotFoundError'; + /** The status code of error. For example, 'ledger_not_found'. */ + code: Scalars['String']['output']; + /** The error message */ + message: Scalars['String']['output']; + /** Whether or not the operation is retryable */ + retryable: Scalars['Boolean']['output']; +}; + +/** An object containing [pagination](https://fragment.dev/guides/query-data#basics-pagination) details. */ +export type PageInfo = { + __typename?: 'PageInfo'; + endCursor?: Maybe; + hasNextPage: Scalars['Boolean']['output']; + hasPreviousPage: Scalars['Boolean']['output']; + startCursor?: Maybe; +}; + +export type Payment = { + __typename?: 'Payment'; + /** The secret handed to the payments SDK to render the payment method capture. */ + clientSecret: Scalars['String']['output']; + /** The status of this Payment. */ + status: PaymentStatus; +}; + +/** + * EXPERIMENTAL — subject to change. + * + * Status of a Payment. + */ +export enum PaymentStatus { + Processing = 'processing', + RequiresConfirmation = 'requires_confirmation', + Settled = 'settled' +} + +/** + * Controls how lines are posted for a Ledger Entry. + * New entries created via the dashboard default to `net_amounts`. + * Existing entries without this field set are treated as `raw_lines`. + */ +export enum PostLinesAs { + /** Lines targeting the same account, currency, and tx are aggregated into a single line with the net amount. Lines that sum to zero are skipped. If all lines sum to zero, no lines are skipped. */ + NetAmounts = 'net_amounts', + /** Lines are posted as-is without aggregation. */ + RawLines = 'raw_lines', + /** Lines with a zero amount are skipped, but lines are not aggregated. If all lines have a zero amount, no lines are skipped. */ + SkipZeroLines = 'skip_zero_lines' +} + +/** + * The posted timestamp window for a clearing account, representing the earliest and latest + * posted timestamps across all currencies. + */ +export type PostedWindow = { + __typename?: 'PostedWindow'; + /** The earliest posted timestamp across all currencies for this clearing account. */ + earliest: Scalars['DateTime']['output']; + /** The latest posted timestamp across all currencies for this clearing account. */ + latest: Scalars['DateTime']['output']; +}; + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type Query = { + __typename?: 'Query'; + _empty?: Maybe; + /** Query Custom Currencies in the workspace */ + customCurrencies: CustomCurrenciesConnection; + /** Get External Account by Link and External ID or FRAGMENT ID. */ + externalAccount?: Maybe; + /** Get a Ledger by ID */ + ledger?: Maybe; + /** Get a Ledger Account by ID */ + ledgerAccount?: Maybe; + /** Get Ledger Entry by ID. */ + ledgerEntry?: Maybe; + /** Query a Ledger Entry Group given its Ledger, key, and value. */ + ledgerEntryGroup?: Maybe; + /** Get the reversal history of a Ledger Entry. */ + ledgerEntryHistory: LedgerEntriesConnection; + /** Get LedgerLine by ID */ + ledgerLine?: Maybe; + /** Query Ledgers in workspace. Ledgers are paginated and returned in reverse-chronological order by their created date. */ + ledgers: LedgersConnection; + /** Get a Link by ID. Returns a BadRequestError if the Link is not found. */ + link?: Maybe; + /** Get all links in a workspace */ + links: LinksConnection; + /** Get a Schema by key. */ + schema?: Maybe; + /** Retrieve all of the Schemas in the workspace. */ + schemas: SchemaConnection; + /** Get a Tx by ID */ + tx?: Maybe; + /** Get the current Workspace */ + workspace: Workspace; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryCustomCurrenciesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryExternalAccountArgs = { + externalAccount: ExternalAccountMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgerArgs = { + ledger: LedgerMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgerAccountArgs = { + ledgerAccount: LedgerAccountMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgerEntryArgs = { + ledgerEntry: LedgerEntryMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgerEntryGroupArgs = { + ledgerEntryGroup: LedgerEntryGroupMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgerEntryHistoryArgs = { + ledgerEntry: LedgerEntryMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgerLineArgs = { + ledgerLine: LedgerLineMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLedgersArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryLinkArgs = { + link: LinkMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QuerySchemaArgs = { + schema: SchemaMatchInput; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QuerySchemasArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** View the API guide [here](https://fragment.dev/api-reference/api-queries) */ +export type QueryTxArgs = { + tx: TxMatchInput; +}; + +/** The consistency configuration of a Ledger Account's balance queries. If not provided as an argument to a balance query, the default behavior is to read eventually consistent balances. See [Configure consistency](https://fragment.dev/guides/configure-consistency). */ +export enum ReadBalanceConsistencyMode { + /** Balance queries will read eventually consistent balances. This is the default behavior if `ReadBalanceConsistencyMode` is not provided as an argument to the balance field. Both Ledger Accounts configured with strongly and eventually consistent balance updates support this enum. */ + Eventual = 'eventual', + /** Balance queries will read strongly consistent balances. This is only allowed if the Ledger Account's `ownBalanceUpdates` in its `consistencyConfig` is `strong`. */ + Strong = 'strong', + /** Balance queries will use the value from the Ledger Account's `ownBalanceUpdates` in its `consistencyConfig`. */ + UseAccount = 'use_account' +} + +export type ReconcileTxResponse = BadRequestError | InternalError | ReconcileTxResult; + +export type ReconcileTxResult = { + __typename?: 'ReconcileTxResult'; + /** The ledger entry that was posted */ + entry: LedgerEntry; + /** True if this request successfully completed before and the previous response is being returned */ + isIkReplay: Scalars['Boolean']['output']; + /** The ledger lines that were created in that entry */ + lines: Array; +}; + +export type ReverseLedgerEntryResponse = BadRequestError | InternalError | ReverseLedgerEntryResult; + +export type ReverseLedgerEntryResult = { + __typename?: 'ReverseLedgerEntryResult'; + /** Whether the reversal was an IK replay */ + isIkReplay: Scalars['Boolean']['output']; + /** The Ledger Entry that was reversed */ + reversedLedgerEntry: LedgerEntry; + /** The reversal Ledger Entry that was created */ + reversingLedgerEntry: LedgerEntry; +}; + +/** A simulated Ledger Entry posted as a part of a Scene. */ +export type SceneEntryInput = { + /** Any parameters to be used as inputs to this simulated Ledger Entry. */ + parameters?: InputMaybe; + /** The type of the simulated Ledger Entry. Must match one of the types provided in schema.ledgerEntries.types. */ + type: Scalars['SafeString']['input']; + /** The version of the Ledger Entry type. */ + typeVersion?: InputMaybe; +}; + +export type SceneEventInput = { + /** The simulated Ledger Entry. */ + entry: SceneEntryInput; + /** The type of the Scene Event. Currently, only entries are supported. */ + eventType: SceneEventType; +}; + +export enum SceneEventType { + Entry = 'entry' +} + +export type SceneInput = { + /** A list of simulated ledger entries that make up the Scene. */ + events: Array; + /** The human-readable name of the Scene. */ + name: Scalars['String']['input']; +}; + +export type Schema = { + __typename?: 'Schema'; + /** + * The identifier for a Schema. + * `key` is unique to a Workspace. + */ + key: Scalars['SafeString']['output']; + /** The paginated list of ledgers the Schema has been applied to. */ + ledgers: LedgersConnection; + /** The name of a Schema. It defaults to the `key` if not provided in your SchemaInput. */ + name: Scalars['String']['output']; + /** The metadata for a specific SchemaVersion. */ + version: SchemaVersion; + /** A paginated list of SchemaVersions. */ + versions: SchemaVersionConnection; +}; + + +export type SchemaLedgersArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +export type SchemaVersionArgs = { + version?: InputMaybe; +}; + + +export type SchemaVersionsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +/** + * A condition that must be met on a Ledger Account balance. The condition can be + * either a `precondition` or `postcondition`. + */ +export type SchemaConditionInput = { + /** A condition on the `ownBalance` of the Ledger Account. */ + ownBalance?: InputMaybe; + /** A condition on the `totalBalance` of the Ledger Account. */ + totalBalance?: InputMaybe; +}; + +/** A paginated list of Schemas in a Workspace. */ +export type SchemaConnection = { + __typename?: 'SchemaConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +/** + * The consistency configuration for entities created within Ledgers created by this Schema. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ +export type SchemaConsistencyConfigInput = { + /** + * The consistency mode for the Ledger Entries list query within Ledgers created by this Schema. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ + entries?: InputMaybe; +}; + +/** + * The consistency modes available for entities created within this Schema. + * + * See [Configure consistency](https://fragment.dev/guides/configure-consistency). + */ +export enum SchemaConsistencyMode { + /** Eventually consistent entity updates */ + Eventual = 'eventual', + /** Strongly consistent entity updates */ + Strong = 'strong' +} + +/** + * Matches a Currency. Can be a built-in [CurrencyCode](https://fragment.dev/api-reference/api-types#scalars-and-enums-currencycode), custom Currency, or a parameterized string. + * If you supply a parameterized string, you must pass in a valid CurrencyCode as a parameter when posting a Ledger Entry. + */ +export type SchemaCurrencyMatchInput = { + /** The currency code. This must either be a [CurrencyCode](https://fragment.dev/api-reference/api-types#scalars-and-enums-currencycode) or a parameterized string that resolves to a CurrencyCode . */ + code: Scalars['ParameterizedString']['input']; + /** The ID for a custom currency. This is specified when creating the custom currency using the [createCustomCurrency](https://fragment.dev/api-reference/api-mutations#createcustomcurrency) mutation. */ + customCurrencyId?: InputMaybe; +}; + +export type SchemaExternalAccountMatchInput = { + /** The External systems's ID of the account */ + externalId?: InputMaybe; + /** The FRAGMENT ID of the external account */ + id?: InputMaybe; + /** The FRAGMENT ID of the link */ + linkId?: InputMaybe; + /** The type of Link this external account belongs to. Must be one of: IncreaseLink, UnitLink, CustomLink, or StripeLink. */ + linkType?: InputMaybe; +}; + +/** Input to the API for creating a Schema. */ +export type SchemaInput = { + /** The Chart of Accounts for the Schema. */ + chartOfAccounts: ChartOfAccountsInput; + /** The consistency configuration for this Schema. */ + consistencyConfig?: InputMaybe; + /** Any groups associated with this Schema. */ + groups?: InputMaybe>; + /** The key of the Schema. This is a stable, unique identifier for the Schema. Uniqueness is enforced at the Workspace level. */ + key: Scalars['SafeString']['input']; + /** The Ledger Entries to add to the Schema. */ + ledgerEntries?: InputMaybe; + /** The human-readable name of the Schema. */ + name?: InputMaybe; + /** Any scenes associated with this Schema. */ + scenes?: InputMaybe>; +}; + +/** A condition that must be met on a field. */ +export type SchemaInt96ConditionInput = { + /** Amount must be exactly equal to this value. You may not specify this alongside `gte` or `lte`. */ + eq?: InputMaybe; + /** Amount must be greater than or equal to this value. */ + gte?: InputMaybe; + /** Amount must be less than or equal to this value. */ + lte?: InputMaybe; +}; + +/** + * Models a Ledger Account in a Schema. + * Upon successfully storing a [Schema](https://fragment.dev/api-reference/api-types#core-types-schema), a [LedgerAccount](https://fragment.dev/api-reference/api-types#core-types-ledgeraccount) will be created for + * each corresponding non-templated `SchemaLedgerAccountInput` in your Chart of Accounts. + */ +export type SchemaLedgerAccountInput = { + /** Ledger Accounts to create as children of this Ledger Account. Ledger Accounts may be nested up to a maximum depth of 10. */ + children?: InputMaybe>; + /** + * EXPERIMENTAL: Whether or not this Ledger Account is a Clearing Account. + * Clearing Accounts have balances that should tend to zero. They are used to track in-progress workflows and payments. + */ + clearing?: InputMaybe; + /** The consistency configuration for this ledger account. See [Configure consistency](https://fragment.dev/guides/configure-consistency). */ + consistencyConfig?: InputMaybe; + /** + * The currency of this Ledger Account. If this is not set, and `currencyMode` is + * not set to `multi`, it is derived from the Chart of Accounts' default. + */ + currency?: InputMaybe; + /** If set to `multi`, creates a multi-currency Ledger Account. If set to `single`, creates a single-currency Ledger Account. */ + currencyMode?: InputMaybe; + /** + * The key of this Ledger Account. Keys are used to formulate the unique path of the Ledger Account in your Chart of Accounts. + * Siblings must have unique keys. + */ + key: Scalars['SafeString']['input']; + /** + * The External Account to link to this Ledger Account. + * It must be provided of `linked` is true. + */ + linkedAccount?: InputMaybe; + /** The human-readable name of this Ledger Account. */ + name?: InputMaybe; + /** EXPERIMENTAL: Marks this as a Payment Account. */ + payment?: InputMaybe; + /** The status of this Ledger Account. Defaults to active. */ + status?: InputMaybe; + /** Whether or not this Ledger Account should be templated. */ + template?: InputMaybe; + /** The type of ledger account to create. Required if this is a top-level Ledger Account. If not provided, the type will be inferred from the parent. */ + type?: InputMaybe; +}; + +/** Matches a Ledger Account in a Schema. */ +export type SchemaLedgerAccountMatchInput = { + /** + * The unique path of the Ledger Account in the Schema. + * This is a slash-delimited string containing the keys of a Ledger Account and all its direct ancestors. + * ex: expense-root/subscriptions/netflix + * For Templated Ledger Accounts, you must supply a parameter in the path that will be used to name an instance of the template. + * ex: `"expense-root/subscriptions/vendor:{{vendor_name}}"` + */ + path: Scalars['ParameterizedString']['input']; +}; + +/** The status of a Ledger Account. */ +export enum SchemaLedgerAccountStatus { + /** The Ledger Account is active. */ + Active = 'active', + /** The Ledger Account is archived. */ + Archived = 'archived', + /** The Ledger Account is disabled. */ + Disabled = 'disabled' +} + +/** The Ledger Entries in your Schema. */ +export type SchemaLedgerEntriesInput = { + /** A list of Ledger Entry definitions. */ + types: Array; +}; + +/** A condition that must be met on a Ledger Account when a Ledger Entry is posted. */ +export type SchemaLedgerEntryConditionInput = { + /** The Ledger Account to apply the condition to. */ + account: SchemaLedgerAccountMatchInput; + /** + * The currency of the balance to apply the condition to. Required if the Ledger Account matched is a multi-currency Ledger Account. + * Otherwise, this field is defaults to the Ledger Account's currency. + */ + currency?: InputMaybe; + /** A `postcondition` must be met after the Ledger Entry updates are applied. */ + postcondition?: InputMaybe; + /** A `precondition` must be met before any Ledger Entry updates are applied. */ + precondition?: InputMaybe; + /** + * Repeated expansion configuration. When set, this condition is expanded at runtime for each element + * in the array parameter named by the key. + */ + repeated?: InputMaybe; +}; + +/** A Ledger Entry Group associated with a Ledger Entry type. */ +export type SchemaLedgerEntryGroupInput = { + /** The key for this Ledger Entry Group. */ + key: Scalars['SafeString']['input']; + /** The value associated with this Ledger Entry Group. */ + value: Scalars['ParameterizedString']['input']; +}; + +/** A Ledger Entry in a Schema. All Ledger Entries defined in a Schema must have a unique `type`. */ +export type SchemaLedgerEntryInput = { + /** Conditions that must be satisfied to post this Ledger Entry. The Ledger Entry will reject with a BadRequestError if any condition is not met. You can only add a condition on a Ledger Account containing a Line in this Ledger Entry. */ + conditions?: InputMaybe>; + /** Human-readable description of the Ledger Entry. */ + description?: InputMaybe; + /** Ledger Entries posted with this type will be in these Ledger Entry Groups. */ + groups?: InputMaybe>; + /** + * The Ledger Lines in the Ledger Entry. + * If provided, when posting a Typed Entry, a [LedgerEntry](https://fragment.dev/api-reference/api-types#core-types-ledgerline) will be posted containing [LedgerLines](https://fragment.dev/api-reference/api-types#core-types-ledgerline) corresponding + * to the values you provide here. If your lines contain parameters, you must supply values for those parameters that balance out the Ledger Entry. If not provided, lines will be required when posting a Typed Entry. + */ + lines?: InputMaybe>; + /** Fixed partial set of parameters to be included in a templated Ledger Entry. */ + parameters?: InputMaybe; + /** Controls how lines are posted. When set to `net_amounts`, all lines targeting the same account, currency, and tx are aggregated into a single line with the net amount, and lines that sum to zero are skipped. When set to `skip_zero_lines`, lines with a zero amount are skipped but not aggregated. In both modes, if all lines are zero, no lines are skipped. When set to `raw_lines`, lines are posted as-is without aggregation. New entries created via the dashboard default to `net_amounts`. Existing entries without this field set are treated as `raw_lines`. */ + postLinesAs?: InputMaybe; + /** The status of this Ledger Entry. Defaults to active. */ + status?: InputMaybe; + /** Ledger Entries posted with this type will be associated with these tags. */ + tags?: InputMaybe>; + /** + * The type of this Ledger Entry. This is a stable, unique identifier for this entry. Uniqueness is enforced at the Schema level. + * You can filter on this field when querying for Ledger Entries. See the docs on [LedgerEntryFilterSet](https://fragment.dev/api-reference/api-types#filter-types-ledgerentriesfilterset) + */ + type: Scalars['SafeString']['input']; + /** The version of the Ledger Entry type. */ + typeVersion?: InputMaybe; +}; + +/** The status of a Ledger Entry. */ +export enum SchemaLedgerEntryStatus { + /** The Ledger Entry is active. */ + Active = 'active', + /** The Ledger Entry is archived. */ + Archived = 'archived', + /** The Ledger Entry is disabled. */ + Disabled = 'disabled' +} + +/** A tag associated with a Ledger Entry type. */ +export type SchemaLedgerEntryTagInput = { + /** The key for this tag. */ + key: Scalars['SafeString']['input']; + /** The value associated with the given key for this tag. */ + value: Scalars['ParameterizedString']['input']; +}; + +/** A Ledger Line in a Ledger Entry. */ +export type SchemaLedgerLineInput = { + /** + * The Ledger Account this Ledger Line will be posted to. + * It supports parameters in its attributes via handlebars syntax. + */ + account: SchemaLedgerAccountMatchInput; + /** The amount of the Ledger Line. It supports parameters via the handlebars syntax and addition (+) and subtraction (-). */ + amount?: InputMaybe; + /** + * The currency of the Ledger Line. This is required if the Ledger Account has currencyMode multi. + * It supports parameters in its attributes via handlebars syntax. + */ + currency?: InputMaybe; + /** Human-readable description of the line. */ + description?: InputMaybe; + /** The key for the Ledger Line. Ledger Line keys must be unique within a Ledger Entry. Key can be filtered on as part of the LedgerLinesFilterSet. */ + key: Scalars['SafeString']['input']; + /** + * Repeated expansion configuration. When set, this line is expanded at runtime for each element + * in the array parameter named by the key. + */ + repeated?: InputMaybe; + /** Tags to attach to this Ledger Line. Supports parameterized values via handlebars syntax. */ + tags?: InputMaybe>; + /** + * The external transaction to reconcile. + * This field is required if the Ledger Account being posted to is a Linked Ledger Account. Otherwise, this field is disallowed. + * It supports parameters in its attributes via handlebars syntax. + * + * See the docs on [reconciling payments](https://fragment.dev/guides/reconcile-payments). + */ + tx?: InputMaybe; +}; + +/** An object used to retrieve a Schema. */ +export type SchemaMatchInput = { + /** + * The key to retrieve a Schema by. + * `key` is unique to a Workspace. + */ + key: Scalars['SafeString']['input']; + /** Optional parameter to specify version of requested Schema. If not provided, it defaults to 0, representing the latest available version for the provided Schema key. */ + version?: InputMaybe; +}; + +/** EXPERIMENTAL: Marks a Ledger Account as a Payment Account. */ +export type SchemaPaymentInput = { + penguin: Scalars['Boolean']['input']; +}; + +/** + * Configuration for repeated expansion of a line or condition. The key names a client-supplied + * array parameter whose elements each generate one copy of the line or condition at runtime. + */ +export type SchemaRepeatedConfigInput = { + /** The key of the array parameter whose elements expand this line or condition. */ + key: Scalars['SafeString']['input']; +}; + +/** + * Matches a transaction at an external system. + * This is used to specify the transaction being reconciled into a Linked Ledger Account + */ +export type SchemaTxMatchInput = { + /** The external system's ID for the transaction. */ + externalId?: InputMaybe; + /** The FRAGMENT ID for the transaction. */ + id?: InputMaybe; +}; + +/** + * An instance of a Schema stored in a Workspace. + * A new SchemaVersion is created each time a Schema is stored. + * It stores the Chart of Accounts and list of Ledger Entries as well as a history of its Ledger migrations. + */ +export type SchemaVersion = { + __typename?: 'SchemaVersion'; + created: Scalars['DateTime']['output']; + json: Scalars['JSON']['output']; + migrations: LedgerMigrationConnection; + /** The version of the schema. */ + version: Scalars['Int']['output']; +}; + +/** A paginated list of SchemaVersions for a given Schema. */ +export type SchemaVersionConnection = { + __typename?: 'SchemaVersionConnection'; + /** The current page of results */ + nodes: Array; + /** Pagination info for this list. */ + pageInfo: PageInfo; +}; + +/** Returned by the [storeSchema](https://fragment.dev/api-reference/api-mutations#storeschema) mutation. */ +export type StoreSchemaResponse = BadRequestError | InternalError | StoreSchemaResult; + +/** `StoreSchemaResult` represents a successful execution of `storeSchema`. */ +export type StoreSchemaResult = { + __typename?: 'StoreSchemaResult'; + /** The Schema that was stored as a result of calling `storeSchema`. */ + schema: Schema; +}; + +export type StringFilter = { + equalTo?: InputMaybe; + /** Must match one of the values provided. Limited to 100 items maximum. */ + in?: InputMaybe>; + /** Must not equal this string value */ + notEqualTo?: InputMaybe; + /** Must not match any of the values provided. Limited to 100 items maximum. */ + notIn?: InputMaybe>; +}; + +export type StringMatchFilter = { + /** Must contain the provided pattern somewhere within the string. For example, 'contains: hat' will match 'hat', 'chat', and 'hate'. */ + contains?: InputMaybe; + /** Must exactly equal the provided value */ + equalTo?: InputMaybe; + /** Must exactly equal one of the provided values. Limited to 100 items maximum. */ + in?: InputMaybe>; + /** Must match the provided pattern. Wildcards ("*") will match any substring */ + matches?: InputMaybe; + /** Must match any one of the provided `matches` patterns, OR-ed together — results are returned as a single paginated list. Each entry uses the same wildcard grammar as `matches`. Cannot be combined with `matches`. Limited to 100 entries. */ + matchesAny?: InputMaybe>; +}; + +export enum StripeEnv { + Livemode = 'livemode', + Testmode = 'testmode' +} + +export type StripeLink = Link & { + __typename?: 'StripeLink'; + /** ISO-8601 timestamp when the Link was created. */ + created: Scalars['String']['output']; + /** URL to the Fragment Dashboard for this Link. */ + dashboardUrl: Scalars['String']['output']; + /** A list of External Accounts associated with this Link. */ + externalAccounts: ExternalAccountsConnection; + /** FRAGMENT ID of the Custom Link. */ + id: Scalars['ID']['output']; + /** Name of the Link as it appears in the Fragment Dashboard. */ + name: Scalars['String']['output']; + /** The environment of the Stripe Link, either testmode or livemode. */ + stripeEnv: StripeEnv; +}; + +export type SyncCustomAccountsResponse = BadRequestError | InternalError | SyncCustomAccountsResult; + +export type SyncCustomAccountsResult = { + __typename?: 'SyncCustomAccountsResult'; + /** The external accounts that were synced. */ + accounts: Array; +}; + +export type SyncCustomTxsResponse = BadRequestError | InternalError | SyncCustomTxsResult; + +export type SyncCustomTxsResult = { + __typename?: 'SyncCustomTxsResult'; + txs: Array; +}; + +/** Filters a result set based on the tags it contains. */ +export type TagFilter = { + /** Matches entries that have ALL of the specified tags. The key and value are both matched exactly. Limited to 10 items maximum. */ + all?: InputMaybe>; + /** Matches tag values based on the existence of the provided string within the tag value. The key is matched exactly. */ + contains?: InputMaybe; + /** Matches tags based on the exact value provided. The key and value are both matched exactly. */ + equalTo?: InputMaybe; + /** Matches tags based on a list of possible tag matches. The key and value are both matched exactly. Limited to 100 items maximum. */ + in?: InputMaybe>; + /** Matches tags where the key exactly equals the provided value. */ + keyEqualTo?: InputMaybe; + /** Matches tags where the key matches any of the provided values. Limited to 100 items maximum. */ + keyIn?: InputMaybe>; + /** Matches tags that do not equal the provided value. The key and value are both matched exactly. */ + notEqualTo?: InputMaybe; + /** Matches tags that do not match any of the provided values. The key and value are both matched exactly. Limited to 100 items maximum. */ + notIn?: InputMaybe>; + /** Matches tags where the key does not equal the provided value. */ + notKeyEqualTo?: InputMaybe; + /** Matches tags where the key does not match any of the provided values. Limited to 100 items maximum. */ + notKeyIn?: InputMaybe>; +}; + +/** Specifies a single tag that an entity is expected to have. You must specify both the key and the value. */ +export type TagMatchInput = { + /** The key of this tag. */ + key: Scalars['SafeString']['input']; + /** The value associated with this tag's key. */ + value: Scalars['SafeString']['input']; +}; + +export type Tx = { + __typename?: 'Tx'; + /** FRAGMENT ID of this transaction's external account */ + accountId: Scalars['ID']['output']; + /** Integer amount in cents. Positive indicates money entering the external account, negative indicates money leaving */ + amount: Scalars['Int96']['output']; + /** Currency of this Tx */ + currency?: Maybe; + /** Date this Tx posted to the external account */ + date: Scalars['Date']['output']; + /** ISO-8601 timestamp when this Tx was deleted */ + deletedAt?: Maybe; + /** Description at the external account */ + description: Scalars['String']['output']; + /** The External Account that this transaction belongs to. */ + externalAccount: ExternalAccount; + /** ID in the external system of this transaction's external account */ + externalAccountId: Scalars['ID']['output']; + /** ID of this transaction in the external system */ + externalId: Scalars['ID']['output']; + /** FRAGMENT ID of this Tx. If you delete a Tx via deleteCustomTxs, it will not show up in listing queries, but can be resolved by if you lookup by its Fragment ID. If you resync a Tx with the same externalId, its Fragment ID will be different than the previous Tx. */ + id: Scalars['ID']['output']; + /** Whether this Tx has been deleted via deleteCustomTxs */ + isDeleted: Scalars['Boolean']['output']; + /** Returns ledger entries that are linked to this transaction. You can link the same external account to multiple ledgers, so there could be multiple entries associated with one transaction - one for each linked ledger account this transaction has been reconciled with */ + ledgerEntries: LedgerEntriesConnection; + /** Same as ledgerEntries, but returns an array of IDs instead */ + ledgerEntryIds?: Maybe>; + /** Same as ledgerLines, but returns an array of IDs instead */ + ledgerLineIds?: Maybe>; + /** Returns ledger lines that are linked to this transaction. You can link the same external account to multiple ledgers, so there could be multipe lines associated with one transaction - one for each linked ledger account this transaction has been reconciled with */ + ledgerLines: LedgerLinesConnection; + /** This transaction's Link. */ + link: Link; + /** FRAGMENT ID of this transaction's Link */ + linkId: Scalars['ID']['output']; + /** ISO-8601 timestamp when this Tx posted to the external account */ + posted: Scalars['DateTime']['output']; + /** When a Tx is deleted and a new Tx is synced with the same externalId, its sequence will be higher than the previous Tx. You can use this to distinguish different instances of Txs that have the same externalId. */ + sequence: Scalars['Int']['output']; + /** @deprecated Callers should not need to query or store this value. */ + workspaceId: Scalars['ID']['output']; +}; + +/** Specify a Tx by using `id` or `externalId`, the Link it belongs to by `linkId`, and the External Account it is a part of by `accountId` or `externalAccountId`. */ +export type TxMatchInput = { + /** The FRAGMENT ID of the external account */ + accountId?: InputMaybe; + /** The external system's ID for the account */ + externalAccountId?: InputMaybe; + /** The external system's ID for the transaction */ + externalId?: InputMaybe; + /** The FRAGMENT ID of the transaction */ + id?: InputMaybe; + /** The FRAGMENT ID of the link */ + linkId?: InputMaybe; +}; + +export enum TxType { + Credit = 'credit', + Debit = 'debit' +} + +export type TxTypeFilter = { + equalTo?: InputMaybe; + /** Must match one of the values provided. Limited to 100 items maximum. */ + in?: InputMaybe>; +}; + +/** A paginated list of Txs */ +export type TxsConnection = { + __typename?: 'TxsConnection'; + /** The current page of results */ + nodes: Array; + /** The [pagination info](https://fragment.dev/api-reference/api-types#connection-types-pageinfo) for this list */ + pageInfo: PageInfo; +}; + +export enum UnitEnv { + Production = 'production', + Sandbox = 'sandbox' +} + +export type UnitLink = Link & { + __typename?: 'UnitLink'; + /** ISO-8601 timestamp when the Link was created. */ + created: Scalars['String']['output']; + /** URL to the Fragment Dashboard for this Link. */ + dashboardUrl: Scalars['String']['output']; + /** A list of External Accounts associated with this Link. */ + externalAccounts: ExternalAccountsConnection; + /** FRAGMENT ID of the Unit Link. */ + id: Scalars['ID']['output']; + /** Name of the Link as it appears in the Dashboard. */ + name: Scalars['String']['output']; + /** The environment of the Unit Link, either sandbox or production. */ + unitEnv: UnitEnv; +}; + +export type UpdateLedgerAccountInput = { + /** The consistency configuration for this ledger account. This defines how updates to this ledger account's balance are handled. */ + consistencyConfig?: InputMaybe; + /** The name to update the ledger account to */ + name?: InputMaybe; +}; + +export type UpdateLedgerAccountResponse = BadRequestError | InternalError | UpdateLedgerAccountResult; + +export type UpdateLedgerAccountResult = { + __typename?: 'UpdateLedgerAccountResult'; + /** The ledger account that was updated */ + ledgerAccount: LedgerAccount; +}; + +export type UpdateLedgerEntryInput = { + /** The list of Groups to add to this Ledger Entry. */ + groups?: InputMaybe>; + /** The list of Tags to add and/or update on this Ledger Entry. */ + tags?: InputMaybe>; + /** The list of Tags to remove from this Ledger Entry. */ + tagsToRemove?: InputMaybe>; +}; + +export type UpdateLedgerEntryResponse = BadRequestError | InternalError | UpdateLedgerEntryResult; + +export type UpdateLedgerEntryResult = { + __typename?: 'UpdateLedgerEntryResult'; + /** The Ledger Entry that was updated. */ + entry: LedgerEntry; +}; + +export type UpdateLedgerInput = { + /** The new Ledger name. */ + name?: InputMaybe; +}; + +export type UpdateLedgerResponse = BadRequestError | InternalError | UpdateLedgerResult; + +export type UpdateLedgerResult = { + __typename?: 'UpdateLedgerResult'; + /** The updated Ledger. */ + ledger: Ledger; +}; + +export type Workspace = { + __typename?: 'Workspace'; + /** The ID of the Workspace */ + id: Scalars['String']['output']; + /** The name of the Workspace */ + name: Scalars['String']['output']; +}; + +export type PostOrderPlacedMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + posted?: InputMaybe; + tags?: InputMaybe | LedgerEntryTagInput>; + groups?: InputMaybe | LedgerEntryGroupInput>; + conditions?: InputMaybe | LedgerEntryConditionInput>; + ledgerIk: Scalars['SafeString']['input']; + user_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + order_cost: Scalars['String']['input']; + currency: Scalars['String']['input']; + platform_fee: Scalars['String']['input']; + driver_fee: Scalars['String']['input']; + restaurant_id: Scalars['String']['input']; + driver_id: Scalars['String']['input']; +}>; + + +export type PostOrderPlacedMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult', isIkReplay: boolean, entry: { __typename?: 'LedgerEntry', ik: string, id: string, created: string, type?: string | null, posted: string, description?: string | null, ledger: { __typename?: 'Ledger', ik: string }, tags: Array<{ __typename?: 'LedgerEntryTag', key: string, value: string }>, groups: Array<{ __typename?: 'LedgerEntryGroup', key: string, value: string }> }, lines: Array<{ __typename?: 'LedgerLine', amount: string, key?: string | null, account: { __typename?: 'LedgerAccount', path: string }, currency?: { __typename?: 'Currency', code: CurrencyCode, customCurrencyId?: string | null } | null }> } | { __typename: 'BadRequestError', code: string, message: string, retryable: boolean } | { __typename: 'InternalError', code: string, message: string, retryable: boolean } }; + +export type PostOrderPlaced_V2MutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + posted?: InputMaybe; + tags?: InputMaybe | LedgerEntryTagInput>; + groups?: InputMaybe | LedgerEntryGroupInput>; + conditions?: InputMaybe | LedgerEntryConditionInput>; + ledgerIk: Scalars['SafeString']['input']; + user_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + order_cost: Scalars['String']['input']; + currency: Scalars['String']['input']; + platform_fee: Scalars['String']['input']; + service_fee: Scalars['String']['input']; + driver_fee: Scalars['String']['input']; + restaurant_id: Scalars['String']['input']; + driver_id: Scalars['String']['input']; +}>; + + +export type PostOrderPlaced_V2Mutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult', isIkReplay: boolean, entry: { __typename?: 'LedgerEntry', ik: string, id: string, created: string, type?: string | null, posted: string, description?: string | null, ledger: { __typename?: 'Ledger', ik: string }, tags: Array<{ __typename?: 'LedgerEntryTag', key: string, value: string }>, groups: Array<{ __typename?: 'LedgerEntryGroup', key: string, value: string }> }, lines: Array<{ __typename?: 'LedgerLine', amount: string, key?: string | null, account: { __typename?: 'LedgerAccount', path: string }, currency?: { __typename?: 'Currency', code: CurrencyCode, customCurrencyId?: string | null } | null }> } | { __typename: 'BadRequestError', code: string, message: string, retryable: boolean } | { __typename: 'InternalError', code: string, message: string, retryable: boolean } }; + +export type PostCardSettleMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + posted?: InputMaybe; + tags?: InputMaybe | LedgerEntryTagInput>; + groups?: InputMaybe | LedgerEntryGroupInput>; + ledgerIk: Scalars['SafeString']['input']; + user_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; +}>; + + +export type PostCardSettleMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult', isIkReplay: boolean, entry: { __typename?: 'LedgerEntry', ik: string, id: string, created: string, type?: string | null, posted: string, description?: string | null, ledger: { __typename?: 'Ledger', ik: string }, tags: Array<{ __typename?: 'LedgerEntryTag', key: string, value: string }>, groups: Array<{ __typename?: 'LedgerEntryGroup', key: string, value: string }> }, lines: Array<{ __typename?: 'LedgerLine', amount: string, key?: string | null, account: { __typename?: 'LedgerAccount', path: string }, currency?: { __typename?: 'Currency', code: CurrencyCode, customCurrencyId?: string | null } | null }> } | { __typename: 'BadRequestError', code: string, message: string, retryable: boolean } | { __typename: 'InternalError', code: string, message: string, retryable: boolean } }; + +export type PostRestaurantPayoutInitiateMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + posted?: InputMaybe; + tags?: InputMaybe | LedgerEntryTagInput>; + groups?: InputMaybe | LedgerEntryGroupInput>; + ledgerIk: Scalars['SafeString']['input']; + restaurant_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; + payout_id: Scalars['String']['input']; +}>; + + +export type PostRestaurantPayoutInitiateMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult', isIkReplay: boolean, entry: { __typename?: 'LedgerEntry', ik: string, id: string, created: string, type?: string | null, posted: string, description?: string | null, ledger: { __typename?: 'Ledger', ik: string }, tags: Array<{ __typename?: 'LedgerEntryTag', key: string, value: string }>, groups: Array<{ __typename?: 'LedgerEntryGroup', key: string, value: string }> }, lines: Array<{ __typename?: 'LedgerLine', amount: string, key?: string | null, account: { __typename?: 'LedgerAccount', path: string }, currency?: { __typename?: 'Currency', code: CurrencyCode, customCurrencyId?: string | null } | null }> } | { __typename: 'BadRequestError', code: string, message: string, retryable: boolean } | { __typename: 'InternalError', code: string, message: string, retryable: boolean } }; + +export type PostRestaurantPayoutSettleMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + posted?: InputMaybe; + tags?: InputMaybe | LedgerEntryTagInput>; + groups?: InputMaybe | LedgerEntryGroupInput>; + ledgerIk: Scalars['SafeString']['input']; + restaurant_id: Scalars['String']['input']; + payout_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; +}>; + + +export type PostRestaurantPayoutSettleMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult', isIkReplay: boolean, entry: { __typename?: 'LedgerEntry', ik: string, id: string, created: string, type?: string | null, posted: string, description?: string | null, ledger: { __typename?: 'Ledger', ik: string }, tags: Array<{ __typename?: 'LedgerEntryTag', key: string, value: string }>, groups: Array<{ __typename?: 'LedgerEntryGroup', key: string, value: string }> }, lines: Array<{ __typename?: 'LedgerLine', amount: string, key?: string | null, account: { __typename?: 'LedgerAccount', path: string }, currency?: { __typename?: 'Currency', code: CurrencyCode, customCurrencyId?: string | null } | null }> } | { __typename: 'BadRequestError', code: string, message: string, retryable: boolean } | { __typename: 'InternalError', code: string, message: string, retryable: boolean } }; + +export type PostDriverPayoutInitiateMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + posted?: InputMaybe; + tags?: InputMaybe | LedgerEntryTagInput>; + groups?: InputMaybe | LedgerEntryGroupInput>; + ledgerIk: Scalars['SafeString']['input']; + driver_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; + payout_id: Scalars['String']['input']; +}>; + + +export type PostDriverPayoutInitiateMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult', isIkReplay: boolean, entry: { __typename?: 'LedgerEntry', ik: string, id: string, created: string, type?: string | null, posted: string, description?: string | null, ledger: { __typename?: 'Ledger', ik: string }, tags: Array<{ __typename?: 'LedgerEntryTag', key: string, value: string }>, groups: Array<{ __typename?: 'LedgerEntryGroup', key: string, value: string }> }, lines: Array<{ __typename?: 'LedgerLine', amount: string, key?: string | null, account: { __typename?: 'LedgerAccount', path: string }, currency?: { __typename?: 'Currency', code: CurrencyCode, customCurrencyId?: string | null } | null }> } | { __typename: 'BadRequestError', code: string, message: string, retryable: boolean } | { __typename: 'InternalError', code: string, message: string, retryable: boolean } }; + +export type PostDriverPayoutSettleMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + posted?: InputMaybe; + tags?: InputMaybe | LedgerEntryTagInput>; + groups?: InputMaybe | LedgerEntryGroupInput>; + ledgerIk: Scalars['SafeString']['input']; + driver_id: Scalars['String']['input']; + payout_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; +}>; + + +export type PostDriverPayoutSettleMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult', isIkReplay: boolean, entry: { __typename?: 'LedgerEntry', ik: string, id: string, created: string, type?: string | null, posted: string, description?: string | null, ledger: { __typename?: 'Ledger', ik: string }, tags: Array<{ __typename?: 'LedgerEntryTag', key: string, value: string }>, groups: Array<{ __typename?: 'LedgerEntryGroup', key: string, value: string }> }, lines: Array<{ __typename?: 'LedgerLine', amount: string, key?: string | null, account: { __typename?: 'LedgerAccount', path: string }, currency?: { __typename?: 'Currency', code: CurrencyCode, customCurrencyId?: string | null } | null }> } | { __typename: 'BadRequestError', code: string, message: string, retryable: boolean } | { __typename: 'InternalError', code: string, message: string, retryable: boolean } }; + +export type PostDisputePayoutInitiateMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + posted?: InputMaybe; + tags?: InputMaybe | LedgerEntryTagInput>; + groups?: InputMaybe | LedgerEntryGroupInput>; + conditions?: InputMaybe | LedgerEntryConditionInput>; + ledgerIk: Scalars['SafeString']['input']; + user_id: Scalars['String']['input']; + disputes_id: Scalars['String']['input']; + amount: Scalars['String']['input']; + currency: Scalars['String']['input']; + payout_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; +}>; + + +export type PostDisputePayoutInitiateMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult', isIkReplay: boolean, entry: { __typename?: 'LedgerEntry', ik: string, id: string, created: string, type?: string | null, posted: string, description?: string | null, ledger: { __typename?: 'Ledger', ik: string }, tags: Array<{ __typename?: 'LedgerEntryTag', key: string, value: string }>, groups: Array<{ __typename?: 'LedgerEntryGroup', key: string, value: string }> }, lines: Array<{ __typename?: 'LedgerLine', amount: string, key?: string | null, account: { __typename?: 'LedgerAccount', path: string }, currency?: { __typename?: 'Currency', code: CurrencyCode, customCurrencyId?: string | null } | null }> } | { __typename: 'BadRequestError', code: string, message: string, retryable: boolean } | { __typename: 'InternalError', code: string, message: string, retryable: boolean } }; + +export type PostDisputePayoutSettleMutationVariables = Exact<{ + ik: Scalars['SafeString']['input']; + posted?: InputMaybe; + tags?: InputMaybe | LedgerEntryTagInput>; + groups?: InputMaybe | LedgerEntryGroupInput>; + conditions?: InputMaybe | LedgerEntryConditionInput>; + ledgerIk: Scalars['SafeString']['input']; + user_id: Scalars['String']['input']; + disputes_id: Scalars['String']['input']; + amount: Scalars['String']['input']; + currency: Scalars['String']['input']; + order_id: Scalars['String']['input']; +}>; + + +export type PostDisputePayoutSettleMutation = { __typename?: 'Mutation', addLedgerEntry: { __typename: 'AddLedgerEntryResult', isIkReplay: boolean, entry: { __typename?: 'LedgerEntry', ik: string, id: string, created: string, type?: string | null, posted: string, description?: string | null, ledger: { __typename?: 'Ledger', ik: string }, tags: Array<{ __typename?: 'LedgerEntryTag', key: string, value: string }>, groups: Array<{ __typename?: 'LedgerEntryGroup', key: string, value: string }> }, lines: Array<{ __typename?: 'LedgerLine', amount: string, key?: string | null, account: { __typename?: 'LedgerAccount', path: string }, currency?: { __typename?: 'Currency', code: CurrencyCode, customCurrencyId?: string | null } | null }> } | { __typename: 'BadRequestError', code: string, message: string, retryable: boolean } | { __typename: 'InternalError', code: string, message: string, retryable: boolean } }; + + +export const PostOrderPlacedDocument = gql` + mutation PostOrderPlaced($ik: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $groups: [LedgerEntryGroupInput!], $conditions: [LedgerEntryConditionInput!], $ledgerIk: SafeString!, $user_id: String!, $order_id: String!, $order_cost: String!, $currency: String!, $platform_fee: String!, $driver_fee: String!, $restaurant_id: String!, $driver_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "order_placed", typeVersion: 1, posted: $posted, tags: $tags, groups: $groups, conditions: $conditions, parameters: {user_id: $user_id, order_id: $order_id, order_cost: $order_cost, currency: $currency, platform_fee: $platform_fee, driver_fee: $driver_fee, restaurant_id: $restaurant_id, driver_id: $driver_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} + `; +export const PostOrderPlaced_V2Document = gql` + mutation PostOrderPlaced_v2($ik: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $groups: [LedgerEntryGroupInput!], $conditions: [LedgerEntryConditionInput!], $ledgerIk: SafeString!, $user_id: String!, $order_id: String!, $order_cost: String!, $currency: String!, $platform_fee: String!, $service_fee: String!, $driver_fee: String!, $restaurant_id: String!, $driver_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "order_placed", typeVersion: 2, posted: $posted, tags: $tags, groups: $groups, conditions: $conditions, parameters: {user_id: $user_id, order_id: $order_id, order_cost: $order_cost, currency: $currency, platform_fee: $platform_fee, service_fee: $service_fee, driver_fee: $driver_fee, restaurant_id: $restaurant_id, driver_id: $driver_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} + `; +export const PostCardSettleDocument = gql` + mutation PostCardSettle($ik: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $groups: [LedgerEntryGroupInput!], $ledgerIk: SafeString!, $user_id: String!, $order_id: String!, $currency: String!, $amount: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "card_settle", typeVersion: 1, posted: $posted, tags: $tags, groups: $groups, parameters: {user_id: $user_id, order_id: $order_id, currency: $currency, amount: $amount}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} + `; +export const PostRestaurantPayoutInitiateDocument = gql` + mutation PostRestaurantPayoutInitiate($ik: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $groups: [LedgerEntryGroupInput!], $ledgerIk: SafeString!, $restaurant_id: String!, $order_id: String!, $currency: String!, $amount: String!, $payout_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "restaurant_payout_initiate", typeVersion: 1, posted: $posted, tags: $tags, groups: $groups, parameters: {restaurant_id: $restaurant_id, order_id: $order_id, currency: $currency, amount: $amount, payout_id: $payout_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} + `; +export const PostRestaurantPayoutSettleDocument = gql` + mutation PostRestaurantPayoutSettle($ik: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $groups: [LedgerEntryGroupInput!], $ledgerIk: SafeString!, $restaurant_id: String!, $payout_id: String!, $currency: String!, $amount: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "restaurant_payout_settle", typeVersion: 1, posted: $posted, tags: $tags, groups: $groups, parameters: {restaurant_id: $restaurant_id, payout_id: $payout_id, currency: $currency, amount: $amount}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} + `; +export const PostDriverPayoutInitiateDocument = gql` + mutation PostDriverPayoutInitiate($ik: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $groups: [LedgerEntryGroupInput!], $ledgerIk: SafeString!, $driver_id: String!, $order_id: String!, $currency: String!, $amount: String!, $payout_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "driver_payout_initiate", typeVersion: 1, posted: $posted, tags: $tags, groups: $groups, parameters: {driver_id: $driver_id, order_id: $order_id, currency: $currency, amount: $amount, payout_id: $payout_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} + `; +export const PostDriverPayoutSettleDocument = gql` + mutation PostDriverPayoutSettle($ik: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $groups: [LedgerEntryGroupInput!], $ledgerIk: SafeString!, $driver_id: String!, $payout_id: String!, $currency: String!, $amount: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "driver_payout_settle", typeVersion: 1, posted: $posted, tags: $tags, groups: $groups, parameters: {driver_id: $driver_id, payout_id: $payout_id, currency: $currency, amount: $amount}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} + `; +export const PostDisputePayoutInitiateDocument = gql` + mutation PostDisputePayoutInitiate($ik: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $groups: [LedgerEntryGroupInput!], $conditions: [LedgerEntryConditionInput!], $ledgerIk: SafeString!, $user_id: String!, $disputes_id: String!, $amount: String!, $currency: String!, $payout_id: String!, $order_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "dispute_payout_initiate", typeVersion: 1, posted: $posted, tags: $tags, groups: $groups, conditions: $conditions, parameters: {user_id: $user_id, disputes_id: $disputes_id, amount: $amount, currency: $currency, payout_id: $payout_id, order_id: $order_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} + `; +export const PostDisputePayoutSettleDocument = gql` + mutation PostDisputePayoutSettle($ik: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $groups: [LedgerEntryGroupInput!], $conditions: [LedgerEntryConditionInput!], $ledgerIk: SafeString!, $user_id: String!, $disputes_id: String!, $amount: String!, $currency: String!, $order_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "dispute_payout_settle", typeVersion: 1, posted: $posted, tags: $tags, groups: $groups, conditions: $conditions, parameters: {user_id: $user_id, disputes_id: $disputes_id, amount: $amount, currency: $currency, order_id: $order_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} + `; + +export type SdkFunctionWrapper = (action: (requestHeaders?:Record) => Promise, operationName: string, operationType?: string, variables?: any) => Promise; + + +const defaultWrapper: SdkFunctionWrapper = (action, _operationName, _operationType, _variables) => action(); + +export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) { + return { + PostOrderPlaced(variables: PostOrderPlacedMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostOrderPlacedDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostOrderPlaced', 'mutation', variables); + }, + PostOrderPlaced_v2(variables: PostOrderPlaced_V2MutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostOrderPlaced_V2Document, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostOrderPlaced_v2', 'mutation', variables); + }, + PostCardSettle(variables: PostCardSettleMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostCardSettleDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostCardSettle', 'mutation', variables); + }, + PostRestaurantPayoutInitiate(variables: PostRestaurantPayoutInitiateMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostRestaurantPayoutInitiateDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostRestaurantPayoutInitiate', 'mutation', variables); + }, + PostRestaurantPayoutSettle(variables: PostRestaurantPayoutSettleMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostRestaurantPayoutSettleDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostRestaurantPayoutSettle', 'mutation', variables); + }, + PostDriverPayoutInitiate(variables: PostDriverPayoutInitiateMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostDriverPayoutInitiateDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostDriverPayoutInitiate', 'mutation', variables); + }, + PostDriverPayoutSettle(variables: PostDriverPayoutSettleMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostDriverPayoutSettleDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostDriverPayoutSettle', 'mutation', variables); + }, + PostDisputePayoutInitiate(variables: PostDisputePayoutInitiateMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostDisputePayoutInitiateDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostDisputePayoutInitiate', 'mutation', variables); + }, + PostDisputePayoutSettle(variables: PostDisputePayoutSettleMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(PostDisputePayoutSettleDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'PostDisputePayoutSettle', 'mutation', variables); + } + }; +} +export type Sdk = ReturnType; +/** + * Typed payloads for batching Ledger Entries with `addLedgerEntries`. + * + * Each payload is derived from the single-entry `addLedgerEntry` operation for + * one `(type, typeVersion)` pair, and builds an `AddLedgerEntryInput` you can + * mix freely with raw, untyped entry inputs in the same batch: + * + * ```ts + * await client.addLedgerEntries({ + * entries: [orderPlacedV1({ ik, ledgerIk, parameters: { ... } })], + * }); + * ``` + * + * A payload takes exactly what its source operation binds, so what you can set + * here is what that entry type accepts — no more. A field you do not set is + * left out of the request rather than sent as `null`. + */ + +/** + * Payload for the `order_placed` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostOrderPlaced` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type OrderPlacedV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + description?: Scalars['String']['input'] | undefined; + user_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + order_cost: Scalars['String']['input']; + currency: Scalars['String']['input']; + platform_fee: Scalars['String']['input']; + driver_fee: Scalars['String']['input']; + restaurant_id: Scalars['String']['input']; + driver_id: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `order_placed` (typeVersion 1). */ +export const orderPlacedV1 = ( + input: OrderPlacedV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + user_id: input.user_id, + order_id: input.order_id, + order_cost: input.order_cost, + currency: input.currency, + platform_fee: input.platform_fee, + driver_fee: input.driver_fee, + restaurant_id: input.restaurant_id, + driver_id: input.driver_id, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'order_placed', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Payload for the `order_placed` (typeVersion 2) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostOrderPlaced_v2` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type OrderPlacedV2 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + description?: Scalars['String']['input'] | undefined; + user_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + order_cost: Scalars['String']['input']; + currency: Scalars['String']['input']; + platform_fee: Scalars['String']['input']; + service_fee: Scalars['String']['input']; + driver_fee: Scalars['String']['input']; + restaurant_id: Scalars['String']['input']; + driver_id: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `order_placed` (typeVersion 2). */ +export const orderPlacedV2 = ( + input: OrderPlacedV2, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + user_id: input.user_id, + order_id: input.order_id, + order_cost: input.order_cost, + currency: input.currency, + platform_fee: input.platform_fee, + service_fee: input.service_fee, + driver_fee: input.driver_fee, + restaurant_id: input.restaurant_id, + driver_id: input.driver_id, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'order_placed', + typeVersion: 2, + }, + ik: input.ik, +}); + +/** + * Payload for the `card_settle` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostCardSettle` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type CardSettleV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + description?: Scalars['String']['input'] | undefined; + conditions?: Array | undefined; + user_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `card_settle` (typeVersion 1). */ +export const cardSettleV1 = ( + input: CardSettleV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + user_id: input.user_id, + order_id: input.order_id, + currency: input.currency, + amount: input.amount, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'card_settle', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Payload for the `restaurant_payout_initiate` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostRestaurantPayoutInitiate` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type RestaurantPayoutInitiateV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + description?: Scalars['String']['input'] | undefined; + conditions?: Array | undefined; + restaurant_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; + payout_id: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `restaurant_payout_initiate` (typeVersion 1). */ +export const restaurantPayoutInitiateV1 = ( + input: RestaurantPayoutInitiateV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + restaurant_id: input.restaurant_id, + order_id: input.order_id, + currency: input.currency, + amount: input.amount, + payout_id: input.payout_id, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'restaurant_payout_initiate', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Payload for the `restaurant_payout_settle` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostRestaurantPayoutSettle` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type RestaurantPayoutSettleV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + description?: Scalars['String']['input'] | undefined; + conditions?: Array | undefined; + restaurant_id: Scalars['String']['input']; + payout_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `restaurant_payout_settle` (typeVersion 1). */ +export const restaurantPayoutSettleV1 = ( + input: RestaurantPayoutSettleV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + restaurant_id: input.restaurant_id, + payout_id: input.payout_id, + currency: input.currency, + amount: input.amount, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'restaurant_payout_settle', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Payload for the `driver_payout_initiate` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostDriverPayoutInitiate` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type DriverPayoutInitiateV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + description?: Scalars['String']['input'] | undefined; + conditions?: Array | undefined; + driver_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; + payout_id: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `driver_payout_initiate` (typeVersion 1). */ +export const driverPayoutInitiateV1 = ( + input: DriverPayoutInitiateV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + driver_id: input.driver_id, + order_id: input.order_id, + currency: input.currency, + amount: input.amount, + payout_id: input.payout_id, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'driver_payout_initiate', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Payload for the `driver_payout_settle` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostDriverPayoutSettle` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type DriverPayoutSettleV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + description?: Scalars['String']['input'] | undefined; + conditions?: Array | undefined; + driver_id: Scalars['String']['input']; + payout_id: Scalars['String']['input']; + currency: Scalars['String']['input']; + amount: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `driver_payout_settle` (typeVersion 1). */ +export const driverPayoutSettleV1 = ( + input: DriverPayoutSettleV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + driver_id: input.driver_id, + payout_id: input.payout_id, + currency: input.currency, + amount: input.amount, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'driver_payout_settle', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Payload for the `dispute_payout_initiate` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostDisputePayoutInitiate` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type DisputePayoutInitiateV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + description?: Scalars['String']['input'] | undefined; + user_id: Scalars['String']['input']; + disputes_id: Scalars['String']['input']; + amount: Scalars['String']['input']; + currency: Scalars['String']['input']; + payout_id: Scalars['String']['input']; + order_id: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `dispute_payout_initiate` (typeVersion 1). */ +export const disputePayoutInitiateV1 = ( + input: DisputePayoutInitiateV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + user_id: input.user_id, + disputes_id: input.disputes_id, + amount: input.amount, + currency: input.currency, + payout_id: input.payout_id, + order_id: input.order_id, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'dispute_payout_initiate', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Payload for the `dispute_payout_settle` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostDisputePayoutSettle` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type DisputePayoutSettleV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + description?: Scalars['String']['input'] | undefined; + user_id: Scalars['String']['input']; + disputes_id: Scalars['String']['input']; + amount: Scalars['String']['input']; + currency: Scalars['String']['input']; + order_id: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `dispute_payout_settle` (typeVersion 1). */ +export const disputePayoutSettleV1 = ( + input: DisputePayoutSettleV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + user_id: input.user_id, + disputes_id: input.disputes_id, + amount: input.amount, + currency: input.currency, + order_id: input.order_id, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'dispute_payout_settle', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Every typed payload builder, keyed by `"@"`. Useful + * when the entry type is only known at runtime. + */ +export const typedLedgerEntryBuilders = { + 'order_placed@1': orderPlacedV1, + 'order_placed@2': orderPlacedV2, + 'card_settle@1': cardSettleV1, + 'restaurant_payout_initiate@1': restaurantPayoutInitiateV1, + 'restaurant_payout_settle@1': restaurantPayoutSettleV1, + 'driver_payout_initiate@1': driverPayoutInitiateV1, + 'driver_payout_settle@1': driverPayoutSettleV1, + 'dispute_payout_initiate@1': disputePayoutInitiateV1, + 'dispute_payout_settle@1': disputePayoutSettleV1, +} as const; diff --git a/tests/fixtures/generated-test-client.ts b/tests/fixtures/generated-test-client.ts index 3461bc3..b47f425 100644 --- a/tests/fixtures/generated-test-client.ts +++ b/tests/fixtures/generated-test-client.ts @@ -3618,4 +3618,194 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = } }; } -export type Sdk = ReturnType; \ No newline at end of file +export type Sdk = ReturnType; +/** + * Typed payloads for batching Ledger Entries with `addLedgerEntries`. + * + * Each payload is derived from the single-entry `addLedgerEntry` operation for + * one `(type, typeVersion)` pair, and builds an `AddLedgerEntryInput` you can + * mix freely with raw, untyped entry inputs in the same batch: + * + * ```ts + * await client.addLedgerEntries({ + * entries: [userFundsAccountV1({ ik, ledgerIk, parameters: { ... } })], + * }); + * ``` + * + * A payload takes exactly what its source operation binds, so what you can set + * here is what that entry type accepts — no more. A field you do not set is + * left out of the request rather than sent as `null`. + */ + +/** + * Payload for the `user-funds-account` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostUserFundsAccount` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type UserFundsAccountV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + amount: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `user-funds-account` (typeVersion 1). */ +export const userFundsAccountV1 = ( + input: UserFundsAccountV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + amount: input.amount, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'user-funds-account', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Payload for the `user-funds-account` (typeVersion 2) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostUserFundsAccount_v2` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type UserFundsAccountV2 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + amount: Scalars['String']['input']; + feeAmount: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `user-funds-account` (typeVersion 2). */ +export const userFundsAccountV2 = ( + input: UserFundsAccountV2, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + amount: input.amount, + feeAmount: input.feeAmount, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'user-funds-account', + typeVersion: 2, + }, + ik: input.ik, +}); + +/** + * Payload for the `fundingSettlement` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostFundingSettlement` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type FundingSettlementV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + amount: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `fundingSettlement` (typeVersion 1). */ +export const fundingSettlementV1 = ( + input: FundingSettlementV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + amount: input.amount, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'fundingSettlement', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Payload for the `payment_processing` (typeVersion 1) Ledger Entry, for use with `addLedgerEntries`. + * + * Derived from the `PostPaymentProcessing` operation, which is what a caller + * may set: these are exactly the fields that operation binds. + */ +export type PaymentProcessingV1 = { + /** The [Idempotency Key](https://fragment.dev/api-reference/api-overview#idempotency) for this Ledger Entry. */ + ik: Scalars['SafeString']['input']; + /** The Idempotency Key of the Ledger to add this Ledger Entry to. */ + ledgerIk: Scalars['SafeString']['input']; + /** ISO 8601 timestamp to post this Ledger Entry at. */ + posted?: Scalars['DateTime']['input'] | undefined; + description?: Scalars['String']['input'] | undefined; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; + amount: Scalars['String']['input']; +}; + +/** Builds an `addLedgerEntries` entry for `payment_processing` (typeVersion 1). */ +export const paymentProcessingV1 = ( + input: PaymentProcessingV1, +): AddLedgerEntryInput => ({ + entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), + ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), + ledger: { ik: input.ledgerIk }, + parameters: { + amount: input.amount, + }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), + type: 'payment_processing', + typeVersion: 1, + }, + ik: input.ik, +}); + +/** + * Every typed payload builder, keyed by `"@"`. Useful + * when the entry type is only known at runtime. + */ +export const typedLedgerEntryBuilders = { + 'user-funds-account@1': userFundsAccountV1, + 'user-funds-account@2': userFundsAccountV2, + 'fundingSettlement@1': fundingSettlementV1, + 'payment_processing@1': paymentProcessingV1, +} as const; diff --git a/tests/generated-sdk.test.ts b/tests/generated-sdk.test.ts index 447d021..dccc496 100644 --- a/tests/generated-sdk.test.ts +++ b/tests/generated-sdk.test.ts @@ -7,7 +7,11 @@ import { CurrencyMode, LedgerAccountTypes, } from "../generated/generated.js"; -import { getSdk } from "./fixtures/generated-test-client.js"; +import { + getSdk, + userFundsAccountV1, + userFundsAccountV2, +} from "./fixtures/generated-test-client.js"; const clientId = process.env.CLIENT_ID; const clientSecret = process.env.CLIENT_SECRET; @@ -375,3 +379,149 @@ test("PostPaymentProcessing method from generated SDK", async () => { } }); +test("addLedgerEntries commits a batch of typed payloads", async () => { + const client = await getClient(); + + const schemaKey = uuidv4(); + const storeSchemaResponse = await client.storeSchema({ + schema: { + key: schemaKey, + chartOfAccounts: { + defaultCurrencyMode: CurrencyMode.Multi, + accounts: [ + { + key: "asset-root", + name: "Asset Root", + type: LedgerAccountTypes.Asset, + children: [], + }, + { + key: "liability-root", + name: "Liability Root", + type: LedgerAccountTypes.Liability, + children: [], + }, + { + key: "expense-root", + name: "Expense Root", + type: LedgerAccountTypes.Expense, + children: [], + }, + ], + }, + ledgerEntries: { + types: [ + { + type: "user-funds-account", + typeVersion: 1, + lines: [ + { + key: "asset-line", + account: { path: "asset-root" }, + amount: "{{amount}}", + currency: { code: CurrencyCode.Usd }, + }, + { + key: "liability-line", + account: { path: "liability-root" }, + amount: "{{amount}}", + currency: { code: CurrencyCode.Usd }, + }, + ], + }, + { + type: "user-funds-account", + typeVersion: 2, + lines: [ + { + key: "asset-line", + account: { path: "asset-root" }, + amount: "{{amount}} - {{feeAmount}}", + currency: { code: CurrencyCode.Usd }, + }, + { + key: "liability-line", + account: { path: "liability-root" }, + amount: "{{amount}}", + currency: { code: CurrencyCode.Usd }, + }, + { + key: "fee-line", + account: { path: "expense-root" }, + amount: "{{feeAmount}}", + currency: { code: CurrencyCode.Usd }, + }, + ], + }, + ], + }, + }, + }); + + expect(storeSchemaResponse.storeSchema.__typename).toEqual( + "StoreSchemaResult" + ); + + const ledgerIk = uuidv4(); + const createLedgerResponse = await client.createLedger({ + ik: ledgerIk, + ledger: { + name: "Test SDK Batch Ledger", + }, + schemaKey, + }); + + expect(createLedgerResponse.createLedger.__typename).toEqual( + "CreateLedgerResult" + ); + + const firstIk = uuidv4(); + const secondIk = uuidv4(); + const entries = [ + userFundsAccountV1({ + ik: firstIk, + ledgerIk, + amount: "200", + }), + userFundsAccountV2({ + ik: secondIk, + ledgerIk, + amount: "300", + feeAmount: "10", + }), + ]; + + const batchResponse = await client.addLedgerEntries({ entries }); + + expect(batchResponse.addLedgerEntries.__typename).toEqual( + "AddLedgerEntriesResult" + ); + if ( + batchResponse.addLedgerEntries.__typename === "AddLedgerEntriesResult" + ) { + const { results } = batchResponse.addLedgerEntries; + // Results come back in the order the entries were sent. + expect(results.map((result) => result.entry.ik)).toEqual([ + firstIk, + secondIk, + ]); + expect(results.every((result) => result.entry.type === "user-funds-account")).toBe( + true + ); + // Version 1 posts 2 lines; version 2 adds the fee line. + expect(results[0].lines).toHaveLength(2); + expect(results[1].lines).toHaveLength(3); + expect(results.every((result) => !result.isIkReplay)).toBe(true); + } + + // Idempotency keys are per entry, so replaying the batch replays each entry. + const replayResponse = await client.addLedgerEntries({ entries }); + + if (replayResponse.addLedgerEntries.__typename === "AddLedgerEntriesResult") { + expect( + replayResponse.addLedgerEntries.results.every( + (result) => result.isIkReplay + ) + ).toBe(true); + } +}); diff --git a/tests/retries.test.ts b/tests/retries.test.ts index bbbe28a..422572e 100644 --- a/tests/retries.test.ts +++ b/tests/retries.test.ts @@ -2,6 +2,7 @@ import { beforeAll, describe, expect, it, vi } from "vitest"; import { setupServer } from "msw/node"; import { http, graphql, HttpResponse } from "msw"; import { createFragmentClient } from "../src/client.js"; +import { BadRequestError, InternalError } from "../src/errors.js"; const fragmentApi = graphql.link(`https://fragment-api.example.com/graphql`); @@ -99,4 +100,52 @@ describe("Client retries", () => { expect(onRetry).toHaveBeenCalledTimes(0); }); + + it("raises a non-retryable internal error without falling through", async () => { + // The mutation cases share a switch; a missing `break` would report this as + // a BadRequestError instead. + server.use( + fragmentApi.mutation("addLedgerEntry", () => + HttpResponse.json({ + data: { + addLedgerEntry: { + __typename: "InternalError", + code: "INTERNAL_ERROR", + message: "Something went wrong on our end", + retryable: false, + }, + }, + }), + ), + ); + + const onRetry = vi.fn(); + const client = createFragmentClient({ + params: { + clientId: "test", + clientSecret: "test", + scope: "*", + authUrl: "https://fragment-auth.example.com/oauth2/token", + apiUrl: "https://fragment-api.example.com/graphql", + }, + retryConfig: { retries: 2, minTimeout: 1, maxTimeout: 1, onRetry }, + }); + + let thrown: unknown; + try { + await client.addLedgerEntry({ + ik: "add-ledger-entry-ik", + ledgerIk: "ledger-ik", + type: "runtime-entry", + parameters: {}, + }); + } catch (error) { + thrown = error; + } + + expect(thrown).toBeInstanceOf(InternalError); + expect(thrown).not.toBeInstanceOf(BadRequestError); + expect((thrown as InternalError).code).toEqual("internal_error"); + expect(onRetry).not.toHaveBeenCalled(); + }); }); diff --git a/tests/template-schema-client.test.ts b/tests/template-schema-client.test.ts new file mode 100644 index 0000000..1437d9c --- /dev/null +++ b/tests/template-schema-client.test.ts @@ -0,0 +1,336 @@ +/** + * Snapshot coverage for the clients generated from `tests/template-schema/`, a + * real Fragment template Schema and the two operation sets the CLI generates + * from it: one that binds only `posted`, and one that also binds `tags`, + * `groups` and `conditions`. Both are committed, so any change to a generated + * identifier or signature shows up as a reviewable diff. + * + * The two sets are what makes the pair worth having: a payload exposes exactly + * what its source operation binds, so the runtime-args payloads accept more + * fields while describing the same entry types with the same parameters. + */ +import { readFileSync } from "fs"; +import path from "path"; +import { describe, expect, it } from "vitest"; +import { parse } from "graphql"; + +import { + deriveTypedEntryPayloads, + nameTypedEntryPayloads, +} from "../src/typedBatchEntries.js"; +import { + disputePayoutSettleV1, + orderPlacedV1, + orderPlacedV2, + typedLedgerEntryBuilders, +} from "./fixtures/generated-template-client.js"; +import { orderPlacedV2 as orderPlacedV2RuntimeArgs } from "./fixtures/generated-template-runtime-args-client.js"; + +const TEMPLATE_DIR = path.join(process.cwd(), "tests/template-schema"); +const FIXTURES_DIR = path.join(process.cwd(), "tests/fixtures"); + +const read = (file: string) => readFileSync(file, "utf-8"); + +/** A payload's own `export type = { ... }` block from a generated client. */ +const payloadType = (client: string, name: string) => { + const start = client.indexOf(`export type ${name} = {`); + expect(start).toBeGreaterThan(-1); + // Skip the declaration line so the type's own name cannot match a field check. + return client.slice(client.indexOf("\n", start), client.indexOf("\n};", start)); +}; + +/** Payload names, identities and wire parameter names, in derivation order. */ +const surfaceOf = (file: string) => + nameTypedEntryPayloads( + deriveTypedEntryPayloads([parse(read(path.join(TEMPLATE_DIR, file)))]), + ).map((payload) => ({ + name: payload.builderName, + type: payload.entryType, + version: payload.typeVersion, + parameters: + payload.parametersMode === "typed" + ? payload.parameters.map((parameter) => parameter.wireName) + : [], + })); + +const generatedClient = read( + path.join(FIXTURES_DIR, "generated-template-client.ts"), +); +const generatedRuntimeArgsClient = read( + path.join(FIXTURES_DIR, "generated-template-runtime-args-client.ts"), +); + +describe("generated template client", () => { + it("exposes the same common fields whichever operation set generated it", () => { + const plain = payloadType(generatedClient, "DisputePayoutInitiateV1"); + const runtimeArgs = payloadType( + generatedRuntimeArgsClient, + "DisputePayoutInitiateV1", + ); + + // These two operation sets come from different CLI generations and bind + // different entry fields -- the runtime-args one binds tags, groups and + // conditions, the plain one does not. Spec 2.3a is what makes that invisible + // to a caller: the common fields are fixed by `LedgerEntryInput`, so + // regenerating with a different CLI cannot move a payload's surface. + [plain, runtimeArgs].forEach((payload) => { + expect(payload).toContain("ledgerIk: Scalars['SafeString']['input'];"); + expect(payload).toContain("posted?: Scalars['DateTime']['input'] | undefined;"); + expect(payload).toContain("description?: Scalars['String']['input'] | undefined;"); + expect(payload).toContain("tags?: Array | undefined;"); + expect(payload).toContain("groups?: Array | undefined;"); + expect(payload).toContain( + "conditions?: Array | undefined;", + ); + + // `lines` is the one field a payload never offers: it cannot be combined + // with an entry that has a `type`. + expect(payload).not.toContain("lines"); + }); + }); + + it("gives every entry type in a set the same common fields", () => { + // `card_settle` binds tags and groups but not conditions. Before spec 2.3a + // that made its payload differ from its siblings'; now it does not. + const cardSettle = payloadType(generatedRuntimeArgsClient, "CardSettleV1"); + + expect(cardSettle).toContain("tags?: Array | undefined;"); + expect(cardSettle).toContain("groups?: Array | undefined;"); + expect(cardSettle).toContain( + "conditions?: Array | undefined;", + ); + }); + + it("matches the payload surface derived from the template Schema", () => { + expect(surfaceOf("queries.graphql")).toMatchInlineSnapshot(` + [ + { + "name": "orderPlacedV1", + "parameters": [ + "user_id", + "order_id", + "order_cost", + "currency", + "platform_fee", + "driver_fee", + "restaurant_id", + "driver_id", + ], + "type": "order_placed", + "version": 1, + }, + { + "name": "orderPlacedV2", + "parameters": [ + "user_id", + "order_id", + "order_cost", + "currency", + "platform_fee", + "service_fee", + "driver_fee", + "restaurant_id", + "driver_id", + ], + "type": "order_placed", + "version": 2, + }, + { + "name": "cardSettleV1", + "parameters": [ + "user_id", + "order_id", + "currency", + "amount", + ], + "type": "card_settle", + "version": 1, + }, + { + "name": "restaurantPayoutInitiateV1", + "parameters": [ + "restaurant_id", + "order_id", + "currency", + "amount", + "payout_id", + ], + "type": "restaurant_payout_initiate", + "version": 1, + }, + { + "name": "restaurantPayoutSettleV1", + "parameters": [ + "restaurant_id", + "payout_id", + "currency", + "amount", + ], + "type": "restaurant_payout_settle", + "version": 1, + }, + { + "name": "driverPayoutInitiateV1", + "parameters": [ + "driver_id", + "order_id", + "currency", + "amount", + "payout_id", + ], + "type": "driver_payout_initiate", + "version": 1, + }, + { + "name": "driverPayoutSettleV1", + "parameters": [ + "driver_id", + "payout_id", + "currency", + "amount", + ], + "type": "driver_payout_settle", + "version": 1, + }, + { + "name": "disputePayoutInitiateV1", + "parameters": [ + "user_id", + "disputes_id", + "amount", + "currency", + "payout_id", + "order_id", + ], + "type": "dispute_payout_initiate", + "version": 1, + }, + { + "name": "disputePayoutSettleV1", + "parameters": [ + "user_id", + "disputes_id", + "amount", + "currency", + "order_id", + ], + "type": "dispute_payout_settle", + "version": 1, + }, + ] + `); + + // The runtime-args operations describe the same Schema, so they must derive + // the same surface, down to parameter order. + expect(surfaceOf("queries.runtime-args.graphql")).toEqual( + surfaceOf("queries.graphql"), + ); + }); + + it("exposes every payload in the runtime registry", () => { + expect(Object.keys(typedLedgerEntryBuilders)).toEqual([ + "order_placed@1", + "order_placed@2", + "card_settle@1", + "restaurant_payout_initiate@1", + "restaurant_payout_settle@1", + "driver_payout_initiate@1", + "driver_payout_settle@1", + "dispute_payout_initiate@1", + "dispute_payout_settle@1", + ]); + }); + + it("keeps snake_case parameter names verbatim, in source order", () => { + const built = orderPlacedV2({ + ik: "order-1", + ledgerIk: "marketplace", + // Given in a different order than the operation declares them. + driver_id: "driver-1", + user_id: "user-1", + order_id: "order-1", + order_cost: "2000", + currency: "USD", + platform_fee: "200", + service_fee: "100", + driver_fee: "300", + restaurant_id: "restaurant-1", + }); + + expect(Object.keys(built.entry.parameters ?? {})).toEqual([ + "user_id", + "order_id", + "order_cost", + "currency", + "platform_fee", + "service_fee", + "driver_fee", + "restaurant_id", + "driver_id", + ]); + expect(built.entry.typeVersion).toEqual(2); + }); + + it("builds the same wire payload from either generated client", () => { + const input = { + ik: "order-1", + ledgerIk: "marketplace", + user_id: "user-1", + order_id: "order-1", + order_cost: "2000", + currency: "USD", + platform_fee: "200", + service_fee: "100", + driver_fee: "300", + restaurant_id: "restaurant-1", + driver_id: "driver-1", + }; + + expect(JSON.stringify(orderPlacedV2RuntimeArgs(input))).toEqual( + JSON.stringify(orderPlacedV2(input)), + ); + }); + + it("posts an unpinned entry type at its default version", () => { + const built = orderPlacedV1({ + ik: "order-1", + ledgerIk: "marketplace", + user_id: "user-1", + order_id: "order-1", + order_cost: "2000", + currency: "USD", + platform_fee: "200", + driver_fee: "300", + restaurant_id: "restaurant-1", + driver_id: "driver-1", + }); + + expect(built.entry.type).toEqual("order_placed"); + expect(built.entry.typeVersion).toEqual(1); + // Nothing the caller left unset reaches the wire. + expect(JSON.stringify(built)).not.toContain("null"); + expect(Object.keys(built.entry).sort()).toEqual([ + "ledger", + "parameters", + "type", + "typeVersion", + ]); + }); + + it("does not offer lines when the operation does not bind them", () => { + const built = disputePayoutSettleV1({ + ik: "dispute-1", + ledgerIk: "marketplace", + // @ts-expect-error this entry type's lines are fixed by the Schema, so its + // operation does not bind them and the payload does not accept them. + lines: [], + user_id: "user-1", + disputes_id: "dispute-1", + amount: "500", + currency: "USD", + order_id: "order-1", + }); + + expect("lines" in built.entry).toBe(false); + }); +}); diff --git a/tests/template-schema/README.md b/tests/template-schema/README.md new file mode 100644 index 0000000..eb74b50 --- /dev/null +++ b/tests/template-schema/README.md @@ -0,0 +1,19 @@ +# template-schema + +Vendored from [`fragment-dev/graphql-queries`](https://github.com/fragment-dev/graphql-queries) +(`template-schema/`). Do not edit these files by hand — copy them in again +instead, and re-run `yarn update-test-schema`. + +| File | What it is | +| --- | --- | +| `schema.json` | A Fragment template Schema (marketplace). | +| `queries.graphql` | Entry-posting mutations the Fragment CLI generates from it. | +| `queries.runtime-args.graphql` | The same mutations, generated with `--include-runtime-args`. | + +The two operation sets bind different entry fields — one binds `typeVersion`, +the other binds `tags`, `groups` and `conditions` — which is why both are here. +What an operation binds is a codegen input, never the transport, so the typed +batch payloads derived from either must be identical. The committed clients in +`tests/fixtures/generated-template-client.ts` and +`generated-template-runtime-args-client.ts` are the snapshots that hold that, +and any change to a generated identifier or signature shows up as a diff there. diff --git a/tests/template-schema/queries.graphql b/tests/template-schema/queries.graphql new file mode 100644 index 0000000..052eb4c --- /dev/null +++ b/tests/template-schema/queries.graphql @@ -0,0 +1,467 @@ +# Generated by the Fragment CLI (@fragment-dev/cli) v2026.8.4-11 +# Generated on 2026-08-04 from template-schema/schema.json +# +# Command: +# fragment gen-graphql --path template-schema/schema.json --output template-schema/queries.graphql +# +# Do not edit by hand -- regenerating overwrites this file, including this header. + +mutation PostOrderPlaced($ik: SafeString!, $posted: DateTime, $ledgerIk: SafeString!, $user_id: String!, $order_id: String!, $order_cost: String!, $currency: String!, $platform_fee: String!, $driver_fee: String!, $restaurant_id: String!, $driver_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "order_placed", typeVersion: 1, posted: $posted, parameters: {user_id: $user_id, order_id: $order_id, order_cost: $order_cost, currency: $currency, platform_fee: $platform_fee, driver_fee: $driver_fee, restaurant_id: $restaurant_id, driver_id: $driver_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} +mutation PostOrderPlaced_v2($ik: SafeString!, $posted: DateTime, $ledgerIk: SafeString!, $user_id: String!, $order_id: String!, $order_cost: String!, $currency: String!, $platform_fee: String!, $service_fee: String!, $driver_fee: String!, $restaurant_id: String!, $driver_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "order_placed", typeVersion: 2, posted: $posted, parameters: {user_id: $user_id, order_id: $order_id, order_cost: $order_cost, currency: $currency, platform_fee: $platform_fee, service_fee: $service_fee, driver_fee: $driver_fee, restaurant_id: $restaurant_id, driver_id: $driver_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} +mutation PostCardSettle($ik: SafeString!, $posted: DateTime, $ledgerIk: SafeString!, $user_id: String!, $order_id: String!, $currency: String!, $amount: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "card_settle", typeVersion: 1, posted: $posted, parameters: {user_id: $user_id, order_id: $order_id, currency: $currency, amount: $amount}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} +mutation PostRestaurantPayoutInitiate($ik: SafeString!, $posted: DateTime, $ledgerIk: SafeString!, $restaurant_id: String!, $order_id: String!, $currency: String!, $amount: String!, $payout_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "restaurant_payout_initiate", typeVersion: 1, posted: $posted, parameters: {restaurant_id: $restaurant_id, order_id: $order_id, currency: $currency, amount: $amount, payout_id: $payout_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} +mutation PostRestaurantPayoutSettle($ik: SafeString!, $posted: DateTime, $ledgerIk: SafeString!, $restaurant_id: String!, $payout_id: String!, $currency: String!, $amount: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "restaurant_payout_settle", typeVersion: 1, posted: $posted, parameters: {restaurant_id: $restaurant_id, payout_id: $payout_id, currency: $currency, amount: $amount}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} +mutation PostDriverPayoutInitiate($ik: SafeString!, $posted: DateTime, $ledgerIk: SafeString!, $driver_id: String!, $order_id: String!, $currency: String!, $amount: String!, $payout_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "driver_payout_initiate", typeVersion: 1, posted: $posted, parameters: {driver_id: $driver_id, order_id: $order_id, currency: $currency, amount: $amount, payout_id: $payout_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} +mutation PostDriverPayoutSettle($ik: SafeString!, $posted: DateTime, $ledgerIk: SafeString!, $driver_id: String!, $payout_id: String!, $currency: String!, $amount: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "driver_payout_settle", typeVersion: 1, posted: $posted, parameters: {driver_id: $driver_id, payout_id: $payout_id, currency: $currency, amount: $amount}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} +mutation PostDisputePayoutInitiate($ik: SafeString!, $posted: DateTime, $ledgerIk: SafeString!, $user_id: String!, $disputes_id: String!, $amount: String!, $currency: String!, $payout_id: String!, $order_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "dispute_payout_initiate", typeVersion: 1, posted: $posted, parameters: {user_id: $user_id, disputes_id: $disputes_id, amount: $amount, currency: $currency, payout_id: $payout_id, order_id: $order_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} +mutation PostDisputePayoutSettle($ik: SafeString!, $posted: DateTime, $ledgerIk: SafeString!, $user_id: String!, $disputes_id: String!, $amount: String!, $currency: String!, $order_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "dispute_payout_settle", typeVersion: 1, posted: $posted, parameters: {user_id: $user_id, disputes_id: $disputes_id, amount: $amount, currency: $currency, order_id: $order_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} diff --git a/tests/template-schema/queries.runtime-args.graphql b/tests/template-schema/queries.runtime-args.graphql new file mode 100644 index 0000000..39f8ec0 --- /dev/null +++ b/tests/template-schema/queries.runtime-args.graphql @@ -0,0 +1,467 @@ +# Generated by the Fragment CLI (@fragment-dev/cli) v2026.8.4-11 +# Generated on 2026-08-04 from template-schema/schema.json +# +# Command: +# fragment gen-graphql --path template-schema/schema.json --output template-schema/queries.runtime-args.graphql --include-runtime-args +# +# Do not edit by hand -- regenerating overwrites this file, including this header. + +mutation PostOrderPlaced($ik: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $groups: [LedgerEntryGroupInput!], $conditions: [LedgerEntryConditionInput!], $ledgerIk: SafeString!, $user_id: String!, $order_id: String!, $order_cost: String!, $currency: String!, $platform_fee: String!, $driver_fee: String!, $restaurant_id: String!, $driver_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "order_placed", typeVersion: 1, posted: $posted, tags: $tags, groups: $groups, conditions: $conditions, parameters: {user_id: $user_id, order_id: $order_id, order_cost: $order_cost, currency: $currency, platform_fee: $platform_fee, driver_fee: $driver_fee, restaurant_id: $restaurant_id, driver_id: $driver_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} +mutation PostOrderPlaced_v2($ik: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $groups: [LedgerEntryGroupInput!], $conditions: [LedgerEntryConditionInput!], $ledgerIk: SafeString!, $user_id: String!, $order_id: String!, $order_cost: String!, $currency: String!, $platform_fee: String!, $service_fee: String!, $driver_fee: String!, $restaurant_id: String!, $driver_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "order_placed", typeVersion: 2, posted: $posted, tags: $tags, groups: $groups, conditions: $conditions, parameters: {user_id: $user_id, order_id: $order_id, order_cost: $order_cost, currency: $currency, platform_fee: $platform_fee, service_fee: $service_fee, driver_fee: $driver_fee, restaurant_id: $restaurant_id, driver_id: $driver_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} +mutation PostCardSettle($ik: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $groups: [LedgerEntryGroupInput!], $ledgerIk: SafeString!, $user_id: String!, $order_id: String!, $currency: String!, $amount: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "card_settle", typeVersion: 1, posted: $posted, tags: $tags, groups: $groups, parameters: {user_id: $user_id, order_id: $order_id, currency: $currency, amount: $amount}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} +mutation PostRestaurantPayoutInitiate($ik: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $groups: [LedgerEntryGroupInput!], $ledgerIk: SafeString!, $restaurant_id: String!, $order_id: String!, $currency: String!, $amount: String!, $payout_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "restaurant_payout_initiate", typeVersion: 1, posted: $posted, tags: $tags, groups: $groups, parameters: {restaurant_id: $restaurant_id, order_id: $order_id, currency: $currency, amount: $amount, payout_id: $payout_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} +mutation PostRestaurantPayoutSettle($ik: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $groups: [LedgerEntryGroupInput!], $ledgerIk: SafeString!, $restaurant_id: String!, $payout_id: String!, $currency: String!, $amount: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "restaurant_payout_settle", typeVersion: 1, posted: $posted, tags: $tags, groups: $groups, parameters: {restaurant_id: $restaurant_id, payout_id: $payout_id, currency: $currency, amount: $amount}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} +mutation PostDriverPayoutInitiate($ik: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $groups: [LedgerEntryGroupInput!], $ledgerIk: SafeString!, $driver_id: String!, $order_id: String!, $currency: String!, $amount: String!, $payout_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "driver_payout_initiate", typeVersion: 1, posted: $posted, tags: $tags, groups: $groups, parameters: {driver_id: $driver_id, order_id: $order_id, currency: $currency, amount: $amount, payout_id: $payout_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} +mutation PostDriverPayoutSettle($ik: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $groups: [LedgerEntryGroupInput!], $ledgerIk: SafeString!, $driver_id: String!, $payout_id: String!, $currency: String!, $amount: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "driver_payout_settle", typeVersion: 1, posted: $posted, tags: $tags, groups: $groups, parameters: {driver_id: $driver_id, payout_id: $payout_id, currency: $currency, amount: $amount}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} +mutation PostDisputePayoutInitiate($ik: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $groups: [LedgerEntryGroupInput!], $conditions: [LedgerEntryConditionInput!], $ledgerIk: SafeString!, $user_id: String!, $disputes_id: String!, $amount: String!, $currency: String!, $payout_id: String!, $order_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "dispute_payout_initiate", typeVersion: 1, posted: $posted, tags: $tags, groups: $groups, conditions: $conditions, parameters: {user_id: $user_id, disputes_id: $disputes_id, amount: $amount, currency: $currency, payout_id: $payout_id, order_id: $order_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} +mutation PostDisputePayoutSettle($ik: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $groups: [LedgerEntryGroupInput!], $conditions: [LedgerEntryConditionInput!], $ledgerIk: SafeString!, $user_id: String!, $disputes_id: String!, $amount: String!, $currency: String!, $order_id: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "dispute_payout_settle", typeVersion: 1, posted: $posted, tags: $tags, groups: $groups, conditions: $conditions, parameters: {user_id: $user_id, disputes_id: $disputes_id, amount: $amount, currency: $currency, order_id: $order_id}} + ) { + __typename + ... on AddLedgerEntryResult { + entry { + ik + id + created + type + posted + description + ledger { + ik + } + tags { + key + value + } + groups { + key + value + } + } + lines { + account { + path + } + amount + currency { + code + customCurrencyId + } + key + } + isIkReplay + } + ... on BadRequestError { + code + message + retryable + } + ... on InternalError { + code + message + retryable + } + } +} diff --git a/tests/template-schema/schema.json b/tests/template-schema/schema.json new file mode 100644 index 0000000..3b9c210 --- /dev/null +++ b/tests/template-schema/schema.json @@ -0,0 +1,1234 @@ +{ + "key": "marketplace-template", + "name": "marketplace-template", + "chartOfAccounts": { + "defaultCurrencyMode": "multi", + "defaultConsistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "accounts": [ + { + "key": "assets", + "type": "asset", + "name": "assets", + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [ + { + "key": "cash", + "type": "asset", + "name": "cash", + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [], + "status": "active" + }, + { + "key": "receivables", + "type": "asset", + "name": "receivables", + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [ + { + "key": "user", + "type": "asset", + "name": "user", + "template": true, + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [ + { + "key": "order", + "type": "asset", + "name": "order", + "template": true, + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [], + "status": "active", + "clearing": true + } + ], + "status": "active" + } + ], + "status": "active" + } + ], + "status": "active" + }, + { + "key": "liabilities", + "type": "liability", + "name": "liabilities", + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [ + { + "key": "payables", + "type": "liability", + "name": "payables", + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [ + { + "key": "restaurant", + "type": "liability", + "name": "restaurant", + "template": true, + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [ + { + "key": "order", + "type": "liability", + "name": "order", + "template": true, + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [], + "status": "active", + "clearing": true + }, + { + "key": "payout", + "type": "liability", + "name": "payout", + "template": true, + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [], + "status": "active", + "clearing": true + } + ], + "status": "active" + }, + { + "key": "driver", + "type": "liability", + "name": "driver", + "template": true, + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [ + { + "key": "order", + "type": "liability", + "name": "order", + "template": true, + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [], + "status": "active", + "clearing": true + }, + { + "key": "payout", + "type": "liability", + "name": "payout", + "template": true, + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [], + "status": "active", + "clearing": true + } + ], + "status": "active" + }, + { + "key": "user", + "type": "liability", + "name": "user", + "template": true, + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [ + { + "key": "disputes", + "type": "liability", + "name": "disputes", + "template": true, + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [ + { + "key": "payout", + "type": "liability", + "name": "payout", + "template": true, + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [], + "status": "active", + "clearing": true + } + ], + "status": "active" + } + ], + "status": "active" + } + ], + "status": "active" + } + ], + "status": "active" + }, + { + "key": "income", + "type": "income", + "name": "income", + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [ + { + "key": "platform_fee", + "type": "income", + "name": "platform_fee", + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [], + "status": "active" + } + ], + "status": "active" + }, + { + "key": "expenses", + "type": "expense", + "name": "expenses", + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [ + { + "key": "disputes", + "type": "expense", + "name": "disputes", + "template": true, + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [ + { + "key": "payout", + "type": "expense", + "name": "payout", + "template": true, + "currencyMode": "multi", + "consistencyConfig": { + "lines": "eventual", + "totalBalanceUpdates": "strong", + "groups": [] + }, + "children": [], + "status": "active" + } + ], + "status": "active" + } + ], + "status": "active" + } + ] + }, + "consistencyConfig": { + "entries": "eventual" + }, + "ledgerEntries": { + "types": [ + { + "conditions": [], + "description": "Buyer places a food order paid by card. The card charge is booked as a receivable from the card network; per-order payables are created for the restaurant and driver; platform fee is recognized as income.", + "groups": [], + "lines": [ + { + "key": "line-aX9LiI8OYR1t", + "account": { + "path": "assets/receivables/user:{{user_id}}/order:{{order_id}}" + }, + "amount": "{{order_cost}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-awQYhSGjNrmz", + "account": { + "path": "assets/receivables/user:{{user_id}}/order:{{order_id}}" + }, + "amount": "{{platform_fee}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-sH8INdJEcuJq", + "account": { + "path": "assets/receivables/user:{{user_id}}/order:{{order_id}}" + }, + "amount": "{{driver_fee}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-RoE5jE2sZoSM", + "account": { + "path": "liabilities/payables/restaurant:{{restaurant_id}}/order:{{order_id}}" + }, + "amount": "{{order_cost}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-kYWR5qby88cZ", + "account": { + "path": "liabilities/payables/driver:{{driver_id}}/order:{{order_id}}" + }, + "amount": "{{driver_fee}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-PdgTyZclO8T4", + "account": { + "path": "income/platform_fee" + }, + "amount": "{{platform_fee}}", + "currency": { + "code": "{{currency}}" + } + } + ], + "postLinesAs": "net_amounts", + "status": "active", + "tags": [ + { + "key": "order_id", + "value": "{{order_id}}" + } + ], + "type": "order_placed", + "typeVersion": 1 + }, + { + "conditions": [], + "description": "v2: Buyer places a food order paid by card. Adds a separate service_fee charged to the buyer and recognized as platform income, alongside the existing platform_fee. Per-order payables are created for the restaurant and driver.", + "groups": [], + "lines": [ + { + "key": "line-s6w7JEGShB4t", + "account": { + "path": "assets/receivables/user:{{user_id}}/order:{{order_id}}" + }, + "amount": "{{order_cost}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-BSMQeAXOR887", + "account": { + "path": "assets/receivables/user:{{user_id}}/order:{{order_id}}" + }, + "amount": "{{platform_fee}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-ROgHdMAebwAu", + "account": { + "path": "assets/receivables/user:{{user_id}}/order:{{order_id}}" + }, + "amount": "{{service_fee}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-ZfvhdcqpuoVP", + "account": { + "path": "assets/receivables/user:{{user_id}}/order:{{order_id}}" + }, + "amount": "{{driver_fee}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-vGkJkALirzrn", + "account": { + "path": "liabilities/payables/restaurant:{{restaurant_id}}/order:{{order_id}}" + }, + "amount": "{{order_cost}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-L2Sl1bCNTT3P", + "account": { + "path": "liabilities/payables/driver:{{driver_id}}/order:{{order_id}}" + }, + "amount": "{{driver_fee}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-SV8LS0hHPe9y", + "account": { + "path": "income/platform_fee" + }, + "amount": "{{platform_fee}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-jd1mtCc0XXPY", + "account": { + "path": "income/platform_fee" + }, + "amount": "{{service_fee}}", + "currency": { + "code": "{{currency}}" + } + } + ], + "postLinesAs": "net_amounts", + "status": "active", + "tags": [ + { + "key": "order_id", + "value": "{{order_id}}" + }, + { + "key": "restaurant_id", + "value": "{{restaurant_id}}" + } + ], + "type": "order_placed", + "typeVersion": 2 + }, + { + "conditions": [ + { + "account": { + "path": "assets/receivables/user:{{user_id}}/order:{{order_id}}" + }, + "currency": { + "code": "{{currency}}" + }, + "postcondition": { + "totalBalance": { + "gte": "0" + } + } + } + ], + "description": "Card network settles a card charge: clears the order's card receivable and deposits funds into cash.", + "groups": [], + "lines": [ + { + "key": "line-1MqNjwZV54G1", + "account": { + "path": "assets/receivables/user:{{user_id}}/order:{{order_id}}" + }, + "amount": "-{{amount}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-n4tIH5XZep5f", + "account": { + "path": "assets/cash" + }, + "amount": "{{amount}}", + "currency": { + "code": "{{currency}}" + } + } + ], + "postLinesAs": "raw_lines", + "status": "active", + "tags": [ + { + "key": "order_id", + "value": "{{order_id}}" + } + ], + "type": "card_settle", + "typeVersion": 1 + }, + { + "conditions": [ + { + "account": { + "path": "liabilities/payables/restaurant:{{restaurant_id}}/order:{{order_id}}" + }, + "currency": { + "code": "{{currency}}" + }, + "postcondition": { + "totalBalance": { + "gte": "0" + } + } + } + ], + "description": "Initiates an ACH payout to a restaurant: moves one or more order payables into a payout batch (payout_id).", + "groups": [], + "lines": [ + { + "key": "line-v7RhhaV2D5LZ", + "account": { + "path": "liabilities/payables/restaurant:{{restaurant_id}}/order:{{order_id}}" + }, + "amount": "-{{amount}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-gLxqKai2j5LE", + "account": { + "path": "liabilities/payables/restaurant:{{restaurant_id}}/payout:{{payout_id}}" + }, + "amount": "{{amount}}", + "currency": { + "code": "{{currency}}" + } + } + ], + "postLinesAs": "raw_lines", + "status": "active", + "tags": [ + { + "key": "restaurant_id", + "value": "{{restaurant_id}}" + }, + { + "key": "order_id", + "value": "{{order_id}}" + }, + { + "key": "payout_id", + "value": "{{payout_id}}" + } + ], + "type": "restaurant_payout_initiate", + "typeVersion": 1 + }, + { + "conditions": [ + { + "account": { + "path": "liabilities/payables/restaurant:{{restaurant_id}}/payout:{{payout_id}}" + }, + "currency": { + "code": "{{currency}}" + }, + "postcondition": { + "totalBalance": { + "gte": "0" + } + } + } + ], + "description": "Settles an ACH payout batch to a restaurant: clears the payout balance and reduces cash.", + "groups": [], + "lines": [ + { + "key": "line-tLx34nt5WFKC", + "account": { + "path": "liabilities/payables/restaurant:{{restaurant_id}}/payout:{{payout_id}}" + }, + "amount": "-{{amount}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-p3ynuNKIF81K", + "account": { + "path": "assets/cash" + }, + "amount": "-{{amount}}", + "currency": { + "code": "{{currency}}" + } + } + ], + "postLinesAs": "raw_lines", + "status": "active", + "tags": [ + { + "key": "restaurant_id", + "value": "{{restaurant_id}}" + }, + { + "key": "payout_id", + "value": "{{payout_id}}" + } + ], + "type": "restaurant_payout_settle", + "typeVersion": 1 + }, + { + "conditions": [ + { + "account": { + "path": "liabilities/payables/driver:{{driver_id}}/order:{{order_id}}" + }, + "currency": { + "code": "{{currency}}" + }, + "postcondition": { + "totalBalance": { + "gte": "0" + } + } + } + ], + "description": "Initiates an ACH payout to a driver: moves one or more order payables into a payout batch (payout_id).", + "groups": [], + "lines": [ + { + "key": "line-FX7v2cZoKRn7", + "account": { + "path": "liabilities/payables/driver:{{driver_id}}/order:{{order_id}}" + }, + "amount": "-{{amount}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-18LThiLLNDW5", + "account": { + "path": "liabilities/payables/driver:{{driver_id}}/payout:{{payout_id}}" + }, + "amount": "{{amount}}", + "currency": { + "code": "{{currency}}" + } + } + ], + "postLinesAs": "raw_lines", + "status": "active", + "tags": [ + { + "key": "driver_id", + "value": "{{driver_id}}" + }, + { + "key": "order_id", + "value": "{{order_id}}" + }, + { + "key": "payout_id", + "value": "{{payout_id}}" + } + ], + "type": "driver_payout_initiate", + "typeVersion": 1 + }, + { + "conditions": [ + { + "account": { + "path": "liabilities/payables/driver:{{driver_id}}/payout:{{payout_id}}" + }, + "currency": { + "code": "{{currency}}" + }, + "postcondition": { + "totalBalance": { + "gte": "0" + } + } + } + ], + "description": "Settles an ACH payout batch to a driver: clears the payout balance and reduces cash.", + "groups": [], + "lines": [ + { + "key": "line-ribhyxr21Qwa", + "account": { + "path": "liabilities/payables/driver:{{driver_id}}/payout:{{payout_id}}" + }, + "amount": "-{{amount}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-UIcHoToL7HSt", + "account": { + "path": "assets/cash" + }, + "amount": "-{{amount}}", + "currency": { + "code": "{{currency}}" + } + } + ], + "postLinesAs": "raw_lines", + "status": "active", + "tags": [ + { + "key": "driver_id", + "value": "{{driver_id}}" + }, + { + "key": "payout_id", + "value": "{{payout_id}}" + } + ], + "type": "driver_payout_settle", + "typeVersion": 1 + }, + { + "conditions": [], + "description": "User charge is disputed", + "groups": [], + "lines": [ + { + "key": "line-WZ5Xt1114ub1", + "account": { + "path": "liabilities/payables/user:{{user_id}}/disputes:{{disputes_id}}" + }, + "amount": "{{amount}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-qS2ANgzPxGxG", + "account": { + "path": "expenses/disputes:{{disputes_id}}/payout:{{payout_id}}" + }, + "amount": "{{amount}}", + "currency": { + "code": "{{currency}}" + } + } + ], + "postLinesAs": "raw_lines", + "status": "active", + "tags": [ + { + "key": "user_id", + "value": "{{user_id}}" + }, + { + "key": "order_id", + "value": "{{order_id}}" + } + ], + "type": "dispute_payout_initiate", + "typeVersion": 1 + }, + { + "conditions": [], + "description": "dispute paid out from cash, payable removed, transfer from held to paid", + "groups": [], + "lines": [ + { + "key": "line-Zh7Qzh5ORaj1", + "account": { + "path": "liabilities/payables/user:{{user_id}}/disputes:{{disputes_id}}" + }, + "amount": "-{{amount}}", + "currency": { + "code": "{{currency}}" + } + }, + { + "key": "line-8hREp3o0EnEL", + "account": { + "path": "assets/cash" + }, + "amount": "-{{amount}}", + "currency": { + "code": "{{currency}}" + } + } + ], + "postLinesAs": "raw_lines", + "status": "active", + "tags": [ + { + "key": "user_id", + "value": "{{user_id}}" + }, + { + "key": "order_id", + "value": "{{order_id}}" + } + ], + "type": "dispute_payout_settle", + "typeVersion": 1 + } + ] + }, + "scenes": [ + { + "name": "Single order", + "events": [ + { + "eventType": "entry", + "entry": { + "type": "order_placed", + "typeVersion": 1, + "parameters": { + "order_cost": "2000", + "platform_fee": "500", + "driver_fee": "500", + "restaurant_id": "Arbys", + "driver_id": "alice", + "order_id": "1", + "currency": "USD", + "user_id": "carol" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "card_settle", + "typeVersion": 1, + "parameters": { + "amount": "3000", + "order_id": "1", + "currency": "USD", + "user_id": "carol" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "restaurant_payout_initiate", + "typeVersion": 1, + "parameters": { + "amount": "2000", + "restaurant_id": "Arbys", + "order_id": "1", + "currency": "USD", + "payout_id": "1" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "driver_payout_initiate", + "typeVersion": 1, + "parameters": { + "amount": "500", + "driver_id": "alice", + "order_id": "1", + "currency": "USD", + "payout_id": "2" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "restaurant_payout_settle", + "typeVersion": 1, + "parameters": { + "amount": "2000", + "restaurant_id": "Arbys", + "currency": "USD", + "payout_id": "1" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "driver_payout_settle", + "typeVersion": 1, + "parameters": { + "amount": "500", + "driver_id": "alice", + "currency": "USD", + "payout_id": "2" + } + } + } + ] + }, + { + "name": "Multiple orders", + "events": [ + { + "eventType": "entry", + "entry": { + "type": "order_placed", + "typeVersion": 1, + "parameters": { + "order_cost": "2000", + "platform_fee": "500", + "driver_fee": "500", + "restaurant_id": "Arbys", + "driver_id": "bob", + "order_id": "1", + "currency": "USD", + "user_id": "carol" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "order_placed", + "typeVersion": 1, + "parameters": { + "order_cost": "3000", + "platform_fee": "1000", + "driver_fee": "1000", + "restaurant_id": "Arbys", + "driver_id": "alice", + "order_id": "2", + "currency": "USD", + "user_id": "dave" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "card_settle", + "typeVersion": 1, + "parameters": { + "amount": "3000", + "order_id": "1", + "currency": "USD", + "user_id": "carol" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "card_settle", + "typeVersion": 1, + "parameters": { + "amount": "5000", + "order_id": "2", + "currency": "USD", + "user_id": "dave" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "restaurant_payout_initiate", + "typeVersion": 1, + "parameters": { + "amount": "2000", + "restaurant_id": "Arbys", + "order_id": "1", + "currency": "USD", + "payout_id": "1" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "restaurant_payout_initiate", + "typeVersion": 1, + "parameters": { + "amount": "3000", + "restaurant_id": "Arbys", + "order_id": "2", + "currency": "USD", + "payout_id": "1" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "driver_payout_initiate", + "typeVersion": 1, + "parameters": { + "amount": "1000", + "driver_id": "alice", + "order_id": "2", + "currency": "USD", + "payout_id": "3" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "driver_payout_initiate", + "typeVersion": 1, + "parameters": { + "amount": "500", + "driver_id": "bob", + "order_id": "1", + "currency": "USD", + "payout_id": "2" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "restaurant_payout_settle", + "typeVersion": 1, + "parameters": { + "amount": "5000", + "restaurant_id": "Arbys", + "currency": "USD", + "payout_id": "1" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "driver_payout_settle", + "typeVersion": 1, + "parameters": { + "amount": "500", + "driver_id": "bob", + "currency": "USD", + "payout_id": "2" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "driver_payout_settle", + "typeVersion": 1, + "parameters": { + "amount": "1000", + "driver_id": "alice", + "currency": "USD", + "payout_id": "3" + } + } + } + ] + }, + { + "name": "Charge Dispute", + "events": [ + { + "eventType": "entry", + "entry": { + "type": "order_placed", + "typeVersion": 1, + "parameters": { + "order_cost": "2000", + "platform_fee": "500", + "driver_fee": "500", + "restaurant_id": "Arbys", + "driver_id": "alice", + "order_id": "1", + "user_id": "carol", + "currency": "USD" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "card_settle", + "typeVersion": 1, + "parameters": { + "amount": "3000", + "order_id": "1", + "user_id": "carol", + "currency": "USD" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "restaurant_payout_initiate", + "typeVersion": 1, + "parameters": { + "amount": "2000", + "restaurant_id": "Arbys", + "order_id": "1", + "currency": "USD", + "payout_id": "1" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "restaurant_payout_settle", + "typeVersion": 1, + "parameters": { + "amount": "2000", + "restaurant_id": "Arbys", + "currency": "USD", + "payout_id": "1" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "driver_payout_initiate", + "typeVersion": 1, + "parameters": { + "amount": "500", + "driver_id": "alice", + "order_id": "1", + "currency": "USD", + "payout_id": "2" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "driver_payout_settle", + "typeVersion": 1, + "parameters": { + "amount": "500", + "driver_id": "alice", + "currency": "USD", + "payout_id": "2" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "dispute_payout_initiate", + "typeVersion": 1, + "parameters": { + "amount": "3000", + "order_id": "1", + "user_id": "carol", + "currency": "USD", + "disputes_id": "123", + "payout_id": "tx_123" + } + } + }, + { + "eventType": "entry", + "entry": { + "type": "dispute_payout_settle", + "typeVersion": 1, + "parameters": { + "amount": "3000", + "order_id": "1", + "user_id": "carol", + "currency": "USD", + "disputes_id": "123" + } + } + } + ] + } + ], + "groups": [] +} diff --git a/tests/typed-batch-entries.test.ts b/tests/typed-batch-entries.test.ts new file mode 100644 index 0000000..8973217 --- /dev/null +++ b/tests/typed-batch-entries.test.ts @@ -0,0 +1,708 @@ +import { describe, expect, it, vi } from "vitest"; +import { parse } from "graphql"; + +import type { TypedEntryPayload } from "../src/typedBatchEntries.js"; +import { + collectScalarNames, + deriveTypedEntryPayloads, + generateTypedEntryPayloads, + nameTypedEntryPayloads, + renderTypedEntryPayloads, +} from "../src/typedBatchEntries.js"; + +const SCHEMA = parse(` + scalar SafeString + scalar DateTime + scalar Int64 + scalar JSON +`); + +const scalars = collectScalarNames(SCHEMA); + +/** A payload's typed parameters, or none when it takes no typed parameters. */ +const parametersOf = (payload: TypedEntryPayload) => + payload.parametersMode === "typed" ? payload.parameters : []; + +const derive = (source: string, warn = vi.fn()) => + deriveTypedEntryPayloads([parse(source)], { warn }); + +const generate = (source: string, warn = vi.fn()) => + generateTypedEntryPayloads({ + documents: [parse(source)], + schema: SCHEMA, + warn, + }); + +/** + * The generated `export type = { ... }` block on its own. The shared + * preamble and runtime helper mention words like `parameters` and `null`, so + * assertions about a payload's fields have to look at just that payload. + */ +const payloadType = (output: string, name: string) => { + const start = output.indexOf(`export type ${name} = {`); + expect(start).toBeGreaterThan(-1); + return output.slice(start, output.indexOf("\n};", start)); +}; + +/** + * The body of a payload's builder — the `AddLedgerEntryInput` it constructs. + * A builder ends `\n});` in its expression form and `\n};` in its block form, + * so take whichever terminator comes first rather than assuming one. + */ +const builderBody = (output: string, name: string) => { + const start = output.indexOf(`export const ${name} = (`); + expect(start).toBeGreaterThan(-1); + const ends = ["\n});", "\n};"] + .map((terminator) => output.indexOf(terminator, start)) + .filter((end) => end > -1); + expect(ends.length).toBeGreaterThan(0); + return output.slice(start, Math.min(...ends)); +}; + +const entryOperation = ({ + name = "PostThing", + type = `"thing"`, + typeVersion = "", + entryFields = "parameters: {amount: $amount}", + variables = "$ik: SafeString!, $ledgerIk: SafeString!, $amount: String!", +}: { + name?: string; + type?: string; + typeVersion?: string; + /** Any further fields of the `entry` object, as written in the operation. */ + entryFields?: string; + variables?: string; +} = {}) => ` + mutation ${name}(${variables}) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: ${type}${typeVersion ? `, typeVersion: ${typeVersion}` : ""}${entryFields ? `, ${entryFields}` : ""}} + ) { + __typename + } + } +`; + +describe("recognition", () => { + it("recognises a typed entry operation", () => { + const payloads = derive(entryOperation()); + + expect(payloads).toHaveLength(1); + expect(payloads[0]).toMatchObject({ + entryType: "thing", + typeVersion: 1, + operationName: "PostThing", + parametersMode: "typed", + }); + }); + + it.each([ + [ + "a query rather than a mutation", + `query PostThing($ik: SafeString!) { addLedgerEntry(ik: $ik, entry: {type: "thing"}) { __typename } }`, + ], + [ + "an anonymous operation", + `mutation { addLedgerEntry(ik: "ik", entry: {type: "thing"}) { __typename } }`, + ], + [ + "more than one selection", + `mutation PostThing { addLedgerEntry(ik: "ik", entry: {type: "thing"}) { __typename } ledgerEntry(ik: "ik") { id } }`, + ], + [ + "a field other than addLedgerEntry", + `mutation PostThing { reconcileTx(ik: "ik", entry: {type: "thing"}) { __typename } }`, + ], + [ + "an entry argument that is a variable", + `mutation PostThing($entry: LedgerEntryInput!) { addLedgerEntry(ik: "ik", entry: $entry) { __typename } }`, + ], + [ + "no entry argument at all", + `mutation PostThing { addLedgerEntry(ik: "ik") { __typename } }`, + ], + [ + "a type that is a variable, as in the SDK's own addLedgerEntry", + `mutation addLedgerEntry($type: String!) { addLedgerEntry(ik: "ik", entry: {type: $type, parameters: {}}) { __typename } }`, + ], + [ + "no type at all", + `mutation PostLines { addLedgerEntry(ik: "ik", entry: {lines: []}) { __typename } }`, + ], + ])("skips %s", (_description, source) => { + const warn = vi.fn(); + + expect(derive(source, warn)).toEqual([]); + // A skip is silent, never an error and never a warning. + expect(warn).not.toHaveBeenCalled(); + }); + + it("does not require a particular operation name", () => { + const payloads = derive(entryOperation({ name: "whateverYouLike" })); + + expect(payloads).toHaveLength(1); + expect(payloads[0].entryType).toEqual("thing"); + }); + + it("renders nothing when no operation is a typed entry operation", () => { + expect( + generate( + `mutation addLedgerEntry($type: String!) { addLedgerEntry(ik: "ik", entry: {type: $type}) { __typename } }`, + ), + ).toEqual(""); + }); +}); + +/** Spec 2.3a: on every payload, whatever its operation binds. */ +const COMMON_FIELD_NAMES = ["posted", "description", "tags", "groups", "conditions"]; + +describe("identity", () => { + it("keeps two versions of one entry type apart", () => { + const payloads = derive( + [ + entryOperation({ name: "PostThing", typeVersion: "1" }), + entryOperation({ + name: "PostThing_v2", + typeVersion: "2", + entryFields: "parameters: {amount: $amount, fee: $fee}", + variables: + "$ik: SafeString!, $ledgerIk: SafeString!, $amount: String!, $fee: String!", + }), + ].join("\n"), + ); + + expect(payloads.map((payload) => payload.typeVersion)).toEqual([1, 2]); + expect(parametersOf(payloads[1]).map((parameter) => parameter.wireName)).toEqual([ + "amount", + "fee", + ]); + }); + + it("generates no payload when the version is the caller's to choose", () => { + const warn = vi.fn(); + const payloads = derive( + `mutation PostThing($ik: SafeString!, $ledgerIk: SafeString!, $version: Int, $amount: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {ik: $ledgerIk}, type: "thing", typeVersion: $version, parameters: {amount: $amount}} + ) { __typename } + }`, + warn, + ); + + // A payload names one version and posts it, so a version bound to a variable + // cannot be honoured — silently posting 1 would discard the caller's value. + expect(payloads).toEqual([]); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.mock.calls[0][0]).toContain("typeVersion"); + }); + + it("normalises an unpinned typeVersion to 1", () => { + expect(derive(entryOperation())[0].typeVersion).toEqual(1); + }); + + it("treats an unpinned operation and one pinning version 1 as one payload", () => { + const payloads = derive( + [ + entryOperation({ name: "PostThing" }), + entryOperation({ name: "PostThingAgain", typeVersion: "1" }), + ].join("\n"), + ); + + expect(payloads).toHaveLength(1); + // First occurrence in input order wins. + expect(payloads[0].operationName).toEqual("PostThing"); + }); + + it("deduplicates two operations at one identity without warning", () => { + const warn = vi.fn(); + const payloads = derive( + [ + entryOperation({ name: "PostThing" }), + entryOperation({ name: "PostThingWithMoreFields" }), + ].join("\n"), + warn, + ); + + expect(payloads).toHaveLength(1); + expect(warn).not.toHaveBeenCalled(); + }); + + it("notices two operations matching the Ledger by different keys", () => { + const warn = vi.fn(); + // Both set `ledger`, but one exposes `ledgerIk` and the other `ledgerId`, so + // comparing the entry field they write would miss it. + const payloads = derive( + [ + entryOperation({ name: "PostThing" }), + `mutation PostThingById($ik: SafeString!, $ledgerId: ID, $amount: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: {id: $ledgerId}, type: "thing", parameters: {amount: $amount}} + ) { __typename } + }`, + ].join("\n"), + warn, + ); + + expect(payloads).toHaveLength(1); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.mock.calls[0][0]).toContain("different entry fields"); + }); + + it("warns when two operations at one identity bind different entry fields", () => { + const warn = vi.fn(); + // Feeding both the plain and the runtime-args generation of one Schema in + // together is the realistic way to hit this: they describe the same entries + // but bind different fields, and the first one in wins. + const payloads = derive( + [ + entryOperation({ name: "PostThing" }), + entryOperation({ + name: "PostThingRuntimeArgs", + entryFields: + "tags: $tags, parameters: {amount: $amount}", + variables: + "$ik: SafeString!, $ledgerIk: SafeString!, $amount: String!, $tags: [LedgerEntryTagInput!]", + }), + ].join("\n"), + warn, + ); + + expect(payloads).toHaveLength(1); + expect(payloads[0].fields.map((field) => field.name)).toEqual([ + "ledgerIk", + ...COMMON_FIELD_NAMES, + ]); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.mock.calls[0][0]).toContain("different entry fields"); + }); + + it("warns when two operations at one identity declare different parameters", () => { + const warn = vi.fn(); + const payloads = derive( + [ + entryOperation({ name: "PostThing" }), + entryOperation({ + name: "PostThingStale", + entryFields: "parameters: {somethingElse: $amount}", + }), + ].join("\n"), + warn, + ); + + expect(payloads).toHaveLength(1); + expect(payloads[0].operationName).toEqual("PostThing"); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.mock.calls[0][0]).toContain("different parameters"); + }); +}); + +describe("parameters", () => { + it("takes required-ness from the variable definition, not the field name", () => { + const payloads = derive( + entryOperation({ + entryFields: "parameters: {amount: $amount, memo: $memo}", + variables: + "$ik: SafeString!, $ledgerIk: SafeString!, $amount: Int64!, $memo: String", + }), + ); + + expect( + parametersOf(payloads[0]).map((parameter) => ({ + wireName: parameter.wireName, + required: parameter.required, + })), + ).toEqual([ + { wireName: "amount", required: true }, + { wireName: "memo", required: false }, + ]); + }); + + it("preserves source order rather than sorting or putting required first", () => { + const payloads = derive( + entryOperation({ + entryFields: + "parameters: {zebra: $zebra, apple: $apple, middle: $middle}", + variables: + "$ik: SafeString!, $ledgerIk: SafeString!, $zebra: String, $apple: String!, $middle: String", + }), + ); + + expect(parametersOf(payloads[0]).map((parameter) => parameter.wireName)).toEqual([ + "zebra", + "apple", + "middle", + ]); + }); + + it("leaves a parameter the operation fixes to the API", () => { + const payloads = derive( + entryOperation({ + entryFields: `parameters: {amount: $amount, currency: "USD", nested: {a: 1}}`, + }), + ); + + // A fixed value is encoded in the Schema, so the API derives it from there: + // the payload neither exposes it nor re-posts it. + expect(parametersOf(payloads[0]).map((parameter) => parameter.wireName)).toEqual([ + "amount", + ]); + }); + + it("falls back to an untyped map when parameters is a variable", () => { + const payloads = derive( + entryOperation({ + entryFields: "parameters: $parameters", + variables: "$ik: SafeString!, $ledgerIk: SafeString!, $parameters: JSON!", + }), + ); + + expect(payloads[0]).toMatchObject({ + entryType: "thing", + parametersMode: "untyped", + }); + // An untyped payload carries the variable's type, not a parameter list. + expect(parametersOf(payloads[0])).toEqual([]); + }); + + it("takes no parameters at all when the operation posts none", () => { + const payloads = derive(entryOperation({ entryFields: "" })); + + expect(payloads[0]).toMatchObject({ parametersMode: "absent" }); + expect(parametersOf(payloads[0])).toEqual([]); + }); + + it("leaves values the operation fixes to the API", () => { + const warn = vi.fn(); + const output = generate( + entryOperation({ + entryFields: `posted: "2024-01-01", parameters: {amount: $amount, currency: "USD"}`, + }), + warn, + ); + + // Both are encoded in the Schema the operation was generated from, so the + // API derives them: the payload neither exposes nor re-posts either one. + expect(builderBody(output, "thingV1")).not.toContain("2024-01-01"); + expect(builderBody(output, "thingV1")).not.toContain("USD"); + expect(warn).not.toHaveBeenCalled(); + }); + it("takes no parameters when the operation fixes all of them", () => { + const output = generate( + entryOperation({ entryFields: `parameters: {currency: "USD"}` }), + ); + + expect(builderBody(output, "thingV1")).not.toContain("USD"); + expect(payloadType(output, "ThingV1")).not.toContain("parameters"); + }); + it("warns and skips a parameter bound to an undeclared variable", () => { + const warn = vi.fn(); + const payloads = derive( + entryOperation({ + entryFields: "parameters: {amount: $amount, mystery: $mystery}", + }), + warn, + ); + + expect(parametersOf(payloads[0]).map((parameter) => parameter.wireName)).toEqual([ + "amount", + ]); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.mock.calls[0][0]).toContain("$mystery"); + }); +}); + +describe("naming", () => { + it("always carries the version the payload resolves to", () => { + const names = nameTypedEntryPayloads( + derive( + [ + entryOperation({ name: "PostThing" }), + entryOperation({ name: "PostThing_v2", typeVersion: "2" }), + ].join("\n"), + ), + ).map((payload) => payload.typeName); + + expect(names).toEqual(["ThingV1", "ThingV2"]); + }); + + it("derives a name from the entry type alone, whatever else is generated", () => { + const alone = nameTypedEntryPayloads(derive(entryOperation()))[0]; + const alongside = nameTypedEntryPayloads( + derive( + [ + entryOperation({ name: "PostOther", type: `"other"` }), + entryOperation(), + entryOperation({ name: "PostThing_v9", typeVersion: "9" }), + ].join("\n"), + ), + ).find((payload) => payload.typeVersion === 1 && payload.entryType === "thing"); + + expect(alongside?.typeName).toEqual(alone.typeName); + expect(alongside?.builderName).toEqual(alone.builderName); + }); + + it.each([ + ["user-funds-account", "UserFundsAccountV1", "userFundsAccountV1"], + ["fundingSettlement", "FundingSettlementV1", "fundingSettlementV1"], + ["payment_processing", "PaymentProcessingV1", "paymentProcessingV1"], + ["2-way-transfer", "_2WayTransferV1", "_2WayTransferV1"], + ])("names %s as %s", (entryType, typeName, builderName) => { + const [payload] = nameTypedEntryPayloads( + derive(entryOperation({ type: JSON.stringify(entryType) })), + ); + + expect(payload.typeName).toEqual(typeName); + expect(payload.builderName).toEqual(builderName); + }); + + it("warns and suffixes when two entry types want one identifier", () => { + const warn = vi.fn(); + const payloads = nameTypedEntryPayloads( + derive( + [ + entryOperation({ name: "PostA", type: `"user-funds"` }), + entryOperation({ name: "PostB", type: `"user_funds"` }), + ].join("\n"), + ), + { warn }, + ); + + expect(payloads.map((payload) => payload.typeName)).toEqual([ + "UserFundsV1", + "UserFundsV1_2", + ]); + expect(warn).toHaveBeenCalledTimes(1); + }); +}); + +describe("rendering", () => { + it("types a required parameter from its variable and an optional one as optional", () => { + const output = generate( + entryOperation({ + entryFields: "parameters: {amount: $amount, memo: $memo, tag: $tag}", + variables: + "$ik: SafeString!, $ledgerIk: SafeString!, $amount: Int64!, $memo: String, $tag: LedgerEntryTagInput", + }), + ); + + const payload = payloadType(output, "ThingV1"); + expect(payload).toContain("amount: Scalars['Int64']['input'];"); + expect(payload).toContain("memo?: Scalars['String']['input'] | undefined;"); + // A non-scalar variable type refers to the generated input type by name. + expect(payload).toContain("tag?: LedgerEntryTagInput | undefined;"); + // InputMaybe and wire-visible null are never used for these types. + expect(payload).not.toContain("InputMaybe"); + expect(payload).not.toContain("| null"); + }); + + it("escapes a parameter that collides with a common field", () => { + const warn = vi.fn(); + const output = generate( + entryOperation({ + entryFields: + "posted: $posted, parameters: {posted: $postedParam, amount: $amount}", + variables: + "$ik: SafeString!, $ledgerIk: SafeString!, $posted: DateTime, $postedParam: String!, $amount: String!", + }), + warn, + ); + + // The entry's own `posted` holds the name, so the parameter takes another — + // but it still posts under the name the Schema knows it by. + const payload = payloadType(output, "ThingV1"); + expect(payload).toContain(" posted?: Scalars['DateTime']['input'] | undefined;"); + expect(payload).toContain(" posted_2: Scalars['String']['input'];"); + expect(builderBody(output, "thingV1")).toContain(" posted: input.posted_2,"); + expect(builderBody(output, "thingV1")).toContain( + "...(input.posted !== undefined && { posted: input.posted }),", + ); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.mock.calls[0][0]).toContain("It still posts as `posted`"); + }); + + it("keeps a wire name verbatim even when it is a reserved word", () => { + const output = generate( + entryOperation({ + entryFields: + "parameters: {class: $reserved, function: $fn, user_id: $userId, userId: $userIdCamel}", + variables: + "$ik: SafeString!, $ledgerIk: SafeString!, $reserved: String!, $fn: String!, $userId: String!, $userIdCamel: String!", + }), + ); + + // Property keys need no escaping in TypeScript, so no wire name is ever + // rewritten, and two names that differ only in casing stay distinct. + // Parameters sit alongside the common fields, keeping their Schema names. + const payload = payloadType(output, "ThingV1"); + expect(payload).toContain(" class: Scalars['String']['input'];"); + expect(payload).toContain(" function: Scalars['String']['input'];"); + expect(payload).toContain(" user_id: Scalars['String']['input'];"); + expect(payload).toContain(" userId: Scalars['String']['input'];"); + // Wire names reach the built entry verbatim, in source order. + expect(builderBody(output, "thingV1")).toContain( + [ + " parameters: {", + " class: input.class,", + " function: input.function,", + " user_id: input.user_id,", + " userId: input.userId,", + " },", + ].join("\n"), + ); + }); + + it("renders lists and untyped parameter fallbacks", () => { + const output = generate( + [ + entryOperation({ + name: "PostThing", + entryFields: "parameters: {amounts: $amounts}", + variables: "$ik: SafeString!, $ledgerIk: SafeString!, $amounts: [Int64!]!", + }), + entryOperation({ + name: "PostRuntime", + type: `"runtime"`, + entryFields: "parameters: $parameters", + variables: "$ik: SafeString!, $ledgerIk: SafeString!, $parameters: JSON!", + }), + ].join("\n"), + ); + + expect(payloadType(output, "ThingV1")).toContain( + "amounts: Array;", + ); + // `$parameters: JSON!` is non-null, so the untyped map is required. + expect(payloadType(output, "RuntimeV1")).toContain( + "parameters: Scalars['JSON']['input'];", + ); + }); + + it("exposes the entry fields the operation binds", () => { + const output = generate( + entryOperation({ + entryFields: "posted: $posted, tags: $tags, parameters: {amount: $amount}", + variables: + "$ik: SafeString!, $ledgerIk: SafeString!, $posted: DateTime, $tags: [LedgerEntryTagInput!], $amount: String!", + }), + ); + + const payload = payloadType(output, "ThingV1"); + expect(payload).toContain("ik: Scalars['SafeString']['input'];"); + expect(payload).toContain("ledgerIk: Scalars['SafeString']['input'];"); + expect(payload).toContain("posted?: Scalars['DateTime']['input'] | undefined;"); + expect(payload).toContain("tags?: Array | undefined;"); + // `lines` cannot be combined with an entry that has a `type`, so it is the + // one LedgerEntryInput field a payload never offers unasked. + expect(payload).not.toContain("lines"); + }); + + it("exposes every common field even when the operation binds none of them", () => { + // Spec 2.3a: the common fields are fixed by `LedgerEntryInput`, not derived + // from the operation. A CLI that stops binding `tags` must not silently + // remove `tags` from the payload, and no CLI generation binds `description` + // at all -- so deriving the set would leave it permanently unreachable. + const output = generate( + entryOperation({ + entryFields: "parameters: {amount: $amount}", + variables: "$ik: SafeString!, $ledgerIk: SafeString!, $amount: String!", + }), + ); + + const payload = payloadType(output, "ThingV1"); + expect(payload).toContain("posted?: Scalars['DateTime']['input'] | undefined;"); + expect(payload).toContain("description?: Scalars['String']['input'] | undefined;"); + expect(payload).toContain("tags?: Array | undefined;"); + expect(payload).toContain("groups?: Array | undefined;"); + expect(payload).toContain( + "conditions?: Array | undefined;", + ); + expect(payload).not.toContain("lines"); + }); + + it("generates no payload for an entry type that takes Ledger Lines", () => { + const warn = vi.fn(); + const output = generate( + entryOperation({ + entryFields: "lines: $lines", + variables: + "$ik: SafeString!, $ledgerIk: SafeString!, $lines: [LedgerLineInput!]!", + }), + warn, + ); + + // `lines` is not a common field, so a payload for this entry type could only + // ever post an entry with no Lines. Better to have none, and say why. + expect(output).toEqual(""); + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.mock.calls[0][0]).toContain("raw `AddLedgerEntryInput`"); + }); + it("never lets the caller set type or typeVersion", () => { + const output = generate( + entryOperation({ + typeVersion: "3", + entryFields: `parameters: {amount: $amount}`, + }), + ); + + const payload = payloadType(output, "ThingV3"); + expect(payload).not.toContain(" type"); + expect(payload).not.toContain(" typeVersion"); + // The version still reaches the wire, from the operation rather than the caller. + expect(builderBody(output, "thingV3")).toContain(" typeVersion: 3,"); + expect(builderBody(output, "thingV3")).toContain(" type: 'thing',"); + }); + it("exposes a whole-object ledger binding as ledger", () => { + const output = generate(` + mutation PostThing($ik: SafeString!, $ledger: LedgerMatchInput, $amount: String!) { + addLedgerEntry( + ik: $ik + entry: {ledger: $ledger, type: "thing", parameters: {amount: $amount}} + ) { + __typename + } + } + `); + + expect(payloadType(output, "ThingV1")).toContain( + "ledger?: LedgerMatchInput | undefined;", + ); + expect(builderBody(output, "thingV1")).toContain( + " ...(input.ledger !== undefined && { ledger: input.ledger }),", + ); + }); + + it("matches the committed snapshot of generated output", () => { + // Generated source is what callers write against, so any change to an + // identifier or a signature has to surface as a reviewable diff. + const payloads = nameTypedEntryPayloads( + derive( + [ + entryOperation({ + name: "PostUserFundsAccount", + type: `"user-funds-account"`, + }), + entryOperation({ + name: "PostUserFundsAccount_v2", + type: `"user-funds-account"`, + typeVersion: "2", + entryFields: + "parameters: {amount: $amount, feeAmount: $feeAmount, memo: $memo}", + variables: + "$ik: SafeString!, $ledgerIk: SafeString!, $amount: String!, $feeAmount: Int64!, $memo: String", + }), + entryOperation({ + name: "PostRuntimeThing", + type: `"runtime-thing"`, + entryFields: "parameters: $parameters", + variables: + "$ik: SafeString!, $ledgerIk: SafeString!, $parameters: JSON!", + }), + ].join("\n"), + ), + ); + + expect(renderTypedEntryPayloads(payloads, scalars)).toMatchSnapshot(); + }); +});