diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b3113e2..877a77d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -52,7 +52,8 @@ jobs: path: | /home/linuxbrew/.linuxbrew ~/.cache/Homebrew - key: ${{ runner.os }}-homebrew-fragment-cli + # v2: the previous key holds a Homebrew too old to pour current bottles. + key: ${{ runner.os }}-homebrew-fragment-cli-v2 restore-keys: | ${{ runner.os }}-homebrew- @@ -63,6 +64,11 @@ jobs: fi echo "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" >> $GITHUB_ENV eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + # The cache restores whatever Homebrew was current when the key was first + # written, and the runner disables auto-update. A months-old Homebrew + # cannot pour a current bottle -- it fails with `unknown install step: + # run` -- so bring Homebrew itself up to date before installing. + /home/linuxbrew/.linuxbrew/bin/brew update --quiet /home/linuxbrew/.linuxbrew/bin/brew tap fragment-dev/tap || true if ! /home/linuxbrew/.linuxbrew/bin/brew list fragment-dev/tap/fragment-cli &>/dev/null; then /home/linuxbrew/.linuxbrew/bin/brew install fragment-dev/tap/fragment-cli diff --git a/src/typedBatchEntries.ts b/src/typedBatchEntries.ts index 98c9e41..b55c595 100644 --- a/src/typedBatchEntries.ts +++ b/src/typedBatchEntries.ts @@ -17,6 +17,7 @@ */ import { Kind, + parseType, valueFromASTUntyped, type DocumentNode, type ListTypeNode, @@ -114,6 +115,28 @@ const defaultWarn: Warn = (message) => { */ 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); @@ -265,6 +288,22 @@ const getFields = ( }); }); + // 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, + source: "variable", + type: parseType(type), + required: false, + }); + }); + return fields; }; diff --git a/tests/__snapshots__/typed-batch-entries.test.ts.snap b/tests/__snapshots__/typed-batch-entries.test.ts.snap index 27b6144..385d3b8 100644 --- a/tests/__snapshots__/typed-batch-entries.test.ts.snap +++ b/tests/__snapshots__/typed-batch-entries.test.ts.snap @@ -30,6 +30,12 @@ export type UserFundsAccountV1 = { 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; parameters: { amount: Scalars['String']['input']; }; @@ -40,10 +46,15 @@ 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.parameters.amount, }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), type: 'user-funds-account', typeVersion: 1, }, @@ -61,6 +72,12 @@ export type UserFundsAccountV2 = { 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; parameters: { amount: Scalars['String']['input']; feeAmount: Scalars['Int64']['input']; @@ -73,12 +90,17 @@ 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.parameters.amount, feeAmount: input.parameters.feeAmount, ...(input.parameters.memo !== undefined && { memo: input.parameters.memo }), }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), type: 'user-funds-account', typeVersion: 2, }, @@ -96,6 +118,12 @@ export type RuntimeThingV1 = { 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. @@ -108,8 +136,13 @@ 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, }, diff --git a/tests/fixtures/generated-edge-case-client.ts b/tests/fixtures/generated-edge-case-client.ts index 6a87c15..31cfe39 100644 --- a/tests/fixtures/generated-edge-case-client.ts +++ b/tests/fixtures/generated-edge-case-client.ts @@ -3493,6 +3493,9 @@ export type RuntimeLinesV1 = { description?: Scalars['String']['input'] | undefined; /** The Ledger Lines to create, for entry types whose lines the Schema does not fix. */ lines: Array; + tags?: Array | undefined; + groups?: Array | undefined; + conditions?: Array | undefined; }; /** Builds an `addLedgerEntries` entry for `runtime_lines` (typeVersion 1). */ @@ -3500,10 +3503,13 @@ export const runtimeLinesV1 = ( input: RuntimeLinesV1, ): AddLedgerEntryInput => ({ entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), ...(input.description !== undefined && { description: input.description }), + ...(input.groups !== undefined && { groups: input.groups }), ledger: { ik: input.ledgerIk }, lines: input.lines, ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), type: 'runtime_lines', typeVersion: 1, }, @@ -3521,6 +3527,12 @@ export type UntypedParametersV1 = { 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. @@ -3533,8 +3545,13 @@ 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, }, @@ -3552,6 +3569,12 @@ export type AllOptionalV2 = { 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; parameters?: { memo?: Scalars['String']['input'] | undefined; note?: Scalars['String']['input'] | undefined; @@ -3568,8 +3591,13 @@ export const allOptionalV2 = ( }; 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, }, @@ -3589,6 +3617,12 @@ export type EitherLedgerKeyV1 = { 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; parameters: { amount: Scalars['String']['input']; }; @@ -3604,10 +3638,15 @@ export const eitherLedgerKeyV1 = ( }; 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.parameters.amount, }, + ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), type: 'either_ledger_key', typeVersion: 1, }, @@ -3626,6 +3665,10 @@ export type FixedValuesV1 = { 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; + groups?: Array | undefined; + conditions?: Array | undefined; parameters: { amount: Scalars['String']['input']; }; @@ -3636,12 +3679,15 @@ export const fixedValuesV1 = ( input: FixedValuesV1, ): AddLedgerEntryInput => ({ entry: { + ...(input.conditions !== undefined && { conditions: input.conditions }), description: 'posted by the nightly sweep', + ...(input.groups !== undefined && { groups: input.groups }), ledger: { ik: input.ledgerIk }, parameters: { amount: input.parameters.amount, currency: 'USD', }, + ...(input.posted !== undefined && { posted: input.posted }), tags: [{ key: 'source', value: 'sweep' }], type: 'fixed_values', typeVersion: 1, diff --git a/tests/fixtures/generated-template-client.ts b/tests/fixtures/generated-template-client.ts index 36c3f97..db889c7 100644 --- a/tests/fixtures/generated-template-client.ts +++ b/tests/fixtures/generated-template-client.ts @@ -4011,6 +4011,10 @@ export type OrderPlacedV1 = { 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; parameters: { user_id: Scalars['String']['input']; order_id: Scalars['String']['input']; @@ -4028,6 +4032,9 @@ 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.parameters.user_id, @@ -4040,6 +4047,7 @@ export const orderPlacedV1 = ( driver_id: input.parameters.driver_id, }, ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), type: 'order_placed', typeVersion: 1, }, @@ -4059,6 +4067,10 @@ export type OrderPlacedV2 = { 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; parameters: { user_id: Scalars['String']['input']; order_id: Scalars['String']['input']; @@ -4077,6 +4089,9 @@ 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.parameters.user_id, @@ -4090,6 +4105,7 @@ export const orderPlacedV2 = ( driver_id: input.parameters.driver_id, }, ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), type: 'order_placed', typeVersion: 2, }, @@ -4109,6 +4125,10 @@ export type CardSettleV1 = { 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; parameters: { user_id: Scalars['String']['input']; order_id: Scalars['String']['input']; @@ -4122,6 +4142,9 @@ 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.parameters.user_id, @@ -4130,6 +4153,7 @@ export const cardSettleV1 = ( amount: input.parameters.amount, }, ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), type: 'card_settle', typeVersion: 1, }, @@ -4149,6 +4173,10 @@ export type RestaurantPayoutInitiateV1 = { 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; parameters: { restaurant_id: Scalars['String']['input']; order_id: Scalars['String']['input']; @@ -4163,6 +4191,9 @@ 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.parameters.restaurant_id, @@ -4172,6 +4203,7 @@ export const restaurantPayoutInitiateV1 = ( payout_id: input.parameters.payout_id, }, ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), type: 'restaurant_payout_initiate', typeVersion: 1, }, @@ -4191,6 +4223,10 @@ export type RestaurantPayoutSettleV1 = { 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; parameters: { restaurant_id: Scalars['String']['input']; payout_id: Scalars['String']['input']; @@ -4204,6 +4240,9 @@ 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.parameters.restaurant_id, @@ -4212,6 +4251,7 @@ export const restaurantPayoutSettleV1 = ( amount: input.parameters.amount, }, ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), type: 'restaurant_payout_settle', typeVersion: 1, }, @@ -4231,6 +4271,10 @@ export type DriverPayoutInitiateV1 = { 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; parameters: { driver_id: Scalars['String']['input']; order_id: Scalars['String']['input']; @@ -4245,6 +4289,9 @@ 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.parameters.driver_id, @@ -4254,6 +4301,7 @@ export const driverPayoutInitiateV1 = ( payout_id: input.parameters.payout_id, }, ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), type: 'driver_payout_initiate', typeVersion: 1, }, @@ -4273,6 +4321,10 @@ export type DriverPayoutSettleV1 = { 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; parameters: { driver_id: Scalars['String']['input']; payout_id: Scalars['String']['input']; @@ -4286,6 +4338,9 @@ 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.parameters.driver_id, @@ -4294,6 +4349,7 @@ export const driverPayoutSettleV1 = ( amount: input.parameters.amount, }, ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), type: 'driver_payout_settle', typeVersion: 1, }, @@ -4313,6 +4369,10 @@ export type DisputePayoutInitiateV1 = { 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; parameters: { user_id: Scalars['String']['input']; disputes_id: Scalars['String']['input']; @@ -4328,6 +4388,9 @@ 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.parameters.user_id, @@ -4338,6 +4401,7 @@ export const disputePayoutInitiateV1 = ( order_id: input.parameters.order_id, }, ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), type: 'dispute_payout_initiate', typeVersion: 1, }, @@ -4357,6 +4421,10 @@ export type DisputePayoutSettleV1 = { 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; parameters: { user_id: Scalars['String']['input']; disputes_id: Scalars['String']['input']; @@ -4371,6 +4439,9 @@ 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.parameters.user_id, @@ -4380,6 +4451,7 @@ export const disputePayoutSettleV1 = ( order_id: input.parameters.order_id, }, ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), type: 'dispute_payout_settle', typeVersion: 1, }, diff --git a/tests/fixtures/generated-template-runtime-args-client.ts b/tests/fixtures/generated-template-runtime-args-client.ts index 0ceb306..5a73f17 100644 --- a/tests/fixtures/generated-template-runtime-args-client.ts +++ b/tests/fixtures/generated-template-runtime-args-client.ts @@ -4036,6 +4036,7 @@ export type OrderPlacedV1 = { tags?: Array | undefined; groups?: Array | undefined; conditions?: Array | undefined; + description?: Scalars['String']['input'] | undefined; parameters: { user_id: Scalars['String']['input']; order_id: Scalars['String']['input']; @@ -4054,6 +4055,7 @@ export const 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: { @@ -4090,6 +4092,7 @@ export type OrderPlacedV2 = { tags?: Array | undefined; groups?: Array | undefined; conditions?: Array | undefined; + description?: Scalars['String']['input'] | undefined; parameters: { user_id: Scalars['String']['input']; order_id: Scalars['String']['input']; @@ -4109,6 +4112,7 @@ export const 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: { @@ -4145,6 +4149,8 @@ export type CardSettleV1 = { posted?: Scalars['DateTime']['input'] | undefined; tags?: Array | undefined; groups?: Array | undefined; + description?: Scalars['String']['input'] | undefined; + conditions?: Array | undefined; parameters: { user_id: Scalars['String']['input']; order_id: Scalars['String']['input']; @@ -4158,6 +4164,8 @@ 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: { @@ -4189,6 +4197,8 @@ export type RestaurantPayoutInitiateV1 = { posted?: Scalars['DateTime']['input'] | undefined; tags?: Array | undefined; groups?: Array | undefined; + description?: Scalars['String']['input'] | undefined; + conditions?: Array | undefined; parameters: { restaurant_id: Scalars['String']['input']; order_id: Scalars['String']['input']; @@ -4203,6 +4213,8 @@ 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: { @@ -4235,6 +4247,8 @@ export type RestaurantPayoutSettleV1 = { posted?: Scalars['DateTime']['input'] | undefined; tags?: Array | undefined; groups?: Array | undefined; + description?: Scalars['String']['input'] | undefined; + conditions?: Array | undefined; parameters: { restaurant_id: Scalars['String']['input']; payout_id: Scalars['String']['input']; @@ -4248,6 +4262,8 @@ 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: { @@ -4279,6 +4295,8 @@ export type DriverPayoutInitiateV1 = { posted?: Scalars['DateTime']['input'] | undefined; tags?: Array | undefined; groups?: Array | undefined; + description?: Scalars['String']['input'] | undefined; + conditions?: Array | undefined; parameters: { driver_id: Scalars['String']['input']; order_id: Scalars['String']['input']; @@ -4293,6 +4311,8 @@ 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: { @@ -4325,6 +4345,8 @@ export type DriverPayoutSettleV1 = { posted?: Scalars['DateTime']['input'] | undefined; tags?: Array | undefined; groups?: Array | undefined; + description?: Scalars['String']['input'] | undefined; + conditions?: Array | undefined; parameters: { driver_id: Scalars['String']['input']; payout_id: Scalars['String']['input']; @@ -4338,6 +4360,8 @@ 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: { @@ -4370,6 +4394,7 @@ export type DisputePayoutInitiateV1 = { tags?: Array | undefined; groups?: Array | undefined; conditions?: Array | undefined; + description?: Scalars['String']['input'] | undefined; parameters: { user_id: Scalars['String']['input']; disputes_id: Scalars['String']['input']; @@ -4386,6 +4411,7 @@ export const 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: { @@ -4420,6 +4446,7 @@ export type DisputePayoutSettleV1 = { tags?: Array | undefined; groups?: Array | undefined; conditions?: Array | undefined; + description?: Scalars['String']['input'] | undefined; parameters: { user_id: Scalars['String']['input']; disputes_id: Scalars['String']['input']; @@ -4435,6 +4462,7 @@ export const 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: { diff --git a/tests/fixtures/generated-test-client.ts b/tests/fixtures/generated-test-client.ts index 3c7d8f9..1a28f9f 100644 --- a/tests/fixtures/generated-test-client.ts +++ b/tests/fixtures/generated-test-client.ts @@ -3641,6 +3641,10 @@ export type UserFundsAccountV1 = { 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; parameters: { amount: Scalars['String']['input']; }; @@ -3651,11 +3655,15 @@ 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.parameters.amount, }, ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), type: 'user-funds-account', typeVersion: 1, }, @@ -3675,6 +3683,10 @@ export type UserFundsAccountV2 = { 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; parameters: { amount: Scalars['String']['input']; feeAmount: Scalars['String']['input']; @@ -3686,12 +3698,16 @@ 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.parameters.amount, feeAmount: input.parameters.feeAmount, }, ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), type: 'user-funds-account', typeVersion: 2, }, @@ -3711,6 +3727,10 @@ export type FundingSettlementV1 = { 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; parameters: { amount: Scalars['String']['input']; }; @@ -3721,11 +3741,15 @@ 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.parameters.amount, }, ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), type: 'fundingSettlement', typeVersion: 1, }, @@ -3745,6 +3769,10 @@ export type PaymentProcessingV1 = { 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; parameters: { amount: Scalars['String']['input']; }; @@ -3755,11 +3783,15 @@ 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.parameters.amount, }, ...(input.posted !== undefined && { posted: input.posted }), + ...(input.tags !== undefined && { tags: input.tags }), type: 'payment_processing', typeVersion: 1, }, diff --git a/tests/template-schema-client.test.ts b/tests/template-schema-client.test.ts index 6511edd..1a3f519 100644 --- a/tests/template-schema-client.test.ts +++ b/tests/template-schema-client.test.ts @@ -58,42 +58,44 @@ const generatedRuntimeArgsClient = read( ); describe("generated template client", () => { - it("exposes the fields each operation set binds, and no others", () => { + it("exposes the same common fields whichever operation set generated it", () => { const plain = payloadType(generatedClient, "DisputePayoutInitiateV1"); const runtimeArgs = payloadType( generatedRuntimeArgsClient, "DisputePayoutInitiateV1", ); - // Both operations bind the entry's Ledger and `posted`. + // 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;"); - }); - - // Only the runtime-args operation binds tags, groups and conditions, so only - // its payload lets a caller set them. - expect(runtimeArgs).toContain("tags?: Array | undefined;"); - expect(runtimeArgs).toContain("groups?: Array | undefined;"); - expect(runtimeArgs).toContain( - "conditions?: Array | undefined;", - ); - expect(plain).not.toContain("tags"); - expect(plain).not.toContain("groups"); - expect(plain).not.toContain("conditions"); + 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;", + ); - // Neither binds lines for this entry type, whose lines the Schema fixes. - expect(plain).not.toContain("lines"); - expect(runtimeArgs).not.toContain("lines"); + // `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("binds different fields per entry type within one operation set", () => { - // `card_settle` binds tags and groups but no conditions; the payload says so. + 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).not.toContain("conditions"); + expect(cardSettle).toContain( + "conditions?: Array | undefined;", + ); }); it("matches the payload surface derived from the template Schema", () => { diff --git a/tests/typed-batch-entries.test.ts b/tests/typed-batch-entries.test.ts index cf94878..12a162b 100644 --- a/tests/typed-batch-entries.test.ts +++ b/tests/typed-batch-entries.test.ts @@ -139,6 +139,9 @@ describe("recognition", () => { }); }); +/** 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( @@ -212,7 +215,10 @@ describe("identity", () => { ); expect(payloads).toHaveLength(1); - expect(payloads[0].fields.map((field) => field.name)).toEqual(["ledgerIk"]); + 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"); }); @@ -504,7 +510,7 @@ describe("rendering", () => { ); }); - it("exposes exactly the entry fields the operation binds", () => { + it("exposes the entry fields the operation binds", () => { const output = generate( entryOperation({ parameters: "posted: $posted, tags: $tags, parameters: {amount: $amount}", @@ -518,10 +524,31 @@ describe("rendering", () => { expect(payload).toContain("ledgerIk: Scalars['SafeString']['input'];"); expect(payload).toContain("posted?: Scalars['DateTime']['input'] | undefined;"); expect(payload).toContain("tags?: Array | undefined;"); - // Nothing the operation leaves out is offered to the caller. - expect(payload).not.toContain("groups"); - expect(payload).not.toContain("conditions"); - expect(payload).not.toContain("description"); + // `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({ + parameters: "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"); });